From 6782ad54e661146c3e3d01f65ad371f5dddd200c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 2 May 2026 00:00:25 +0200 Subject: [PATCH 01/26] GSI: new functions for cold proxies --- src/db/db/gsiDeclDbCell.cc | 132 +++++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 7 deletions(-) diff --git a/src/db/db/gsiDeclDbCell.cc b/src/db/db/gsiDeclDbCell.cc index 6e0718fe3..cbe730fa4 100644 --- a/src/db/db/gsiDeclDbCell.cc +++ b/src/db/db/gsiDeclDbCell.cc @@ -32,6 +32,7 @@ #include "dbLibraryProxy.h" #include "dbLibraryManager.h" #include "dbLibrary.h" +#include "dbColdProxy.h" #include "dbLayout.h" #include "dbLayoutUtils.h" #include "dbLayerMapping.h" @@ -949,6 +950,11 @@ static std::vector caller_cells (const db::Cell *c) return std::vector (ids.begin (), ids.end ()); } +static bool is_cold_proxy (const db::Cell *cell) +{ + return dynamic_cast (cell) != 0; +} + static bool is_library_cell (const db::Cell *cell) { return dynamic_cast (cell) != 0; @@ -974,6 +980,36 @@ static db::Library *library (const db::Cell *cell) } } +static std::string library_name (const db::Cell *cell) +{ + const db::ColdProxy *cp = dynamic_cast (cell); + if (cp) { + return cp->context_info ().lib_name; + } else { + const db::Library *l = library (cell); + if (l) { + return l->get_name (); + } else { + return std::string (); + } + } +} + +static std::string library_cell_name (const db::Cell *cell) +{ + const db::ColdProxy *cp = dynamic_cast (cell); + if (cp) { + return cp->context_info ().cell_name; + } else { + const db::Library *l = library (cell); + if (l) { + return l->layout ().cell_name (library_cell_index (cell)); + } else { + return std::string (); + } + } +} + static void change_library_ref (db::Cell *cell, db::lib_id_type lib_id, db::cell_index_type cell_index) { db::LibraryProxy *l = dynamic_cast (cell); @@ -1173,13 +1209,25 @@ static const std::vector &pcell_parameters (const db::Cell *cell) static tl::Variant pcell_parameter (const db::Cell *cell, const std::string &name) { - return cell->layout ()->get_pcell_parameter (cell->cell_index (), name); + const db::ColdProxy *cp = dynamic_cast (cell); + if (cp) { + const auto &pp = cp->context_info ().pcell_parameters; + auto i = pp.find (name); + return i != pp.end () ? i->second : tl::Variant (); + } else { + return cell->layout ()->get_pcell_parameter (cell->cell_index (), name); + } } static std::map pcell_parameters_by_name (const db::Cell *cell) { - tl_assert (cell->layout () != 0); - return cell->layout ()->get_named_pcell_parameters (cell->cell_index ()); + const db::ColdProxy *cp = dynamic_cast (cell); + if (cp) { + return cp->context_info ().pcell_parameters; + } else { + tl_assert (cell->layout () != 0); + return cell->layout ()->get_named_pcell_parameters (cell->cell_index ()); + } } static void refresh (db::Cell *cell) @@ -1203,6 +1251,21 @@ static const db::PCellDeclaration *pcell_declaration (const db::Cell *cell) } } +static std::string pcell_name (const db::Cell *cell) +{ + const db::ColdProxy *cp = dynamic_cast (cell); + if (cp) { + return cp->context_info ().pcell_name; + } else { + const db::PCellDeclaration *pd = pcell_declaration (cell); + if (pd) { + return pd->name (); + } else { + return std::string (); + } + } +} + static const db::PCellDeclaration *pcell_declaration_of_inst (const db::Cell *cell, const db::Cell::instance_type &ref) { tl_assert (cell->layout () != 0); @@ -3193,7 +3256,7 @@ Class decl_Cell ("db", "Cell", "This method has been introduced in version 0.20.\n" ) + gsi::method ("is_proxy?", &db::Cell::is_proxy, - "@brief Returns true, if the cell presents some external entity \n" + "@brief Returns true, if the cell presents some external entity\n" "A cell may represent some data which is imported from some other source, i.e.\n" "a library. Such cells are called \"proxy cells\". For a library reference, the\n" "proxy cell is some kind of pointer to the library and the cell within the library.\n" @@ -3201,11 +3264,31 @@ Class decl_Cell ("db", "Cell", "For PCells, this data can even be computed through some script.\n" "A PCell proxy represents all instances with a given set of parameters.\n" "\n" - "Proxy cells cannot be modified, except that pcell parameters can be modified\n" - "and PCell instances can be recomputed.\n" + "Proxy cells should not be modified directly - i.e. the shapes or instances should not\n" + "be touched. However, you can change PCell parameters (\\change_pcell_parameter, \\change_pcell_parameters)\n" + "or change the library reference (\\change_ref).\n" "\n" "This method has been introduced in version 0.22.\n" ) + + gsi::method_ext ("is_cold_proxy?", &is_cold_proxy, + "@brief Returns true, if the cell is a 'cold proxy'\n" + "Cold proxies are cells that refer to a library but can temporarily not be resolved -\n" + "for example, because the library is not installed. Such cells are basically placeholders\n" + "for library references and also carry PCell parameter information needed to establish\n" + "the link to the library cell again, once the library is available again.\n" + "\n" + "You can use \\library_name to obtain the library name the proxy points to, " + "\\library_cell_name to obtain the cell name in that library, " + "\\pcell_name to obtain the PCell name if it is a PCell proxy, " + "and \\pcell_parameters_by_name to obtain the PCell parameters.\n" + "\n" + "Cold proxies cannot be created or modified. Cold proxies are basically error indicators " + "and should be fixed by installing the respective library. Their layout state\n" + "reflects the last version of the layout when the cell was functional and properly\n" + "linked to a library. Correspondingly, they can be used in read-only applications.\n" + "\n" + "This method has been introduced in version 0.30.9.\n" + ) + gsi::method_ext ("is_library_cell?", &is_library_cell, "@brief Returns true, if the cell is a proxy cell pointing to a library cell\n" "If the cell is imported from some library, this attribute returns true.\n" @@ -3231,10 +3314,30 @@ Class decl_Cell ("db", "Cell", ) + gsi::method_ext ("library", &library, "@brief Returns a reference to the library from which the cell is imported\n" - "if the cell is not imported from a library, this reference is nil.\n" + "If the cell is not imported from a library, this reference is nil.\n" "\n" "This method has been introduced in version 0.22.\n" ) + + gsi::method_ext ("library_name", &library_cell_name, + "@brief Returns the cell name inside the library from which the cell is imported\n" + "If the cell is not imported from a library, the return value is an empty string.\n" + "This method is basically a convenience function, equivalent to taking the name\n" + "from \\library and \\library_cell_index.\n" + "\n" + "However, this method also works for 'cold proxies' (see \\is_cold_proxy?)\n" + "for which it delivers the name of cell inside the (missing) library.\n" + "\n" + "This method has been introduced in version 0.30.8.\n" + ) + + gsi::method_ext ("library_name", &library_name, + "@brief Returns the name of the library from which the cell is imported\n" + "If the cell is not imported from a library, the return value is an empty string.\n" + "This method is basically a convenience function, equivalent to taking the name\n" + "from \\library. However, this method also works for 'cold proxies' (see \\is_cold_proxy?)\n" + "for which it delivers the name of the (missing) library.\n" + "\n" + "This method has been introduced in version 0.30.8.\n" + ) + gsi::method_ext ("change_ref", &change_library_ref, gsi::arg ("lib_id"), gsi::arg ("lib_cell_index"), "@brief Changes the reference to a different library cell\n" "This method requires a cell that is a library reference (i.e. \\is_library_cell? is true). It will " @@ -3307,6 +3410,9 @@ Class decl_Cell ("db", "Cell", "If the cell is not a PCell variant or the name is not a valid PCell parameter name, " "the return value is nil.\n" "\n" + "This method also works for 'cold proxies' (see \\is_cold_proxy?)\n" + "for which it delivers the value of the given stored PCell parameter.\n" + "\n" "This method has been introduced in version 0.25." ) + gsi::method_ext ("pcell_parameters_by_name", &pcell_parameters_by_name, @@ -3316,8 +3422,20 @@ Class decl_Cell ("db", "Cell", "method returns an empty dictionary. This method also returns the PCell parameters if\n" "the cell is a PCell imported from a library.\n" "\n" + "This method also works for 'cold proxies' (see \\is_cold_proxy?)\n" + "for which it delivers the names and values of the stored PCell parameters.\n" + "\n" "This method has been introduced in version 0.24.\n" ) + + gsi::method_ext ("pcell_name", &pcell_name, + "@brief Returns the PCell name if the cell is a PCell variant\n" + "If this cell is not a PCell variant, this method returns an empty string.\n" + "This method is basically a convenience function, equivalent to taking the name\n" + "from \\pcell_declaration. However, this method also works for 'cold proxies' (see \\is_cold_proxy?)\n" + "for which it delivers the name of the (missing) PCell.\n" + "\n" + "This method has been introduced in version 0.30.9.\n" + ) + gsi::method_ext ("pcell_declaration", &pcell_declaration, "@brief Returns a reference to the PCell declaration\n" "If this cell is not a PCell variant, this method returns nil.\n" From 0728feba33e7b6a632e1381262d17ed6f53e52b5 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 2 May 2026 00:20:57 +0200 Subject: [PATCH 02/26] Bugfix: rbaTests:dbPCellTests were disabled, added tests for new features for cold proxies --- src/db/db/gsiDeclDbCell.cc | 2 +- testdata/ruby/dbPCells.rb | 88 +++++++++++++++++++++++++++++++------- 2 files changed, 74 insertions(+), 16 deletions(-) diff --git a/src/db/db/gsiDeclDbCell.cc b/src/db/db/gsiDeclDbCell.cc index cbe730fa4..89b7867f3 100644 --- a/src/db/db/gsiDeclDbCell.cc +++ b/src/db/db/gsiDeclDbCell.cc @@ -3318,7 +3318,7 @@ Class decl_Cell ("db", "Cell", "\n" "This method has been introduced in version 0.22.\n" ) + - gsi::method_ext ("library_name", &library_cell_name, + gsi::method_ext ("library_cell_name", &library_cell_name, "@brief Returns the cell name inside the library from which the cell is imported\n" "If the cell is not imported from a library, the return value is an empty string.\n" "This method is basically a convenience function, equivalent to taking the name\n" diff --git a/testdata/ruby/dbPCells.rb b/testdata/ruby/dbPCells.rb index 6ba044e78..864b82ead 100644 --- a/testdata/ruby/dbPCells.rb +++ b/testdata/ruby/dbPCells.rb @@ -277,7 +277,7 @@ def norm_hash(hash) class DBPCellAPI_TestClass < TestBase - def _test_1 + def test_1 # PCellParameterDeclaration @@ -351,7 +351,7 @@ def _test_1 class DBPCell_TestClass < TestBase - def _test_1 + def test_1 # instantiate and register the library tl = PCellTestLib::new @@ -593,7 +593,7 @@ def test_1a end - def _test_2 + def test_2 # instantiate and register the library tl = PCellTestLib::new @@ -632,7 +632,7 @@ def _test_2 end - def _test_3 + def test_3 # instantiate and register the library tl = PCellTestLib::new @@ -657,7 +657,7 @@ def _test_3 end - def _test_4 + def test_4 # instantiate and register the library tl = PCellTestLib::new @@ -674,7 +674,7 @@ def _test_4 end - def _test_5 + def test_5 # instantiate and register the library tl = PCellTestLib::new @@ -691,7 +691,7 @@ def _test_5 end - def _test_6 + def test_6 # instantiate and register the library tl = PCellTestLib::new @@ -707,7 +707,7 @@ def _test_6 end - def _test_7 + def test_7 # instantiate and register the library tl = PCellTestLib::new @@ -725,7 +725,7 @@ def _test_7 end - def _test_8 + def test_8 # instantiate and register the library tl = PCellTestLib::new @@ -747,7 +747,7 @@ def _test_8 end - def _test_9 + def test_9 layout = RBA::Layout::new @@ -813,7 +813,7 @@ def reregister_pcell end - def _test_10 + def test_10 lib = CircleLib1782::new("CircleLib") @@ -864,7 +864,7 @@ def _test_10 end - def _test_11 + def test_11 lib = CircleLib1782::new("CircleLib") @@ -899,7 +899,7 @@ def _test_11 end - def _test_12 + def test_12 if !RBA.constants.member?(:PCellDeclarationHelper) return @@ -924,7 +924,7 @@ def _test_12 end # convert to static cell - def _test_13 + def test_13 if !RBA.constants.member?(:PCellDeclarationHelper) return @@ -954,11 +954,69 @@ def _test_13 end + # cold proxies + def test_14 + + # instantiate and register the library + tl = PCellTestLib::new + + ly = RBA::Layout::new(true) + ly.dbu = 0.01 + + li1 = ly.layer(1, 0) + + scell = ly.create_cell("A") + + param = { "w" => 4.0, "h" => 8.0, "l" => RBA::LayerInfo::new(1, 0) } + cell = ly.create_cell("Box", "PCellTestLib", param) + cell.name = "B" + + assert_equal(scell.is_proxy?, false) + assert_equal(scell.is_library_cell?, false) + assert_equal(scell.is_cold_proxy?, false) + assert_equal(scell.pcell_parameter("h").to_s, "") + assert_equal(scell.pcell_parameter("x").to_s, "") + assert_equal(scell.pcell_parameters_by_name.to_s, "{}") + assert_equal(scell.pcell_name, "") + assert_equal(scell.library_name, "") + assert_equal(scell.library_cell_name, "") + + assert_equal(cell.is_proxy?, true) + assert_equal(cell.is_library_cell?, true) + assert_equal(cell.is_cold_proxy?, false) + assert_equal(cell.pcell_parameter("h").to_s, "8.0") + assert_equal(cell.pcell_parameter("x").to_s, "") + assert_equal(cell.pcell_parameters_by_name.to_s, "{\"h\"=>8.0, \"l\"=><1/0>, \"w\"=>4.0}") + assert_equal(cell.pcell_name, "Box") + assert_equal(cell.library_cell_name, "Box_L1d0_W4p000_H8p000") # it's the library's PCell proxy + assert_equal(cell.library_name, "PCellTestLib") + assert_equal(cell.display_title, "PCellTestLib.Box(L=1/0,W=4.000,H=8.000)") + + tl._destroy + + # the cell got destroyed and replaced by a cold proxy + assert_equal(cell._destroyed?, true) + + cell = ly.cell("B") + + assert_equal(cell.is_proxy?, true) + assert_equal(cell.is_library_cell?, false) + assert_equal(cell.is_cold_proxy?, true) + assert_equal(cell.pcell_parameter("h").to_s, "8.0") + assert_equal(cell.pcell_parameter("x").to_s, "") + assert_equal(cell.pcell_parameters_by_name.to_s, "{\"h\"=>8.0, \"l\"=><1/0>, \"w\"=>4.0}") + assert_equal(cell.pcell_name, "Box") + assert_equal(cell.library_cell_name, "") # it's not a static library cell + assert_equal(cell.library_name, "PCellTestLib") + assert_equal(cell.display_title, "PCellTestLib.Box") + + end + end class DBPCellParameterStates_TestClass < TestBase - def _test_1 + def test_1 ps = RBA::PCellParameterState::new From d8016a94468e4a461c014f3afa25125321940db7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 2 May 2026 00:29:07 +0200 Subject: [PATCH 03/26] Polishing doc. --- src/db/db/gsiDeclDbCell.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/db/db/gsiDeclDbCell.cc b/src/db/db/gsiDeclDbCell.cc index 89b7867f3..07ca93e64 100644 --- a/src/db/db/gsiDeclDbCell.cc +++ b/src/db/db/gsiDeclDbCell.cc @@ -3272,20 +3272,20 @@ Class decl_Cell ("db", "Cell", ) + gsi::method_ext ("is_cold_proxy?", &is_cold_proxy, "@brief Returns true, if the cell is a 'cold proxy'\n" - "Cold proxies are cells that refer to a library but can temporarily not be resolved -\n" + "Cold proxies are cells that refer to a library cell or PCell variant, but can temporarily not be resolved -\n" "for example, because the library is not installed. Such cells are basically placeholders\n" "for library references and also carry PCell parameter information needed to establish\n" - "the link to the library cell again, once the library is available again.\n" + "the link to the library PCell, once the library is available again.\n" "\n" - "You can use \\library_name to obtain the library name the proxy points to, " + "You can use \\library_name to obtain the name of the library the proxy points to, " "\\library_cell_name to obtain the cell name in that library, " "\\pcell_name to obtain the PCell name if it is a PCell proxy, " - "and \\pcell_parameters_by_name to obtain the PCell parameters.\n" + "and \\pcell_parameter or \\pcell_parameters_by_name to obtain the PCell parameters.\n" "\n" "Cold proxies cannot be created or modified. Cold proxies are basically error indicators " "and should be fixed by installing the respective library. Their layout state\n" "reflects the last version of the layout when the cell was functional and properly\n" - "linked to a library. Correspondingly, they can be used in read-only applications.\n" + "linked to a library. Still, they can be used in read-only applications.\n" "\n" "This method has been introduced in version 0.30.9.\n" ) + @@ -3323,6 +3323,8 @@ Class decl_Cell ("db", "Cell", "If the cell is not imported from a library, the return value is an empty string.\n" "This method is basically a convenience function, equivalent to taking the name\n" "from \\library and \\library_cell_index.\n" + "Note that for PCells, 'library_cell_name' is the name of the PCell proxy cell inside " + "the library, not the name of the PCell.\n" "\n" "However, this method also works for 'cold proxies' (see \\is_cold_proxy?)\n" "for which it delivers the name of cell inside the (missing) library.\n" From 1740523092f5d9d09e3cbe4fd0e1adce34390348 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 2 May 2026 15:46:29 +0200 Subject: [PATCH 04/26] Introducing a number of convenience methods for Layout * Layout#delete_cells with a list of cell object * Layout#delete_cell_rec with a cell object * Layout#prune_cell with a cell object * Layout#prune_cells for pruning multiple cells in one call * Layout#prune_subcells with a cell object * Layout#prune_subcells with multiple root cells * Layout#flatten with a cell object * Layout#flatten_into with cell objects * Layout#delete_cell with a cell object * Default value "all" for "levels" argument in "prune_subcells" and "prune_cell" * Default value "all" for "levels" and "true" for "prune" argument in "flatten" * Default value "all" for "levels" and "unity" for "trans" argument in "flatten_into" --- src/db/db/gsiDeclDbLayout.cc | 266 ++++++++++++++++++++++++++++++-- testdata/ruby/dbLayoutTests1.rb | 164 +++++++++++++++++--- 2 files changed, 395 insertions(+), 35 deletions(-) diff --git a/src/db/db/gsiDeclDbLayout.cc b/src/db/db/gsiDeclDbLayout.cc index 9d76ae5e9..46329ec13 100644 --- a/src/db/db/gsiDeclDbLayout.cc +++ b/src/db/db/gsiDeclDbLayout.cc @@ -642,12 +642,22 @@ static tl::Variant get_property_from_id (db::properties_id_type id, const tl::Va } static void -delete_cells (db::Layout *layout, const std::vector &cell_indices) +delete_cells (db::Layout *layout, const std::vector &cell_indexes) { - for (auto ci = cell_indices.begin (); ci != cell_indices.end (); ++ci) { + for (auto ci = cell_indexes.begin (); ci != cell_indexes.end (); ++ci) { check_cell_index (layout, *ci); } - layout->delete_cells (cell_indices.begin (), cell_indices.end ()); + layout->delete_cells (cell_indexes.begin (), cell_indexes.end ()); +} + +static void +delete_cells_ptr (db::Layout *layout, const std::vector &cells) +{ + std::set cell_indexes; + for (auto c = cells.begin (); c != cells.end (); ++c) { + cell_indexes.insert ((*c)->cell_index ()); + } + layout->delete_cells (cell_indexes); } static void @@ -657,20 +667,76 @@ delete_cell_rec (db::Layout *layout, db::cell_index_type cell_index) layout->delete_cell_rec (cell_index); } -static void +static void +delete_cell_rec_ptr (db::Layout *layout, db::Cell *cell) +{ + layout->delete_cell_rec (cell->cell_index ()); +} + +static void prune_cell (db::Layout *layout, db::cell_index_type cell_index, int levels) { check_cell_index (layout, cell_index); layout->prune_cell (cell_index, levels); } -static void +static void +prune_cell_ptr (db::Layout *layout, db::Cell *cell, int levels) +{ + layout->prune_cell (cell->cell_index (), levels); +} + +static void +prune_cells (db::Layout *layout, const std::vector &cell_indexes, int levels) +{ + for (auto ci = cell_indexes.begin (); ci != cell_indexes.end (); ++ci) { + check_cell_index (layout, *ci); + } + layout->prune_cells (cell_indexes.begin (), cell_indexes.end (), levels); +} + +static void +prune_cells_ptr (db::Layout *layout, const std::vector &cells, int levels) +{ + std::set cell_indexes; + for (auto c = cells.begin (); c != cells.end (); ++c) { + cell_indexes.insert ((*c)->cell_index ()); + } + layout->prune_cells (cell_indexes, levels); +} + +static void prune_subcells (db::Layout *layout, db::cell_index_type cell_index, int levels) { check_cell_index (layout, cell_index); layout->prune_subcells (cell_index, levels); } +static void +prune_subcells_ptr (db::Layout *layout, db::Cell *cell, int levels) +{ + layout->prune_subcells (cell->cell_index (), levels); +} + +static void +prune_subcells_many (db::Layout *layout, const std::vector &cell_indexes, int levels) +{ + for (auto ci = cell_indexes.begin (); ci != cell_indexes.end (); ++ci) { + check_cell_index (layout, *ci); + } + layout->prune_subcells (cell_indexes.begin (), cell_indexes.end (), levels); +} + +static void +prune_subcells_many_ptr (db::Layout *layout, const std::vector &cells, int levels) +{ + std::set cell_indexes; + for (auto c = cells.begin (); c != cells.end (); ++c) { + cell_indexes.insert ((*c)->cell_index ()); + } + layout->prune_subcells (cell_indexes, levels); +} + static void flatten (db::Layout *layout, db::cell_index_type cell_index, int levels, bool prune) { @@ -678,7 +744,16 @@ flatten (db::Layout *layout, db::cell_index_type cell_index, int levels, bool pr layout->flatten (layout->cell (cell_index), levels, prune); } -static void +static void +flatten_ptr (db::Layout *layout, db::Cell *cell, int levels, bool prune) +{ + if (! cell) { + return; + } + layout->flatten (*cell, levels, prune); +} + +static void flatten_into (db::Layout *layout, db::cell_index_type cell_index, db::cell_index_type target_cell_index, const db::ICplxTrans &t, int levels) { check_cell_index (layout, cell_index); @@ -686,7 +761,16 @@ flatten_into (db::Layout *layout, db::cell_index_type cell_index, db::cell_index layout->flatten (layout->cell (cell_index), layout->cell (target_cell_index), t, levels); } -static void +static void +flatten_into_ptr (db::Layout *layout, db::Cell *cell, db::Cell *target_cell, const db::ICplxTrans &t, int levels) +{ + if (! cell || ! target_cell) { + return; + } + layout->flatten (*cell, *target_cell, t, levels); +} + +static void write_simple (db::Layout *layout, const std::string &filename) { db::SaveLayoutOptions options; @@ -866,6 +950,11 @@ static void delete_cell (db::Layout *ly, db::cell_index_type ci) ly->delete_cell (ci); } +static void delete_cell_ptr (db::Layout *ly, db::Cell *cell) +{ + ly->delete_cell (cell->cell_index ()); +} + static void rename_cell (db::Layout *ly, db::cell_index_type ci, const std::string &name) { check_cell_index (ly, ci); @@ -1655,6 +1744,20 @@ Class decl_Layout ("db", "Layout", "\n" "This method has been introduced in version 0.20.\n" ) + + gsi::method_ext ("delete_cell", &delete_cell_ptr, gsi::arg ("cell"), + "@brief Deletes a cell\n" + "\n" + "This deletes a cell but not the sub cells of the cell.\n" + "These subcells will likely become new top cells unless they are used\n" + "otherwise.\n" + "All instances of this cell are deleted as well.\n" + "Hint: to delete multiple cells, use \"delete_cells\" which is \n" + "far more efficient in this case.\n" + "\n" + "@param cell The cell to delete\n" + "\n" + "This convenience variant taking a cell object has been introduced in version 0.30.9.\n" + ) + gsi::method_ext ("delete_cells", &delete_cells, gsi::arg ("cell_index_list"), "@brief Deletes multiple cells\n" "\n" @@ -1667,19 +1770,73 @@ Class decl_Layout ("db", "Layout", "\n" "This method has been introduced in version 0.20.\n" ) + - gsi::method_ext ("prune_subcells", &prune_subcells, gsi::arg ("cell_index"), gsi::arg ("levels"), + gsi::method_ext ("delete_cells", &delete_cells_ptr, gsi::arg ("cell_list"), + "@brief Deletes multiple cells\n" + "\n" + "This deletes the cells but not the sub cells of these cells.\n" + "These subcells will likely become new top cells unless they are used\n" + "otherwise.\n" + "All instances of these cells are deleted as well.\n" + "\n" + "@param cell_list An list of cells to delete\n" + "\n" + "This convenience variant taking a list of cell objects has been introduced in version 0.30.9.\n" + ) + + gsi::method_ext ("prune_subcells", &prune_subcells, gsi::arg ("cell_index"), gsi::arg ("levels", -1), "@brief Deletes all sub cells of the cell which are not used otherwise down to the specified level of hierarchy\n" "\n" "This deletes all sub cells of the cell which are not used otherwise.\n" "All instances of the deleted cells are deleted as well.\n" "It is possible to specify how many levels of hierarchy below the given root cell are considered.\n" "\n" + "A variant exists that takes a list of cell indexes and which is more efficient than calling\n" + "'prune_subcells' multiple times on a single cell.\n" + "\n" "@param cell_index The root cell from which to delete a sub cells\n" "@param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.)\n" "\n" "This method has been introduced in version 0.20.\n" ) + - gsi::method_ext ("prune_cell", &prune_cell, gsi::arg ("cell_index"), gsi::arg ("levels"), + gsi::method_ext ("prune_subcells", &prune_subcells_ptr, gsi::arg ("cell"), gsi::arg ("levels", -1), + "@brief Deletes all sub cells of the cell which are not used otherwise down to the specified level of hierarchy\n" + "\n" + "This deletes all sub cells of the cell which are not used otherwise.\n" + "All instances of the deleted cells are deleted as well.\n" + "It is possible to specify how many levels of hierarchy below the given root cell are considered.\n" + "\n" + "A variant exists that takes a list of cells and which is more efficient than calling\n" + "'prune_subcells' multiple times on a single cell.\n" + "\n" + "@param cell The root cell from which to delete a sub cells\n" + "@param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.)\n" + "\n" + "This convenience variant taking a list of cell objects has been introduced in version 0.30.9.\n" + ) + + gsi::method_ext ("prune_subcells", &prune_subcells_many, gsi::arg ("cell_index_list"), gsi::arg ("levels", -1), + "@brief Deletes all sub cells of the given cells which are not used otherwise down to the specified level of hierarchy\n" + "\n" + "This deletes all sub cells of the given cells which are not used otherwise.\n" + "All instances of the deleted cells are deleted as well.\n" + "It is possible to specify how many levels of hierarchy below the given root cell are considered.\n" + "\n" + "@param cell_index_list The root cells from which to delete the sub cells\n" + "@param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.)\n" + "\n" + "This method has been introduced in version 0.30.9.\n" + ) + + gsi::method_ext ("prune_subcells", &prune_subcells_many_ptr, gsi::arg ("cell_list"), gsi::arg ("levels", -1), + "@brief Deletes all sub cells of the given cells which are not used otherwise down to the specified level of hierarchy\n" + "\n" + "This deletes all sub cells of the given cells which are not used otherwise.\n" + "All instances of the deleted cells are deleted as well.\n" + "It is possible to specify how many levels of hierarchy below the given root cell are considered.\n" + "\n" + "@param cell_list The root cells from which to delete the sub cells\n" + "@param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.)\n" + "\n" + "This method has been introduced in version 0.30.9.\n" + ) + + gsi::method_ext ("prune_cell", &prune_cell, gsi::arg ("cell_index"), gsi::arg ("levels", -1), "@brief Deletes a cell plus subcells not used otherwise\n" "\n" "This deletes a cell and also all sub cells of the cell which are not used otherwise.\n" @@ -1687,10 +1844,53 @@ Class decl_Layout ("db", "Layout", "only the direct children of the cell are deleted with the cell itself.\n" "All instances of this cell are deleted as well.\n" "\n" + "A version that allows pruning multiple cells in one call is \\prune_cells.\n" + "\n" "@param cell_index The index of the cell to delete\n" "@param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.)\n" "\n" - "This method has been introduced in version 0.20.\n" + "This method has been introduced in version 0.20. The 'levels' argument was made optional in version 0.30.9.\n" + ) + + gsi::method_ext ("prune_cell", &prune_cell_ptr, gsi::arg ("cell"), gsi::arg ("levels", -1), + "@brief Deletes a cell plus subcells not used otherwise\n" + "\n" + "This deletes a cell and also all sub cells of the cell which are not used otherwise.\n" + "The number of hierarchy levels to consider can be specified as well. One level of hierarchy means that " + "only the direct children of the cell are deleted with the cell itself.\n" + "All instances of this cell are deleted as well.\n" + "\n" + "A version that allows pruning multiple cells in one call is \\prune_cells.\n" + "\n" + "@param cell The cell to delete\n" + "@param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.)\n" + "\n" + "This convenience variant taking a cell object has been introduced in version 0.30.9.\n" + ) + + gsi::method_ext ("prune_cells", &prune_cells, gsi::arg ("cell_indexes"), gsi::arg ("levels", -1), + "@brief Deletes cells plus subcells not used otherwise\n" + "\n" + "This deletes the given cells and also all sub cells of the cells which are not used otherwise.\n" + "The number of hierarchy levels to consider can be specified as well. One level of hierarchy means that " + "only the direct children of the cell are deleted with the cell itself.\n" + "All instances of the pruned cells are deleted as well.\n" + "\n" + "@param cell_indexes The indexes of the cells to delete\n" + "@param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.)\n" + "\n" + "This method has been introduced in version 0.30.9.\n" + ) + + gsi::method_ext ("prune_cells", &prune_cells_ptr, gsi::arg ("cells"), gsi::arg ("levels", -1), + "@brief Deletes cells plus subcells not used otherwise\n" + "\n" + "This deletes the given cells and also all sub cells of the cells which are not used otherwise.\n" + "The number of hierarchy levels to consider can be specified as well. One level of hierarchy means that " + "only the direct children of the cell are deleted with the cell itself.\n" + "All instances of the pruned cells are deleted as well.\n" + "\n" + "@param cells The cells to delete\n" + "@param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.)\n" + "\n" + "This method has been introduced in version 0.30.9.\n" ) + gsi::method_ext ("delete_cell_rec", &delete_cell_rec, gsi::arg ("cell_index"), "@brief Deletes a cell plus all subcells\n" @@ -1702,6 +1902,16 @@ Class decl_Layout ("db", "Layout", "\n" "This method has been introduced in version 0.20.\n" ) + + gsi::method_ext ("delete_cell_rec", &delete_cell_rec_ptr, gsi::arg ("cell"), + "@brief Deletes a cell plus all subcells\n" + "\n" + "This deletes a cell and also all sub cells of the cell.\n" + "In contrast to \\prune_cell, all cells are deleted together with their instances even if they are used otherwise.\n" + "\n" + "@param cell The cell to delete\n" + "\n" + "This convenience variant taking a cell object has been introduced in version 0.30.9.\n" + ) + gsi::method_ext ("insert", &insert_region, gsi::arg ("cell_index"), gsi::arg ("layer"), gsi::arg ("region"), "@brief Inserts a region into the given cell and layer\n" @@ -1750,7 +1960,7 @@ Class decl_Layout ("db", "Layout", "\n" "This method has been introduced in version 0.27.\n" ) + - gsi::method_ext ("flatten", &flatten, gsi::arg ("cell_index"), gsi::arg ("levels"), gsi::arg ("prune"), + gsi::method_ext ("flatten", &flatten, gsi::arg ("cell_index"), gsi::arg ("levels", -1), gsi::arg ("prune", true), "@brief Flattens the given cell\n" "\n" "This method propagates all shapes and instances from the specified number of hierarchy levels below into the given cell.\n" @@ -1761,9 +1971,22 @@ Class decl_Layout ("db", "Layout", "@param levels The number of hierarchy levels to flatten (-1: all, 0: none, 1: one level etc.)\n" "@param prune Set to true to remove orphan cells.\n" "\n" - "This method has been introduced in version 0.20.\n" + "This method has been introduced in version 0.20. The 'levels' and 'prune' arguments have been made optional in version 0.30.9.\n" + ) + + gsi::method_ext ("flatten", &flatten_ptr, gsi::arg ("cell"), gsi::arg ("levels", -1), gsi::arg ("prune", true), + "@brief Flattens the given cell\n" + "\n" + "This method propagates all shapes and instances from the specified number of hierarchy levels below into the given cell.\n" + "It also removes the instances of the cells from which the shapes came from, but does not remove the cells themselves if prune is set to false.\n" + "If prune is set to true, these cells are removed if not used otherwise.\n" + "\n" + "@param cell The cell which should be flattened\n" + "@param levels The number of hierarchy levels to flatten (-1: all, 0: none, 1: one level etc.)\n" + "@param prune Set to true to remove orphan cells.\n" + "\n" + "This convenience variant taking a cell object has been introduced in version 0.30.9.\n" ) + - gsi::method_ext ("flatten_into", &flatten_into, gsi::arg ("source_cell_index"), gsi::arg ("target_cell_index"), gsi::arg ("trans"), gsi::arg ("levels"), + gsi::method_ext ("flatten_into", &flatten_into, gsi::arg ("source_cell_index"), gsi::arg ("target_cell_index"), gsi::arg ("trans", db::ICplxTrans (), "unity"), gsi::arg ("levels", -1), "@brief Flattens the given cell into another cell\n" "\n" "This method works like 'flatten', but allows specification of a target cell which can be different from the source cell plus " @@ -1776,7 +1999,22 @@ Class decl_Layout ("db", "Layout", "@param trans The transformation to apply on the output shapes and instances\n" "@param levels The number of hierarchy levels to flatten (-1: all, 0: none, 1: one level etc.)\n" "\n" - "This method has been introduced in version 0.24.\n" + "This method has been introduced in version 0.24. The 'trans' and 'levels' arguments have been made optional is version 0.30.9.\n" + ) + + gsi::method_ext ("flatten_into", &flatten_into_ptr, gsi::arg ("source_cell"), gsi::arg ("target_cell"), gsi::arg ("trans", db::ICplxTrans (), "unity"), gsi::arg ("levels", -1), + "@brief Flattens the given cell into another cell\n" + "\n" + "This method works like 'flatten', but allows specification of a target cell which can be different from the source cell plus " + "a transformation which is applied for all shapes and instances in the target cell.\n" + "\n" + "In contrast to the 'flatten' method, the source cell is not modified.\n" + "\n" + "@param source_cell The source cell which should be flattened\n" + "@param target_cell The target cell into which the resulting objects are written\n" + "@param trans The transformation to apply on the output shapes and instances\n" + "@param levels The number of hierarchy levels to flatten (-1: all, 0: none, 1: one level etc.)\n" + "\n" + "This convenience variant taking a cell objects has been introduced in version 0.30.9.\n" ) + gsi::method ("start_changes", &db::Layout::start_changes, "@brief Signals the start of an operation bringing the layout into invalid state\n" diff --git a/testdata/ruby/dbLayoutTests1.rb b/testdata/ruby/dbLayoutTests1.rb index ecf2fa19b..97c903762 100644 --- a/testdata/ruby/dbLayoutTests1.rb +++ b/testdata/ruby/dbLayoutTests1.rb @@ -335,34 +335,21 @@ def test_5a c2.insert(RBA::CellInstArray.new(c3.cell_index, RBA::Trans.new(RBA::Point.new(1100, 0)))) assert_equal(c0.is_empty?, false) + c0_index = c0.cell_index + assert_equal(collect_hier(l), "[c0](P=)(C=c1,c2,c3)/[c1](P=c0)(C=)/[c2](P=c0)(C=c3)/[c3](P=c0,c2)(C=)"); - c0_index = c0.cell_index + ll = l.dup assert_equal(l.is_valid_cell_index?(c0_index), true) - l.delete_cell(c0.cell_index) + l.delete_cell(c0_index) assert_equal(l.is_valid_cell_index?(c0_index), false) assert_equal(collect_hier(l), "[c1](P=)(C=)/[c2](P=)(C=c3)/[c3](P=c2)(C=)"); assert_equal(c3.is_empty?, true) - l = RBA::Layout.new - l.insert_layer_at(0, RBA::LayerInfo.new(1, 0)) - c0 = l.cell(l.add_cell("c0")) - c1 = l.cell(l.add_cell("c1")) - c2 = l.cell(l.add_cell("c2")) - c3 = l.cell(l.add_cell("c3")) - assert_equal(c0.is_empty?, true) - - tt = RBA::Trans.new - c0.insert(RBA::CellInstArray.new(c1.cell_index, tt)) - c0.insert(RBA::CellInstArray.new(c2.cell_index, RBA::Trans.new(RBA::Point.new(100, -100)))) - c0.insert(RBA::CellInstArray.new(c3.cell_index, RBA::Trans.new(1))) - c2.insert(RBA::CellInstArray.new(c3.cell_index, RBA::Trans.new(RBA::Point.new(1100, 0)))) - assert_equal(c0.is_empty?, false) - - assert_equal(collect_hier(l), "[c0](P=)(C=c1,c2,c3)/[c1](P=c0)(C=)/[c2](P=c0)(C=c3)/[c3](P=c0,c2)(C=)"); + l = ll + ll = l.dup - c0_index = c0.cell_index assert_equal(l.is_valid_cell_index?(c0_index), true) l.cell(c0_index).delete assert_equal(l.is_valid_cell_index?(c0_index), false) @@ -370,6 +357,16 @@ def test_5a assert_equal(collect_hier(l), "[c1](P=)(C=)/[c2](P=)(C=c3)/[c3](P=c2)(C=)"); assert_equal(c3.is_empty?, true) + l = ll + ll = l.dup + + assert_equal(l.is_valid_cell_index?(c0_index), true) + # with cell object + l.delete_cell(l.cell(c0_index)) + assert_equal(l.is_valid_cell_index?(c0_index), false) + assert_equal(collect_hier(l), "[c1](P=)(C=)/[c2](P=)(C=c3)/[c3](P=c2)(C=)"); + assert_equal(c3.is_empty?, true) + end def test_5b @@ -413,10 +410,17 @@ def test_5b assert_equal(collect_hier(l), "[c1](P=)(C=)/[c3](P=)(C=)"); l = ll + ll = l.dup # Hint: even though we deleted c0 and c2, their indices are still valid l.delete_cells([c2_index, c0_index]) assert_equal(collect_hier(l), "[c1](P=)(C=)/[c3](P=)(C=)"); + l = ll + ll = l.dup + # with cell objects + l.delete_cells([l.cell(c2_index), l.cell(c0_index)]) + assert_equal(collect_hier(l), "[c1](P=)(C=)/[c3](P=)(C=)"); + end def test_5d @@ -487,6 +491,7 @@ def test_5d c0_index = c0.cell_index c2_index = c2.cell_index + c3_index = c3.cell_index ll = l.dup @@ -499,6 +504,42 @@ def test_5d l.cell(c2_index).prune_cell(-1) assert_equal(collect_hier(l), "[c0](P=)(C=c1,c3)/[c1](P=c0)(C=)/[c3](P=c0)(C=)"); + l = ll + ll = l.dup + # As method of Layout + l.prune_cell(c2_index, -1) + assert_equal(collect_hier(l), "[c0](P=)(C=c1,c3)/[c1](P=c0)(C=)/[c3](P=c0)(C=)"); + + l = ll + ll = l.dup + # As method of Layout with default argument + l.prune_cell(c2_index) + assert_equal(collect_hier(l), "[c0](P=)(C=c1,c3)/[c1](P=c0)(C=)/[c3](P=c0)(C=)"); + + l = ll + ll = l.dup + # As method of Layout with cell object + l.prune_cell(l.cell(c2_index), -1) + assert_equal(collect_hier(l), "[c0](P=)(C=c1,c3)/[c1](P=c0)(C=)/[c3](P=c0)(C=)"); + + l = ll + ll = l.dup + # As method of Layout with cell object and default argument + l.prune_cell(l.cell(c2_index), -1) + assert_equal(collect_hier(l), "[c0](P=)(C=c1,c3)/[c1](P=c0)(C=)/[c3](P=c0)(C=)"); + + l = ll + ll = l.dup + # prune c2 and c3 + l.prune_cells([ c2_index, c3_index ], -1) + assert_equal(collect_hier(l), "[c0](P=)(C=c1)/[c1](P=c0)(C=)"); + + l = ll + ll = l.dup + # prune c2 and c3 with cell objects + l.prune_cells([ l.cell(c2_index), l.cell(c3_index) ], -1) + assert_equal(collect_hier(l), "[c0](P=)(C=c1)/[c1](P=c0)(C=)"); + end def test_5e @@ -546,6 +587,12 @@ def test_5e l.delete_cell_rec(c2_index) assert_equal(collect_hier(l), "[c0](P=)(C=c1)/[c1](P=c0)(C=)"); + l = ll + ll = l.dup + # with cell object + l.delete_cell_rec(l.cell(c2_index)) + assert_equal(collect_hier(l), "[c0](P=)(C=c1)/[c1](P=c0)(C=)"); + end def test_5f @@ -589,12 +636,38 @@ def test_5f l = ll ll = l.dup - l.flatten(c0_index, -1, true) + l.flatten(c0_index) assert_equal(collect_hier(l), "[c0](P=)(C=)"); ii = l.begin_shapes(c0_index, 0); assert_equal(collect(ii, l), "[c0](0,100;1000,1200)/[c0](0,100;1000,1200)/[c0](100,0;1100,1100)/[c0](1200,0;2200,1100)/[c0](-1200,0;-100,1000)"); + l = ll + ll = l.dup + c0flat = l.create_cell("c0flat") + l.flatten_into(c0_index, c0flat.cell_index) + assert_equal(collect_hier(l), "[c0](P=)(C=c1,c2,c3)/[c1](P=c0)(C=)/[c2](P=c0)(C=c3)/[c3](P=c0,c2)(C=)/[c0flat](P=)(C=)"); + + ii = l.begin_shapes(c0flat, 0); + assert_equal(collect(ii, l), "[c0flat](0,100;1000,1200)/[c0flat](0,100;1000,1200)/[c0flat](100,0;1100,1100)/[c0flat](1200,0;2200,1100)/[c0flat](-1200,0;-100,1000)"); + + l = ll + ll = l.dup + l.flatten(l.cell(c0_index), -1, true) # with cell object + assert_equal(collect_hier(l), "[c0](P=)(C=)"); + + ii = l.begin_shapes(c0_index, 0); + assert_equal(collect(ii, l), "[c0](0,100;1000,1200)/[c0](0,100;1000,1200)/[c0](100,0;1100,1100)/[c0](1200,0;2200,1100)/[c0](-1200,0;-100,1000)"); + + l = ll + ll = l.dup + c0flat = l.create_cell("c0flat") + l.flatten_into(l.cell(c0_index), c0flat, RBA::ICplxTrans::new(2.0), -1) + assert_equal(collect_hier(l), "[c0](P=)(C=c1,c2,c3)/[c1](P=c0)(C=)/[c2](P=c0)(C=c3)/[c3](P=c0,c2)(C=)/[c0flat](P=)(C=)"); + + ii = l.begin_shapes(c0flat, 0); + assert_equal(collect(ii, l), "[c0flat](0,200;2000,2400)/[c0flat](0,200;2000,2400)/[c0flat](200,0;2200,2200)/[c0flat](2400,0;4400,2200)/[c0flat](-2400,0;-200,2000)"); + l = ll ll = l.dup l.flatten(c0_index, 0, false) @@ -748,10 +821,59 @@ def test_5g assert_equal(collect_hier(l), "[c0](P=)(C=c1,c2,c3)/[c1](P=c0)(C=)/[c2](P=c0)(C=)/[c3](P=c0)(C=)"); l = ll + ll = l.dup # Hint: even though we deleted c0 and c2, their indices are still valid l.cell(c0_index).prune_subcells(1) assert_equal(collect_hier(l), "[c0](P=)(C=)"); + l = ll + ll = l.dup + # As method of Layout + l.prune_subcells(c0_index, -1) + assert_equal(collect_hier(l), "[c0](P=)(C=)"); + + l = ll + ll = l.dup + # As method of Layout with default argument + l.prune_subcells(c0_index) + assert_equal(collect_hier(l), "[c0](P=)(C=)"); + + l = ll + ll = l.dup + # As method of Layout with Cell object + l.prune_subcells(l.cell(c0_index), -1) + assert_equal(collect_hier(l), "[c0](P=)(C=)"); + + l = ll + ll = l.dup + # As method of Layout with Cell object and default argument + l.prune_subcells(l.cell(c0_index)) + assert_equal(collect_hier(l), "[c0](P=)(C=)"); + + l = ll + ll = l.dup + # As method of Layout with multiple cells + l.prune_subcells([ c0_index ], -1) + assert_equal(collect_hier(l), "[c0](P=)(C=)"); + + l = ll + ll = l.dup + # As method of Layout with multiple cells and default argument + l.prune_subcells([ c0_index ]) + assert_equal(collect_hier(l), "[c0](P=)(C=)"); + + l = ll + ll = l.dup + # As method of Layout with multiple cells and Cell object + l.prune_subcells([ l.cell(c0_index) ], -1) + assert_equal(collect_hier(l), "[c0](P=)(C=)"); + + l = ll + ll = l.dup + # As method of Layout with multiple cells and Cell object and default argument + l.prune_subcells([ l.cell(c0_index) ]) + assert_equal(collect_hier(l), "[c0](P=)(C=)"); + end def test_6 @@ -2365,7 +2487,7 @@ def test_issue1549 end # Properties IDs - def test_issue1549 + def test_propertyIDs ly = RBA::Layout::new From aa50562e38218bb95fe14c22df6ea2f649678eba Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Wed, 20 May 2026 22:52:34 +0200 Subject: [PATCH 05/26] [consider merging] Fixed a type bug in l2n reader --- src/db/db/dbLayoutToNetlistReader.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/db/dbLayoutToNetlistReader.cc b/src/db/db/dbLayoutToNetlistReader.cc index b4dcf2608..2a62e55ef 100644 --- a/src/db/db/dbLayoutToNetlistReader.cc +++ b/src/db/db/dbLayoutToNetlistReader.cc @@ -458,7 +458,7 @@ void LayoutToNetlistStandardReader::read_netlist (db::Netlist *netlist, db::Layo std::string param_name; read_word_or_quoted (param_name); int primary = read_int (); - int default_value = read_double (); + double default_value = read_double (); if (! dc->has_parameter_with_name (param_name)) { db::DeviceParameterDefinition pd; pd.set_name (param_name); From 014f3d064426e0faf94bba587a143671c92d6d27 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 21 May 2026 19:27:48 +0200 Subject: [PATCH 06/26] [consider merging] tab order in density map dialog --- src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui index ded9b81ce..b099be6a8 100644 --- a/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui +++ b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui @@ -552,6 +552,8 @@ le_pixel_size + le_window_size + cb_boundary_mode sb_threads layer_cb cb_source_layer From b5be4fc497c92e858bee9c6e6303090bf775d50e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 00:00:30 +0200 Subject: [PATCH 07/26] Small bugfix on object snap - observe snap directions Problem: snapping with angle constraints and object snapping sometimes gave results not conforming to the angle constaints. This happened when shape edges are parallel to the cutlines that define the angle constraint. --- src/laybasic/laybasic/laySnap.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/laybasic/laybasic/laySnap.cc b/src/laybasic/laybasic/laySnap.cc index 7088b5fe9..e53b25794 100644 --- a/src/laybasic/laybasic/laySnap.cc +++ b/src/laybasic/laybasic/laySnap.cc @@ -554,7 +554,7 @@ class ContourFinder } } - if (! any_point) { + if (! any_point && ! m_projection_constraint) { // no certain direction to look into: // compute the projection of the point and if within a From 4d77056da1e1f8030be955820566a0192e8011d7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 00:11:57 +0200 Subject: [PATCH 08/26] For more clarity change 'snap to objects (unless disabled in template)' to 'never snap to object' (inverted) in rulers config. --- src/ant/ant/RulerConfigPage4.ui | 6 +++--- src/ant/ant/antConfigPage.cc | 4 ++-- src/doc/doc/manual/ruler_properties.xml | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ant/ant/RulerConfigPage4.ui b/src/ant/ant/RulerConfigPage4.ui index 1f5ec4a29..c732cebde 100644 --- a/src/ant/ant/RulerConfigPage4.ui +++ b/src/ant/ant/RulerConfigPage4.ui @@ -594,12 +594,12 @@ - + If checked, snap to edges or vertices of objects unless disabled above - Snap to objects + Never snap to objects @@ -893,7 +893,7 @@ style_cb outline_cb t_angle_cb - t_snap_cbx + t_never_snap_cbx diff --git a/src/ant/ant/antConfigPage.cc b/src/ant/ant/antConfigPage.cc index f92c2c58e..9706615ef 100644 --- a/src/ant/ant/antConfigPage.cc +++ b/src/ant/ant/antConfigPage.cc @@ -375,7 +375,7 @@ ConfigPage4::show () mp_ui->style_cb->setCurrentIndex ((unsigned int) m_ruler_templates [m_current_template].style ()); mp_ui->outline_cb->setCurrentIndex ((unsigned int) m_ruler_templates [m_current_template].outline ()); mp_ui->t_angle_cb->setCurrentIndex ((unsigned int) m_ruler_templates [m_current_template].angle_constraint ()); - mp_ui->t_snap_cbx->setChecked (m_ruler_templates [m_current_template].snap ()); + mp_ui->t_never_snap_cbx->setChecked (! m_ruler_templates [m_current_template].snap ()); mp_ui->t_mode_cb->setCurrentIndex ((unsigned int) m_ruler_templates [m_current_template].mode ()); mp_ui->main_position->setCurrentIndex ((unsigned int) m_ruler_templates [m_current_template].main_position ()); @@ -410,7 +410,7 @@ ConfigPage4::commit () ant::Template::ruler_mode_type mode = ant::Template::ruler_mode_type (mp_ui->t_mode_cb->currentIndex ()); m_ruler_templates [m_current_template].set_mode (mode); - m_ruler_templates [m_current_template].snap (mp_ui->t_snap_cbx->isChecked ()); + m_ruler_templates [m_current_template].snap (! mp_ui->t_never_snap_cbx->isChecked ()); m_ruler_templates [m_current_template].set_main_position (Object::position_type (mp_ui->main_position->currentIndex ())); m_ruler_templates [m_current_template].set_main_xalign (Object::alignment_type (mp_ui->main_xalign->currentIndex ())); diff --git a/src/doc/doc/manual/ruler_properties.xml b/src/doc/doc/manual/ruler_properties.xml index 8a23920d4..184c02f41 100644 --- a/src/doc/doc/manual/ruler_properties.xml +++ b/src/doc/doc/manual/ruler_properties.xml @@ -33,14 +33,14 @@ just being horizontal. By default, the ruler uses the global setting. It can however be configured to provide its own constraint. -
  • Object snapping: each ruler can be configure to snap to the closest object edge or vertex. - By default, the rulers use the global setting. It may be disabled however for each ruler. -
  • Mode: in normal mode, two clicks are required to define a ruler: to set the first point and to set the second one. In "Single click" mode, a single click will set both points to the same. In "Auto measure" mode, the points will be determined by looking for edges in the vicinity of the click point and adjusting the points accordingly.
  • +
  • Never snap to objects: if this option is on, the ruler never snaps to drawing + objects, even if that global option is enabled. +
  • From e42bb63e8a1e0d53e4c98db6c8c138b64f20ce40 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 00:13:51 +0200 Subject: [PATCH 09/26] Renaming 'editor options' to 'tool options' --- src/lay/lay/layMainWindow.cc | 4 ++-- src/layview/layview/layEditorOptionsPages.cc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index f63026e95..2ad58023c 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -290,7 +290,7 @@ MainWindow::MainWindow (QApplication *app, const char *name, bool undo_enabled) connect (mp_libs_dock_widget, SIGNAL (visibilityChanged (bool)), this, SLOT (dock_widget_visibility_changed (bool))); m_libs_visible = true; - mp_eo_dock_widget = new QDockWidget (QObject::tr ("Editor Options"), this); + mp_eo_dock_widget = new QDockWidget (QObject::tr ("Tool Options"), this); mp_eo_dock_widget->setObjectName (QString::fromUtf8 ("eo_dock_widget")); mp_eo_dock_widget->setMinimumHeight (150); mp_eo_stack = new ControlWidgetStack (mp_eo_dock_widget, "eo_stack"); @@ -4502,7 +4502,7 @@ class MainWindowPluginDeclaration at = "edit_menu.end"; menu_entries.push_back (lay::separator ("edit_options_group:edit_mode", "edit_menu.end")); - menu_entries.push_back (lay::menu_item ("cm_edit_options", "edit_options:edit_mode", "edit_menu.end", tl::to_string (QObject::tr ("Editor Options")) + "(F3)")); + menu_entries.push_back (lay::menu_item ("cm_edit_options", "edit_options:edit_mode", "edit_menu.end", tl::to_string (QObject::tr ("Tool Options")) + "(F3)")); at = "file_menu.end"; menu_entries.push_back (lay::menu_item ("cm_new_layout", "new_layout:edit:edit_mode", at, tl::to_string (QObject::tr ("New Layout")))); diff --git a/src/layview/layview/layEditorOptionsPages.cc b/src/layview/layview/layEditorOptionsPages.cc index 6e3dd2d6a..3bc397f9b 100644 --- a/src/layview/layview/layEditorOptionsPages.cc +++ b/src/layview/layview/layEditorOptionsPages.cc @@ -482,7 +482,7 @@ EditorOptionsModalPages::update_title () if (mp_single_page) { setWindowTitle (tl::to_qstring (mp_single_page->title ())); } else { - setWindowTitle (tr ("Editor Options")); + setWindowTitle (tr ("Tool Options")); } } From 3371333dd7a2878eb99173e4e90f82fc8f4601b3 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 00:14:18 +0200 Subject: [PATCH 10/26] img plugin does not need editor options pages. --- src/img/img/imgPlugin.cc | 9 --------- src/img/img/imgPlugin.h | 1 - 2 files changed, 10 deletions(-) diff --git a/src/img/img/imgPlugin.cc b/src/img/img/imgPlugin.cc index 3438f7343..ea435d66a 100644 --- a/src/img/img/imgPlugin.cc +++ b/src/img/img/imgPlugin.cc @@ -62,15 +62,6 @@ PluginDeclaration::get_options (std::vector < std::pair (cfg_images_visible, "true")); } -std::vector -PluginDeclaration::additional_editor_options_pages () const -{ - std::vector names; - // TODO: provide in a central place instead of borrowing from the edt module - names.push_back ("GenericEditorOptions"); - return names; -} - static tl::RegisteredClass config_decl (new img::PluginDeclaration (), 4000, "img::Plugin"); } diff --git a/src/img/img/imgPlugin.h b/src/img/img/imgPlugin.h index 0214662fc..3302cbc3c 100644 --- a/src/img/img/imgPlugin.h +++ b/src/img/img/imgPlugin.h @@ -39,7 +39,6 @@ class PluginDeclaration virtual lay::Plugin *create_plugin (db::Manager *manager, lay::Dispatcher *, lay::LayoutViewBase *view) const; virtual bool implements_editable (std::string &title) const; virtual void get_options (std::vector < std::pair > &options) const; - virtual std::vector additional_editor_options_pages () const; }; } From 98a8d370aaed52a05b6f7c7108901418916b228a Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 00:15:51 +0200 Subject: [PATCH 11/26] Tweaking the snap behavior on ruler move a little. --- src/ant/ant/antService.cc | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index fe45fbecb..faed89a27 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -1610,7 +1610,7 @@ Service::move_transform (const db::DPoint & /*p*/, db::DFTrans tr, lay::angle_co return; } - auto ac_eff = ac == lay::AC_Global ? m_snap_mode : ac; + auto ac_eff = ac == lay::AC_Global ? lay::AC_Any : ac; clear_mouse_cursors (); if (m_move_mode == MoveSelected) { @@ -1634,11 +1634,12 @@ Service::move (const db::DPoint &p, lay::angle_constraint_type ac) return; } - auto ac_eff = ac == lay::AC_Global ? m_snap_mode : ac; clear_mouse_cursors (); if (m_move_mode == MoveSelected) { + auto ac_eff = ac == lay::AC_Global ? lay::AC_Any : ac; + db::DVector dp = p - m_p1; dp = lay::snap_angle (dp, ac_eff); @@ -1656,7 +1657,28 @@ Service::move (const db::DPoint &p, lay::angle_constraint_type ac) } else if (m_move_mode != MoveNone) { - db::DPoint ps = snap2_visual (m_p1, p, &m_current, ac); + db::DPoint ps; + + if (m_move_mode == MoveP1 && m_current.segments () == 1) { + + // when moving p1 in a normal ruler, observe the currently active angle constraints and use p2 as reference + db::DPoint pref = m_current.seg_p2 (m_seg_index); + ps = snap2_visual (pref, p, &m_current, ac); + + } else if (m_move_mode == MoveP2 && m_current.segments () == 1) { + + // when moving p2 in a normal ruler, observe the currently active angle constraints and use p1 as reference + db::DPoint pref = m_current.seg_p1 (m_seg_index); + ps = snap2_visual (pref, p, &m_current, ac); + + } else { + + // other move modes use the angle constraint imposed by the modifier buttons + // and the start point for reference + ps = snap2_visual (m_p1, p, &m_current, ac == lay::AC_Global ? lay::AC_Any : ac); + + } + m_trans = db::DTrans (ps - m_p1); apply_partial_move (ps); From c8362d16f133a41e4ce94812be0b9c7f2ad25ce0 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 00:16:35 +0200 Subject: [PATCH 12/26] Adding a ruler options widget page --- src/ant/ant/RulerOptions.ui | 242 +++++++++++++++++++++++++++++ src/ant/ant/ant.pro | 3 + src/ant/ant/antRulerOptionsPage.cc | 114 ++++++++++++++ src/ant/ant/antRulerOptionsPage.h | 62 ++++++++ 4 files changed, 421 insertions(+) create mode 100644 src/ant/ant/RulerOptions.ui create mode 100644 src/ant/ant/antRulerOptionsPage.cc create mode 100644 src/ant/ant/antRulerOptionsPage.h diff --git a/src/ant/ant/RulerOptions.ui b/src/ant/ant/RulerOptions.ui new file mode 100644 index 000000000..151f8f658 --- /dev/null +++ b/src/ant/ant/RulerOptions.ui @@ -0,0 +1,242 @@ + + + RulerOptions + + + + 0 + 0 + 400 + 446 + + + + + 0 + 0 + + + + Form + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + + + 0 + 0 + 400 + 446 + + + + + 2 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + Snapping + + + + 4 + + + 4 + + + 4 + + + 4 + + + 6 + + + 2 + + + + + Snap to grid + + + + + + + Snap to edge/vertex + + + + + + + + + + Angle Constraints + + + + 4 + + + 4 + + + 4 + + + 4 + + + 6 + + + 2 + + + + + + 1 + 0 + + + + QComboBox::AdjustToContents + + + + Any Angle + + + + + Diagonal + + + + + Diagonal only + + + + + Orthogonal + + + + + Horizontal only + + + + + Vertical only + + + + + + + + Ruler direction + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Note: certain rulers may ignore these options + + + true + + + + + + + Qt::Vertical + + + + 121 + 70 + + + + + + + + + + + + scrollArea + snap_to_grid_cbx + snap_to_objects_cbx + angle_cb + + + + diff --git a/src/ant/ant/ant.pro b/src/ant/ant/ant.pro index 1d1286c73..dbd46c374 100644 --- a/src/ant/ant/ant.pro +++ b/src/ant/ant/ant.pro @@ -14,6 +14,7 @@ FORMS = \ RulerConfigPage3.ui \ RulerConfigPage4.ui \ RulerPropertiesPage.ui \ + RulerOptions.ui \ } @@ -22,10 +23,12 @@ FORMS = \ HEADERS = \ antConfigPage.h \ antPropertiesPage.h \ + antRulerOptionsPage.h SOURCES = \ antConfigPage.cc \ antPropertiesPage.cc \ + antRulerOptionsPage.cc # Enabled without Qt: diff --git a/src/ant/ant/antRulerOptionsPage.cc b/src/ant/ant/antRulerOptionsPage.cc new file mode 100644 index 000000000..4fa248315 --- /dev/null +++ b/src/ant/ant/antRulerOptionsPage.cc @@ -0,0 +1,114 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2026 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "antRulerOptionsPage.h" +#include "antConfig.h" +#include "laySnap.h" +#include "layConverters.h" +#include "layDispatcher.h" +#include "tlString.h" +#include "tlClassRegistry.h" + +#include "ui_RulerOptions.h" + +namespace ant +{ + +// ------------------------------------------------------------------ +// RulerOptionsPage implementation + +RulerOptionsPage::RulerOptionsPage (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher) + : lay::EditorOptionsPageWidget (view, dispatcher) +{ + mp_ui = new Ui::RulerOptions (); + mp_ui->setupUi (this); + + connect (mp_ui->angle_cb, SIGNAL (activated (int)), this, SLOT (edited ())); + connect (mp_ui->snap_to_grid_cbx, SIGNAL (clicked ()), this, SLOT (edited ())); + connect (mp_ui->snap_to_objects_cbx, SIGNAL (clicked ()), this, SLOT (edited ())); +} + +RulerOptionsPage::~RulerOptionsPage () +{ + delete mp_ui; + mp_ui = 0; +} + +std::string +RulerOptionsPage::title () const +{ + return tl::to_string (QObject::tr ("Ruler Options")); +} + +// Must match order in angle_cb +static std::vector s_ac_options = +{ + lay::AC_Any, + lay::AC_Diagonal, + lay::AC_DiagonalOnly, + lay::AC_Ortho, + lay::AC_Horizontal, + lay::AC_Vertical +}; + +void +RulerOptionsPage::apply (lay::Dispatcher *root) +{ + lay::angle_constraint_type ac = lay::AC_Any; + int ai = mp_ui->angle_cb->currentIndex (); + if (ai >= 0 && ai < int (s_ac_options.size ())) { + ac = s_ac_options [ai]; + } + + lay::ACConverter acc; + root->config_set (cfg_ruler_snap_mode, acc.to_string (ac)); + + root->config_set (cfg_ruler_obj_snap, tl::to_string (mp_ui->snap_to_objects_cbx->isChecked ())); + root->config_set (cfg_ruler_grid_snap, tl::to_string (mp_ui->snap_to_grid_cbx->isChecked ())); +} + +void +RulerOptionsPage::setup (lay::Dispatcher *root) +{ + lay::ACConverter acc; + lay::angle_constraint_type ac; + + ac = lay::AC_Any; + root->config_get (cfg_ruler_snap_mode, ac, acc); + for (int ai = 0; ai < int (s_ac_options.size ()); ++ai) { + if (s_ac_options [ai] == ac) { + mp_ui->angle_cb->setCurrentIndex (ai); + } + } + + bool snap_to_grid = false; + root->config_get (cfg_ruler_grid_snap, snap_to_grid); + mp_ui->snap_to_grid_cbx->setChecked (snap_to_grid); + + bool snap_to_objects = false; + root->config_get (cfg_ruler_obj_snap, snap_to_objects); + mp_ui->snap_to_objects_cbx->setChecked (snap_to_objects); +} + +static tl::RegisteredClass s_factory_ruler_options (new lay::EditorOptionsPageFactory ("ant::RulerOptions"), 0); + +} diff --git a/src/ant/ant/antRulerOptionsPage.h b/src/ant/ant/antRulerOptionsPage.h new file mode 100644 index 000000000..cd934adbc --- /dev/null +++ b/src/ant/ant/antRulerOptionsPage.h @@ -0,0 +1,62 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2026 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_antRulerOptionsPage +#define HDR_antRulerOptionsPage + +#include "antCommon.h" + +#include "layEditorOptionsPageWidget.h" + +namespace Ui +{ + class RulerOptions; +} + +namespace ant +{ + +/** + * @brief The generic properties page + */ +class RulerOptionsPage + : public lay::EditorOptionsPageWidget +{ +Q_OBJECT + +public: + RulerOptionsPage (lay::LayoutViewBase *view, lay::Dispatcher *dispatcher); + ~RulerOptionsPage (); + + virtual std::string title () const; + virtual int order () const { return -10; } + void apply (lay::Dispatcher *root); + void setup (lay::Dispatcher *root); + +private: + Ui::RulerOptions *mp_ui; +}; + +} + +#endif From cb918203c715b9856d12f79820e12942909f97f7 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 00:17:25 +0200 Subject: [PATCH 13/26] Adding the ruler options widget and disabling editor options in viewer mode --- src/ant/ant/antPlugin.cc | 6 +++--- src/ant/ant/antPlugin.h | 2 +- src/edt/edt/edtPlugin.cc | 6 +++--- src/laybasic/laybasic/layMove.cc | 10 +++++++--- src/laybasic/laybasic/layPlugin.h | 2 +- src/laybasic/laybasic/laySelector.cc | 8 +++++--- src/layview/layview/gsiDeclLayPluginFactory.cc | 2 +- src/layview/layview/layEditorOptionsFrame.cc | 2 +- 8 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/ant/ant/antPlugin.cc b/src/ant/ant/antPlugin.cc index 0fc87fed9..8235a563f 100644 --- a/src/ant/ant/antPlugin.cc +++ b/src/ant/ant/antPlugin.cc @@ -25,6 +25,7 @@ #include "layConverters.h" #include "layDispatcher.h" #include "layAbstractMenu.h" +#include "layLayoutViewBase.h" #include "tlColor.h" #include "tlLog.h" #if defined(HAVE_QT) @@ -139,11 +140,10 @@ PluginDeclaration::create_plugin (db::Manager *manager, lay::Dispatcher *, lay:: } std::vector -PluginDeclaration::additional_editor_options_pages () const +PluginDeclaration::additional_editor_options_pages (lay::LayoutViewBase * /*view*/) const { std::vector names; - // TODO: provide in a central place instead of borrowing from the edt module - names.push_back ("GenericEditorOptions"); + names.push_back ("ant::RulerOptions"); return names; } diff --git a/src/ant/ant/antPlugin.h b/src/ant/ant/antPlugin.h index 334a46002..25a13a7a5 100644 --- a/src/ant/ant/antPlugin.h +++ b/src/ant/ant/antPlugin.h @@ -54,7 +54,7 @@ class PluginDeclaration virtual void uninitialize (lay::Dispatcher *); virtual bool menu_activated (const std::string &symbol) const; - virtual std::vector additional_editor_options_pages () const; + virtual std::vector additional_editor_options_pages (lay::LayoutViewBase *view) const; void register_annotation_template (const ant::Template &t, lay::Plugin *plugin = 0); void unregister_annotation_template (const std::string &category, lay::Plugin *plugin = 0); diff --git a/src/edt/edt/edtPlugin.cc b/src/edt/edt/edtPlugin.cc index 7b803db76..1eeb20541 100644 --- a/src/edt/edt/edtPlugin.cc +++ b/src/edt/edt/edtPlugin.cc @@ -142,8 +142,8 @@ class PluginDeclaration } } - virtual std::vector additional_editor_options_pages () const - { + virtual std::vector additional_editor_options_pages (lay::LayoutViewBase *) const + { std::vector names; names.push_back ("GenericEditorOptions"); return names; @@ -415,7 +415,7 @@ class PartialPluginDeclaration return true; } - virtual std::vector additional_editor_options_pages () const + virtual std::vector additional_editor_options_pages (lay::LayoutViewBase *) const { std::vector names; names.push_back ("GenericEditorOptions"); diff --git a/src/laybasic/laybasic/layMove.cc b/src/laybasic/laybasic/layMove.cc index b270ab585..f5eb6489a 100644 --- a/src/laybasic/laybasic/layMove.cc +++ b/src/laybasic/laybasic/layMove.cc @@ -482,11 +482,15 @@ class MoveServiceDeclaration return new MoveService (view); } - virtual std::vector additional_editor_options_pages () const + virtual std::vector additional_editor_options_pages (lay::LayoutViewBase *view) const { std::vector names; - // TODO: provide in a central place instead of borrowing from the edt module - names.push_back ("GenericEditorOptions"); + if (view->is_editable ()) { + // TODO: provide in a central place instead of borrowing from the edt module + names.push_back ("GenericEditorOptions"); + } + // TODO: provide in a central place instead of borrowing from the ant module + names.push_back ("ant::RulerOptions"); return names; } }; diff --git a/src/laybasic/laybasic/layPlugin.h b/src/laybasic/laybasic/layPlugin.h index 8b8812bed..c2a1407d2 100644 --- a/src/laybasic/laybasic/layPlugin.h +++ b/src/laybasic/laybasic/layPlugin.h @@ -354,7 +354,7 @@ Q_OBJECT * In addition to providing pages through "get_editor_options_pages", the plugin can request pages * from globally registered factories by name. */ - virtual std::vector additional_editor_options_pages () const + virtual std::vector additional_editor_options_pages (lay::LayoutViewBase * /*view*/) const { return std::vector (); } diff --git a/src/laybasic/laybasic/laySelector.cc b/src/laybasic/laybasic/laySelector.cc index 241b9f496..287dab404 100644 --- a/src/laybasic/laybasic/laySelector.cc +++ b/src/laybasic/laybasic/laySelector.cc @@ -333,11 +333,13 @@ class SelectionServiceDeclaration return new SelectionService (view); } - virtual std::vector additional_editor_options_pages () const + virtual std::vector additional_editor_options_pages (lay::LayoutViewBase *view) const { std::vector names; - // TODO: provide in a central place instead of borrowing from the edt module - names.push_back ("GenericEditorOptions"); + if (view->is_editable ()) { + // TODO: provide in a central place instead of borrowing from the edt module + names.push_back ("GenericEditorOptions"); + } return names; } }; diff --git a/src/layview/layview/gsiDeclLayPluginFactory.cc b/src/layview/layview/gsiDeclLayPluginFactory.cc index ce4f81afd..bc72cd656 100644 --- a/src/layview/layview/gsiDeclLayPluginFactory.cc +++ b/src/layview/layview/gsiDeclLayPluginFactory.cc @@ -318,7 +318,7 @@ class PluginFactoryBase m_additional_editor_options_pages.push_back (name); } - virtual std::vector additional_editor_options_pages () const + virtual std::vector additional_editor_options_pages (lay::LayoutViewBase *) const { return m_additional_editor_options_pages; } diff --git a/src/layview/layview/layEditorOptionsFrame.cc b/src/layview/layview/layEditorOptionsFrame.cc index 2d6fab391..ab527000a 100644 --- a/src/layview/layview/layEditorOptionsFrame.cc +++ b/src/layview/layview/layEditorOptionsFrame.cc @@ -56,7 +56,7 @@ EditorOptionsFrame::populate (LayoutViewBase *view) for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { cls->get_editor_options_pages (editor_options_pages, view, view->dispatcher ()); - std::vector ap = cls->additional_editor_options_pages (); + std::vector ap = cls->additional_editor_options_pages (view); for (auto i = ap.begin (); i != ap.end (); ++i) { additional_pages [*i].push_back (cls.operator-> ()); } From 4cc1d2ff9a9820ee7bc8dde44fb614e7e279a5d9 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 14:09:08 +0200 Subject: [PATCH 14/26] Fixed a potential crash This bug was triggered during development and happened because the tool bar build code was invalidating QAction objects indirectly while they are used for building the main menu. Solution is to separate building of menu bar and tool bar / context menus. --- src/laybasic/laybasic/layAbstractMenu.cc | 224 ++++++++++++----------- 1 file changed, 121 insertions(+), 103 deletions(-) diff --git a/src/laybasic/laybasic/layAbstractMenu.cc b/src/laybasic/laybasic/layAbstractMenu.cc index 637f9ab8f..3ab6d1c06 100644 --- a/src/laybasic/laybasic/layAbstractMenu.cc +++ b/src/laybasic/laybasic/layAbstractMenu.cc @@ -1280,31 +1280,138 @@ AbstractMenu::extra_menu_name () void AbstractMenu::build (QMenuBar *mbar, QToolBar *tbar) { - if (tbar) { - tbar->clear (); - } - - std::set > present_actions; + // Build the menu bar if (mbar) { + std::set > present_actions; + QList a = mbar->actions (); for (QList::const_iterator i = a.begin (); i != a.end (); ++i) { present_actions.insert (std::make_pair (id_from_action (*i), *i)); } - } + // NOTE: on MacOS, once the top level menu is created, we should not + // modify it and place menu items into the "Extras" menu instead. + bool menu_frozen = ! s_can_move_menu && ! present_actions.empty (); + + // NOTE: the "extras" menu is relevant on MacOS only + QMenu *extras_menu = find_extras_menu (mbar); + if (extras_menu) { + extras_menu->clear (); + } + + QAction *prev_action = 0; + + for (std::list::iterator c = m_root.children.begin (); c != m_root.children.end (); ++c) { + + if (c->has_submenu ()) { + + if (c->name ().find ("@") == 0) { + + // done later. + + } else { + + if (c->menu () == 0) { + + // NOTE: we intentionally do not make the item owner of the menu action + // as implicitly deleting it might cause trouble on MacOS. Instead we + // explicitly delete below or keep the action. + QMenu *menu = new QMenu (mp_dispatcher->menu_parent_widget ()); + menu->setTitle (tl::to_qstring (c->action ()->get_title ())); + c->set_action (new Action (menu, false), true); + + // This case happens when we dynamically create menus. + // MacOS does not like generating top-level menus dynamically, so + // we put them into the "_extra" top level one. + if (menu_frozen) { + if (extras_menu) { + extras_menu->addMenu (menu); + } + } else { + prev_action = insert_action_after (mbar, prev_action, menu->menuAction ()); + } + + } else { + + // Move the action to the end if present in the menu already + std::set >::iterator a = present_actions.find (std::make_pair (id_from_action (c->menu ()->menuAction ()), c->menu ()->menuAction ())); + if (a != present_actions.end ()) { + if (s_can_move_menu) { + mbar->removeAction (a->second); + insert_action_after (mbar, prev_action, a->second); + } + prev_action = a->second; + present_actions.erase (*a); + } else if (menu_frozen) { + if (extras_menu) { + extras_menu->addMenu (c->menu ()); + } + } else { + prev_action = insert_action_after (mbar, prev_action, c->menu ()->menuAction ()); + } + + } + + // NOTE: the "extras" menu is built implicitly. You cannot put anything there. + if (c->menu () != extras_menu) { + build (c->menu (), c->children); + } + + } + + } else { + + // Move the action to the end if present in the menu already + std::set >::iterator a = present_actions.find (std::make_pair (id_from_action (c->action ()->qaction ()), c->action ()->qaction ())); + if (a != present_actions.end ()) { + if (s_can_move_menu) { + mbar->removeAction (a->second); + insert_action_after (mbar, prev_action, a->second); + } + prev_action = a->second; + present_actions.erase (*a); + } else if (menu_frozen) { + QMenu *extras_menu = find_extras_menu (mbar); + if (extras_menu) { + extras_menu->addAction (c->action ()->qaction ()); + } + } else { + prev_action = insert_action_after (mbar, prev_action, c->action ()->qaction ()); + } + + } + + } + + // Disable the (maybe new) "extras" menu if empty + extras_menu = find_extras_menu (mbar); + if (extras_menu) { + extras_menu->setEnabled (! extras_menu->isEmpty ()); + } - // NOTE: on MacOS, once the top level menu is created, we should not - // modify it and place menu items into the "Extras" menu instead. - bool menu_frozen = ! s_can_move_menu && ! present_actions.empty (); + // Remove all actions that have vanished + for (std::set >::iterator a = present_actions.begin (); a != present_actions.end (); ++a) { + if (s_can_move_menu) { + mbar->removeAction (a->second); + delete a->second; + } else { + if (a->second->menu ()) { + a->second->menu ()->clear (); + } + a->second->setEnabled (false); + } + } - // NOTE: the "extras" menu is relevant on MacOS only - QMenu *extras_menu = find_extras_menu (mbar); - if (extras_menu) { - extras_menu->clear (); } - QAction *prev_action = 0; + // Finally build the context menus and toolbar. + // NOTE: this is done afterwards, as generating the submenus, specifically for the toolbar menu buttons + // messes with the QActions stored in "present_actions". + + if (tbar) { + tbar->clear (); + } for (std::list::iterator c = m_root.children.begin (); c != m_root.children.end (); ++c) { @@ -1338,100 +1445,11 @@ AbstractMenu::build (QMenuBar *mbar, QToolBar *tbar) // prepare a detached menu which can be used as context menus build (c->menu (), c->children); - } else if (mbar) { - - if (c->menu () == 0) { - - // NOTE: we intentionally do not make the item owner of the menu action - // as implicitly deleting it might cause trouble on MacOS. Instead we - // explicitly delete below or keep the action. - QMenu *menu = new QMenu (mp_dispatcher->menu_parent_widget ()); - menu->setTitle (tl::to_qstring (c->action ()->get_title ())); - c->set_action (new Action (menu, false), true); - - // This case happens when we dynamically create menus. - // MacOS does not like generating top-level menus dynamically, so - // we put them into the "_extra" top level one. - if (menu_frozen) { - if (extras_menu) { - extras_menu->addMenu (menu); - } - } else { - prev_action = insert_action_after (mbar, prev_action, menu->menuAction ()); - } - - } else { - - // Move the action to the end if present in the menu already - std::set >::iterator a = present_actions.find (std::make_pair (id_from_action (c->menu ()->menuAction ()), c->menu ()->menuAction ())); - if (a != present_actions.end ()) { - if (s_can_move_menu) { - mbar->removeAction (a->second); - insert_action_after (mbar, prev_action, a->second); - } - prev_action = a->second; - present_actions.erase (*a); - } else if (menu_frozen) { - if (extras_menu) { - extras_menu->addMenu (c->menu ()); - } - } else { - prev_action = insert_action_after (mbar, prev_action, c->menu ()->menuAction ()); - } - - } - - // NOTE: the "extras" menu is built implicitly. You cannot put anything there. - if (c->menu () != extras_menu) { - build (c->menu (), c->children); - } - - } - - } else if (mbar) { - - // Move the action to the end if present in the menu already - std::set >::iterator a = present_actions.find (std::make_pair (id_from_action (c->action ()->qaction ()), c->action ()->qaction ())); - if (a != present_actions.end ()) { - if (s_can_move_menu) { - mbar->removeAction (a->second); - insert_action_after (mbar, prev_action, a->second); - } - prev_action = a->second; - present_actions.erase (*a); - } else if (menu_frozen) { - QMenu *extras_menu = find_extras_menu (mbar); - if (extras_menu) { - extras_menu->addAction (c->action ()->qaction ()); - } - } else { - prev_action = insert_action_after (mbar, prev_action, c->action ()->qaction ()); } } } - - // Disable the (maybe new) "extras" menu if empty - extras_menu = find_extras_menu (mbar); - if (extras_menu) { - extras_menu->setEnabled (! extras_menu->isEmpty ()); - } - - // Remove all actions that have vanished - if (mbar) { - for (std::set >::iterator a = present_actions.begin (); a != present_actions.end (); ++a) { - if (s_can_move_menu) { - mbar->removeAction (a->second); - delete a->second; - } else { - if (a->second->menu ()) { - a->second->menu ()->clear (); - } - a->second->setEnabled (false); - } - } - } } void From 378d2cd961dc026799e7c49ff26d481154475524 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 14:10:45 +0200 Subject: [PATCH 15/26] Changed visibility management of tool options (aka "Editor options") Now, the tool options dock is either visible or not. Visibility can be configured like for the other docks. Key binding stays "F3" by default for the dock visibility. The dock is populated with pages depending on the tool. It may also be empty. --- src/lay/lay/layConfig.h | 1 + src/lay/lay/layMainConfigPages.cc | 1 + src/lay/lay/layMainWindow.cc | 123 +++++++++++++----------------- src/lay/lay/layMainWindow.h | 1 - src/lay/lay/layNavigator.cc | 1 + 5 files changed, 54 insertions(+), 73 deletions(-) diff --git a/src/lay/lay/layConfig.h b/src/lay/lay/layConfig.h index 86850a4e2..7ef712ab2 100644 --- a/src/lay/lay/layConfig.h +++ b/src/lay/lay/layConfig.h @@ -56,6 +56,7 @@ static const std::string cfg_show_hierarchy_panel ("show-hierarchy-panel"); static const std::string cfg_show_libraries_view ("show-libraries-view"); static const std::string cfg_show_bookmarks_view ("show-bookmarks-view"); static const std::string cfg_show_layer_panel ("show-layer-panel"); +static const std::string cfg_show_tool_options ("show-tool-options"); static const std::string cfg_window_state ("window-state"); static const std::string cfg_layout_file_watcher_enabled ("layout-file-watcher-enabled"); static const std::string cfg_window_geometry ("window-geometry"); diff --git a/src/lay/lay/layMainConfigPages.cc b/src/lay/lay/layMainConfigPages.cc index c56f48357..ffe99975b 100644 --- a/src/lay/lay/layMainConfigPages.cc +++ b/src/lay/lay/layMainConfigPages.cc @@ -74,6 +74,7 @@ class MainPluginDeclaration options.push_back (std::pair (cfg_show_libraries_view, "true")); options.push_back (std::pair (cfg_show_bookmarks_view, "false")); options.push_back (std::pair (cfg_show_layer_panel, "true")); + options.push_back (std::pair (cfg_show_tool_options, "true")); options.push_back (std::pair (cfg_layout_file_watcher_enabled, "true")); options.push_back (std::pair (cfg_window_state, "")); options.push_back (std::pair (cfg_window_geometry, "")); diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index 2ad58023c..6a2582643 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -274,6 +274,34 @@ MainWindow::MainWindow (QApplication *app, const char *name, bool undo_enabled) connect (action, SIGNAL (triggered ()), this, SLOT (clone ())); mp_tab_bar->addAction (action); + mp_view_stack = new ViewWidgetStack (mp_main_frame); + mp_view_stack->setObjectName (QString::fromUtf8 ("view_stack")); + vbl->addWidget (mp_view_stack); + + mp_navigator_dock_widget = new QDockWidget (QObject::tr ("Navigator"), this); + mp_navigator_dock_widget->setObjectName (QString::fromUtf8 ("navigator_dock_widget")); + mp_navigator = new Navigator (this); + mp_navigator_dock_widget->setWidget (mp_navigator); + mp_navigator_dock_widget->setFocusProxy (mp_navigator); + connect (mp_navigator_dock_widget, SIGNAL (visibilityChanged (bool)), this, SLOT (dock_widget_visibility_changed (bool))); + m_navigator_visible = true; + + mp_lp_dock_widget = new QDockWidget (QObject::tr ("Layers"), this); + mp_lp_dock_widget->setObjectName (QString::fromUtf8 ("lp_dock_widget")); + mp_lp_stack = new ControlWidgetStack (mp_lp_dock_widget, "lp_stack"); + mp_lp_dock_widget->setWidget (mp_lp_stack); + mp_lp_dock_widget->setFocusProxy (mp_lp_stack); + connect (mp_lp_dock_widget, SIGNAL (visibilityChanged (bool)), this, SLOT (dock_widget_visibility_changed (bool))); + m_lp_visible = true; + + mp_layer_toolbox_dock_widget = new QDockWidget (QObject::tr ("Layer Toolbox"), this); + mp_layer_toolbox_dock_widget->setObjectName (QString::fromUtf8 ("lt_dock_widget")); + mp_layer_toolbox_stack = new ControlWidgetStack (mp_layer_toolbox_dock_widget, "layer_toolbox_stack", true); + mp_layer_toolbox_dock_widget->setWidget (mp_layer_toolbox_stack); + mp_layer_toolbox_dock_widget->setFocusProxy (mp_layer_toolbox_stack); + connect (mp_layer_toolbox_dock_widget, SIGNAL (visibilityChanged (bool)), this, SLOT (dock_widget_visibility_changed (bool))); + m_layer_toolbox_visible = true; + mp_hp_dock_widget = new QDockWidget (QObject::tr ("Cells"), this); mp_hp_dock_widget->setObjectName (QString::fromUtf8 ("hp_dock_widget")); mp_hp_stack = new ControlWidgetStack (mp_hp_dock_widget, "hp_stack"); @@ -290,15 +318,6 @@ MainWindow::MainWindow (QApplication *app, const char *name, bool undo_enabled) connect (mp_libs_dock_widget, SIGNAL (visibilityChanged (bool)), this, SLOT (dock_widget_visibility_changed (bool))); m_libs_visible = true; - mp_eo_dock_widget = new QDockWidget (QObject::tr ("Tool Options"), this); - mp_eo_dock_widget->setObjectName (QString::fromUtf8 ("eo_dock_widget")); - mp_eo_dock_widget->setMinimumHeight (150); - mp_eo_stack = new ControlWidgetStack (mp_eo_dock_widget, "eo_stack"); - mp_eo_dock_widget->setWidget (mp_eo_stack); - mp_eo_dock_widget->setFocusProxy (mp_eo_stack); - connect (mp_eo_dock_widget, SIGNAL (visibilityChanged (bool)), this, SLOT (dock_widget_visibility_changed (bool))); - m_eo_visible = true; - mp_bm_dock_widget = new QDockWidget (QObject::tr ("Bookmarks"), this); mp_bm_dock_widget->setObjectName (QString::fromUtf8 ("bookmarks_dock_widget")); mp_bm_stack = new ControlWidgetStack (mp_bm_dock_widget, "bookmarks_stack"); @@ -307,33 +326,14 @@ MainWindow::MainWindow (QApplication *app, const char *name, bool undo_enabled) connect (mp_bm_dock_widget, SIGNAL (visibilityChanged (bool)), this, SLOT (dock_widget_visibility_changed (bool))); m_bm_visible = true; - mp_view_stack = new ViewWidgetStack (mp_main_frame); - mp_view_stack->setObjectName (QString::fromUtf8 ("view_stack")); - vbl->addWidget (mp_view_stack); - - mp_layer_toolbox_dock_widget = new QDockWidget (QObject::tr ("Layer Toolbox"), this); - mp_layer_toolbox_dock_widget->setObjectName (QString::fromUtf8 ("lt_dock_widget")); - mp_layer_toolbox_stack = new ControlWidgetStack (mp_layer_toolbox_dock_widget, "layer_toolbox_stack", true); - mp_layer_toolbox_dock_widget->setWidget (mp_layer_toolbox_stack); - mp_layer_toolbox_dock_widget->setFocusProxy (mp_layer_toolbox_stack); - connect (mp_layer_toolbox_dock_widget, SIGNAL (visibilityChanged (bool)), this, SLOT (dock_widget_visibility_changed (bool))); - m_layer_toolbox_visible = true; - - mp_lp_dock_widget = new QDockWidget (QObject::tr ("Layers"), this); - mp_lp_dock_widget->setObjectName (QString::fromUtf8 ("lp_dock_widget")); - mp_lp_stack = new ControlWidgetStack (mp_lp_dock_widget, "lp_stack"); - mp_lp_dock_widget->setWidget (mp_lp_stack); - mp_lp_dock_widget->setFocusProxy (mp_lp_stack); - connect (mp_lp_dock_widget, SIGNAL (visibilityChanged (bool)), this, SLOT (dock_widget_visibility_changed (bool))); - m_lp_visible = true; - - mp_navigator_dock_widget = new QDockWidget (QObject::tr ("Navigator"), this); - mp_navigator_dock_widget->setObjectName (QString::fromUtf8 ("navigator_dock_widget")); - mp_navigator = new Navigator (this); - mp_navigator_dock_widget->setWidget (mp_navigator); - mp_navigator_dock_widget->setFocusProxy (mp_navigator); - connect (mp_navigator_dock_widget, SIGNAL (visibilityChanged (bool)), this, SLOT (dock_widget_visibility_changed (bool))); - m_navigator_visible = true; + mp_eo_dock_widget = new QDockWidget (QObject::tr ("Tool Options"), this); + mp_eo_dock_widget->setObjectName (QString::fromUtf8 ("eo_dock_widget")); + mp_eo_dock_widget->setMinimumHeight (150); + mp_eo_stack = new ControlWidgetStack (mp_eo_dock_widget, "eo_stack"); + mp_eo_dock_widget->setWidget (mp_eo_stack); + mp_eo_dock_widget->setFocusProxy (mp_eo_stack); + connect (mp_eo_dock_widget, SIGNAL (visibilityChanged (bool)), this, SLOT (dock_widget_visibility_changed (bool))); + m_eo_visible = true; // Add dock widgets #if QT_VERSION >= 0x040500 @@ -567,7 +567,7 @@ MainWindow::technology_changed () } void -MainWindow::dock_widget_visibility_changed (bool visible) +MainWindow::dock_widget_visibility_changed (bool /*visible*/) { if (sender () == mp_lp_dock_widget) { dispatcher ()->config_set (cfg_show_layer_panel, tl::to_string (!mp_lp_dock_widget->isHidden ())); @@ -582,7 +582,7 @@ MainWindow::dock_widget_visibility_changed (bool visible) } else if (sender () == mp_layer_toolbox_dock_widget) { dispatcher ()->config_set (cfg_show_layer_toolbox, tl::to_string (!mp_layer_toolbox_dock_widget->isHidden ())); } else if (sender () == mp_eo_dock_widget) { - m_eo_visible = visible; + dispatcher ()->config_set (cfg_show_tool_options, tl::to_string (!mp_eo_dock_widget->isHidden ())); } } @@ -1133,6 +1133,17 @@ MainWindow::configure (const std::string &name, const std::string &value) return true; + } else if (name == cfg_show_tool_options) { + + tl::from_string (value, m_eo_visible); + if (m_eo_visible) { + mp_eo_dock_widget->show (); + } else { + mp_eo_dock_widget->hide (); + } + + return true; + } else if (name == cfg_navigator_show_images) { bool flag = false; @@ -1321,6 +1332,7 @@ MainWindow::read_dock_widget_state () dispatcher ()->config_set (cfg_show_bookmarks_view, tl::to_string (!mp_bm_dock_widget->isHidden ())); dispatcher ()->config_set (cfg_show_navigator, tl::to_string (!mp_navigator_dock_widget->isHidden ())); dispatcher ()->config_set (cfg_show_layer_toolbox, tl::to_string (!mp_layer_toolbox_dock_widget->isHidden ())); + dispatcher ()->config_set (cfg_show_tool_options, tl::to_string (!mp_eo_dock_widget->isHidden ())); } void @@ -1699,38 +1711,6 @@ MainWindow::select_mode (int m) } } - update_editor_options_dock (); - - } -} - -void -MainWindow::update_editor_options_dock () -{ - // if the current mode supports editing, show the editor options panel - - const lay::PluginDeclaration *pd_sel = 0; - for (tl::Registrar::iterator cls = tl::Registrar::begin (); cls != tl::Registrar::end (); ++cls) { - const lay::PluginDeclaration *pd = cls.operator-> (); - if (pd->id () == m_mode) { - pd_sel = pd; - } - } - - bool eo_visible = false; - if (mp_eo_stack && pd_sel) { - eo_visible = pd_sel->editable_enabled (); - } - if (current_view () && eo_visible) { - lay::EditorOptionsPageCollection *eo_pages = current_view ()->editor_options_pages (); - if (! eo_pages || ! eo_pages->has_content ()) { - eo_visible = false; - } - } - - if (eo_visible != m_eo_visible) { - m_eo_visible = eo_visible; - show_dock_widget (mp_eo_dock_widget, m_eo_visible); } } @@ -2499,7 +2479,6 @@ MainWindow::select_view (int index) current_view_changed (); - update_editor_options_dock (); clear_current_pos (); edits_enabled_changed (); clear_messages (); @@ -4502,7 +4481,6 @@ class MainWindowPluginDeclaration at = "edit_menu.end"; menu_entries.push_back (lay::separator ("edit_options_group:edit_mode", "edit_menu.end")); - menu_entries.push_back (lay::menu_item ("cm_edit_options", "edit_options:edit_mode", "edit_menu.end", tl::to_string (QObject::tr ("Tool Options")) + "(F3)")); at = "file_menu.end"; menu_entries.push_back (lay::menu_item ("cm_new_layout", "new_layout:edit:edit_mode", at, tl::to_string (QObject::tr ("New Layout")))); @@ -4568,6 +4546,7 @@ class MainWindowPluginDeclaration menu_entries.push_back (lay::config_menu_item ("show_hierarchy_panel", at, tl::to_string (QObject::tr ("Cells")), cfg_show_hierarchy_panel, "?")); menu_entries.push_back (lay::config_menu_item ("show_libraries_view", at, tl::to_string (QObject::tr ("Libraries")), cfg_show_libraries_view, "?")); menu_entries.push_back (lay::config_menu_item ("show_bookmarks_view", at, tl::to_string (QObject::tr ("Bookmarks")), cfg_show_bookmarks_view, "?")); + menu_entries.push_back (lay::config_menu_item ("show_tool_options", at, tl::to_string (QObject::tr ("Tool Options(F3)")), cfg_show_tool_options, "?")); menu_entries.push_back (lay::menu_item ("cm_reset_window_state", "reset_window_state", at, tl::to_string (QObject::tr ("Restore Window")))), menu_entries.push_back (lay::separator ("selection_group", at)); menu_entries.push_back (lay::config_menu_item ("transient_selection", at, tl::to_string (QObject::tr ("Highlight Object Under Mouse")), cfg_sel_transient_mode, "?")); diff --git a/src/lay/lay/layMainWindow.h b/src/lay/lay/layMainWindow.h index a22ac7bc9..ade27bd24 100644 --- a/src/lay/lay/layMainWindow.h +++ b/src/lay/lay/layMainWindow.h @@ -859,7 +859,6 @@ protected slots: void interactive_close_view (int from, int to, bool invert_range, bool all_cellviews); void call_on_current_view (void (lay::LayoutView::*func) (), const std::string &op_desc); void current_view_changed (); - void update_editor_options_dock (); void update_window_title (); void update_tab_title (int i); void add_view (LayoutViewWidget *view); diff --git a/src/lay/lay/layNavigator.cc b/src/lay/lay/layNavigator.cc index 1de9b1a2b..31f571560 100644 --- a/src/lay/lay/layNavigator.cc +++ b/src/lay/lay/layNavigator.cc @@ -576,6 +576,7 @@ Navigator::showEvent (QShowEvent *) void Navigator::closeEvent (QCloseEvent *) { + // TODO: this is most likely never called mp_main_window->dispatcher ()->config_set (cfg_show_navigator, "false"); mp_main_window->dispatcher ()->config_end (); } From 8992222d9d69fdc88a86ae19ef0e5de5c48cd542 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 15:10:21 +0200 Subject: [PATCH 16/26] Enabling Qt-less builds --- src/ant/ant/antRulerOptionsPage.cc | 5 +++++ src/ant/ant/antRulerOptionsPage.h | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/ant/ant/antRulerOptionsPage.cc b/src/ant/ant/antRulerOptionsPage.cc index 4fa248315..199525ad3 100644 --- a/src/ant/ant/antRulerOptionsPage.cc +++ b/src/ant/ant/antRulerOptionsPage.cc @@ -20,6 +20,8 @@ */ +#if defined(HAVE_QT) + #include "antRulerOptionsPage.h" #include "antConfig.h" #include "laySnap.h" @@ -112,3 +114,6 @@ RulerOptionsPage::setup (lay::Dispatcher *root) static tl::RegisteredClass s_factory_ruler_options (new lay::EditorOptionsPageFactory ("ant::RulerOptions"), 0); } + +#endif + diff --git a/src/ant/ant/antRulerOptionsPage.h b/src/ant/ant/antRulerOptionsPage.h index cd934adbc..4c20b573a 100644 --- a/src/ant/ant/antRulerOptionsPage.h +++ b/src/ant/ant/antRulerOptionsPage.h @@ -20,6 +20,7 @@ */ +#if defined(HAVE_QT) #ifndef HDR_antRulerOptionsPage #define HDR_antRulerOptionsPage @@ -60,3 +61,6 @@ Q_OBJECT } #endif + +#endif + From 472a7aa7fdca36712559e1d959edd861ae7a882f Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 19:17:23 +0200 Subject: [PATCH 17/26] Adding Gaussian hat average mode to density map --- .../lay_plugin/DensityMapDialog.ui | 102 +++++++++++------- .../lay_plugin/layDensityMapDialog.cc | 85 ++++++++++++--- .../lay_plugin/layDensityMapDialog.h | 5 +- 3 files changed, 142 insertions(+), 50 deletions(-) diff --git a/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui index ded9b81ce..3b69acc98 100644 --- a/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui +++ b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui @@ -57,31 +57,47 @@ - - - - Pixel size - - - - - + + 0 0 + + QComboBox::AdjustToContents + + + + Periodic (repeat region seamlessly) + + + + + Use zero density outside + + + + + Use 100% density outside + + + + + Use average density outside + + - - + + - µm + Boundary mode - + @@ -101,13 +117,6 @@ - - - - Threads - - - @@ -128,15 +137,46 @@ + + + + + 0 + 0 + + + + + + + + µm + + + + + + + Threads + + + + + + + Pixel size + + + - + - Boundary mode + Averaging mode - + 0 @@ -144,26 +184,16 @@ - QComboBox::AdjustToContents + QComboBox::AdjustToContentsOnFirstShow - Periodic (repeat region seamlessly) + Square hat - Use zero density outside - - - - - Use 100% density outside - - - - - Use average density outside + Gaussian hat (σ = window size / 2) diff --git a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc index b6e280797..baba18a87 100644 --- a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc @@ -66,10 +66,17 @@ static const std::string boundary_mode_average ("boundary-mode-average"); // items in boundary_mode_cb static const std::vector boundary_modes = { boundary_mode_periodic, boundary_mode_zero, boundary_mode_one, boundary_mode_average }; +static const std::string averaging_mode_square ("averaging-mode-square"); +static const std::string averaging_mode_gaussian ("averaging-mode-gaussian"); + +// items in average_mode_cb +static const std::vector averaging_modes = { averaging_mode_square, averaging_mode_gaussian }; + static const std::string cfg_density_map_region_mode ("density-map-region-mode"); static const std::string cfg_density_map_layer_mode ("density-map-layer-mode"); static const std::string cfg_density_map_pixel_size ("density-map-pixel-size"); static const std::string cfg_density_map_window_size ("density-map-window-size"); +static const std::string cfg_density_map_averaging_mode ("density-map-averaging-mode"); static const std::string cfg_density_map_boundary_mode ("density-map-boundary-mode"); static const std::string cfg_density_map_threads ("density-map-threads"); static const std::string cfg_density_map_source_layer ("density-map-source-layer"); @@ -90,6 +97,7 @@ class DensityMapDialogPluginDeclaration options.push_back (std::make_pair (cfg_density_map_pixel_size, "100")); options.push_back (std::make_pair (cfg_density_map_window_size, "0")); options.push_back (std::make_pair (cfg_density_map_boundary_mode, boundary_mode_periodic)); + options.push_back (std::make_pair (cfg_density_map_averaging_mode, averaging_mode_square)); options.push_back (std::make_pair (cfg_density_map_threads, "1")); } @@ -307,6 +315,18 @@ DensityMapDialog::configure (const std::string &name, const std::string &value) cb_boundary_mode->setCurrentIndex (mode); return true; + } else if (name == cfg_density_map_averaging_mode) { + + int mode = 0; + for (size_t i = 0; i < averaging_modes.size (); ++i) { + if (averaging_modes[i] == value) { + mode = int (i); + } + } + + cb_averaging_mode->setCurrentIndex (mode); + return true; + } else if (name == cfg_density_map_threads) { int thr = 1; @@ -390,6 +410,12 @@ DensityMapDialog::make_density_map () } par.boundary_mode = boundary_modes [boundary_mode]; + int averaging_mode = cb_averaging_mode->currentIndex (); + if (averaging_mode < 0 || averaging_mode >= int (averaging_modes.size ())) { + return; + } + par.averaging_mode = averaging_modes [averaging_mode]; + int region_mode = region_cb->currentIndex (); if (region_mode < 0 || region_mode >= int (region_modes.size ())) { return; @@ -514,6 +540,7 @@ DensityMapDialog::make_density_map () dispatcher ()->config_set (cfg_density_map_layer_mode, lm); dispatcher ()->config_set (cfg_density_map_region_mode, rm); dispatcher ()->config_set (cfg_density_map_boundary_mode, par.boundary_mode); + dispatcher ()->config_set (cfg_density_map_averaging_mode, par.averaging_mode); dispatcher ()->config_set (cfg_density_map_threads, tl::to_string (par.threads)); dispatcher ()->config_set (cfg_density_map_pixel_size, tl::to_string (par.pixel_size)); dispatcher ()->config_set (cfg_density_map_window_size, tl::to_string (par.window_size)); @@ -644,7 +671,39 @@ DensityMapDialog::compute_density_map (const DensityMapParameters &par) unsigned int nw = (unsigned int) (std::max (0.0, std::min (1000.0, floor (par.window_size / par.pixel_size + 0.5)))); if (nw > 1) { - average_window (*img_object, par.boundary_mode, nw); + + if (par.averaging_mode == averaging_mode_square) { + + std::vector weights; + + int wh = nw / 2; + weights.resize (wh * 2 + 1, 1.0); + if (nw % 2 == 0) { + weights.front () = weights.back () = 0.5; + } + + average_window (*img_object, par.boundary_mode, wh, weights); + + } else if (par.averaging_mode == averaging_mode_gaussian) { + + std::vector weights; + + int wh = nw * 3 / 2; + + // Note: the averaging kernel (f = exp(-r^2) = exp(-(x^2+y^2)) can be decomposed + // into f = f(x)*f(y); f(x) = exp(-x^2); f(y) = exp(-y^2). + // Hence we can use a 1d-kernel here and average vertically first, then horizontally. + + weights.reserve (wh * 2 + 1); + for (int i = -wh; i <= wh; ++i) { + double x = double (i) / (nw * 0.5); + weights.push_back (exp (-x * x)); + } + + average_window (*img_object, par.boundary_mode, wh, weights); + + } + } } @@ -658,8 +717,15 @@ inline int safe_mod (int a, int b) } void -DensityMapDialog::average_window (img::Object &img_object, const std::string boundary_mode, int nw) +DensityMapDialog::average_window (img::Object &img_object, const std::string boundary_mode, int wh, const std::vector &weights) { + tl_assert (weights.size () == size_t (wh * 2 + 1)); + + double ws = 0.0; + for (auto i = weights.begin (); i != weights.end (); ++i) { + ws += *i; + } + bool periodic = (boundary_mode == boundary_mode_periodic); int nx = img_object.width (); @@ -673,7 +739,6 @@ DensityMapDialog::average_window (img::Object &img_object, const std::string bou } else if (boundary_mode == boundary_mode_average) { - double outside = 0.0; const float *d = img_object.float_data (); for (size_t i = size_t (nx) * size_t (ny); i > 0; --i) { outside += *d++; @@ -689,13 +754,10 @@ DensityMapDialog::average_window (img::Object &img_object, const std::string bou for (int y = 0; y < ny; ++y) { - int wh = nw / 2; - double fb = (nw % 2 == 0) ? 0.5 : 1.0; - for (int dy = -wh; dy <= wh; ++dy) { // top and bottom row count half in case of even nw - double f = (dy == -wh || dy == wh) ? fb : 1.0; + double f = weights [dy + wh]; std::vector::iterator d = vavg_data.begin () + y * nx; if (periodic || (y + dy >= 0 && y + dy < ny)) { @@ -715,7 +777,7 @@ DensityMapDialog::average_window (img::Object &img_object, const std::string bou // horizontal sum - outside *= nw; // because we do normalization later + outside *= ws; // because we do normalization later // TODO: transposing the image would make things more efficient @@ -724,13 +786,10 @@ DensityMapDialog::average_window (img::Object &img_object, const std::string bou for (int x = 0; x < nx; ++x) { - int wh = nw / 2; - double fb = (nw % 2 == 0) ? 0.5 : 1.0; - for (int dx = -wh; dx <= wh; ++dx) { // top and bottom row count half in case of even nw - double f = (dx == -wh || dx == wh) ? fb : 1.0; + double f = weights [dx + wh]; std::vector::iterator d = havg_data.begin () + x; @@ -753,7 +812,7 @@ DensityMapDialog::average_window (img::Object &img_object, const std::string bou } // take the average - double s = 1.0 / (double (nw) * double (nw)); + double s = 1.0 / (ws * ws); for (auto i = havg_data.begin (); i != havg_data.end (); ++i) { *i *= s; } diff --git a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h index bd99bbbe8..ebaa4752f 100644 --- a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h @@ -76,6 +76,9 @@ public slots: // The boundary mode std::string boundary_mode; + // The averaging mode + std::string averaging_mode; + // The number of threads to use int threads; }; @@ -88,7 +91,7 @@ public slots: void make_density_map (); void compute_density_map (const DensityMapParameters &par); - void average_window (img::Object &img_object, const std::string boundary_mode, int nw); + void average_window (img::Object &img_object, const std::string boundary_mode, int wh, const std::vector &weights); }; From 95271e3edc747bf36a453598983a2c245fb23736 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 19:17:33 +0200 Subject: [PATCH 18/26] Fixing color interpolation of false color maps Problem was when an interval was separated from the rest through a discontinuous color and did not have any color range (start and end color same). The new scheme fixes the problem by computing a data mapping that has two slightly distinct values for the two discontinuous colors. --- src/img/img/imgObject.cc | 167 ++++++++++++++++++-------------- src/img/img/imgObject.h | 2 +- src/img/unit_tests/imgObject.cc | 43 ++++++++ 3 files changed, 140 insertions(+), 72 deletions(-) diff --git a/src/img/img/imgObject.cc b/src/img/img/imgObject.cc index 4f6d97cf3..f823a3198 100644 --- a/src/img/img/imgObject.cc +++ b/src/img/img/imgObject.cc @@ -47,6 +47,65 @@ namespace img { +// -------------------------------------------------------------------------------------- + +namespace +{ + +struct compare_first_of_node +{ + bool operator() (const std::pair > &a, const std::pair > &b) const + { + return a.first < b.first; + } +}; + +} + +static tl::Color +interpolated_color2 (const std::pair > &n1, const std::pair > &n2, double x) +{ + double x1 = n1.first; + double x2 = n2.first; + + unsigned int h1 = 0, s1 = 0, v1 = 0; + n1.second.second.get_hsv (h1, s1, v1); + + unsigned int h2 = 0, s2 = 0, v2 = 0; + n2.second.first.get_hsv (h2, s2, v2); + + int h = int (0.5 + h1 + double(x - x1) * double (int (h2) - int (h1)) / double(x2 - x1)); + int s = int (0.5 + s1 + double(x - x1) * double (int (s2) - int (s1)) / double(x2 - x1)); + int v = int (0.5 + v1 + double(x - x1) * double (int (v2) - int (v1)) / double(x2 - x1)); + + return tl::Color::from_hsv ((unsigned int) h, (unsigned int) s, (unsigned int) v); +} + +tl::Color +interpolated_color (const DataMapping::false_color_nodes_type &nodes, double x) +{ + if (nodes.size () < 1) { + + return tl::Color (); + + } else if (nodes.size () < 2) { + + return x < nodes[0].first ? nodes[0].second.first : nodes[0].second.second; + + } else { + + std::vector > >::const_iterator p = std::lower_bound (nodes.begin (), nodes.end (), std::make_pair (x, std::make_pair (tl::Color (), tl::Color ())), compare_first_of_node ()); + if (p == nodes.end ()) { + return nodes.back ().second.second; + } else if (p == nodes.begin ()) { + return nodes.front ().second.first; + } else { + return interpolated_color2 (p[-1], *p, x); + } + + } +} + // -------------------------------------------------------------------------------------- // img::DataMapping implementation @@ -153,6 +212,21 @@ DataMapping::operator< (const DataMapping &d) const return false; } +static inline double color_to_channel_value (const tl::Color &c, unsigned int channel) +{ + double y = 0.0; + + if (channel == 0) { + y = c.red (); + } else if (channel == 1) { + y = c.green (); + } else if (channel == 2) { + y = c.blue (); + } + + return y / 255.0; +} + tl::DataMappingBase * DataMapping::create_data_mapping (bool monochrome, double xmin, double xmax, unsigned int channel) const { @@ -191,6 +265,8 @@ DataMapping::create_data_mapping (bool monochrome, double xmin, double xmax, uns if (monochrome && false_color_nodes.size () > 1) { tl::TableDataMapping *gray_to_color = new tl::TableDataMapping (); + double xfence = false_color_nodes.front ().first - 1.0; + double xeps = 1e-5; for (unsigned int i = 1; i < false_color_nodes.size (); ++i) { @@ -212,35 +288,35 @@ DataMapping::create_data_mapping (bool monochrome, double xmin, double xmax, uns for (int j = 0; j < n; ++j) { - tl::Color c = interpolated_color (false_color_nodes, x); + tl::Color c = interpolated_color2 (false_color_nodes [i - 1], false_color_nodes [i], x); - double y = 0.0; - if (channel == 0) { - y = c.red (); - } else if (channel == 1) { - y = c.green (); - } else if (channel == 2) { - y = c.blue (); - } - - gray_to_color->push_back (x, y / 255.0); + double xx = std::max (xfence * (1.0 + xeps), x); + xfence = xx; + double y = color_to_channel_value (c, channel); + gray_to_color->push_back (xx, y); x += dx; } - } + // on discontinuous colors add another entry that manifests the discontinuity + if (i + 1 < false_color_nodes.size () && false_color_nodes [i].second.first != false_color_nodes [i].second.second) { + + double x = false_color_nodes [i].first; + double xx = std::max (xfence * (1.0 + xeps), x); + xfence = xx; + double y = color_to_channel_value (false_color_nodes [i].second.first, channel); + gray_to_color->push_back (xx, y); + + } - double ylast = 0.0; - if (channel == 0) { - ylast = false_color_nodes.back ().second.second.red (); - } else if (channel == 1) { - ylast = false_color_nodes.back ().second.second.green (); - } else if (channel == 2) { - ylast = false_color_nodes.back ().second.second.blue (); } - gray_to_color->push_back (false_color_nodes.back ().first, ylast / 255.0); + double x = false_color_nodes.back ().first; + double xx = std::max (xfence * (1.0 + xeps), x); + xfence = xx; + double ylast = color_to_channel_value (false_color_nodes.back ().second.first, channel); + gray_to_color->push_back (xx, ylast); dm = new tl::CombinedDataMapping ( to_pixel, @@ -266,57 +342,6 @@ DataMapping::create_data_mapping (bool monochrome, double xmin, double xmax, uns return dm; } -// -------------------------------------------------------------------------------------- - -namespace -{ - -struct compare_first_of_node -{ - bool operator() (const std::pair > &a, const std::pair > &b) const - { - return a.first < b.first; - } -}; - -} - -tl::Color -interpolated_color (const DataMapping::false_color_nodes_type &nodes, double x) -{ - if (nodes.size () < 1) { - return tl::Color (); - } else if (nodes.size () < 2) { - return x < nodes[0].first ? nodes[0].second.first : nodes[0].second.second; - } else { - - std::vector > >::const_iterator p = std::lower_bound (nodes.begin (), nodes.end (), std::make_pair (x, std::make_pair (tl::Color (), tl::Color ())), compare_first_of_node ()); - if (p == nodes.end ()) { - return nodes.back ().second.second; - } else if (p == nodes.begin ()) { - return nodes.front ().second.first; - } else { - - double x1 = p[-1].first; - double x2 = p->first; - - unsigned int h1 = 0, s1 = 0, v1 = 0; - p[-1].second.second.get_hsv (h1, s1, v1); - - unsigned int h2 = 0, s2 = 0, v2 = 0; - p->second.first.get_hsv (h2, s2, v2); - - int h = int (0.5 + h1 + double(x - x1) * double (int (h2) - int (h1)) / double(x2 - x1)); - int s = int (0.5 + s1 + double(x - x1) * double (int (s2) - int (s1)) / double(x2 - x1)); - int v = int (0.5 + v1 + double(x - x1) * double (int (v2) - int (v1)) / double(x2 - x1)); - - return tl::Color::from_hsv ((unsigned int) h, (unsigned int) s, (unsigned int) v); - - } - - } -} - // -------------------------------------------------------------------------------------- // img::DataHeader definition and implementation diff --git a/src/img/img/imgObject.h b/src/img/img/imgObject.h index 6658d2ae9..8d254ee51 100644 --- a/src/img/img/imgObject.h +++ b/src/img/img/imgObject.h @@ -145,7 +145,7 @@ struct IMG_PUBLIC DataMapping /** * @brief A helper function to interpolate a color in the color bar at a given x */ -tl::Color interpolated_color (const DataMapping::false_color_nodes_type &nodes, double x); +IMG_PUBLIC tl::Color interpolated_color (const DataMapping::false_color_nodes_type &nodes, double x); /** * @brief A image object diff --git a/src/img/unit_tests/imgObject.cc b/src/img/unit_tests/imgObject.cc index bd4d5d7e9..30d1ca625 100644 --- a/src/img/unit_tests/imgObject.cc +++ b/src/img/unit_tests/imgObject.cc @@ -328,3 +328,46 @@ TEST(3) EXPECT_EQ (image.mask (1, 2), false); } +// color interpolation +TEST(4) +{ + img::DataMapping::false_color_nodes_type nodes; + + nodes.push_back (std::make_pair (0.0, std::make_pair (tl::color_t (0xff0000), tl::color_t (0xff0000)))); + nodes.push_back (std::make_pair (1.0, std::make_pair (tl::color_t (0x0000ff), tl::color_t (0xffffff)))); + nodes.push_back (std::make_pair (2.0, std::make_pair (tl::color_t (0x000000), tl::color_t (0xffffff)))); + + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.0)).to_string (), "#ff0000"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.001)).to_string (), "#ff0000"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.5)).to_string (), "#00ff00"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.999)).to_string (), "#0000ff"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.0)).to_string (), "#0000ff"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.001)).to_string (), "#ffffff"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.5)).to_string (), "#808080"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.999)).to_string (), "#000000"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 2.0)).to_string (), "#000000"); + + nodes.clear (); + + nodes.push_back (std::make_pair (0.0, std::make_pair (tl::color_t (0xff0000), tl::color_t (0xff0000)))); + nodes.push_back (std::make_pair (1.0, std::make_pair (tl::color_t (0xff0000), tl::color_t (0x0000ff)))); + nodes.push_back (std::make_pair (2.0, std::make_pair (tl::color_t (0x0000ff), tl::color_t (0x0000ff)))); + + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.0)).to_string (), "#ff0000"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.5)).to_string (), "#ff0000"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.999)).to_string (), "#ff0000"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.0)).to_string (), "#ff0000"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.001)).to_string (), "#0000ff"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.5)).to_string (), "#0000ff"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 1.999)).to_string (), "#0000ff"); + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 2.0)).to_string (), "#0000ff"); + + nodes.clear (); + + nodes.push_back (std::make_pair (0.0, std::make_pair (tl::color_t (0x0000ff), tl::color_t (0x0000ff)))); + nodes.push_back (std::make_pair (0.275591, std::make_pair (tl::color_t (0x0000ff), tl::color_t (0x000000)))); + nodes.push_back (std::make_pair (0.643701, std::make_pair (tl::color_t (0xffffff), tl::color_t (0xff0000)))); + nodes.push_back (std::make_pair (1.0, std::make_pair (tl::color_t (0xff0000), tl::color_t (0xff0000)))); + + EXPECT_EQ (tl::Color (img::interpolated_color (nodes, 0.7)).to_string (), "#ff0000"); +} From 9415e409163524460e56e49121185c3a48e44bba Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 24 May 2026 19:21:44 +0200 Subject: [PATCH 19/26] Updating test data --- testdata/lvs/split_substrate.l2n | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testdata/lvs/split_substrate.l2n b/testdata/lvs/split_substrate.l2n index 95cb99951..0fe18254e 100644 --- a/testdata/lvs/split_substrate.l2n +++ b/testdata/lvs/split_substrate.l2n @@ -17,8 +17,8 @@ L(l10) L(l2) L(l9) L(l6) -L(l21) L(l18) +L(l21) L(l20) L(l19) C(l3 l3 l10) @@ -40,9 +40,9 @@ CS(l10 l3) C(l2 l8 l2) C(l9 l8 l9 l21 l19) C(l6 l8 l6) +C(l18 l7 l18) C(l21 l22 l9 l21) CS(l21 l22) -C(l18 l7 l18) C(l20 l22 l7 l20) C(l19 l9 l19) G(l22 IOSUB) From 249de433506034f43ed12965c7a5a967e31d4d19 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 25 May 2026 00:07:30 +0200 Subject: [PATCH 20/26] Fixed some compiler warnings --- src/db/db/dbDeepShapeStore.cc | 2 -- src/db/db/dbEdgePairFilters.h | 1 - src/db/db/dbEdgesUtils.h | 1 - 3 files changed, 4 deletions(-) diff --git a/src/db/db/dbDeepShapeStore.cc b/src/db/db/dbDeepShapeStore.cc index 5ded121e1..3ff11ccce 100644 --- a/src/db/db/dbDeepShapeStore.cc +++ b/src/db/db/dbDeepShapeStore.cc @@ -1389,7 +1389,6 @@ namespace { public: DeepShapeStoreToShapeTransformer (const DeepShapeStore &dss, const db::Layout &layout) - : mp_layout (& layout) { // gets the text annotation property ID - // this is how the texts are passed for annotating the net names @@ -1445,7 +1444,6 @@ namespace private: std::pair m_text_annot_name_id; - const db::Layout *mp_layout; }; } diff --git a/src/db/db/dbEdgePairFilters.h b/src/db/db/dbEdgePairFilters.h index 3d8342b93..0881b2649 100644 --- a/src/db/db/dbEdgePairFilters.h +++ b/src/db/db/dbEdgePairFilters.h @@ -119,7 +119,6 @@ class DB_PUBLIC InternalAngleEdgePairFilter virtual bool wants_variants () const { return false; } private: - bool m_inverted; db::EdgeAngleChecker m_checker; }; diff --git a/src/db/db/dbEdgesUtils.h b/src/db/db/dbEdgesUtils.h index ad6acd0b7..b2c02005a 100644 --- a/src/db/db/dbEdgesUtils.h +++ b/src/db/db/dbEdgesUtils.h @@ -244,7 +244,6 @@ struct DB_PUBLIC EdgeOrientationFilter } private: - bool m_inverse; db::MagnificationAndOrientationReducer m_vars; EdgeAngleChecker m_checker; }; From 37977d21e9b75f52f925a20c9d25f02fbd640a08 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 25 May 2026 00:09:38 +0200 Subject: [PATCH 21/26] Fixing issue #2350 (strm2oas writes empty OAS) Problem was that the OASIS writer was simply ignoring all top level proxy cells (PCells, Library references). The original bug #1835 fixed that by changing the reader behavior, so it would keep top level proxies. However, doing the spin through the writer got them removed and in addition, the cleanup happing during editing would also remove them. Solution is to centralize the strategy of cleaning cells. The cleanup now is changed to not remove proxy top cells if they are the only ones. This is consistent with the previous reader-only behavior. The writer implements the same behavior by means dropping cells marked for cleanup, instead of simply skipping all proxy cells. --- src/db/db/dbCommonReader.cc | 25 +--- src/db/db/dbLayout.cc | 101 +++++++++++--- src/db/db/dbLayout.h | 13 ++ src/db/db/dbSaveLayoutOptions.cc | 17 ++- src/db/unit_tests/dbLayoutTests.cc | 131 ++++++++++++++++++ .../gds2/db_plugin/dbGDS2WriterBase.cc | 2 +- .../oasis/db_plugin/dbOASISWriter.cc | 10 +- testdata/lstream/pcells_au.oas | Bin 366 -> 420 bytes 8 files changed, 245 insertions(+), 54 deletions(-) diff --git a/src/db/db/dbCommonReader.cc b/src/db/db/dbCommonReader.cc index 617ea3f1f..15cb0fb2e 100644 --- a/src/db/db/dbCommonReader.cc +++ b/src/db/db/dbCommonReader.cc @@ -600,30 +600,7 @@ CommonReader::read (db::Layout &layout, const db::LoadLayoutOptions &options) // A cleanup may be necessary because of the following scenario: if library proxies contain subcells // which are proxies themselves, the proxy update may make them orphans (the proxies are regenerated). // The cleanup will removed these. - - // Adressing issue #1835 (reading proxy-only GDS file renders empty layout) we do not delete - // the first (non-cold) proxy if there are only proxy top cells. - // We never clean up the top cell if there is a single one. This catches the case of having - // defunct proxies for top cells. - - std::set keep; - if (layout.end_top_cells () - layout.begin_top_down () == 1) { - keep.insert (*layout.begin_top_down ()); - } else { - for (auto c = layout.begin_top_down (); c != layout.end_top_cells (); ++c) { - const db::Cell *cptr = &layout.cell (*c); - if (cptr->is_proxy ()) { - if (! dynamic_cast (cptr) && keep.empty ()) { - keep.insert (*c); - } - } else { - keep.clear (); - break; - } - } - } - - layout.cleanup (keep); + layout.cleanup (); return layer_map_out (); } diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index fb9cbbc04..071f30aff 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -1723,6 +1723,84 @@ Layout::refresh () } } +std::set +Layout::cells_to_cleanup (const std::set &keep_always) const +{ + std::set to_clean; + + // Adressing issue #1835 (reading proxy-only GDS file renders empty layout) we do not delete + // the first (non-cold) proxy if there are only proxy top cells. + // We never clean up the top cell if there is a single one. This catches the case of having + // defunct proxies for top cells. + + if (end_top_cells () - begin_top_down () > 1) { + + std::set keep; + + for (auto c = begin_top_down (); c != end_top_cells (); ++c) { + const db::Cell *cptr = &cell (*c); + if (cptr->is_proxy ()) { + if (! dynamic_cast (cptr) && keep.empty ()) { + keep.insert (*c); + } + } else { + keep.clear (); + break; + } + + } + + for (auto c = begin_top_down (); c != end_top_cells (); ++c) { + if (cell (*c).is_proxy () && keep.find (*c) == keep.end () && keep_always.find (*c) == keep_always.end ()) { + to_clean.insert (*c); + } + } + + } + + // determine all cells that can be deleted as well because they are called + // by cells registered for cleanup and nowhere else + + if (! to_clean.empty ()) { + + // collect the called cells + std::set all_called; + for (auto c = to_clean.begin (); c != to_clean.end (); ++c) { + cell (*c).collect_called_cells (all_called, -1); + } + + // remove all called cells which are not proxies - we don't want to + // clean up those and their children. + std::set called; + for (auto c = all_called.begin (); c != all_called.end (); ++c) { + if (cell (*c).is_proxy ()) { + called.insert (*c); + } + } + + // From these cells erase all cells that have parents outside the subtree of our cell. + // Make sure this is done recursively by doing this top-down. + for (auto c = begin_top_down (); c != end_top_down (); ++c) { + if (called.find (*c) != called.end ()) { + const db::Cell &ccref = cell (*c); + for (db::Cell::parent_cell_iterator pc = ccref.begin_parent_cells (); pc != ccref.end_parent_cells (); ++pc) { + if (to_clean.find (*pc) == to_clean.end () && called.find (*pc) == called.end ()) { + // we have a parent outside the subset considered currently (either the cell was never in or + // it was removed itself already): remove this cell from the set of valid subcells. + called.erase (*c); + break; + } + } + } + } + + to_clean.insert (called.begin (), called.end ()); + + } + + return to_clean; +} + void Layout::cleanup (const std::set &keep) { @@ -1801,28 +1879,17 @@ Layout::cleanup (const std::set &keep) } - std::set cells_to_delete; - - // deleting cells may create new top cells which need to be deleted as well, hence we iterate - // until there are no more cells to delete - while (true) { - // delete all cells that are top cells and are proxies. Those cells are proxies no longer required. - for (top_down_iterator c = begin_top_down (); c != end_top_cells (); ++c) { - if (cell (*c).is_proxy () && keep.find (*c) == keep.end ()) { - cells_to_delete.insert (*c); - } - } + // Remove all cells that are targeted for cleanup - if (cells_to_delete.empty ()) { - break; + { + std::set cells_to_delete = cells_to_cleanup (keep); + if (! cells_to_delete.empty ()) { + delete_cells (cells_to_delete); } - - delete_cells (cells_to_delete); - cells_to_delete.clear (); - } + // Try to ensure that cell names reflect the library cell names. The latter is good for LVS for example. for (auto c = m_lib_proxy_map.begin (); c != m_lib_proxy_map.end (); ++c) { diff --git a/src/db/db/dbLayout.h b/src/db/db/dbLayout.h index b9bc0f1c9..36ec43dc5 100644 --- a/src/db/db/dbLayout.h +++ b/src/db/db/dbLayout.h @@ -1682,6 +1682,19 @@ class DB_PUBLIC Layout */ void cleanup (const std::set &keep = std::set ()); + /** + * @brief Gets a list of cells which are cleanup candidates + * + * The returned list also includes cells that are indirectly cleaned up. + * The cleanup only removes top cells which represent unused proxies + * with some exceptions - for example proxies that are single top cells + * are retained. + * + * However, removing the top cells should not produce new top cells + * of proxies, so these cells need to be removed as well. + */ + std::set cells_to_cleanup (const std::set &keep = std::set ()) const; + /** * @brief Calls "update" on all cells of the layout * diff --git a/src/db/db/dbSaveLayoutOptions.cc b/src/db/db/dbSaveLayoutOptions.cc index 236a3f38c..33c14497b 100644 --- a/src/db/db/dbSaveLayoutOptions.cc +++ b/src/db/db/dbSaveLayoutOptions.cc @@ -372,12 +372,19 @@ SaveLayoutOptions::get_cells (const db::Layout &layout, std::set cleaned_cells = layout.cells_to_cleanup (); + bool has_skipped_replica = false; // check if we have skipped replicas if (has_context) { for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end () && ! has_skipped_replica; ++cell) { - has_skipped_replica = cell->can_skip_replica (); + if (cell->can_skip_replica () && cleaned_cells.find (cell->cell_index ()) == cleaned_cells.end ()) { + has_skipped_replica = true; + } } } @@ -386,7 +393,7 @@ SaveLayoutOptions::get_cells (const db::Layout &layout, std::set is_top ()) { + if (cell->is_top () && cleaned_cells.find (cell->cell_index ()) == cleaned_cells.end ()) { cells.insert (cell->cell_index ()); collect_called_cells_unskipped (cell->cell_index (), layout, cells); } @@ -394,8 +401,10 @@ SaveLayoutOptions::get_cells (const db::Layout &layout, std::set cell_index ()); + for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end (); ++cell) { + if (cleaned_cells.find (cell->cell_index ()) == cleaned_cells.end ()) { + cells.insert (cell->cell_index ()); + } } } diff --git a/src/db/unit_tests/dbLayoutTests.cc b/src/db/unit_tests/dbLayoutTests.cc index 9b6178ca8..c1dee66d1 100644 --- a/src/db/unit_tests/dbLayoutTests.cc +++ b/src/db/unit_tests/dbLayoutTests.cc @@ -1040,3 +1040,134 @@ TEST(101_CopyTreeDoesNotModifyPolygons) EXPECT_EQ (l2s (l), "begin_lib 0.001\nbegin_cell {TOP}\nboundary 1 0 {0 0} {0 1000} {500 1000} {1500 1000} {1000 1000} {1000 0} {0 0}\nend_cell\nend_lib\n"); } +namespace +{ + +class LIBT_L + : public db::Library +{ +public: + LIBT_L (tl::TestBase *_this) + : Library () + { + set_name("L"); + set_description("A test library."); + + layout ().dbu (0.001); + + db::LayerProperties p; + + p.layer = 23; + p.datatype = 0; + unsigned int l_cont = layout ().insert_layer (p); + + p.layer = 16; + p.datatype = 0; + unsigned int l_gate = layout ().insert_layer (p); + + db::Cell &cell_a = layout ().cell (layout ().add_cell ("A")); + cell_a.shapes(l_cont).insert(db::Box (50, 50, 150, 150)); + cell_a.shapes(l_gate).insert(db::Box (0, 0, 200, 1000)); + + db::Cell &top = layout ().cell (layout ().add_cell ("TOP")); + top.insert (db::CellInstArray (db::CellInst (cell_a.cell_index ()), db::Trans (db::Vector (0, 0)))); + } + + ~LIBT_L() + { + // .. nothing yet .. + } +}; + +} + +static std::string cells2string (const db::Layout &layout, bool top = true) +{ + std::string s; + auto cto = top ? layout.end_top_cells () : layout.end_top_down (); + for (auto c = layout.begin_top_down (); c != cto; ++c) { + if (! s.empty ()) { + s += ","; + } + if (layout.cell (*c).is_proxy ()) { + s += "*"; + } + s += layout.cell_name (*c); + } + return s; +} + +// issue #2350 +TEST(101_CleanupBehavior) +{ + std::unique_ptr lib (new LIBT_L (_this)); + db::LibraryManager::instance ().register_lib (lib.get ()); + + db::Manager m (true); + db::Layout layout (&m); + layout.do_cleanup (true); + layout.dbu (0.001); + + db::Cell &top = layout.cell (layout.add_cell ("TOPTOP")); + + db::cell_index_type lib_top = lib->layout ().cell_by_name ("TOP").second; + db::cell_index_type lp1 = layout.get_lib_proxy (lib.get (), lib_top); + + db::Instance i1 = top.insert (db::CellInstArray (db::CellInst (lp1), db::Trans (db::Vector (0, 0)))); + + db::Layout layout2 = layout; + layout2.do_cleanup (true); + + EXPECT_EQ (cells2string (layout2, false), "TOPTOP,*TOP,*A"); + EXPECT_EQ (cells2string (layout2), "TOPTOP"); + + layout2.cleanup (); + + // cleanup does not change anything as the top cell is not a proxy + EXPECT_EQ (cells2string (layout2, false), "TOPTOP,*TOP,*A"); + EXPECT_EQ (cells2string (layout2), "TOPTOP"); + + top.erase (i1); + + layout2 = layout; + layout2.do_cleanup (true); + + EXPECT_EQ (cells2string (layout2, false), "TOPTOP,*TOP,*A"); + EXPECT_EQ (cells2string (layout2), "TOPTOP,*TOP"); + + layout2.cleanup (); + + // cleanup removes the proxy and subcell + EXPECT_EQ (cells2string (layout2, false), "TOPTOP"); + EXPECT_EQ (cells2string (layout2), "TOPTOP"); + + // now we borrow *A (LIB.A) and place it in TOPTOP + db::cell_index_type ci_a = layout.cell_by_name ("A").second; + EXPECT_EQ (layout.cell (ci_a).is_proxy (), true); + top.insert (db::CellInstArray (db::CellInst (ci_a), db::Trans (db::Vector (0, 0)))); + + layout2 = layout; + layout2.do_cleanup (true); + + EXPECT_EQ (cells2string (layout2, false), "TOPTOP,*TOP,*A"); + EXPECT_EQ (cells2string (layout2), "TOPTOP,*TOP"); + + layout2.cleanup (); + + // cleanup removes the *TOP proxy, but not *A as it is used by TOPTOP + EXPECT_EQ (cells2string (layout2, false), "TOPTOP,*A"); + EXPECT_EQ (cells2string (layout2), "TOPTOP"); + + layout2 = layout; + layout2.delete_cell (top.cell_index ()); // layout and layout2 use the same cell indexes + layout2.do_cleanup (true); + + EXPECT_EQ (cells2string (layout2, false), "*TOP,*A"); + EXPECT_EQ (cells2string (layout2), "*TOP"); + + layout2.cleanup (); + + // cleanup does not remove the proxy as it is the only top cell + EXPECT_EQ (cells2string (layout2, false), "*TOP,*A"); + EXPECT_EQ (cells2string (layout2), "*TOP"); +} diff --git a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc index 712ea2bd8..ce62968e2 100644 --- a/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc +++ b/src/plugins/streamers/gds2/db_plugin/dbGDS2WriterBase.cc @@ -582,7 +582,7 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S // don't write ghost cells unless they are not empty (any more) // also don't write proxy cells which are not employed - if (! cref.is_real_ghost_cell () && (! cref.is_proxy () || ! cref.is_top ())) { + if (! cref.is_real_ghost_cell ()) { try { bool skip_body = options.write_context_info () && cref.can_skip_replica (); diff --git a/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.cc b/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.cc index 93e0cafd0..bebf4d175 100644 --- a/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.cc +++ b/src/plugins/streamers/oasis/db_plugin/dbOASISWriter.cc @@ -1437,12 +1437,6 @@ OASISWriter::write_layername_table (size_t &layernames_table_pos, const std::vec end_table (layernames_table_pos); } -static bool must_write_cell (const db::Cell &cref) -{ - // Don't write proxy cells which are not employed - return ! cref.is_proxy () || ! cref.is_top (); -} - void OASISWriter::create_cell_nstrings (const db::Layout &layout, const std::set &cell_set) { @@ -1511,13 +1505,13 @@ OASISWriter::write (db::Layout &layout, tl::OutputStream &stream, const db::Save cells_by_index.reserve (cell_set.size ()); for (db::Layout::bottom_up_const_iterator cell = layout.begin_bottom_up (); cell != layout.end_bottom_up (); ++cell) { - if (cell_set.find (*cell) != cell_set.end () && must_write_cell (layout.cell (*cell))) { + if (cell_set.find (*cell) != cell_set.end ()) { cells.push_back (*cell); } } for (db::Layout::const_iterator cell = layout.begin (); cell != layout.end (); ++cell) { - if (cell_set.find (cell->cell_index ()) != cell_set.end () && must_write_cell (layout.cell (cell->cell_index ()))) { + if (cell_set.find (cell->cell_index ()) != cell_set.end ()) { cells_by_index.push_back (cell->cell_index ()); } } diff --git a/testdata/lstream/pcells_au.oas b/testdata/lstream/pcells_au.oas index 88943799d812b233eab24fa7b84a1005ad322b89..3554c35e72dd92551d6ce77c6d74660080bcfe96 100644 GIT binary patch delta 88 zcmaFIw1jzr9IK2t3qQj|xd3}kHfPTuXCGHy22pKB1{T)E@-3W;CNM4pQVZoa{~hd09sxKEdT%j From 7508c8e4a43ad45a30af1da2e397242ff534476d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 25 May 2026 01:13:58 +0200 Subject: [PATCH 22/26] Enabling context writing also for top cells - this allows the OASIS writer to generate proxy cells on output during fix of issue #2350 --- src/db/db/dbLayout.cc | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index 071f30aff..a69e8b4bb 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -3032,12 +3032,7 @@ Layout::has_context_info (cell_index_type cell_index) const } } - const db::Cell &cref = cell (cell_index); - if (cref.is_proxy () && ! cref.is_top ()) { - return true; - } else { - return false; - } + return cell (cell_index).is_proxy (); } bool From a10cfce1e7e1335d794bcd05e2da9d38be82cc58 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 25 May 2026 19:23:39 +0200 Subject: [PATCH 23/26] Testdata variants needed (one for clang?) --- ...it_substrate.l2n => split_substrate.l2n.1} | 0 testdata/lvs/split_substrate.l2n.2 | 215 ++++++++++++++++++ 2 files changed, 215 insertions(+) rename testdata/lvs/{split_substrate.l2n => split_substrate.l2n.1} (100%) create mode 100644 testdata/lvs/split_substrate.l2n.2 diff --git a/testdata/lvs/split_substrate.l2n b/testdata/lvs/split_substrate.l2n.1 similarity index 100% rename from testdata/lvs/split_substrate.l2n rename to testdata/lvs/split_substrate.l2n.1 diff --git a/testdata/lvs/split_substrate.l2n.2 b/testdata/lvs/split_substrate.l2n.2 new file mode 100644 index 000000000..95cb99951 --- /dev/null +++ b/testdata/lvs/split_substrate.l2n.2 @@ -0,0 +1,215 @@ +#%l2n-klayout +W(TOP) +U(0.001) +L(l3 '1/0') +L(l4 '3/0') +L(l15 '3/1') +L(l8 '4/0') +L(l11 '5/0') +L(l12 '6/0') +L(l16 '6/1') +L(l13 '7/0') +L(l14 '8/0') +L(l17) +L(l22 '10/0') +L(l7) +L(l10) +L(l2) +L(l9) +L(l6) +L(l21) +L(l18) +L(l20) +L(l19) +C(l3 l3 l10) +C(l4 l4 l15 l11) +C(l15 l4 l15) +C(l8 l8 l12 l10 l2 l9 l6) +CS(l8 l10 l2 l9 l6) +C(l11 l4 l11 l12) +CS(l11 l4) +C(l12 l8 l11 l12 l16 l13) +C(l16 l12 l16) +C(l13 l12 l13 l14) +C(l14 l13 l14 l17) +C(l17 l14 l17) +C(l22 l22 l21 l20) +C(l7 l7 l18 l20) +C(l10 l3 l8 l10) +CS(l10 l3) +C(l2 l8 l2) +C(l9 l8 l9 l21 l19) +C(l6 l8 l6) +C(l21 l22 l9 l21) +CS(l21 l22) +C(l18 l7 l18) +C(l20 l22 l7 l20) +C(l19 l9 l19) +G(l22 IOSUB) +G(l18 SUBSTRATE) +G(l19 SUBSTRATE) +GS(l19 SUBSTRATE) +H(W B('Net with incomplete wiring (soft-connected partial nets)') C(TOP) X('soft-connection-check')) +H(B('\tPartial net #1: TOP/INV[r0 3,1.6]:$1 - $2') C(TOP) Q('(1.5,3.95;1.5,4.85;5.3,4.85;5.3,3.95)')) +H(B('\tPartial net #2: TOP/INV[r0 7.7,1.6]:$2 - $2') C(TOP) Q('(6.2,3.95;6.2,4.85;10,4.85;10,3.95)')) +K(PMOS MOS4) +K(NMOS MOS4) +D(D$PMOS PMOS + T(S + R(l2 (-900 -475) (775 950)) + ) + T(G + R(l4 (-125 -475) (250 950)) + ) + T(D + R(l2 (125 -475) (775 950)) + ) + T(B + R(l3 (-125 -475) (250 950)) + ) +) +D(D$NMOS NMOS + T(S + R(l6 (-900 -475) (775 950)) + ) + T(G + R(l4 (-125 -475) (250 950)) + ) + T(D + R(l6 (125 -475) (775 950)) + ) + T(B + R(l7 (-125 -475) (250 950)) + ) +) +X(INV + R((-1500 -800) (3800 4600)) + N(1 I($1) + R(l8 (1700 100) (200 200)) + R(l8 (-200 -600) (200 200)) + R(l8 (-1610 -210) (220 220)) + R(l8 (-220 180) (220 220)) + R(l12 (890 -760) (800 900)) + R(l12 (-1980 -830) (360 760)) + R(l13 (-305 -705) (250 250)) + R(l13 (-250 150) (250 250)) + R(l13 (1175 -225) (200 200)) + R(l13 (-200 -600) (200 200)) + R(l14 (-3400 -350) (3000 900)) + R(l14 (-200 -900) (1000 900)) + R(l9 (-700 -950) (400 1000)) + R(l6 (-1875 -975) (775 950)) + ) + N(2 I($2) + R(l3 (-1500 1800) (3000 2000)) + R(l3 (-200 -2000) (1000 2000)) + R(l8 (-2010 -1310) (220 220)) + R(l8 (-220 180) (220 220)) + R(l8 (1190 -210) (200 200)) + R(l8 (-200 -600) (200 200)) + R(l12 (-1680 -280) (360 760)) + R(l12 (820 -830) (800 900)) + R(l13 (-1925 -775) (250 250)) + R(l13 (-250 150) (250 250)) + R(l13 (1175 -225) (200 200)) + R(l13 (-200 -600) (200 200)) + R(l14 (-3400 -350) (3000 900)) + R(l14 (-200 -900) (1000 900)) + R(l10 (-700 -950) (400 1000)) + R(l2 (-1875 -975) (775 950)) + ) + N(3 I($4) + R(l4 (-125 -250) (250 2500)) + R(l4 (-250 -3050) (250 1600)) + R(l4 (-250 1200) (250 1600)) + ) + N(4 I($5) + R(l8 (-510 -310) (220 220)) + R(l8 (-220 180) (220 220)) + R(l8 (-220 2180) (220 220)) + R(l8 (-220 180) (220 220)) + R(l12 (-290 -3530) (360 2840)) + R(l12 (-360 -2800) (360 760)) + R(l12 (-360 2040) (360 760)) + R(l2 (-680 -855) (775 950)) + R(l6 (-775 -3750) (775 950)) + ) + N(5 I($I11)) + P(2) + P(3) + P(4) + P(1) + P(5) + D(1 D$PMOS + Y(0 2800) + E(L 0.25) + E(W 0.95) + E(AS 0.73625) + E(AD 0.73625) + E(PS 3.45) + E(PD 3.45) + T(S 4) + T(G 3) + T(D 2) + T(B 2) + ) + D(2 D$NMOS + Y(0 0) + E(L 0.25) + E(W 0.95) + E(AS 0.73625) + E(AD 0.73625) + E(PS 3.45) + E(PD 3.45) + T(S 4) + T(G 3) + T(D 1) + T(B 5) + ) +) +X(TOP + R((1334 621) (9246 5073)) + N(1 I($1) + R(l4 (2920 2600) (3980 400)) + R(l11 (-300 -300) (200 200)) + R(l12 (-300 -300) (690 400)) + ) + N(2 I(A) + R(l4 (7700 2600) (2880 400)) + R(l15 (-2380 -200) (0 0)) + ) + N(3 I(Q) + R(l12 (1810 2600) (690 400)) + R(l16 (-400 -200) (0 0)) + ) + N(4 I(IOSUB) + R(l22 (1334 621) (4230 5073)) + R(l21 (-964 -4594) (400 1000)) + R(l20 (-2125 -975) (250 950)) + ) + N(5 I($7) + R(l3 (4000 3400) (2700 2000)) + ) + N(6 I(SUBSTRATE) + R(l18 (7575 1125) (250 950)) + R(l19 (1475 -975) (400 1000)) + ) + P(2 I(A)) + P(3 I(Q)) + P(4 I(IOSUB)) + P(6 I(SUBSTRATE)) + X(1 INV Y(3000 1600) + P(0 5) + P(1 1) + P(2 3) + P(3 4) + P(4 4) + ) + X(2 INV Y(7700 1600) + P(0 5) + P(1 2) + P(2 1) + P(3 6) + P(4 6) + ) +) From 2ea09362571004c5c1d764d573335facaff013ea Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 25 May 2026 20:39:01 +0200 Subject: [PATCH 24/26] Preparations for 0.30.9 --- Changelog | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ Changelog.Debian | 7 +++++ version.sh | 4 +-- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/Changelog b/Changelog index 959aa1868..9a07c1d8b 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,69 @@ +0.30.9 (2026-05-25): +* Bugfix: %GITHUB%/issues/2331 Screen size error (Assertion tlPixelBuffer.cc:260 n < m_height was not true) +* Enhancement: %GITHUB%/issues/2335 Editor Options (F3) behaviour has changed +* Enhancement: %GITHUB%/issues/2337 [Feature Request] Add to_bytes and from_bytes to shape classes +* Bugfix: %GITHUB%/issues/2339 Ruby ReportDatabase merging can lead to internal error +* Bugfix: %GITHUB%/issues/2343 copy_tree implicitly drops collinear points +* Bugfix: %GITHUB%/issues/2344 Instance properties inside a PCell are not preserved in KLayout +* Bugfix: %GITHUB%/issues/2345 Substrate sequestration + Although this ticket was more a discussion, this enhancement fixes the behavior of + empty layers in deep mode, which allows placing device terminals on them. + This eliminates the need for the workaround mentioned in the ticket. +* Bugfix: %GITHUB%/issues/2350 strm2oas klayout/testdata/gds/issue_1835.gds a.oas, a.oas is empty + With this fix, it is possible to save and load layouts that use a single library references + or PCell variants as top cell. This is not strictly a supported use model, but may be + useful in some cases. The "strm2xxx" tools now allow translating such files from OASIS + to GDS and back. +* Bugfix: %GITHUB%/issues/2356 Edit options always shown, even for klayout -ne +* Enhancement: because of issue #2356 and #2335, the "Editor Options" dock was overhauled + * It is called "Tool Options" now as it hosts not just shape editor options + * The Ruler tool got its own page as the original page was not functional and + rulers have other options. This replicates some settings from the "Ruler + and annotations setup" page. + * The dock widget's visibility can be controlled in the same way than the other + docks. The "Edit/Editor Options" entry moved to "View/Tool Options". It turns + on or off the tool options dock, which is populated with pages depending on the tool. + The default key binding (F3) will now show or hide this dock. +* Enhancement: A feature to compute a density map. It is found inside Tools/Density Map. + The density map can be computed from one or multiple layers, the region can be + specified in terms of visible region, rulers or bounding boxes of certain layers + or the whole cell. The tool will generate an image overlay over the layout + holding the density data. +* Bugfix: using "xkill" on a KLayout instance ended in a crash +* Enhancement: settings of search features are persisted (see discussion 2868) +* Enhancement: when moving rulers with "snap to objects", all points of the + rulers snap to edges or vertexes now. +* Bugfix: false color maps in images where incorrectly interpolated in some + cases. +* Bugfix: small bugfix in L2N/LVSDB reader - default values of parameters were + treated as int. +* Bugfix: properties on objects with properties (such as BoxWithProperties) where + not properly serialized. Specifically their representation did not include the + type. Hence it was not possible to retrieve properties on deserialization with + "from_s". The fix is to annotate the property values with type information. + NOTE: this is a change that is not backward-compatible. +* Bugfix: snapping rulers to edges parallel to the allowed ruler direction resulted + in skew rulers. +* Enhancement: renamed ruler option for more clarity - changed + 'snap to objects (unless disabled in template)' to 'never snap to object' (inverted) +* Enhancement: enhancements in the script API + * Cell#is_cold_proxy?: indicates if a cell is a "defunc" library reference + * Cell#library_cell_name: gets the library cell name for a "defunc" reference + * Cell#library_name: gets the library name for a "defunc" reference + * Cell#pcell_name: gets the pcell name for a "defunct" reference + * Layout#delete_cells: now available with a list of cell object + * Layout#delete_cell_rec: now available with a cell object + * Layout#prune_cell: now available with a cell object + * Layout#prune_cells: for pruning multiple cells in one call + * Layout#prune_subcells: now with a cell object + * Layout#prune_subcells: now with multiple root cells + * Layout#flatten: now with a cell object + * Layout#flatten_into: now with cell objects + * Layout#delete_cell: now with a cell object + * Default value "all" for "levels" argument in "prune_subcells" and "prune_cell" + * Default value "all" for "levels" and "true" for "prune" argument in "flatten" + * Default value "all" for "levels" and "unity" for "trans" argument in "flatten_into" + 0.30.8 (2026-04-14): * Enhancement: %GITHUB%/issues/2248 Switch layouts but without losing the handle * Bugfix: %GITHUB%/issues/2299 gf180mcu DRC performance regression. diff --git a/Changelog.Debian b/Changelog.Debian index 3b572f5f4..44cba48fc 100644 --- a/Changelog.Debian +++ b/Changelog.Debian @@ -1,3 +1,10 @@ +klayout (0.30.9-1) unstable; urgency=low + + * New features and bugfixes + - See changelog + + -- Matthias Köfferlein Mon, 25 May 2026 19:46:05 +0200 + klayout (0.30.8-1) unstable; urgency=low * New features and bugfixes diff --git a/version.sh b/version.sh index c7782a9d7..664c35ca2 100644 --- a/version.sh +++ b/version.sh @@ -2,10 +2,10 @@ # This script is sourced to define the main version parameters # The main version -KLAYOUT_VERSION="0.30.8" +KLAYOUT_VERSION="0.30.9" # The version used for PyPI (don't use variables here!) -KLAYOUT_PYPI_VERSION="0.30.8" +KLAYOUT_PYPI_VERSION="0.30.9" # The build date KLAYOUT_VERSION_DATE=$(date "+%Y-%m-%d") From 6e0d968058cbdd0f84cb90419b9e2cb0a8688342 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 25 May 2026 22:01:03 +0200 Subject: [PATCH 25/26] Python stubs regenerated, DRC/LVS doc updated --- src/doc/doc/about/drc_ref_netter.xml | 4 +- src/pymod/distutils_src/klayout/dbcore.pyi | 1231 +++++++++++++++++-- src/pymod/distutils_src/klayout/rdbcore.pyi | 8 +- 3 files changed, 1149 insertions(+), 94 deletions(-) diff --git a/src/doc/doc/about/drc_ref_netter.xml b/src/doc/doc/about/drc_ref_netter.xml index afee536b7..aca1a28e7 100644 --- a/src/doc/doc/about/drc_ref_netter.xml +++ b/src/doc/doc/about/drc_ref_netter.xml @@ -574,7 +574,9 @@ in connect, soft_connect,

    If layers are not named, they will be given a name made from the name_prefix and an incremental number when the layer is used for the -first time. +first time. As the default name prefix is "l", you may can name conflicts +with auto-named layers, if you register explicit layer names like "l5". +Consider changing the name prefix in that case to some prefix you are not using.

    name can only be used once on a layer and the layer names must be unique (not taken by another layer). diff --git a/src/pymod/distutils_src/klayout/dbcore.pyi b/src/pymod/distutils_src/klayout/dbcore.pyi index 3f94bc341..1592a7260 100644 --- a/src/pymod/distutils_src/klayout/dbcore.pyi +++ b/src/pymod/distutils_src/klayout/dbcore.pyi @@ -73,6 +73,15 @@ class Box: @brief Sets the top coordinate of the box """ @classmethod + def from_bytes(cls, s: bytes) -> Box: + r""" + @brief Creates a box object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_dbox(cls, dbox: DBox) -> Box: r""" @brief Creates an integer coordinate box from a floating-point coordinate box @@ -728,6 +737,15 @@ class Box: This method has been introduced in version 0.23. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this box + + This string can be turned into a box again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_dtype(self, dbu: Optional[float] = ...) -> DBox: r""" @brief Converts the box to a floating-point coordinate box @@ -809,6 +827,24 @@ class BoxWithProperties(Box): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> BoxWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> BoxWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, box: Box, properties: Dict[Any, Any]) -> BoxWithProperties: @@ -844,7 +880,7 @@ class BoxWithProperties(Box): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> BoxWithProperties: @@ -856,7 +892,7 @@ class BoxWithProperties(Box): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: Box) -> None: @@ -1010,9 +1046,18 @@ class BoxWithProperties(Box): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -2183,6 +2228,23 @@ class Cell: It has been added in version 0.16. """ ... + def is_cold_proxy(self) -> bool: + r""" + @brief Returns true, if the cell is a 'cold proxy' + Cold proxies are cells that refer to a library cell or PCell variant, but can temporarily not be resolved - + for example, because the library is not installed. Such cells are basically placeholders + for library references and also carry PCell parameter information needed to establish + the link to the library PCell, once the library is available again. + + You can use \library_name to obtain the name of the library the proxy points to, \library_cell_name to obtain the cell name in that library, \pcell_name to obtain the PCell name if it is a PCell proxy, and \pcell_parameter or \pcell_parameters_by_name to obtain the PCell parameters. + + Cold proxies cannot be created or modified. Cold proxies are basically error indicators and should be fixed by installing the respective library. Their layout state + reflects the last version of the layout when the cell was functional and properly + linked to a library. Still, they can be used in read-only applications. + + This method has been introduced in version 0.30.9. + """ + ... def is_const_object(self) -> bool: r""" @brief Returns a value indicating whether the reference is a const reference @@ -2271,7 +2333,7 @@ class Cell: ... def is_proxy(self) -> bool: r""" - @brief Returns true, if the cell presents some external entity + @brief Returns true, if the cell presents some external entity A cell may represent some data which is imported from some other source, i.e. a library. Such cells are called "proxy cells". For a library reference, the proxy cell is some kind of pointer to the library and the cell within the library. @@ -2279,8 +2341,9 @@ class Cell: For PCells, this data can even be computed through some script. A PCell proxy represents all instances with a given set of parameters. - Proxy cells cannot be modified, except that pcell parameters can be modified - and PCell instances can be recomputed. + Proxy cells should not be modified directly - i.e. the shapes or instances should not + be touched. However, you can change PCell parameters (\change_pcell_parameter, \change_pcell_parameters) + or change the library reference (\change_ref). This method has been introduced in version 0.22. """ @@ -2318,7 +2381,7 @@ class Cell: def library(self) -> LibraryBase: r""" @brief Returns a reference to the library from which the cell is imported - if the cell is not imported from a library, this reference is nil. + If the cell is not imported from a library, this reference is nil. This method has been introduced in version 0.22. """ @@ -2340,6 +2403,31 @@ class Cell: This method has been introduced in version 0.22. """ ... + def library_cell_name(self) -> str: + r""" + @brief Returns the cell name inside the library from which the cell is imported + If the cell is not imported from a library, the return value is an empty string. + This method is basically a convenience function, equivalent to taking the name + from \library and \library_cell_index. + Note that for PCells, 'library_cell_name' is the name of the PCell proxy cell inside the library, not the name of the PCell. + + However, this method also works for 'cold proxies' (see \is_cold_proxy?) + for which it delivers the name of cell inside the (missing) library. + + This method has been introduced in version 0.30.8. + """ + ... + def library_name(self) -> str: + r""" + @brief Returns the name of the library from which the cell is imported + If the cell is not imported from a library, the return value is an empty string. + This method is basically a convenience function, equivalent to taking the name + from \library. However, this method also works for 'cold proxies' (see \is_cold_proxy?) + for which it delivers the name of the (missing) library. + + This method has been introduced in version 0.30.8. + """ + ... def merge_meta_info(self, other: Cell) -> None: r""" @brief Merges the meta information from the other cell into this cell @@ -2541,6 +2629,17 @@ class Cell: This method has been introduced in version 0.22. """ ... + def pcell_name(self) -> str: + r""" + @brief Returns the PCell name if the cell is a PCell variant + If this cell is not a PCell variant, this method returns an empty string. + This method is basically a convenience function, equivalent to taking the name + from \pcell_declaration. However, this method also works for 'cold proxies' (see \is_cold_proxy?) + for which it delivers the name of the (missing) PCell. + + This method has been introduced in version 0.30.9. + """ + ... @overload def pcell_parameter(self, instance: Instance, name: str) -> Any: r""" @@ -2560,6 +2659,9 @@ class Cell: If the cell is a PCell variant, this method returns the parameter with the given name. If the cell is not a PCell variant or the name is not a valid PCell parameter name, the return value is nil. + This method also works for 'cold proxies' (see \is_cold_proxy?) + for which it delivers the value of the given stored PCell parameter. + This method has been introduced in version 0.25. """ ... @@ -2595,6 +2697,9 @@ class Cell: method returns an empty dictionary. This method also returns the PCell parameters if the cell is a PCell imported from a library. + This method also works for 'cold proxies' (see \is_cold_proxy?) + for which it delivers the names and values of the stored PCell parameters. + This method has been introduced in version 0.24. """ ... @@ -6314,6 +6419,15 @@ class CplxTrans: "mirroring" describes a reflection at the x-axis which is included in the transformation prior to rotation.@param m The new mirror flag """ @classmethod + def from_bytes(cls, s: bytes) -> CplxTrans: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_dtrans(cls, trans: DCplxTrans, dbu: Optional[float] = ...) -> CplxTrans: r""" @brief Creates an integer-to-floating-point coordinate transformation from another coordinate flavour @@ -7253,6 +7367,15 @@ class CplxTrans: Rotation angles are rounded down to multiples of 90 degree. Magnification is fixed to 1.0. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itrans(self, dbu: Optional[float] = ...) -> ICplxTrans: r""" @brief Converts the transformation to another transformation with integer input and output coordinates @@ -7556,6 +7679,15 @@ class DBox: @brief Sets the top coordinate of the box """ @classmethod + def from_bytes(cls, s: bytes) -> DBox: + r""" + @brief Creates a box object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_ibox(cls, box: Box) -> DBox: r""" @brief Creates a floating-point coordinate box from an integer coordinate box @@ -8211,6 +8343,15 @@ class DBox: This method has been introduced in version 0.23. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this box + + This string can be turned into a box again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itype(self, dbu: Optional[float] = ...) -> Box: r""" @brief Converts the box to an integer coordinate box @@ -8292,6 +8433,24 @@ class DBoxWithProperties(DBox): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> DBoxWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> DBoxWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, box: DBox, properties: Dict[Any, Any]) -> DBoxWithProperties: @@ -8327,7 +8486,7 @@ class DBoxWithProperties(DBox): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> DBoxWithProperties: @@ -8339,7 +8498,7 @@ class DBoxWithProperties(DBox): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: DBox) -> None: @@ -8493,9 +8652,18 @@ class DBoxWithProperties(DBox): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -9275,6 +9443,15 @@ class DCplxTrans: "mirroring" describes a reflection at the x-axis which is included in the transformation prior to rotation.@param m The new mirror flag """ @classmethod + def from_bytes(cls, s: bytes) -> DCplxTrans: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_itrans(cls, trans: CplxTrans, dbu: Optional[float] = ...) -> DCplxTrans: r""" @brief Creates a floating-point coordinate transformation from another coordinate flavour @@ -10203,6 +10380,15 @@ class DCplxTrans: Rotation angles are rounded down to multiples of 90 degree. Magnification is fixed to 1.0. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itrans(self, dbu: Optional[float] = ...) -> ICplxTrans: r""" @brief Converts the transformation to another transformation with integer input and output coordinates @@ -10506,6 +10692,15 @@ class DEdge: This method has been added in version 0.23. """ @classmethod + def from_bytes(cls, s: bytes) -> DEdge: + r""" + @brief Creates an edge object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_iedge(cls, edge: Edge) -> DEdge: r""" @brief Creates a floating-point coordinate edge from an integer coordinate edge @@ -11210,6 +11405,15 @@ class DEdge: This method has been introduced in version 0.23. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this edge + + This string can be turned into an edge again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itype(self, dbu: Optional[float] = ...) -> Edge: r""" @brief Converts the edge to an integer coordinate edge @@ -11318,6 +11522,15 @@ class DEdgePair: Symmetric edge pairs have been introduced in version 0.27. """ @classmethod + def from_bytes(cls, s: bytes) -> DEdgePair: + r""" + @brief Creates an edge pair object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_s(cls, s: str) -> DEdgePair: r""" @brief Creates an object from a string @@ -11613,6 +11826,15 @@ class DEdgePair: @param e The enlargement (set to zero for exact representation) """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this edge pair + + This string can be turned into an edge pair again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itype(self, dbu: Optional[float] = ...) -> EdgePair: r""" @brief Converts the edge pair to an integer coordinate edge pair @@ -11685,6 +11907,24 @@ class DEdgePairWithProperties(DEdgePair): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> DEdgePairWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> DEdgePairWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, edge_pair: DEdgePair, properties: Dict[Any, Any]) -> DEdgePairWithProperties: @@ -11720,7 +11960,7 @@ class DEdgePairWithProperties(DEdgePair): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> DEdgePairWithProperties: @@ -11732,7 +11972,7 @@ class DEdgePairWithProperties(DEdgePair): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: DEdgePair) -> None: @@ -11886,9 +12126,18 @@ class DEdgePairWithProperties(DEdgePair): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -11944,6 +12193,24 @@ class DEdgeWithProperties(DEdge): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> DEdgeWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> DEdgeWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, edge: DEdge, properties: Dict[Any, Any]) -> DEdgeWithProperties: @@ -11979,7 +12246,7 @@ class DEdgeWithProperties(DEdge): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> DEdgeWithProperties: @@ -11991,7 +12258,7 @@ class DEdgeWithProperties(DEdge): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: DEdge) -> None: @@ -12145,9 +12412,18 @@ class DEdgeWithProperties(DEdge): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -12240,6 +12516,15 @@ class DPath: @brief Set the width """ @classmethod + def from_bytes(cls, s: bytes) -> DPath: + r""" + @brief Creates a path object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_ipath(cls, path: Path) -> DPath: r""" @brief Creates a floating-point coordinate path from an integer coordinate path @@ -12683,6 +12968,15 @@ class DPath: The returned polygon is not guaranteed to be non-selfoverlapping. This may happen if the path overlaps itself or contains very short segments. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this path + + This string can be turned into a path again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itype(self, dbu: Optional[float] = ...) -> Path: r""" @brief Converts the path to an integer coordinate path @@ -12764,6 +13058,24 @@ class DPathWithProperties(DPath): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> DPathWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> DPathWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, path: DPath, properties: Dict[Any, Any]) -> DPathWithProperties: @@ -12799,7 +13111,7 @@ class DPathWithProperties(DPath): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> DPathWithProperties: @@ -12811,7 +13123,7 @@ class DPathWithProperties(DPath): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: DPath) -> None: @@ -12965,9 +13277,18 @@ class DPathWithProperties(DPath): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -13032,6 +13353,15 @@ class DPoint: @brief Write accessor to the y coordinate """ @classmethod + def from_bytes(cls, s: bytes) -> DPoint: + r""" + @brief Creates a point object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_ipoint(cls, point: Point) -> DPoint: r""" @brief Creates a floating-point coordinate point from an integer coordinate point @@ -13442,6 +13772,15 @@ class DPoint: @param d The other point to compute the distance to. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this point + + This string can be turned into a point again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itype(self, dbu: Optional[float] = ...) -> Point: r""" @brief Converts the point to an integer coordinate point @@ -13529,6 +13868,15 @@ class DPolygon: """ ... @classmethod + def from_bytes(cls, s: bytes) -> DPolygon: + r""" + @brief Creates a polygon object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_ipoly(cls, polygon: Polygon) -> DPolygon: r""" @brief Creates a floating-point coordinate polygon from an integer coordinate polygon @@ -14277,6 +14625,15 @@ class DPolygon: This method has been introduced in version 0.25.3. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this polygon + + This string can be turned into a polygon again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itype(self, dbu: Optional[float] = ...) -> Polygon: r""" @brief Converts the polygon to an integer coordinate polygon @@ -14424,6 +14781,24 @@ class DPolygonWithProperties(DPolygon): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> DPolygonWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> DPolygonWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, polygon: DPolygon, properties: Dict[Any, Any]) -> DPolygonWithProperties: @@ -14459,7 +14834,7 @@ class DPolygonWithProperties(DPolygon): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> DPolygonWithProperties: @@ -14471,7 +14846,7 @@ class DPolygonWithProperties(DPolygon): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: DPolygon) -> None: @@ -14625,9 +15000,18 @@ class DPolygonWithProperties(DPolygon): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -14709,6 +15093,15 @@ class DSimplePolygon: """ ... @classmethod + def from_bytes(cls, s: bytes) -> DSimplePolygon: + r""" + @brief Creates a polygon object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_ipoly(cls, polygon: SimplePolygon) -> DSimplePolygon: r""" @brief Creates a floating-point coordinate polygon from an integer coordinate polygon @@ -15242,6 +15635,15 @@ class DSimplePolygon: This method has been introduced in version 0.25.3. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this polygon + + This string can be turned into a polygon again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itype(self, dbu: Optional[float] = ...) -> SimplePolygon: r""" @brief Converts the polygon to an integer coordinate polygon @@ -15387,6 +15789,24 @@ class DSimplePolygonWithProperties(DSimplePolygon): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> DSimplePolygonWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> DSimplePolygonWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, polygon: DSimplePolygon, properties: Dict[Any, Any]) -> DSimplePolygonWithProperties: @@ -15422,7 +15842,7 @@ class DSimplePolygonWithProperties(DSimplePolygon): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> DSimplePolygonWithProperties: @@ -15434,7 +15854,7 @@ class DSimplePolygonWithProperties(DSimplePolygon): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: DSimplePolygon) -> None: @@ -15588,9 +16008,18 @@ class DSimplePolygonWithProperties(DSimplePolygon): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -15759,6 +16188,15 @@ class DText: This method has been introduced in version 0.23. """ @classmethod + def from_bytes(cls, s: bytes) -> DText: + r""" + @brief Creates a text object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_s(cls, s: str) -> DText: r""" @brief Creates an object from a string @@ -16099,6 +16537,15 @@ class DText: This convenience method has been added in version 0.28. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this text object + + This string can be turned into a text object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itype(self, dbu: Optional[float] = ...) -> Text: r""" @brief Converts the text to an integer coordinate text @@ -16165,6 +16612,24 @@ class DTextWithProperties(DText): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> DTextWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> DTextWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, text: DText, properties: Dict[Any, Any]) -> DTextWithProperties: @@ -16200,7 +16665,7 @@ class DTextWithProperties(DText): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> DTextWithProperties: @@ -16212,7 +16677,7 @@ class DTextWithProperties(DText): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: DText) -> None: @@ -16366,9 +16831,18 @@ class DTextWithProperties(DText): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -16522,6 +16996,15 @@ class DTrans: This method was introduced in version 0.20. """ @classmethod + def from_bytes(cls, s: bytes) -> DTrans: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_itrans(cls, trans: Trans) -> DTrans: r""" @brief Creates a floating-point coordinate transformation from an integer coordinate transformation @@ -17367,6 +17850,15 @@ class DTrans: If this property is true, the transformation is composed of a mirroring at the x-axis followed by a rotation by the angle given by the \angle property. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itype(self, dbu: Optional[float] = ...) -> Trans: r""" @brief Converts the transformation to an integer coordinate transformation @@ -17623,6 +18115,15 @@ class DVector: @brief Write accessor to the y coordinate """ @classmethod + def from_bytes(cls, s: bytes) -> DVector: + r""" + @brief Creates a vector object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_s(cls, s: str) -> DVector: r""" @brief Creates an object from a string @@ -17977,6 +18478,15 @@ class DVector: 'sq_abs' is an alias provided for compatibility with the former point type. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this vector + + This string can be turned into a vector again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itype(self, dbu: Optional[float] = ...) -> Vector: r""" @brief Converts the point to an integer coordinate point @@ -22058,6 +22568,15 @@ class Edge: This method has been added in version 0.23. """ @classmethod + def from_bytes(cls, s: bytes) -> Edge: + r""" + @brief Creates an edge object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_dedge(cls, dedge: DEdge) -> Edge: r""" @brief Creates an integer coordinate edge from a floating-point coordinate edge @@ -22762,6 +23281,15 @@ class Edge: This method has been introduced in version 0.23. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this edge + + This string can be turned into an edge again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_dtype(self, dbu: Optional[float] = ...) -> DEdge: r""" @brief Converts the edge to a floating-point coordinate edge @@ -23860,6 +24388,15 @@ class EdgePair: Symmetric edge pairs have been introduced in version 0.27. """ @classmethod + def from_bytes(cls, s: bytes) -> EdgePair: + r""" + @brief Creates an edge pair object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_s(cls, s: str) -> EdgePair: r""" @brief Creates an object from a string @@ -24155,6 +24692,15 @@ class EdgePair: @param e The enlargement (set to zero for exact representation) """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this edge pair + + This string can be turned into an edge pair again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_dtype(self, dbu: Optional[float] = ...) -> DEdgePair: r""" @brief Converts the edge pair to a floating-point coordinate edge pair @@ -25421,6 +25967,24 @@ class EdgePairWithProperties(EdgePair): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> EdgePairWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> EdgePairWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, edge_pair: EdgePair, properties: Dict[Any, Any]) -> EdgePairWithProperties: @@ -25456,7 +26020,7 @@ class EdgePairWithProperties(EdgePair): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> EdgePairWithProperties: @@ -25468,7 +26032,7 @@ class EdgePairWithProperties(EdgePair): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: EdgePair) -> None: @@ -25622,9 +26186,18 @@ class EdgePairWithProperties(EdgePair): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -29222,6 +29795,24 @@ class EdgeWithProperties(Edge): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> EdgeWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> EdgeWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, edge: Edge, properties: Dict[Any, Any]) -> EdgeWithProperties: @@ -29257,7 +29848,7 @@ class EdgeWithProperties(Edge): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> DEdgeWithProperties: @@ -29269,7 +29860,7 @@ class EdgeWithProperties(Edge): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: Edge) -> None: @@ -29423,9 +30014,18 @@ class EdgeWithProperties(Edge): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -33467,6 +34067,15 @@ class ICplxTrans: "mirroring" describes a reflection at the x-axis which is included in the transformation prior to rotation.@param m The new mirror flag """ @classmethod + def from_bytes(cls, s: bytes) -> ICplxTrans: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_dtrans(cls, trans: DCplxTrans, dbu: Optional[float] = ...) -> ICplxTrans: r""" @brief Creates an integer coordinate transformation from another coordinate flavour @@ -34412,6 +35021,15 @@ class ICplxTrans: Rotation angles are rounded down to multiples of 90 degree. Magnification is fixed to 1.0. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itrans(self, dbu: Optional[float] = ...) -> DCplxTrans: r""" @brief Converts the transformation to another transformation with floating-point input and output coordinates @@ -35882,11 +36500,11 @@ class Instance: Starting with version 0.25 the displacement is of vector type. Setter: - @brief Sets the displacement vector for the 'a' axis + @brief Sets the displacement vector for the 'a' axis in micrometer units - If the instance was not an array instance before it is made one. + Like \a= with an integer displacement, this method will set the displacement vector but it accepts a vector in micrometer units that is of \DVector type. The vector will be translated to database units internally. - This method has been introduced in version 0.23. Starting with version 0.25 the displacement is of vector type. + This method has been introduced in version 0.25. """ b: Vector r""" @@ -35895,11 +36513,11 @@ class Instance: Starting with version 0.25 the displacement is of vector type. Setter: - @brief Sets the displacement vector for the 'b' axis + @brief Sets the displacement vector for the 'b' axis in micrometer units - If the instance was not an array instance before it is made one. + Like \b= with an integer displacement, this method will set the displacement vector but it accepts a vector in micrometer units that is of \DVector type. The vector will be translated to database units internally. - This method has been introduced in version 0.23. Starting with version 0.25 the displacement is of vector type. + This method has been introduced in version 0.25. """ cell: Cell r""" @@ -35942,10 +36560,9 @@ class Instance: @brief Gets the complex transformation of the instance or the first instance in the array This method is always valid compared to \trans, since simple transformations can be expressed as complex transformations as well. Setter: - @brief Sets the complex transformation of the instance or the first instance in the array (in micrometer units) - This method sets the transformation the same way as \cplx_trans=, but the displacement of this transformation is given in micrometer units. It is internally translated into database units. + @brief Sets the complex transformation of the instance or the first instance in the array - This method has been introduced in version 0.25. + This method has been introduced in version 0.23. """ da: DVector r""" @@ -36440,7 +37057,7 @@ class Instance: r""" @brief Gets the layout this instance is contained in - This const version of the method has been introduced in version 0.25. + This method has been introduced in version 0.22. """ ... @overload @@ -36448,7 +37065,7 @@ class Instance: r""" @brief Gets the layout this instance is contained in - This method has been introduced in version 0.22. + This const version of the method has been introduced in version 0.25. """ ... def pcell_declaration(self) -> PCellDeclaration_Native: @@ -39833,6 +40450,24 @@ class Layout: This method has been introduce in version 0.24. """ ... + @overload + def delete_cell(self, cell: Cell) -> None: + r""" + @brief Deletes a cell + + This deletes a cell but not the sub cells of the cell. + These subcells will likely become new top cells unless they are used + otherwise. + All instances of this cell are deleted as well. + Hint: to delete multiple cells, use "delete_cells" which is + far more efficient in this case. + + @param cell The cell to delete + + This convenience variant taking a cell object has been introduced in version 0.30.9. + """ + ... + @overload def delete_cell(self, cell_index: int) -> None: r""" @brief Deletes a cell @@ -39849,6 +40484,20 @@ class Layout: This method has been introduced in version 0.20. """ ... + @overload + def delete_cell_rec(self, cell: Cell) -> None: + r""" + @brief Deletes a cell plus all subcells + + This deletes a cell and also all sub cells of the cell. + In contrast to \prune_cell, all cells are deleted together with their instances even if they are used otherwise. + + @param cell The cell to delete + + This convenience variant taking a cell object has been introduced in version 0.30.9. + """ + ... + @overload def delete_cell_rec(self, cell_index: int) -> None: r""" @brief Deletes a cell plus all subcells @@ -39861,6 +40510,7 @@ class Layout: This method has been introduced in version 0.20. """ ... + @overload def delete_cells(self, cell_index_list: Sequence[int]) -> None: r""" @brief Deletes multiple cells @@ -39876,6 +40526,21 @@ class Layout: """ ... @overload + def delete_cells(self, cell_list: Sequence[Cell]) -> None: + r""" + @brief Deletes multiple cells + + This deletes the cells but not the sub cells of these cells. + These subcells will likely become new top cells unless they are used + otherwise. + All instances of these cells are deleted as well. + + @param cell_list An list of cells to delete + + This convenience variant taking a list of cell objects has been introduced in version 0.30.9. + """ + ... + @overload def delete_layer(self, layer: LayerInfo) -> None: r""" @brief Deletes a layer @@ -40046,7 +40711,24 @@ class Layout: This method has been introduced in version 0.23 and has been extended to name queries in version 0.28.11. """ ... - def flatten(self, cell_index: int, levels: int, prune: bool) -> None: + @overload + def flatten(self, cell: Cell, levels: Optional[int] = ..., prune: Optional[bool] = ...) -> None: + r""" + @brief Flattens the given cell + + This method propagates all shapes and instances from the specified number of hierarchy levels below into the given cell. + It also removes the instances of the cells from which the shapes came from, but does not remove the cells themselves if prune is set to false. + If prune is set to true, these cells are removed if not used otherwise. + + @param cell The cell which should be flattened + @param levels The number of hierarchy levels to flatten (-1: all, 0: none, 1: one level etc.) + @param prune Set to true to remove orphan cells. + + This convenience variant taking a cell object has been introduced in version 0.30.9. + """ + ... + @overload + def flatten(self, cell_index: int, levels: Optional[int] = ..., prune: Optional[bool] = ...) -> None: r""" @brief Flattens the given cell @@ -40058,10 +40740,28 @@ class Layout: @param levels The number of hierarchy levels to flatten (-1: all, 0: none, 1: one level etc.) @param prune Set to true to remove orphan cells. - This method has been introduced in version 0.20. + This method has been introduced in version 0.20. The 'levels' and 'prune' arguments have been made optional in version 0.30.9. + """ + ... + @overload + def flatten_into(self, source_cell: Cell, target_cell: Cell, trans: Optional[ICplxTrans] = ..., levels: Optional[int] = ...) -> None: + r""" + @brief Flattens the given cell into another cell + + This method works like 'flatten', but allows specification of a target cell which can be different from the source cell plus a transformation which is applied for all shapes and instances in the target cell. + + In contrast to the 'flatten' method, the source cell is not modified. + + @param source_cell The source cell which should be flattened + @param target_cell The target cell into which the resulting objects are written + @param trans The transformation to apply on the output shapes and instances + @param levels The number of hierarchy levels to flatten (-1: all, 0: none, 1: one level etc.) + + This convenience variant taking a cell objects has been introduced in version 0.30.9. """ ... - def flatten_into(self, source_cell_index: int, target_cell_index: int, trans: ICplxTrans, levels: int) -> None: + @overload + def flatten_into(self, source_cell_index: int, target_cell_index: int, trans: Optional[ICplxTrans] = ..., levels: Optional[int] = ...) -> None: r""" @brief Flattens the given cell into another cell @@ -40074,7 +40774,7 @@ class Layout: @param trans The transformation to apply on the output shapes and instances @param levels The number of hierarchy levels to flatten (-1: all, 0: none, 1: one level etc.) - This method has been introduced in version 0.24. + This method has been introduced in version 0.24. The 'trans' and 'levels' arguments have been made optional is version 0.30.9. """ ... def get_info(self, index: int) -> LayerInfo: @@ -40598,7 +41298,8 @@ class Layout: This method has been introduced in version 0.24. """ ... - def prune_cell(self, cell_index: int, levels: int) -> None: + @overload + def prune_cell(self, cell: Cell, levels: Optional[int] = ...) -> None: r""" @brief Deletes a cell plus subcells not used otherwise @@ -40606,13 +41307,81 @@ class Layout: The number of hierarchy levels to consider can be specified as well. One level of hierarchy means that only the direct children of the cell are deleted with the cell itself. All instances of this cell are deleted as well. + A version that allows pruning multiple cells in one call is \prune_cells. + + @param cell The cell to delete + @param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.) + + This convenience variant taking a cell object has been introduced in version 0.30.9. + """ + ... + @overload + def prune_cell(self, cell_index: int, levels: Optional[int] = ...) -> None: + r""" + @brief Deletes a cell plus subcells not used otherwise + + This deletes a cell and also all sub cells of the cell which are not used otherwise. + The number of hierarchy levels to consider can be specified as well. One level of hierarchy means that only the direct children of the cell are deleted with the cell itself. + All instances of this cell are deleted as well. + + A version that allows pruning multiple cells in one call is \prune_cells. + @param cell_index The index of the cell to delete @param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.) - This method has been introduced in version 0.20. + This method has been introduced in version 0.20. The 'levels' argument was made optional in version 0.30.9. + """ + ... + @overload + def prune_cells(self, cell_indexes: Sequence[int], levels: Optional[int] = ...) -> None: + r""" + @brief Deletes cells plus subcells not used otherwise + + This deletes the given cells and also all sub cells of the cells which are not used otherwise. + The number of hierarchy levels to consider can be specified as well. One level of hierarchy means that only the direct children of the cell are deleted with the cell itself. + All instances of the pruned cells are deleted as well. + + @param cell_indexes The indexes of the cells to delete + @param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.) + + This method has been introduced in version 0.30.9. + """ + ... + @overload + def prune_cells(self, cells: Sequence[Cell], levels: Optional[int] = ...) -> None: + r""" + @brief Deletes cells plus subcells not used otherwise + + This deletes the given cells and also all sub cells of the cells which are not used otherwise. + The number of hierarchy levels to consider can be specified as well. One level of hierarchy means that only the direct children of the cell are deleted with the cell itself. + All instances of the pruned cells are deleted as well. + + @param cells The cells to delete + @param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.) + + This method has been introduced in version 0.30.9. + """ + ... + @overload + def prune_subcells(self, cell: Cell, levels: Optional[int] = ...) -> None: + r""" + @brief Deletes all sub cells of the cell which are not used otherwise down to the specified level of hierarchy + + This deletes all sub cells of the cell which are not used otherwise. + All instances of the deleted cells are deleted as well. + It is possible to specify how many levels of hierarchy below the given root cell are considered. + + A variant exists that takes a list of cells and which is more efficient than calling + 'prune_subcells' multiple times on a single cell. + + @param cell The root cell from which to delete a sub cells + @param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.) + + This convenience variant taking a list of cell objects has been introduced in version 0.30.9. """ ... - def prune_subcells(self, cell_index: int, levels: int) -> None: + @overload + def prune_subcells(self, cell_index: int, levels: Optional[int] = ...) -> None: r""" @brief Deletes all sub cells of the cell which are not used otherwise down to the specified level of hierarchy @@ -40620,6 +41389,9 @@ class Layout: All instances of the deleted cells are deleted as well. It is possible to specify how many levels of hierarchy below the given root cell are considered. + A variant exists that takes a list of cell indexes and which is more efficient than calling + 'prune_subcells' multiple times on a single cell. + @param cell_index The root cell from which to delete a sub cells @param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.) @@ -40627,6 +41399,36 @@ class Layout: """ ... @overload + def prune_subcells(self, cell_index_list: Sequence[int], levels: Optional[int] = ...) -> None: + r""" + @brief Deletes all sub cells of the given cells which are not used otherwise down to the specified level of hierarchy + + This deletes all sub cells of the given cells which are not used otherwise. + All instances of the deleted cells are deleted as well. + It is possible to specify how many levels of hierarchy below the given root cell are considered. + + @param cell_index_list The root cells from which to delete the sub cells + @param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.) + + This method has been introduced in version 0.30.9. + """ + ... + @overload + def prune_subcells(self, cell_list: Sequence[Cell], levels: Optional[int] = ...) -> None: + r""" + @brief Deletes all sub cells of the given cells which are not used otherwise down to the specified level of hierarchy + + This deletes all sub cells of the given cells which are not used otherwise. + All instances of the deleted cells are deleted as well. + It is possible to specify how many levels of hierarchy below the given root cell are considered. + + @param cell_list The root cells from which to delete the sub cells + @param levels The number of hierarchy levels to consider (-1: all, 0: none, 1: one level etc.) + + This method has been introduced in version 0.30.9. + """ + ... + @overload def read(self, filename: str) -> LayerMap: r""" @brief Load the layout from the given file @@ -48146,17 +48948,17 @@ class NetTerminalRef: @overload def device(self) -> Device: r""" - @brief Gets the device reference. + @brief Gets the device reference (non-const version). Gets the device object that this connection is made to. + + This constness variant has been introduced in version 0.26.8 """ ... @overload def device(self) -> Device: r""" - @brief Gets the device reference (non-const version). + @brief Gets the device reference. Gets the device object that this connection is made to. - - This constness variant has been introduced in version 0.26.8 """ ... def device_class(self) -> DeviceClass: @@ -49152,17 +49954,17 @@ class Netlist: @overload def circuit_by_cell_index(self, cell_index: int) -> Circuit: r""" - @brief Gets the circuit object for a given cell index. + @brief Gets the circuit object for a given cell index (const version). If the cell index is not valid or no circuit is registered with this index, nil is returned. + + This constness variant has been introduced in version 0.26.8. """ ... @overload def circuit_by_cell_index(self, cell_index: int) -> Circuit: r""" - @brief Gets the circuit object for a given cell index (const version). + @brief Gets the circuit object for a given cell index. If the cell index is not valid or no circuit is registered with this index, nil is returned. - - This constness variant has been introduced in version 0.26.8. """ ... @overload @@ -49446,7 +50248,7 @@ class Netlist: @overload def top_circuit(self) -> Circuit: r""" - @brief Gets the top circuit (const version). + @brief Gets the top circuit. This method will return nil, if there is no top circuit. It will raise an error, if there is more than a single top circuit. This convenience method has been added in version 0.29.5. @@ -49455,7 +50257,7 @@ class Netlist: @overload def top_circuit(self) -> Circuit: r""" - @brief Gets the top circuit. + @brief Gets the top circuit (const version). This method will return nil, if there is no top circuit. It will raise an error, if there is more than a single top circuit. This convenience method has been added in version 0.29.5. @@ -53335,6 +54137,15 @@ class Path: @brief Set the width """ @classmethod + def from_bytes(cls, s: bytes) -> Path: + r""" + @brief Creates a path object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_dpath(cls, dpath: DPath) -> Path: r""" @brief Creates an integer coordinate path from a floating-point coordinate path @@ -53775,6 +54586,15 @@ class Path: The returned polygon is not guaranteed to be non-selfoverlapping. This may happen if the path overlaps itself or contains very short segments. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this path + + This string can be turned into a path again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_dtype(self, dbu: Optional[float] = ...) -> DPath: r""" @brief Converts the path to a floating-point coordinate path @@ -53859,6 +54679,24 @@ class PathWithProperties(Path): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> PathWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> PathWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, path: Path, properties: Dict[Any, Any]) -> PathWithProperties: @@ -53894,7 +54732,7 @@ class PathWithProperties(Path): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> DPathWithProperties: @@ -53906,7 +54744,7 @@ class PathWithProperties(Path): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: Path) -> None: @@ -54060,9 +54898,18 @@ class PathWithProperties(Path): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -54218,6 +55065,15 @@ class Point: @brief Write accessor to the y coordinate """ @classmethod + def from_bytes(cls, s: bytes) -> Point: + r""" + @brief Creates a point object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_dpoint(cls, dpoint: DPoint) -> Point: r""" @brief Creates an integer coordinate point from a floating-point coordinate point @@ -54628,6 +55484,15 @@ class Point: @param d The other point to compute the distance to. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this point + + This string can be turned into a point again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_dtype(self, dbu: Optional[float] = ...) -> DPoint: r""" @brief Converts the point to a floating-point coordinate point @@ -54762,6 +55627,15 @@ class Polygon: """ ... @classmethod + def from_bytes(cls, s: bytes) -> Polygon: + r""" + @brief Creates a polygon object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_dpoly(cls, dpolygon: DPolygon) -> Polygon: r""" @brief Creates an integer coordinate polygon from a floating-point coordinate polygon @@ -55693,6 +56567,15 @@ class Polygon: This method has been introduced in version 0.25.3. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this polygon + + This string can be turned into a polygon again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_dtype(self, dbu: Optional[float] = ...) -> DPolygon: r""" @brief Converts the polygon to a floating-point coordinate polygon @@ -57404,6 +58287,24 @@ class PolygonWithProperties(Polygon): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> PolygonWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> PolygonWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, polygon: Polygon, properties: Dict[Any, Any]) -> PolygonWithProperties: @@ -57439,7 +58340,7 @@ class PolygonWithProperties(Polygon): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> DPolygonWithProperties: @@ -57451,7 +58352,7 @@ class PolygonWithProperties(Polygon): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: Polygon) -> None: @@ -57605,9 +58506,18 @@ class PolygonWithProperties(Polygon): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -68559,6 +69469,15 @@ class SimplePolygon: """ ... @classmethod + def from_bytes(cls, s: bytes) -> SimplePolygon: + r""" + @brief Creates a polygon object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_dpoly(cls, dpolygon: DSimplePolygon) -> SimplePolygon: r""" @brief Creates an integer coordinate polygon from a floating-point coordinate polygon @@ -69206,6 +70125,15 @@ class SimplePolygon: This method has been introduced in version 0.25.3. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this polygon + + This string can be turned into a polygon again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_dtype(self, dbu: Optional[float] = ...) -> DSimplePolygon: r""" @brief Converts the polygon to a floating-point coordinate polygon @@ -69356,6 +70284,24 @@ class SimplePolygonWithProperties(SimplePolygon): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> SimplePolygonWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> SimplePolygonWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, polygon: SimplePolygon, properties: Dict[Any, Any]) -> SimplePolygonWithProperties: @@ -69391,7 +70337,7 @@ class SimplePolygonWithProperties(SimplePolygon): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> DSimplePolygonWithProperties: @@ -69403,7 +70349,7 @@ class SimplePolygonWithProperties(SimplePolygon): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: SimplePolygon) -> None: @@ -69557,9 +70503,18 @@ class SimplePolygonWithProperties(SimplePolygon): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -69704,32 +70659,32 @@ class SubCircuit(NetlistObject): @overload def circuit(self) -> Circuit: r""" - @brief Gets the circuit the subcircuit lives in (non-const version). + @brief Gets the circuit the subcircuit lives in. This is NOT the circuit which is referenced. For getting the circuit that the subcircuit references, use \circuit_ref. - - This constness variant has been introduced in version 0.26.8 """ ... @overload def circuit(self) -> Circuit: r""" - @brief Gets the circuit the subcircuit lives in. + @brief Gets the circuit the subcircuit lives in (non-const version). This is NOT the circuit which is referenced. For getting the circuit that the subcircuit references, use \circuit_ref. + + This constness variant has been introduced in version 0.26.8 """ ... @overload def circuit_ref(self) -> Circuit: r""" - @brief Gets the circuit referenced by the subcircuit. + @brief Gets the circuit referenced by the subcircuit (non-const version). + + + This constness variant has been introduced in version 0.26.8 """ ... @overload def circuit_ref(self) -> Circuit: r""" - @brief Gets the circuit referenced by the subcircuit (non-const version). - - - This constness variant has been introduced in version 0.26.8 + @brief Gets the circuit referenced by the subcircuit. """ ... @overload @@ -69775,17 +70730,17 @@ class SubCircuit(NetlistObject): @overload def net_for_pin(self, pin_id: int) -> Net: r""" - @brief Gets the net connected to the specified pin of the subcircuit (non-const version). + @brief Gets the net connected to the specified pin of the subcircuit. If the pin is not connected, nil is returned for the net. - - This constness variant has been introduced in version 0.26.8 """ ... @overload def net_for_pin(self, pin_id: int) -> Net: r""" - @brief Gets the net connected to the specified pin of the subcircuit. + @brief Gets the net connected to the specified pin of the subcircuit (non-const version). If the pin is not connected, nil is returned for the net. + + This constness variant has been introduced in version 0.26.8 """ ... ... @@ -70435,8 +71390,7 @@ class Text: Setter: @brief Sets the vertical alignment - This property specifies how the text is aligned relative to the anchor point. - This property has been introduced in version 0.22 and extended to enums in 0.28. + This is the version accepting integer values. It's provided for backward compatibility. """ x: int r""" @@ -70463,6 +71417,15 @@ class Text: This method has been introduced in version 0.23. """ @classmethod + def from_bytes(cls, s: bytes) -> Text: + r""" + @brief Creates a text object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_s(cls, s: str) -> Text: r""" @brief Creates an object from a string @@ -70801,6 +71764,15 @@ class Text: This convenience method has been added in version 0.28. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this text object + + This string can be turned into a text object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_dtype(self, dbu: Optional[float] = ...) -> DText: r""" @brief Converts the text to a floating-point coordinate text @@ -72145,6 +73117,24 @@ class TextWithProperties(Text): Setter: @brief Sets the properties ID of the object """ + @classmethod + def from_bytes(cls, s: bytes) -> TextWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod + def from_s(cls, s: str) -> TextWithProperties: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... @overload @classmethod def new(cls, text: Text, properties: Dict[Any, Any]) -> TextWithProperties: @@ -72180,7 +73170,7 @@ class TextWithProperties(Text): ... def __repr__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def __rmul__(self, f: float) -> DTextWithProperties: @@ -72192,7 +73182,7 @@ class TextWithProperties(Text): ... def __str__(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... def _assign(self, other: Text) -> None: @@ -72346,9 +73336,18 @@ class TextWithProperties(Text): This method may change the properties ID. Note: GDS only supports integer keys. OASIS supports numeric and string keys. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_s(self) -> str: r""" - @brief Returns a string representing the polygon + @brief Returns a string representing the object """ ... @overload @@ -74192,6 +75191,15 @@ class Trans: This method was introduced in version 0.20. """ @classmethod + def from_bytes(cls, s: bytes) -> Trans: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_dtrans(cls, dtrans: DTrans) -> Trans: r""" @brief Creates an integer coordinate transformation from a floating-point coordinate transformation @@ -75037,6 +76045,15 @@ class Trans: If this property is true, the transformation is composed of a mirroring at the x-axis followed by a rotation by the angle given by the \angle property. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_dtype(self, dbu: Optional[float] = ...) -> DTrans: r""" @brief Converts the transformation to a floating-point coordinate transformation @@ -76006,6 +77023,15 @@ class VCplxTrans: "mirroring" describes a reflection at the x-axis which is included in the transformation prior to rotation.@param m The new mirror flag """ @classmethod + def from_bytes(cls, s: bytes) -> VCplxTrans: + r""" + @brief Creates an object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_s(cls, s: str) -> VCplxTrans: r""" @brief Creates an object from a string @@ -76942,6 +77968,15 @@ class VCplxTrans: Rotation angles are rounded down to multiples of 90 degree. Magnification is fixed to 1.0. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this object + + This string can be turned into an object again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_itrans(self, dbu: Optional[float] = ...) -> DCplxTrans: r""" @brief Converts the transformation to another transformation with floating-point output coordinates @@ -77452,6 +78487,15 @@ class Vector: @brief Write accessor to the y coordinate """ @classmethod + def from_bytes(cls, s: bytes) -> Vector: + r""" + @brief Creates a vector object from a binary serialization + Creates the object from a binary representation (as returned by \to_bytes) + + This method has been added in version 0.30.9. + """ + ... + @classmethod def from_s(cls, s: str) -> Vector: r""" @brief Creates an object from a string @@ -77806,6 +78850,15 @@ class Vector: 'sq_abs' is an alias provided for compatibility with the former point type. """ ... + def to_bytes(self) -> bytes: + r""" + @brief Returns a binary string representing this vector + + This string can be turned into a vector again by using \from_bytes + . + This method has been added in version 0.30.9. + """ + ... def to_dtype(self, dbu: Optional[float] = ...) -> DVector: r""" @brief Converts the vector to a floating-point coordinate vector diff --git a/src/pymod/distutils_src/klayout/rdbcore.pyi b/src/pymod/distutils_src/klayout/rdbcore.pyi index 1d547f277..4d1cf22e0 100644 --- a/src/pymod/distutils_src/klayout/rdbcore.pyi +++ b/src/pymod/distutils_src/klayout/rdbcore.pyi @@ -1270,17 +1270,17 @@ class RdbReference: @overload def database(self) -> ReportDatabase: r""" - @brief Gets the database object that category is associated with (non-const version) + @brief Gets the database object that category is associated with - This method has been introduced in version 0.29. + This method has been introduced in version 0.23. """ ... @overload def database(self) -> ReportDatabase: r""" - @brief Gets the database object that category is associated with + @brief Gets the database object that category is associated with (non-const version) - This method has been introduced in version 0.23. + This method has been introduced in version 0.29. """ ... def destroy(self) -> None: From 7d5f61db591547e20868184701feb76bcf63326c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 26 May 2026 23:26:16 +0200 Subject: [PATCH 26/26] Issue #2360: Implemented name sorting by default (can be turned off) for 'make_top_level_pins' --- src/db/db/dbNetlist.cc | 36 +++++++++++++++++-- src/db/db/dbNetlist.h | 2 +- src/db/db/gsiDeclDbNetlist.cc | 7 ++-- .../dbLayoutToNetlistWriterTests.cc | 2 +- testdata/lvs/bbdevices1b.lvs | 1 + testdata/lvs/bbdevices2b.lvs | 1 + testdata/lvs/bbdevices4b.lvs | 1 + testdata/lvs/bbdevices5b.lvs | 1 + testdata/lvs/bbdevices6b.lvs | 1 + testdata/lvs/custom_compare.lvs | 1 + testdata/lvs/double_height.cir | 2 +- testdata/lvs/double_height.lvs | 1 + testdata/lvs/double_height.lvsdb | 4 +-- testdata/lvs/double_height2.lvs | 1 + testdata/lvs/double_height2_texts.lvs | 1 + testdata/lvs/enable_wl1.lvs | 1 + testdata/lvs/enable_wl2.lvs | 1 + testdata/lvs/enable_wl3.lvs | 1 + testdata/lvs/flag_missing_ports.lvs | 1 + testdata/lvs/floating.lvs | 1 + testdata/lvs/invchain_cheat.lvs | 1 + testdata/lvs/layer_names.lvs | 1 + testdata/lvs/must_connect1.lvs | 1 + testdata/lvs/must_connect1_tl.lvs | 1 + testdata/lvs/must_connect2.lvs | 1 + testdata/lvs/must_connect3.lvs | 1 + testdata/lvs/nand2_split_gate.lvs | 1 + testdata/lvs/nand2_split_gate_early.lvs | 1 + testdata/lvs/res_combine1.lvs | 1 + testdata/lvs/res_combine2.lvs | 1 + testdata/lvs/res_combine3.lvs | 1 + testdata/lvs/ringo_device_subcircuits.lvs | 1 + testdata/lvs/ringo_layout_var.lvs | 1 + testdata/lvs/ringo_mixed_hierarchy.lvs | 1 + testdata/lvs/ringo_simple.lvs | 1 + testdata/lvs/ringo_simple_blackboxing.lvs | 2 +- .../lvs/ringo_simple_blackboxing_netter.lvs | 2 +- testdata/lvs/ringo_simple_compare2.lvs | 1 + testdata/lvs/ringo_simple_device_scaling.lvs | 1 + testdata/lvs/ringo_simple_dmos.lvs | 1 + testdata/lvs/ringo_simple_dmos_fixed.lvs | 1 + testdata/lvs/ringo_simple_dummy_device.lvs | 1 + .../lvs/ringo_simple_implicit_connections.lvs | 1 + testdata/lvs/ringo_simple_io.lvs | 1 + testdata/lvs/ringo_simple_io2.lvs | 1 + ...ngo_simple_net_and_circuit_equivalence.lvs | 1 + testdata/lvs/ringo_simple_pin_swapping.lvs | 1 + .../lvs/ringo_simple_same_device_classes.lvs | 1 + testdata/lvs/ringo_simple_simplification.lvs | 2 +- ...ringo_simple_simplification_with_align.lvs | 1 + testdata/lvs/ringo_simple_with_tol.lvs | 1 + testdata/lvs/ringo_simple_with_tol_early.lvs | 1 + testdata/lvs/soft_connect1.lvs | 1 + testdata/lvs/soft_connect1a.lvs | 1 + testdata/lvs/soft_connect2.lvs | 1 + testdata/lvs/soft_connect3.lvs | 1 + testdata/lvs/soft_connect4.lvs | 1 + testdata/lvs/soft_connect5.lvs | 1 + testdata/lvs/soft_connect6.lvs | 1 + testdata/lvs/split_substrate.lvs | 1 + 60 files changed, 97 insertions(+), 13 deletions(-) diff --git a/src/db/db/dbNetlist.cc b/src/db/db/dbNetlist.cc index 8e13ab793..fbe959837 100644 --- a/src/db/db/dbNetlist.cc +++ b/src/db/db/dbNetlist.cc @@ -626,7 +626,26 @@ void Netlist::purge_devices () } } -void Netlist::make_top_level_pins () +namespace { + + struct CompareNetByName + { + CompareNetByName (const db::Netlist *netlist) + : mp_netlist (netlist) + { } + + bool operator() (const db::Net *a, const db::Net *b) + { + return mp_netlist->normalize_name (a->name ()) < mp_netlist->normalize_name (b->name ()); + } + + private: + const db::Netlist *mp_netlist; + }; + +} + +void Netlist::make_top_level_pins (bool sort_by_name) { size_t ntop = top_circuit_count (); for (top_down_circuit_iterator c = begin_top_down (); c != end_top_down () && ntop > 0; ++c, --ntop) { @@ -635,14 +654,25 @@ void Netlist::make_top_level_pins () if (circuit->pin_count () == 0) { + std::vector nets_for_pins; + // create pins for the named nets and connect them for (Circuit::net_iterator n = circuit->begin_nets (); n != circuit->end_nets (); ++n) { if (! n->name ().empty () && n->terminal_count () + n->subcircuit_pin_count () > 0) { - Pin pin = circuit->add_pin (n->name ()); - circuit->connect_pin (pin.id (), n.operator-> ()); + nets_for_pins.push_back (n.operator-> ()); } } + if (sort_by_name) { + std::stable_sort (nets_for_pins.begin (), nets_for_pins.end (), CompareNetByName (this)); + } + + // create pins for the named nets and connect them + for (auto n = nets_for_pins.begin (); n != nets_for_pins.end (); ++n) { + Pin pin = circuit->add_pin ((*n)->name ()); + circuit->connect_pin (pin.id (), *n); + } + } } diff --git a/src/db/db/dbNetlist.h b/src/db/db/dbNetlist.h index 958df8b38..73848ec92 100644 --- a/src/db/db/dbNetlist.h +++ b/src/db/db/dbNetlist.h @@ -523,7 +523,7 @@ class DB_PUBLIC Netlist * referenced by subcircuits) into pins. This method can be used before purge to * avoid that purge will remove nets which are directly connecting to subcircuits. */ - void make_top_level_pins (); + void make_top_level_pins (bool sort_by_name = true); /** * @brief Purge unused nets, circuits and subcircuits diff --git a/src/db/db/gsiDeclDbNetlist.cc b/src/db/db/gsiDeclDbNetlist.cc index b297e004b..f5e1ee291 100644 --- a/src/db/db/gsiDeclDbNetlist.cc +++ b/src/db/db/gsiDeclDbNetlist.cc @@ -2240,11 +2240,14 @@ Class decl_dbNetlist ("db", "Netlist", "For example, serial or parallel resistors can be combined into " "a single resistor.\n" ) + - gsi::method ("make_top_level_pins", &db::Netlist::make_top_level_pins, + gsi::method ("make_top_level_pins", &db::Netlist::make_top_level_pins, gsi::arg ("sorted_by_name", true), "@brief Creates pins for top-level circuits.\n" "This method will turn all named nets of top-level circuits (such that are not " "referenced by subcircuits) into pins. This method can be used before purge to " - "avoid that purge will remove nets which are directly connecting to subcircuits." + "avoid that purge will remove nets which are directly connecting to subcircuits.\n" + "\n" + "Starting from version 0.30.9, the pins will be sorted by name by default. Sorting can be " + "disabled by setting \\sorted_by_name to false for backward compatibility." ) + gsi::method ("purge", &db::Netlist::purge, "@brief Purge unused nets, circuits and subcircuits.\n" diff --git a/src/db/unit_tests/dbLayoutToNetlistWriterTests.cc b/src/db/unit_tests/dbLayoutToNetlistWriterTests.cc index 5c9ea6012..67648a5b0 100644 --- a/src/db/unit_tests/dbLayoutToNetlistWriterTests.cc +++ b/src/db/unit_tests/dbLayoutToNetlistWriterTests.cc @@ -163,7 +163,7 @@ TEST(1_WriterBasic) rpoly_lbl.reset (0); l2n.extract_netlist (); - l2n.netlist ()->make_top_level_pins (); + l2n.netlist ()->make_top_level_pins (false); l2n.netlist ()->purge (); std::string path = tmp_file ("tmp_l2nwriter_1.txt"); diff --git a/testdata/lvs/bbdevices1b.lvs b/testdata/lvs/bbdevices1b.lvs index cba829291..3c8ff0e32 100644 --- a/testdata/lvs/bbdevices1b.lvs +++ b/testdata/lvs/bbdevices1b.lvs @@ -70,6 +70,7 @@ connect(layer.btp, layer.bm1) connect(layer.bdpPad, layer.bm1) blank_circuit("*TEST") +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/bbdevices2b.lvs b/testdata/lvs/bbdevices2b.lvs index cba829291..3c8ff0e32 100644 --- a/testdata/lvs/bbdevices2b.lvs +++ b/testdata/lvs/bbdevices2b.lvs @@ -70,6 +70,7 @@ connect(layer.btp, layer.bm1) connect(layer.bdpPad, layer.bm1) blank_circuit("*TEST") +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/bbdevices4b.lvs b/testdata/lvs/bbdevices4b.lvs index cba829291..3c8ff0e32 100644 --- a/testdata/lvs/bbdevices4b.lvs +++ b/testdata/lvs/bbdevices4b.lvs @@ -70,6 +70,7 @@ connect(layer.btp, layer.bm1) connect(layer.bdpPad, layer.bm1) blank_circuit("*TEST") +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/bbdevices5b.lvs b/testdata/lvs/bbdevices5b.lvs index cba829291..3c8ff0e32 100644 --- a/testdata/lvs/bbdevices5b.lvs +++ b/testdata/lvs/bbdevices5b.lvs @@ -70,6 +70,7 @@ connect(layer.btp, layer.bm1) connect(layer.bdpPad, layer.bm1) blank_circuit("*TEST") +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/bbdevices6b.lvs b/testdata/lvs/bbdevices6b.lvs index cba829291..3c8ff0e32 100644 --- a/testdata/lvs/bbdevices6b.lvs +++ b/testdata/lvs/bbdevices6b.lvs @@ -70,6 +70,7 @@ connect(layer.btp, layer.bm1) connect(layer.bdpPad, layer.bm1) blank_circuit("*TEST") +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/custom_compare.lvs b/testdata/lvs/custom_compare.lvs index 25e60364c..9b58b6bd0 100644 --- a/testdata/lvs/custom_compare.lvs +++ b/testdata/lvs/custom_compare.lvs @@ -63,6 +63,7 @@ end netlist.device_class_by_name("RES").equal_parameters = ResistorComparator::new() # Netlist normalization +netlist.make_top_level_pins(false) netlist.simplify # Hierarchy alignment (flatten out unmatched cells) diff --git a/testdata/lvs/double_height.cir b/testdata/lvs/double_height.cir index 6c6f92e17..1e727a252 100644 --- a/testdata/lvs/double_height.cir +++ b/testdata/lvs/double_height.cir @@ -1,6 +1,6 @@ * Extracted by KLayout -.SUBCKT INVCHAIN IN OUT VSS VDD +.SUBCKT INVCHAIN IN OUT VDD VSS X$1 VDD IN \$1 \$1 OUT VSS INV2 .ENDS INVCHAIN diff --git a/testdata/lvs/double_height.lvs b/testdata/lvs/double_height.lvs index ec154d59f..eb5e89129 100644 --- a/testdata/lvs/double_height.lvs +++ b/testdata/lvs/double_height.lvs @@ -123,6 +123,7 @@ connect_implicit("INV2", "VSS") # Compare section +# NOTE: indirectly tests "make_top_level_pins" with name sorting (#2360) netlist.simplify align diff --git a/testdata/lvs/double_height.lvsdb b/testdata/lvs/double_height.lvsdb index f9b66a23d..849f93776 100644 --- a/testdata/lvs/double_height.lvsdb +++ b/testdata/lvs/double_height.lvsdb @@ -185,8 +185,8 @@ J( ) P(2 I(IN)) P(3 I(OUT)) - P(4 I(VSS)) P(5 I(VDD)) + P(4 I(VSS)) X(1 INV2 Y(0 0) P(0 5) P(1 2) @@ -318,8 +318,8 @@ Z( N(4 2 1) P(0 () 1) P(1 () 1) - P(3 () 1) P(2 () 1) + P(3 () 1) X(1 1 1) ) ) diff --git a/testdata/lvs/double_height2.lvs b/testdata/lvs/double_height2.lvs index 71e32a05c..1060dec05 100644 --- a/testdata/lvs/double_height2.lvs +++ b/testdata/lvs/double_height2.lvs @@ -130,6 +130,7 @@ connect_implicit("DOESNOTEXIST", "DOESNOTEXIST") # Compare section +netlist.make_top_level_pins(false) netlist.simplify align diff --git a/testdata/lvs/double_height2_texts.lvs b/testdata/lvs/double_height2_texts.lvs index c3d169a9d..a2882395e 100644 --- a/testdata/lvs/double_height2_texts.lvs +++ b/testdata/lvs/double_height2_texts.lvs @@ -130,6 +130,7 @@ connect_implicit("DOESNOTEXIST", "DOESNOTEXIST") # Compare section +netlist.make_top_level_pins(false) netlist.simplify align diff --git a/testdata/lvs/enable_wl1.lvs b/testdata/lvs/enable_wl1.lvs index a58903723..c97bbda95 100644 --- a/testdata/lvs/enable_wl1.lvs +++ b/testdata/lvs/enable_wl1.lvs @@ -47,6 +47,7 @@ connect(contact, metal1_not_res) connect(metal1_not_res, metal1_lbl) align +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/enable_wl2.lvs b/testdata/lvs/enable_wl2.lvs index 945a89294..7ca716ee3 100644 --- a/testdata/lvs/enable_wl2.lvs +++ b/testdata/lvs/enable_wl2.lvs @@ -28,6 +28,7 @@ connect(contact, metal1_not_res) connect(metal1_not_res, metal1_lbl) align +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/enable_wl3.lvs b/testdata/lvs/enable_wl3.lvs index 8f5405307..552524f62 100644 --- a/testdata/lvs/enable_wl3.lvs +++ b/testdata/lvs/enable_wl3.lvs @@ -28,6 +28,7 @@ connect(contact, metal1_not_res) connect(metal1_not_res, metal1_lbl) align +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/flag_missing_ports.lvs b/testdata/lvs/flag_missing_ports.lvs index 4c35142a3..cf98f6dd7 100644 --- a/testdata/lvs/flag_missing_ports.lvs +++ b/testdata/lvs/flag_missing_ports.lvs @@ -68,6 +68,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/floating.lvs b/testdata/lvs/floating.lvs index 8a3c6417e..d0e6c1e09 100644 --- a/testdata/lvs/floating.lvs +++ b/testdata/lvs/floating.lvs @@ -68,6 +68,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify align diff --git a/testdata/lvs/invchain_cheat.lvs b/testdata/lvs/invchain_cheat.lvs index 93c6206e7..379a0c601 100644 --- a/testdata/lvs/invchain_cheat.lvs +++ b/testdata/lvs/invchain_cheat.lvs @@ -123,6 +123,7 @@ connect_global(bulk, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify align diff --git a/testdata/lvs/layer_names.lvs b/testdata/lvs/layer_names.lvs index 9ea2cf9ca..071e511fd 100644 --- a/testdata/lvs/layer_names.lvs +++ b/testdata/lvs/layer_names.lvs @@ -96,6 +96,7 @@ ok || raise("ptie layer already used - this is an error") # Compare section +netlist.make_top_level_pins(false) netlist.simplify align diff --git a/testdata/lvs/must_connect1.lvs b/testdata/lvs/must_connect1.lvs index 799fa0364..0981cde4c 100644 --- a/testdata/lvs/must_connect1.lvs +++ b/testdata/lvs/must_connect1.lvs @@ -132,6 +132,7 @@ connect_implicit("INVCHAIN", "VDD") # Compare section +netlist.make_top_level_pins(false) netlist.simplify align diff --git a/testdata/lvs/must_connect1_tl.lvs b/testdata/lvs/must_connect1_tl.lvs index 4e9cc35ed..60379dc81 100644 --- a/testdata/lvs/must_connect1_tl.lvs +++ b/testdata/lvs/must_connect1_tl.lvs @@ -136,6 +136,7 @@ connect_implicit("INVCHAIN", "VDD") # Compare section +netlist.make_top_level_pins(false) netlist.simplify align diff --git a/testdata/lvs/must_connect2.lvs b/testdata/lvs/must_connect2.lvs index ac34742e7..22ead7473 100644 --- a/testdata/lvs/must_connect2.lvs +++ b/testdata/lvs/must_connect2.lvs @@ -134,6 +134,7 @@ connect_global(bulk, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify align diff --git a/testdata/lvs/must_connect3.lvs b/testdata/lvs/must_connect3.lvs index ac34742e7..22ead7473 100644 --- a/testdata/lvs/must_connect3.lvs +++ b/testdata/lvs/must_connect3.lvs @@ -134,6 +134,7 @@ connect_global(bulk, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify align diff --git a/testdata/lvs/nand2_split_gate.lvs b/testdata/lvs/nand2_split_gate.lvs index 1c4a795bf..ca4ce42f6 100644 --- a/testdata/lvs/nand2_split_gate.lvs +++ b/testdata/lvs/nand2_split_gate.lvs @@ -76,6 +76,7 @@ connect_global(ptie, "SUBSTRATE") netlist join_symmetric_nets("*") +netlist.make_top_level_pins(false) netlist.simplify # Compare section diff --git a/testdata/lvs/nand2_split_gate_early.lvs b/testdata/lvs/nand2_split_gate_early.lvs index bf48ae960..e128d1693 100644 --- a/testdata/lvs/nand2_split_gate_early.lvs +++ b/testdata/lvs/nand2_split_gate_early.lvs @@ -77,6 +77,7 @@ connect_global(ptie, "SUBSTRATE") # Extract, simplify netlist +netlist.make_top_level_pins(false) netlist.simplify # Compare section diff --git a/testdata/lvs/res_combine1.lvs b/testdata/lvs/res_combine1.lvs index 3dba3b639..db30261d1 100644 --- a/testdata/lvs/res_combine1.lvs +++ b/testdata/lvs/res_combine1.lvs @@ -93,6 +93,7 @@ schematic.simplify # Netlist vs. netlist align +netlist.make_top_level_pins(false) netlist.simplify no_lvs_hints compare diff --git a/testdata/lvs/res_combine2.lvs b/testdata/lvs/res_combine2.lvs index 395f84765..012d2b4cf 100644 --- a/testdata/lvs/res_combine2.lvs +++ b/testdata/lvs/res_combine2.lvs @@ -92,6 +92,7 @@ schematic.simplify # Netlist vs. netlist align +netlist.make_top_level_pins(false) netlist.simplify no_lvs_hints compare diff --git a/testdata/lvs/res_combine3.lvs b/testdata/lvs/res_combine3.lvs index c609c0824..0aabff3ab 100644 --- a/testdata/lvs/res_combine3.lvs +++ b/testdata/lvs/res_combine3.lvs @@ -87,6 +87,7 @@ schematic.simplify # Netlist vs. netlist align +netlist.make_top_level_pins(false) netlist.simplify no_lvs_hints compare diff --git a/testdata/lvs/ringo_device_subcircuits.lvs b/testdata/lvs/ringo_device_subcircuits.lvs index f12f62d8d..8a9f32ecb 100644 --- a/testdata/lvs/ringo_device_subcircuits.lvs +++ b/testdata/lvs/ringo_device_subcircuits.lvs @@ -120,6 +120,7 @@ same_device_classes("PMOS", $change_case ? "xpMos" : "XPMOS") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_layout_var.lvs b/testdata/lvs/ringo_layout_var.lvs index afe79c3e2..ca04cf5fd 100644 --- a/testdata/lvs/ringo_layout_var.lvs +++ b/testdata/lvs/ringo_layout_var.lvs @@ -72,6 +72,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_mixed_hierarchy.lvs b/testdata/lvs/ringo_mixed_hierarchy.lvs index cba9caa86..c1149c265 100644 --- a/testdata/lvs/ringo_mixed_hierarchy.lvs +++ b/testdata/lvs/ringo_mixed_hierarchy.lvs @@ -68,6 +68,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify align diff --git a/testdata/lvs/ringo_simple.lvs b/testdata/lvs/ringo_simple.lvs index 057f39c25..44b535f51 100644 --- a/testdata/lvs/ringo_simple.lvs +++ b/testdata/lvs/ringo_simple.lvs @@ -68,6 +68,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_blackboxing.lvs b/testdata/lvs/ringo_simple_blackboxing.lvs index 2fe02fd83..e464f5c00 100644 --- a/testdata/lvs/ringo_simple_blackboxing.lvs +++ b/testdata/lvs/ringo_simple_blackboxing.lvs @@ -78,7 +78,7 @@ schematic.blank_circuit("INVX1") schematic.blank_circuit("INVX2") schematic.blank_circuit("ND2X1") -netlist.make_top_level_pins +netlist.make_top_level_pins(false) netlist.purge netlist.combine_devices netlist.purge_nets diff --git a/testdata/lvs/ringo_simple_blackboxing_netter.lvs b/testdata/lvs/ringo_simple_blackboxing_netter.lvs index 4cbbc0591..55f4d4fd5 100644 --- a/testdata/lvs/ringo_simple_blackboxing_netter.lvs +++ b/testdata/lvs/ringo_simple_blackboxing_netter.lvs @@ -76,7 +76,7 @@ connect_global(ptie, "SUBSTRATE") netlist.flatten_circuit("INVCHAIN") -netlist.make_top_level_pins +netlist.make_top_level_pins(false) netlist.purge netlist.combine_devices netlist.purge_nets diff --git a/testdata/lvs/ringo_simple_compare2.lvs b/testdata/lvs/ringo_simple_compare2.lvs index abaeaff92..1b56db53f 100644 --- a/testdata/lvs/ringo_simple_compare2.lvs +++ b/testdata/lvs/ringo_simple_compare2.lvs @@ -68,6 +68,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_device_scaling.lvs b/testdata/lvs/ringo_simple_device_scaling.lvs index afb3caebb..e248bcd12 100644 --- a/testdata/lvs/ringo_simple_device_scaling.lvs +++ b/testdata/lvs/ringo_simple_device_scaling.lvs @@ -70,6 +70,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_dmos.lvs b/testdata/lvs/ringo_simple_dmos.lvs index 0879f5e1b..1af75b266 100644 --- a/testdata/lvs/ringo_simple_dmos.lvs +++ b/testdata/lvs/ringo_simple_dmos.lvs @@ -75,6 +75,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_dmos_fixed.lvs b/testdata/lvs/ringo_simple_dmos_fixed.lvs index 5f375e95f..279ee200e 100644 --- a/testdata/lvs/ringo_simple_dmos_fixed.lvs +++ b/testdata/lvs/ringo_simple_dmos_fixed.lvs @@ -77,6 +77,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_dummy_device.lvs b/testdata/lvs/ringo_simple_dummy_device.lvs index a33340a2d..9c1219cd4 100644 --- a/testdata/lvs/ringo_simple_dummy_device.lvs +++ b/testdata/lvs/ringo_simple_dummy_device.lvs @@ -68,6 +68,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_implicit_connections.lvs b/testdata/lvs/ringo_simple_implicit_connections.lvs index 056cf97d8..4e583d46c 100644 --- a/testdata/lvs/ringo_simple_implicit_connections.lvs +++ b/testdata/lvs/ringo_simple_implicit_connections.lvs @@ -72,6 +72,7 @@ connect_implicit("VDD") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_io.lvs b/testdata/lvs/ringo_simple_io.lvs index 215b556ce..92a30d54b 100644 --- a/testdata/lvs/ringo_simple_io.lvs +++ b/testdata/lvs/ringo_simple_io.lvs @@ -69,6 +69,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_io2.lvs b/testdata/lvs/ringo_simple_io2.lvs index bea93496f..8042382aa 100644 --- a/testdata/lvs/ringo_simple_io2.lvs +++ b/testdata/lvs/ringo_simple_io2.lvs @@ -102,6 +102,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_net_and_circuit_equivalence.lvs b/testdata/lvs/ringo_simple_net_and_circuit_equivalence.lvs index 02724522a..0e8e2d201 100644 --- a/testdata/lvs/ringo_simple_net_and_circuit_equivalence.lvs +++ b/testdata/lvs/ringo_simple_net_and_circuit_equivalence.lvs @@ -76,6 +76,7 @@ same_circuits("INV", $change_case ? "invX1" : "INVX1") same_circuits("DOESNOTEXIST", "DOESNOTEXIST2") same_nets("DOESNOTEXIST", "ENABLE", "DOESNOTEXIST2", "ENABLE") +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_pin_swapping.lvs b/testdata/lvs/ringo_simple_pin_swapping.lvs index 166fa320f..4b9fd5a04 100644 --- a/testdata/lvs/ringo_simple_pin_swapping.lvs +++ b/testdata/lvs/ringo_simple_pin_swapping.lvs @@ -73,6 +73,7 @@ connect_global(ptie, "SUBSTRATE") equivalent_pins("DOESNOTEXIST", 4, 5) +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_same_device_classes.lvs b/testdata/lvs/ringo_simple_same_device_classes.lvs index 8005d70cc..b2d0c4053 100644 --- a/testdata/lvs/ringo_simple_same_device_classes.lvs +++ b/testdata/lvs/ringo_simple_same_device_classes.lvs @@ -91,6 +91,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify same_device_classes("NM", "NMOS") diff --git a/testdata/lvs/ringo_simple_simplification.lvs b/testdata/lvs/ringo_simple_simplification.lvs index 9579e11cd..1833b7a87 100644 --- a/testdata/lvs/ringo_simple_simplification.lvs +++ b/testdata/lvs/ringo_simple_simplification.lvs @@ -70,7 +70,7 @@ connect_global(ptie, "SUBSTRATE") netlist.flatten_circuit("INVCHAIN") -netlist.make_top_level_pins +netlist.make_top_level_pins(false) netlist.purge netlist.combine_devices netlist.purge_nets diff --git a/testdata/lvs/ringo_simple_simplification_with_align.lvs b/testdata/lvs/ringo_simple_simplification_with_align.lvs index 4591cf3e6..73ae01353 100644 --- a/testdata/lvs/ringo_simple_simplification_with_align.lvs +++ b/testdata/lvs/ringo_simple_simplification_with_align.lvs @@ -70,6 +70,7 @@ connect_global(ptie, "SUBSTRATE") align +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_with_tol.lvs b/testdata/lvs/ringo_simple_with_tol.lvs index 5b2f6dcbd..0d234e6b9 100644 --- a/testdata/lvs/ringo_simple_with_tol.lvs +++ b/testdata/lvs/ringo_simple_with_tol.lvs @@ -73,6 +73,7 @@ tolerance("PMOS", "W", 0.01, 0.1) # relative + absolute tolerance("NMOS", "L", :absolute => 0.01) tolerance("NMOS", "W", :relative => 0.07) +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/ringo_simple_with_tol_early.lvs b/testdata/lvs/ringo_simple_with_tol_early.lvs index 7f8fcef72..25b79e53a 100644 --- a/testdata/lvs/ringo_simple_with_tol_early.lvs +++ b/testdata/lvs/ringo_simple_with_tol_early.lvs @@ -73,6 +73,7 @@ connect_global(ptie, "SUBSTRATE") # Compare section +netlist.make_top_level_pins(false) netlist.simplify compare diff --git a/testdata/lvs/soft_connect1.lvs b/testdata/lvs/soft_connect1.lvs index a4ed6551d..b41b177b8 100644 --- a/testdata/lvs/soft_connect1.lvs +++ b/testdata/lvs/soft_connect1.lvs @@ -87,6 +87,7 @@ soft_connect_global(ptie, "SUBSTRATE") # Netlist section (NOTE: we only check log here) netlist +netlist.make_top_level_pins(false) netlist.simplify diff --git a/testdata/lvs/soft_connect1a.lvs b/testdata/lvs/soft_connect1a.lvs index 4063000dc..9b490f4fe 100644 --- a/testdata/lvs/soft_connect1a.lvs +++ b/testdata/lvs/soft_connect1a.lvs @@ -87,6 +87,7 @@ soft_connect_global(ptie, "SUBSTRATE") # Netlist section (NOTE: we only check log here) netlist +netlist.make_top_level_pins(false) netlist.simplify diff --git a/testdata/lvs/soft_connect2.lvs b/testdata/lvs/soft_connect2.lvs index a4ed6551d..b41b177b8 100644 --- a/testdata/lvs/soft_connect2.lvs +++ b/testdata/lvs/soft_connect2.lvs @@ -87,6 +87,7 @@ soft_connect_global(ptie, "SUBSTRATE") # Netlist section (NOTE: we only check log here) netlist +netlist.make_top_level_pins(false) netlist.simplify diff --git a/testdata/lvs/soft_connect3.lvs b/testdata/lvs/soft_connect3.lvs index a4ed6551d..b41b177b8 100644 --- a/testdata/lvs/soft_connect3.lvs +++ b/testdata/lvs/soft_connect3.lvs @@ -87,6 +87,7 @@ soft_connect_global(ptie, "SUBSTRATE") # Netlist section (NOTE: we only check log here) netlist +netlist.make_top_level_pins(false) netlist.simplify diff --git a/testdata/lvs/soft_connect4.lvs b/testdata/lvs/soft_connect4.lvs index fcf0d3d22..ca1a4fa64 100644 --- a/testdata/lvs/soft_connect4.lvs +++ b/testdata/lvs/soft_connect4.lvs @@ -88,6 +88,7 @@ soft_connect_global(ptie, "SUBSTRATE") # for debugging: _make_soft_connection_diodes(true) netlist +netlist.make_top_level_pins(false) netlist.simplify diff --git a/testdata/lvs/soft_connect5.lvs b/testdata/lvs/soft_connect5.lvs index fcf0d3d22..ca1a4fa64 100644 --- a/testdata/lvs/soft_connect5.lvs +++ b/testdata/lvs/soft_connect5.lvs @@ -88,6 +88,7 @@ soft_connect_global(ptie, "SUBSTRATE") # for debugging: _make_soft_connection_diodes(true) netlist +netlist.make_top_level_pins(false) netlist.simplify diff --git a/testdata/lvs/soft_connect6.lvs b/testdata/lvs/soft_connect6.lvs index fcf0d3d22..ca1a4fa64 100644 --- a/testdata/lvs/soft_connect6.lvs +++ b/testdata/lvs/soft_connect6.lvs @@ -88,6 +88,7 @@ soft_connect_global(ptie, "SUBSTRATE") # for debugging: _make_soft_connection_diodes(true) netlist +netlist.make_top_level_pins(false) netlist.simplify diff --git a/testdata/lvs/split_substrate.lvs b/testdata/lvs/split_substrate.lvs index 43204067d..2bf709152 100644 --- a/testdata/lvs/split_substrate.lvs +++ b/testdata/lvs/split_substrate.lvs @@ -102,6 +102,7 @@ connect_global(iosub, "IOSUB") # for debugging: _make_soft_connection_diodes(true) netlist +netlist.make_top_level_pins(false) netlist.simplify