diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d83d24aa..33a57a8c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,7 +66,7 @@ jobs: mkdir -p $HOST_CCACHE_DIR - name: Build wheels (ARM) if: matrix.os == 'ubuntu-24.04-arm' - uses: pypa/cibuildwheel@v3.4.0 + uses: pypa/cibuildwheel@v3.4.1 env: # override the default CentOS “yum install … ccache” and drop ccache CIBW_BEFORE_ALL_LINUX: | @@ -81,7 +81,7 @@ jobs: - name: Build wheels (all other platforms) if: matrix.os != 'ubuntu-24.04-arm' - uses: pypa/cibuildwheel@v3.4.0 + uses: pypa/cibuildwheel@v3.4.1 env: CIBW_BUILD: ${{ matrix.cibuild }} CIBW_ARCHS_MACOS: ${{ matrix.macos-arch }} @@ -127,7 +127,7 @@ jobs: merge-multiple: true path: dist - - uses: pypa/gh-action-pypi-publish@v1.13.0 + - uses: pypa/gh-action-pypi-publish@v1.14.0 continue-on-error: true # might fail if we don't bump the version with: user: __token__ @@ -144,7 +144,7 @@ jobs: merge-multiple: true path: dist - - uses: pypa/gh-action-pypi-publish@v1.13.0 + - uses: pypa/gh-action-pypi-publish@v1.14.0 with: user: __token__ password: ${{ secrets.pypi_password }} diff --git a/macbuild/build4mac_env.py b/macbuild/build4mac_env.py index 363ab78c8..f09f8bbb4 100755 --- a/macbuild/build4mac_env.py +++ b/macbuild/build4mac_env.py @@ -328,7 +328,7 @@ RubyTahoe = { 'exe': '/System/Library/Frameworks/Ruby.framework/Versions # install with 'sudo port install ruby34' # [Key Type Name] = 'MP34' Ruby34MacPorts = { 'exe': '/opt/local/bin/ruby3.4', - 'inc': '/opt/local/include/ruby-3.4.8', + 'inc': '/opt/local/include/ruby-3.4.9', 'lib': '/opt/local/lib/libruby.3.4.dylib' } @@ -567,8 +567,8 @@ if _have_Homebrew_Python: # [4] KLayout executables including buddy tools #----------------------------------------------------- KLayoutExecs = [ 'klayout' ] -KLayoutExecs += [ 'strm2cif', 'strm2dxf', 'strm2gds', 'strm2gdstxt', 'strm2mag', 'strm2oas' ] -KLayoutExecs += [ 'strm2txt', 'strmclip', 'strmcmp', 'strmrun', 'strmxor' ] +KLayoutExecs += [ 'strm2cif', 'strm2dxf', 'strm2gds', 'strm2gdstxt', 'strm2lstr', 'strm2mag' ] +KLayoutExecs += [ 'strm2oas', 'strm2txt', 'strmclip', 'strmcmp', 'strmrun', 'strmxor' ] #---------------- # End of File diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index 74f9f4df6..fe45fbecb 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -1541,6 +1541,19 @@ Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::ang } } +static int snap_prio (lay::PointSnapToObjectResult::ObjectSnap os) +{ + if (os == lay::PointSnapToObjectResult::ObjectVertex) { + return 3; + } else if (os == lay::PointSnapToObjectResult::ObjectEdge) { + return 2; + } else if (os == lay::PointSnapToObjectResult::ObjectUnspecific) { + return 1; + } else { + return 0; + } +} + void Service::snap_rulers (lay::angle_constraint_type ac) { @@ -1566,7 +1579,7 @@ Service::snap_rulers (lay::angle_constraint_type ac) auto snp = snap2_details (org1, p1, ruler, ac); double dist = p1.distance (snp.snapped_point); - if (min_dist < 0 || dist < min_dist) { + if (min_dist < 0 || snap_prio (snp.object_snap) > snap_prio (min_snp.object_snap) || (snap_prio (snp.object_snap) == snap_prio (min_snp.object_snap) && dist < min_dist)) { min_snp = snp; min_dist = dist; min_delta = snp.snapped_point - p1; @@ -1575,7 +1588,7 @@ Service::snap_rulers (lay::angle_constraint_type ac) snp = snap2_details (org2, p2, ruler, ac); dist = p2.distance (snp.snapped_point); - if (min_dist < 0 || dist < min_dist) { + if (min_dist < 0 || snap_prio (snp.object_snap) > snap_prio (min_snp.object_snap) || (snap_prio (snp.object_snap) == snap_prio (min_snp.object_snap) && dist < min_dist)) { min_snp = snp; min_dist = dist; min_delta = snp.snapped_point - p2; @@ -1631,10 +1644,10 @@ Service::move (const db::DPoint &p, lay::angle_constraint_type ac) m_trans = db::DTrans (dp + (m_p1 - db::DPoint ()) - m_trans.disp ()) * m_trans * db::DTrans (db::DPoint () - m_p1); - propose_move_transformation (m_trans, 1); - snap_rulers (ac_eff); + propose_move_transformation (m_trans, 1); + for (std::vector::iterator r = m_rulers.begin (); r != m_rulers.end (); ++r) { (*r)->transform_by (db::DCplxTrans (m_trans)); } @@ -1834,6 +1847,7 @@ Service::edit_cancel () m_move_mode = MoveNone; m_selected.clear (); selection_to_view (); + clear_mouse_cursors (); } } diff --git a/src/db/db/dbEdgePairs.cc b/src/db/db/dbEdgePairs.cc index cd43514b8..de4b3ab24 100644 --- a/src/db/db/dbEdgePairs.cc +++ b/src/db/db/dbEdgePairs.cc @@ -103,6 +103,13 @@ EdgePairs::EdgePairs (DeepShapeStore &dss) mp_delegate = new DeepEdgePairs (DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ())); } +void +EdgePairs::convert_to_deep (const db::DeepLayer &layer) +{ + tl_assert (mp_delegate->deep () == 0); + set_delegate (copy_data_id (new db::DeepEdgePairs (layer))); +} + void EdgePairs::write (const std::string &fn) const { diff --git a/src/db/db/dbEdgePairs.h b/src/db/db/dbEdgePairs.h index 7b2b4ed7f..f75d7d773 100644 --- a/src/db/db/dbEdgePairs.h +++ b/src/db/db/dbEdgePairs.h @@ -204,6 +204,11 @@ public: */ explicit EdgePairs (DeepShapeStore &dss); + /** + * @brief Converts the shape collection to a deep one using the specified layer + */ + virtual void convert_to_deep (const db::DeepLayer &layer); + /** * @brief Writes the edge pair collection to a file * diff --git a/src/db/db/dbEdges.cc b/src/db/db/dbEdges.cc index 1cad75949..f64d23747 100644 --- a/src/db/db/dbEdges.cc +++ b/src/db/db/dbEdges.cc @@ -114,6 +114,13 @@ Edges::Edges (DeepShapeStore &dss) mp_delegate = new DeepEdges (DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ())); } +void +Edges::convert_to_deep (const db::DeepLayer &layer) +{ + tl_assert (mp_delegate->deep () == 0); + set_delegate (copy_data_id (new db::DeepEdges (layer))); +} + const db::RecursiveShapeIterator & Edges::iter () const { diff --git a/src/db/db/dbEdges.h b/src/db/db/dbEdges.h index ff8007d19..a12d09c58 100644 --- a/src/db/db/dbEdges.h +++ b/src/db/db/dbEdges.h @@ -264,6 +264,11 @@ public: */ explicit Edges (DeepShapeStore &dss); + /** + * @brief Converts the shape collection to a deep one using the specified layer + */ + virtual void convert_to_deep (const db::DeepLayer &layer); + /** * @brief Implementation of the ShapeCollection interface */ diff --git a/src/db/db/dbLayoutUtils.cc b/src/db/db/dbLayoutUtils.cc index 2a71a0c1a..5809458de 100644 --- a/src/db/db/dbLayoutUtils.cc +++ b/src/db/db/dbLayoutUtils.cc @@ -218,6 +218,7 @@ copy_or_propagate_shapes (db::Layout &target, db::Cell &target_cell = target.cell (cm->second); transformer->insert_transformed (target_cell.shapes (target_layer), source_cell.shapes (source_layer), trans * propagate_trans); + } } diff --git a/src/db/db/dbLibraryProxy.cc b/src/db/db/dbLibraryProxy.cc index 84317d341..b1c24f2bb 100644 --- a/src/db/db/dbLibraryProxy.cc +++ b/src/db/db/dbLibraryProxy.cc @@ -244,7 +244,11 @@ LibraryProxy::update (db::ImportLayerMapping *layer_mapping) inst.transform_into (db::ICplxTrans (lib->layout ().dbu () / layout ()->dbu ())); - insert (inst); + if (i->has_prop_id ()) { + insert (db::CellInstArrayWithProperties (inst, i->prop_id ())); + } else { + insert (inst); + } } } diff --git a/src/db/db/dbNetlistDeviceExtractor.cc b/src/db/db/dbNetlistDeviceExtractor.cc index 1eb73b68f..9424007b8 100644 --- a/src/db/db/dbNetlistDeviceExtractor.cc +++ b/src/db/db/dbNetlistDeviceExtractor.cc @@ -130,8 +130,10 @@ void NetlistDeviceExtractor::extract (db::DeepShapeStore &dss, unsigned int layo std::pair alias = dss.layer_for_flat (tl::id_of (l->second->get_delegate ())); if (alias.first) { - // use deep layer alias for a given flat one (if found) - layers.push_back (alias.second.layer ()); + // use deep layer alias for a given flat one (if found) and convert layer to a deep one + db::DeepLayer dl = alias.second; + l->second->convert_to_deep (dl); + layers.push_back (dl.layer ()); } else { throw tl::Exception (tl::sprintf (tl::to_string (tr ("Invalid region passed to input layer '%s' for device extraction (device %s): must be of deep region kind")), ld->name, name ())); } diff --git a/src/db/db/dbPolygon.h b/src/db/db/dbPolygon.h index 5835c0976..8ecadc16e 100644 --- a/src/db/db/dbPolygon.h +++ b/src/db/db/dbPolygon.h @@ -1581,7 +1581,7 @@ public: void translate (const polygon &d, const T &t, db::generic_repository &, db::ArrayRepository &) { *this = d; - transform (t); + transform (t, false); } /** diff --git a/src/db/db/dbRegion.cc b/src/db/db/dbRegion.cc index f290b87d3..8e40874fd 100644 --- a/src/db/db/dbRegion.cc +++ b/src/db/db/dbRegion.cc @@ -132,6 +132,13 @@ Region::Region (DeepShapeStore &dss) mp_delegate = new db::DeepRegion (db::DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ())); } +void +Region::convert_to_deep (const db::DeepLayer &layer) +{ + tl_assert (mp_delegate->deep () == 0); + set_delegate (copy_data_id (new db::DeepRegion (layer))); +} + void Region::write (const std::string &fn) const { diff --git a/src/db/db/dbRegion.h b/src/db/db/dbRegion.h index 655462a01..3a230fdf1 100644 --- a/src/db/db/dbRegion.h +++ b/src/db/db/dbRegion.h @@ -256,6 +256,11 @@ public: */ void write (const std::string &fn) const; + /** + * @brief Converts the shape collection to a deep one using the specified layer + */ + virtual void convert_to_deep (const db::DeepLayer &layer); + /** * @brief Implementation of the ShapeCollection interface */ diff --git a/src/db/db/dbShapeCollection.h b/src/db/db/dbShapeCollection.h index a03c008b2..0f48ffef9 100644 --- a/src/db/db/dbShapeCollection.h +++ b/src/db/db/dbShapeCollection.h @@ -75,7 +75,12 @@ class DB_PUBLIC ShapeCollectionDelegateBase : public tl::UniqueId { public: - ShapeCollectionDelegateBase () { } + ShapeCollectionDelegateBase () + : m_data_id (tl::id_of (this)) + { + // .. nothing yet .. + } + virtual ~ShapeCollectionDelegateBase () { } virtual DeepShapeCollectionDelegateBase *deep () { return 0; } @@ -88,6 +93,22 @@ public: apply_property_translator (db::PropertiesTranslator::make_remove_all ()); } } + + tl::id_type data_id () const + { + return m_data_id; + } + +private: + friend class ShapeCollection; + + tl::id_type m_data_id; + + // used for conversion to deep + void set_data_id (tl::id_type data_id) + { + m_data_id = data_id; + } }; /** @@ -102,6 +123,11 @@ public: virtual ShapeCollectionDelegateBase *get_delegate () const = 0; + /** + * @brief Converts the shape collection to a deep one using the specified layer + */ + virtual void convert_to_deep (const db::DeepLayer &layer) = 0; + /** * @brief Applies a PropertyTranslator * @@ -111,6 +137,14 @@ public: * delivered by "properties_repository". */ void apply_property_translator (const db::PropertiesTranslator &pt); + +protected: + template + Delegate *copy_data_id (Delegate *dlg) + { + dlg->set_data_id (get_delegate ()->data_id ()); + return dlg; + } }; } diff --git a/src/db/db/dbTexts.cc b/src/db/db/dbTexts.cc index 902a5cf2e..1e15801ed 100644 --- a/src/db/db/dbTexts.cc +++ b/src/db/db/dbTexts.cc @@ -99,6 +99,13 @@ Texts::Texts (DeepShapeStore &dss) mp_delegate = new DeepTexts (DeepLayer (&dss, layout_index, dss.layout (layout_index).insert_layer ())); } +void +Texts::convert_to_deep (const db::DeepLayer &layer) +{ + tl_assert (mp_delegate->deep () == 0); + set_delegate (copy_data_id (new db::DeepTexts (layer))); +} + void Texts::write (const std::string &fn) const { diff --git a/src/db/db/dbTexts.h b/src/db/db/dbTexts.h index 74441be07..276f9b415 100644 --- a/src/db/db/dbTexts.h +++ b/src/db/db/dbTexts.h @@ -208,6 +208,11 @@ public: */ void write (const std::string &fn) const; + /** + * @brief Converts the shape collection to a deep one using the specified layer + */ + virtual void convert_to_deep (const db::DeepLayer &layer); + /** * @brief Implementation of the ShapeCollection interface */ diff --git a/src/db/db/gsiDeclDbEdgePairs.cc b/src/db/db/gsiDeclDbEdgePairs.cc index 0ea5564f0..5a865399a 100644 --- a/src/db/db/gsiDeclDbEdgePairs.cc +++ b/src/db/db/gsiDeclDbEdgePairs.cc @@ -630,9 +630,9 @@ static bool is_deep (const db::EdgePairs *ep) return dynamic_cast (ep->delegate ()) != 0; } -static size_t id (const db::EdgePairs *ep) +static size_t data_id (const db::EdgePairs *ep) { - return tl::id_of (ep->delegate ()); + return ep->delegate ()->data_id (); } static db::EdgePairs filtered (const db::EdgePairs *r, const gsi::EdgePairFilterBase *f) @@ -1114,7 +1114,7 @@ Class decl_EdgePairs (decl_dbShapeCollection, "db", "EdgePairs", "\n" "This method has been added in version 0.26." ) + - method_ext ("data_id", &id, + method_ext ("data_id", &data_id, "@brief Returns the data ID (a unique identifier for the underlying data storage)\n" "\n" "This method has been added in version 0.26." diff --git a/src/db/db/gsiDeclDbEdges.cc b/src/db/db/gsiDeclDbEdges.cc index a434a7d60..667878d62 100644 --- a/src/db/db/gsiDeclDbEdges.cc +++ b/src/db/db/gsiDeclDbEdges.cc @@ -892,9 +892,9 @@ static db::Edges *new_texts_as_dots2 (const db::RecursiveShapeIterator &si, db:: return new db::Edges (db::Region (si).texts_as_dots (pat, pattern, dss)); } -static size_t id (const db::Edges *e) +static size_t data_id (const db::Edges *e) { - return tl::id_of (e->delegate ()); + return e->delegate ()->data_id (); } static std::vector andnot_with_edges (const db::Edges *r, const db::Edges &other) @@ -2572,7 +2572,7 @@ Class decl_Edges (decl_dbShapeCollection, "db", "Edges", "\n" "This method has been added in version 0.26." ) + - method_ext ("data_id", &id, + method_ext ("data_id", &data_id, "@brief Returns the data ID (a unique identifier for the underlying data storage)\n" "\n" "This method has been added in version 0.26." diff --git a/src/db/db/gsiDeclDbRegion.cc b/src/db/db/gsiDeclDbRegion.cc index 528342e4a..de29f6ba7 100644 --- a/src/db/db/gsiDeclDbRegion.cc +++ b/src/db/db/gsiDeclDbRegion.cc @@ -1288,9 +1288,9 @@ static bool is_deep (const db::Region *region) return dynamic_cast (region->delegate ()) != 0; } -static size_t id (const db::Region *r) +static size_t data_id (const db::Region *r) { - return tl::id_of (r->delegate ()); + return r->delegate ()->data_id (); } @@ -4214,7 +4214,7 @@ Class decl_Region (decl_dbShapeCollection, "db", "Region", "\n" "This method has been added in version 0.26." ) + - method_ext ("data_id", &id, + method_ext ("data_id", &data_id, "@brief Returns the data ID (a unique identifier for the underlying data storage)\n" "\n" "This method has been added in version 0.26." diff --git a/src/db/db/gsiDeclDbTexts.cc b/src/db/db/gsiDeclDbTexts.cc index 22209b412..21f04d888 100644 --- a/src/db/db/gsiDeclDbTexts.cc +++ b/src/db/db/gsiDeclDbTexts.cc @@ -477,9 +477,9 @@ static bool is_deep (const db::Texts *t) return dynamic_cast (t->delegate ()) != 0; } -static size_t id (const db::Texts *t) +static size_t data_id (const db::Texts *t) { - return tl::id_of (t->delegate ()); + return t->delegate ()->data_id (); } static db::Texts filtered (const db::Texts *r, const gsi::TextFilterBase *f) @@ -686,7 +686,7 @@ Class decl_Texts (decl_dbShapeCollection, "db", "Texts", method_ext ("is_deep?", &is_deep, "@brief Returns true if the edge pair collection is a deep (hierarchical) one\n" ) + - method_ext ("data_id", &id, + method_ext ("data_id", &data_id, "@brief Returns the data ID (a unique identifier for the underlying data storage)\n" ) + method ("+|join", &db::Texts::operator+, gsi::arg ("other"), diff --git a/src/db/unit_tests/dbLayoutTests.cc b/src/db/unit_tests/dbLayoutTests.cc index d7a503335..9b6178ca8 100644 --- a/src/db/unit_tests/dbLayoutTests.cc +++ b/src/db/unit_tests/dbLayoutTests.cc @@ -1009,3 +1009,34 @@ TEST(100_UndoOfDeleteLayer) EXPECT_EQ (l.get_properties (li).to_string (), "1/0"); EXPECT_EQ (l.get_properties (li2).to_string (), "2/0"); } + +// issue #2343 +TEST(101_CopyTreeDoesNotModifyPolygons) +{ + db::Manager m; + db::Layout l (&m); + db::Cell &top = l.cell (l.add_cell ("TOP")); + l.insert_layer (db::LayerProperties (1, 0)); + + std::unique_ptr t (new db::Layout ()); + db::Cell &ttop = t->cell (t->add_cell ("TOP")); + unsigned int tl1 = t->insert_layer (db::LayerProperties (1, 0)); + + std::vector pts = { + { 0, 0 }, { 0, 1000 }, { 500, 1000 }, { 1500, 1000 }, { 1000, 1000 }, { 1000, 0 } + }; + + db::Polygon poly; + poly.assign_hull (pts.begin (), pts.end (), false /*don't compress*/, false /*don't remove reflected*/); + ttop.shapes (tl1).insert (poly); + + EXPECT_EQ (l2s (*t), "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"); + + db::CellMapping cm; + cm.create_single_mapping (l, top.cell_index (), *t, ttop.cell_index ()); + l.copy_tree_shapes (*t, cm); + + // polygon is still not normalized + 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"); +} + diff --git a/src/db/unit_tests/dbLibrariesTests.cc b/src/db/unit_tests/dbLibrariesTests.cc index 70f1fba5c..e3bb44f8b 100644 --- a/src/db/unit_tests/dbLibrariesTests.cc +++ b/src/db/unit_tests/dbLibrariesTests.cc @@ -34,6 +34,7 @@ #include "dbTestSupport.h" #include "dbFileBasedLibrary.h" #include "dbColdProxy.h" +#include "dbTextWriter.h" #include "tlStream.h" #include "tlStaticObjects.h" #include "tlUnitTest.h" @@ -861,3 +862,64 @@ TEST(7_monsterlib) // but the layout did not change db::compare_layouts (_this, layout, tl::testsrc () + "/testdata/libman/design_au5.gds", db::NormalizationMode (db::NoNormalization | db::WithoutCellNames | db::AsPolygons)); } + +namespace { + +class PCellWithChildDeclaration : + public db::PCellDeclaration +{ + void produce (const db::Layout &layout, const std::vector & /*layer_ids*/, const db::pcell_parameters_type & /*parameters*/, db::Cell &cell) const + { + auto cid = const_cast (layout).add_cell ("CHILD"); + + db::PropertiesSet ps; + ps.insert ("id", tl::Variant ("my_id")); + + auto ps_id = db::properties_id (ps); + cell.insert (db::CellInstArrayWithProperties (db::CellInstArray (cid, db::Trans ()), ps_id)); + } +}; + +} + +static std::string l2s (const db::Layout &layout) +{ + tl::OutputStringStream os; + tl::OutputStream ostream (os); + db::TextWriter writer (ostream); + writer.write (layout); + return os.string (); +} + +// PCells with subcells with properties +TEST(8_issue2344) +{ + std::unique_ptr lib (new db::Library ()); + lib->set_name ("__PCellLibrary"); + lib->layout ().register_pcell ("PCell1", new PCellWithChildDeclaration ()); + db::LibraryManager::instance ().register_lib (lib.get ()); + + db::Layout ly; + std::pair pc = lib->layout ().pcell_by_name ("PCell1"); + tl_assert (pc.first); + + db::cell_index_type lib_cell = lib->layout ().get_pcell_variant_dict (pc.second, std::map ()); + ly.get_lib_proxy (lib.get (), lib_cell); + + EXPECT_EQ (l2s (ly), + "begin_lib 0.001\n" + "begin_cell {CHILD}\n" + "end_cell\n" + "begin_cell {PCell1}\n" + "set props {\n" + " {{id} {my_id}}\n" + "}\n" + "srefp $props {CHILD} 0 0 1 {0 0}\n" + "end_cell\n" + "end_lib\n" + ); + + db::LibraryManager::instance ().delete_lib (lib.release ()); + EXPECT (true); +} + diff --git a/src/drc/drc/built-in-macros/_drc_netter.rb b/src/drc/drc/built-in-macros/_drc_netter.rb index e5ec8f0a2..75506aae2 100644 --- a/src/drc/drc/built-in-macros/_drc_netter.rb +++ b/src/drc/drc/built-in-macros/_drc_netter.rb @@ -323,7 +323,9 @@ module DRC # # 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/img/img/imgObject.cc b/src/img/img/imgObject.cc index 6642ccdcb..4f6d97cf3 100644 --- a/src/img/img/imgObject.cc +++ b/src/img/img/imgObject.cc @@ -1009,6 +1009,7 @@ Object::operator= (const img::Object &d) m_trans = d.m_trans; m_filename = d.m_filename; + m_tag = d.m_tag; mp_data = d.mp_data; if (mp_data) { @@ -1963,6 +1964,7 @@ void Object::swap (Object &other) { m_filename.swap (other.m_filename); + m_tag.swap (other.m_tag); std::swap (m_trans, other.m_trans); std::swap (mp_data, other.mp_data); std::swap (m_id, other.m_id); @@ -1979,6 +1981,12 @@ Object::swap (Object &other) std::swap (m_updates_enabled, other.m_updates_enabled); } +void +Object::set_tag (const std::string &tag) +{ + m_tag = tag; +} + size_t Object::width () const { diff --git a/src/img/img/imgObject.h b/src/img/img/imgObject.h index a48e72551..6658d2ae9 100644 --- a/src/img/img/imgObject.h +++ b/src/img/img/imgObject.h @@ -581,6 +581,22 @@ public: return d; } + /** + * @brief Sets the tag string + * + * The tag string is an arbitrary string that can be used to identify + * the image. It is not persisted and it not considered for equality or sorting. + */ + void set_tag (const std::string &tag); + + /** + * @brief Gets the tag string + */ + const std::string tag () const + { + return m_tag; + } + /** * @brief Accessor to the width property */ @@ -1036,6 +1052,7 @@ protected: private: std::string m_filename; + std::string m_tag; db::Matrix3d m_trans; DataHeader *mp_data; size_t m_id; diff --git a/src/img/img/imgPropertiesPage.cc b/src/img/img/imgPropertiesPage.cc index cdb14e1d1..83938a4d8 100644 --- a/src/img/img/imgPropertiesPage.cc +++ b/src/img/img/imgPropertiesPage.cc @@ -314,46 +314,48 @@ PropertiesPage::min_max_value_changed () emit edited (); } +bool +PropertiesPage::update_controls () +{ + bool has_error = false; + + value_le->setText (QString ()); + value_le->setEnabled (false); + + colors->setEnabled (false_color_control->has_selection ()); + colors->set_single_mode (false); + + if (false_color_control->has_selection () && false_color_control->selected_node () > 0 && false_color_control->selected_node () < int (false_color_control->nodes ().size ()) - 1) { + + double xmin, xmax; + get_xmin_xmax (xmin, xmax, has_error); + + if (! has_error) { + + double x = false_color_control->nodes () [false_color_control->selected_node ()].first; + double xx = x * (xmax - xmin) + xmin; + + value_le->setText (tl::to_qstring (tl::sprintf ("%.4g", xx))); + value_le->setEnabled (true); + + } + + } else if (false_color_control->has_selection ()) { + + colors->set_single_mode (true); + + } + + return has_error; +} + void PropertiesPage::color_mapping_changed () { - if (! m_no_signals) { - - bool has_error = false; - - value_le->setText (QString ()); - value_le->setEnabled (false); - - colors->setEnabled (false_color_control->has_selection ()); - colors->set_single_mode (false); - - if (false_color_control->has_selection () && false_color_control->selected_node () > 0 && false_color_control->selected_node () < int (false_color_control->nodes ().size ()) - 1) { - - double xmin, xmax; - get_xmin_xmax (xmin, xmax, has_error); - - if (! has_error) { - - double x = false_color_control->nodes () [false_color_control->selected_node ()].first; - double xx = x * (xmax - xmin) + xmin; - - value_le->setText (tl::to_qstring (tl::sprintf ("%.4g", xx))); - value_le->setEnabled (true); - - } - - } else if (false_color_control->has_selection ()) { - - colors->set_single_mode (true); - - } - - if (! has_error) { - m_in_color_mapping_signal = true; - emit edited (); - m_in_color_mapping_signal = false; - } - + if (! m_no_signals && ! update_controls ()) { + m_in_color_mapping_signal = true; + emit edited (); + m_in_color_mapping_signal = false; } } @@ -509,6 +511,7 @@ PropertiesPage::update () m_no_signals = false; + update_controls (); recompute_histogram (); } diff --git a/src/img/img/imgPropertiesPage.h b/src/img/img/imgPropertiesPage.h index c799775b0..06d73b173 100644 --- a/src/img/img/imgPropertiesPage.h +++ b/src/img/img/imgPropertiesPage.h @@ -101,6 +101,7 @@ private: void invalidate (); void init (); void get_xmin_xmax (double &xmin, double &xmax, bool &has_error_out); + bool update_controls (); }; } diff --git a/src/lay/lay/layApplication.cc b/src/lay/lay/layApplication.cc index 63b6450cf..259a272b0 100644 --- a/src/lay/lay/layApplication.cc +++ b/src/lay/lay/layApplication.cc @@ -1658,6 +1658,12 @@ GuiApplication::event (QEvent *event) return QApplication::event(event); } +static void atexit_handler () +{ + if (lay::ApplicationBase::instance ()) { + lay::ApplicationBase::instance ()->shutdown (); + } +} int GuiApplication::exec () @@ -1693,6 +1699,10 @@ GuiApplication::exec () } + // register an exit handler to shutdown cleanly in case of an explicit exit + // inside the code + ::atexit (&atexit_handler); + return QApplication::exec (); } diff --git a/src/lay/lay/layApplication.h b/src/lay/lay/layApplication.h index e268df204..2a1322dfe 100644 --- a/src/lay/lay/layApplication.h +++ b/src/lay/lay/layApplication.h @@ -105,6 +105,15 @@ public: */ void exit (int result); + /** + * @brief Shut down the application + * + * Calling this function will close the main window and + * prepare for exit. Unlike "exit", it dows not actually exit + * the process. + */ + virtual void shutdown (); + /** * @brief Return the program's version */ @@ -330,7 +339,6 @@ public: protected: virtual void setup () = 0; - virtual void shutdown (); virtual void prepare_recording (const std::string >f_record, bool gtf_record_incremental); virtual void start_recording (); virtual lay::Dispatcher *dispatcher () const = 0; diff --git a/src/lay/lay/layClipDialog.cc b/src/lay/lay/layClipDialog.cc index a9dc2ae84..ca273cb56 100644 --- a/src/lay/lay/layClipDialog.cc +++ b/src/lay/lay/layClipDialog.cc @@ -168,12 +168,14 @@ BEGIN_PROTECTED } else if (rb_shapes->isChecked ()) { + lay::CellView ccv = view ()->cellview (cb_layer->cv_index ()); int sel_layer = cb_layer->current_layer (); - if (sel_layer < 0 || ! cv->layout ().is_valid_layer (sel_layer)) { + + if (! ccv.is_valid () || sel_layer < 0 || ! ccv->layout ().is_valid_layer (sel_layer)) { throw tl::Exception (tl::to_string (QObject::tr ("No valid layer selected to get clip boxes from"))); } - db::collect_clip_boxes (cv->layout (), cv.cell_index (), (unsigned int) sel_layer, clip_boxes); + db::collect_clip_boxes (ccv->layout (), ccv.cell_index (), (unsigned int) sel_layer, clip_boxes); } diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index 13c76abcf..f63026e95 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -165,7 +165,6 @@ show_dock_widget (QDockWidget *dock_widget, bool visible) MainWindow::MainWindow (QApplication *app, const char *name, bool undo_enabled) : QMainWindow (0), - tl::Object (), lay::DispatcherDelegate (), m_dispatcher (this), m_text_progress (this, 10 /*verbosity threshold*/), diff --git a/src/lay/lay/layMainWindow.h b/src/lay/lay/layMainWindow.h index e1fc24972..a22ac7bc9 100644 --- a/src/lay/lay/layMainWindow.h +++ b/src/lay/lay/layMainWindow.h @@ -94,7 +94,6 @@ class ProgressWidget; */ class LAY_PUBLIC MainWindow : public QMainWindow, - public tl::Object, public gsi::ObjectBase, public lay::DispatcherDelegate { diff --git a/src/laybasic/laybasic/layDispatcher.h b/src/laybasic/laybasic/layDispatcher.h index f42b60585..df26686d8 100644 --- a/src/laybasic/laybasic/layDispatcher.h +++ b/src/laybasic/laybasic/layDispatcher.h @@ -49,6 +49,7 @@ class ConfigureAction; * @brief A delegate by which the dispatcher can submit notification events */ class LAYBASIC_PUBLIC DispatcherDelegate + : public tl::Object { public: /** @@ -271,7 +272,7 @@ private: #if defined(HAVE_QT) QWidget *mp_menu_parent_widget; #endif - DispatcherDelegate *mp_delegate; + tl::weak_ptr mp_delegate; }; } diff --git a/src/laybasic/laybasic/layEditorOptionsPage.h b/src/laybasic/laybasic/layEditorOptionsPage.h index c5d605bff..9e7deedd3 100644 --- a/src/laybasic/laybasic/layEditorOptionsPage.h +++ b/src/laybasic/laybasic/layEditorOptionsPage.h @@ -252,7 +252,7 @@ public: /** * @brief Gets a value indicating whether the page is for that specific plugin (given by a declaration object) */ - bool for_plugin_declaration (const lay::PluginDeclaration *pd) + bool for_plugin_declaration (const lay::PluginDeclaration *pd) const { return m_plugin_declarations.find (pd) != m_plugin_declarations.end (); } diff --git a/src/laybasic/laybasic/layLayoutCanvas.cc b/src/laybasic/laybasic/layLayoutCanvas.cc index bfa1a1747..2d93485f0 100644 --- a/src/laybasic/laybasic/layLayoutCanvas.cc +++ b/src/laybasic/laybasic/layLayoutCanvas.cc @@ -947,8 +947,10 @@ LayoutCanvas::screenshot () void LayoutCanvas::resize_event (unsigned int width, unsigned int height) { - unsigned int w = width * dpr () + 0.5, h = height * dpr () + 0.5; - unsigned int wl = width * m_oversampling * dpr () + 0.5, hl = height * m_oversampling * dpr () + 0.5; + unsigned int w = (unsigned int) ceil (width * dpr () - db::epsilon); + unsigned int h = (unsigned int) ceil (height * dpr () - db::epsilon); + unsigned int wl = w * m_oversampling; + unsigned int hl = h * m_oversampling; if (m_viewport.width () != w || m_viewport.height () != h || m_viewport_l.width () != wl || m_viewport_l.height () != hl) { @@ -957,8 +959,8 @@ LayoutCanvas::resize_event (unsigned int width, unsigned int height) m_image_cache.clear (); // set the viewport to the new size - m_viewport.set_size (width * dpr () + 0.5, height * dpr () + 0.5); - m_viewport_l.set_size (width * m_oversampling * dpr () + 0.5, height * m_oversampling * dpr () + 0.5); + m_viewport.set_size (w, h); + m_viewport_l.set_size (wl, hl); mouse_event_trans (db::DCplxTrans (1.0 / dpr ()) * m_viewport.trans ()); do_redraw_all (true); diff --git a/src/laybasic/laybasic/layLayoutViewBase.h b/src/laybasic/laybasic/layLayoutViewBase.h index e754ebfbb..e3a1ff7b3 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.h +++ b/src/laybasic/laybasic/layLayoutViewBase.h @@ -2225,6 +2225,14 @@ public: return pi; } + /** + * @brief Gets the active plugin + */ + lay::Plugin *active_plugin () const + { + return mp_active_plugin; + } + /** * @brief Create a plugin of the given type * @@ -3214,11 +3222,6 @@ protected: void init (db::Manager *mgr); - lay::Plugin *active_plugin () const - { - return mp_active_plugin; - } - virtual LayoutView *get_ui (); bool configure (const std::string &name, const std::string &value); diff --git a/src/laybasic/laybasic/layLayoutViewConfig.cc b/src/laybasic/laybasic/layLayoutViewConfig.cc index f16fec969..2f68789bc 100644 --- a/src/laybasic/laybasic/layLayoutViewConfig.cc +++ b/src/laybasic/laybasic/layLayoutViewConfig.cc @@ -48,6 +48,12 @@ public: options.push_back (std::pair (cfg_layers_always_show_ld, "true")); options.push_back (std::pair (cfg_layers_always_show_layout_index, "false")); options.push_back (std::pair (cfg_test_shapes_in_view, "false")); + options.push_back (std::pair (cfg_layer_search_as_expressions, "true")); + options.push_back (std::pair (cfg_layer_search_as_filter, "false")); + options.push_back (std::pair (cfg_layer_search_case_sensitive, "true")); + options.push_back (std::pair (cfg_cell_search_as_expressions, "true")); + options.push_back (std::pair (cfg_cell_search_as_filter, "false")); + options.push_back (std::pair (cfg_cell_search_case_sensitive, "true")); options.push_back (std::pair (cfg_flat_cell_list, "false")); options.push_back (std::pair (cfg_split_cell_list, "false")); options.push_back (std::pair (cfg_cell_list_sorting, "by-name")); diff --git a/src/laybasic/laybasic/layMove.cc b/src/laybasic/laybasic/layMove.cc index b23f7c3bc..b270ab585 100644 --- a/src/laybasic/laybasic/layMove.cc +++ b/src/laybasic/laybasic/layMove.cc @@ -437,9 +437,12 @@ MoveService::drag_cancel () { m_shift = db::DPoint (); if (m_dragging) { + show_toolbox (false); ui ()->ungrab_mouse (this); + m_dragging = false; + } } diff --git a/src/laybasic/laybasic/laySelector.cc b/src/laybasic/laybasic/laySelector.cc index 9d60b1e63..241b9f496 100644 --- a/src/laybasic/laybasic/laySelector.cc +++ b/src/laybasic/laybasic/laySelector.cc @@ -332,6 +332,14 @@ public: { return new SelectionService (view); } + + virtual std::vector 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 selection_service_decl (new SelectionServiceDeclaration (), -980, "laybasic::SelectionServicePlugin"); diff --git a/src/laybasic/laybasic/laybasicConfig.h b/src/laybasic/laybasic/laybasicConfig.h index 78dc40fbf..11194126b 100644 --- a/src/laybasic/laybasic/laybasicConfig.h +++ b/src/laybasic/laybasic/laybasicConfig.h @@ -135,6 +135,13 @@ static const std::string cfg_layers_always_show_layout_index ("layers-always-sho static const std::string cfg_reader_options_show_always ("reader-options-show-always"); static const std::string cfg_tip_window_hidden ("tip-window-hidden"); +static const std::string cfg_layer_search_as_expressions ("layer-search-as-expressions"); +static const std::string cfg_layer_search_as_filter ("layer-search-as-filter"); +static const std::string cfg_layer_search_case_sensitive ("layer-search-case-sensitive"); +static const std::string cfg_cell_search_as_expressions ("cell-search-as-expressions"); +static const std::string cfg_cell_search_as_filter ("cell-search-as-filter"); +static const std::string cfg_cell_search_case_sensitive ("cell-search-case-sensitive"); + static const std::string cfg_bitmap_oversampling ("bitmap-oversampling"); static const std::string cfg_highres_mode ("highres-mode"); static const std::string cfg_subres_mode ("subres-mode"); diff --git a/src/layui/layui/layHierarchyControlPanel.cc b/src/layui/layui/layHierarchyControlPanel.cc index 2b948058c..4a7d9a6ef 100644 --- a/src/layui/layui/layHierarchyControlPanel.cc +++ b/src/layui/layui/layHierarchyControlPanel.cc @@ -264,7 +264,7 @@ HierarchyControlPanel::HierarchyControlPanel (lay::LayoutViewBase *view, QWidget mp_search_edit_box->set_escape_signal_enabled (true); mp_search_edit_box->set_tab_signal_enabled (true); connect (mp_search_edit_box, SIGNAL (returnPressed ()), this, SLOT (search_editing_finished ())); - connect (mp_search_edit_box, SIGNAL (textEdited (const QString &)), this, SLOT (search_edited ())); + connect (mp_search_edit_box, SIGNAL (textEdited (const QString &)), this, SLOT (search_edited_no_signal ())); connect (mp_search_edit_box, SIGNAL (esc_pressed ()), this, SLOT (search_editing_finished ())); connect (mp_search_edit_box, SIGNAL (tab_pressed ()), this, SLOT (search_next ())); connect (mp_search_edit_box, SIGNAL (backtab_pressed ()), this, SLOT (search_prev ())); @@ -452,8 +452,42 @@ HierarchyControlPanel::search_triggered (const QString &t) } } +void +HierarchyControlPanel::set_search_as_filter (bool f) +{ + if (f != search_as_filter ()) { + mp_filter->setChecked (f); + search_edited_no_signal (); + } +} + +void +HierarchyControlPanel::set_search_case_sensitive (bool f) +{ + if (f != search_case_sensitive ()) { + mp_case_sensitive->setChecked (f); + search_edited_no_signal (); + } +} + +void +HierarchyControlPanel::set_search_as_expression (bool f) +{ + if (f != search_as_expression ()) { + mp_use_regular_expressions->setChecked (f); + search_edited_no_signal (); + } +} + void HierarchyControlPanel::search_edited () +{ + search_edited_no_signal (); + emit search_options_changed (); +} + +void +HierarchyControlPanel::search_edited_no_signal () { bool filter_invalid = false; diff --git a/src/layui/layui/layHierarchyControlPanel.h b/src/layui/layui/layHierarchyControlPanel.h index f363d77a1..e7b6bf044 100644 --- a/src/layui/layui/layHierarchyControlPanel.h +++ b/src/layui/layui/layHierarchyControlPanel.h @@ -152,6 +152,52 @@ public: return m_active_index; } + + /** + * @brief Sets the "as_filter" flag for the search feature + * + * If this flag is set, search expressions are applied as filter + */ + void set_search_as_filter (bool f); + + /** + * @brief Gets the "search_as_filter" flag + */ + bool search_as_filter () + { + return mp_filter->isChecked (); + } + + /** + * @brief Sets the "case_sensitive" flag for the search feature + * + * If this flag is set, search expressions are case sensitive + */ + void set_search_case_sensitive (bool f); + + /** + * @brief Gets the "case_sensitive" flag for the search feature + */ + bool search_case_sensitive () + { + return mp_case_sensitive->isChecked (); + } + + /** + * @brief Sets the "as_expression" flag for the search feature + * + * If this flag is set, search expressions are handled as glob expressions + */ + void set_search_as_expression (bool f); + + /** + * @brief Gets the "as_expression" flag for the search feature + */ + bool search_as_expression () + { + return mp_use_regular_expressions->isChecked (); + } + /** * @brief Returns the paths of the selected cells */ @@ -273,6 +319,7 @@ public: signals: void cell_selected (cell_path_type path, int cellview_index); void active_cellview_changed (int cellview_index); + void search_options_changed (); public slots: void clicked (const QModelIndex &index); @@ -283,6 +330,7 @@ public slots: void context_menu (const QPoint &pt); void search_triggered (const QString &t); void search_edited (); + void search_edited_no_signal (); void search_editing_finished (); void search_next (); void search_prev (); diff --git a/src/layui/layui/layLayerControlPanel.cc b/src/layui/layui/layLayerControlPanel.cc index b7a2ba70c..d6861df73 100644 --- a/src/layui/layui/layLayerControlPanel.cc +++ b/src/layui/layui/layLayerControlPanel.cc @@ -257,7 +257,7 @@ LayerControlPanel::LayerControlPanel (lay::LayoutViewBase *view, db::Manager *ma mp_search_edit_box->set_escape_signal_enabled (true); mp_search_edit_box->set_tab_signal_enabled (true); connect (mp_search_edit_box, SIGNAL (returnPressed ()), this, SLOT (search_editing_finished ())); - connect (mp_search_edit_box, SIGNAL (textEdited (const QString &)), this, SLOT (search_edited ())); + connect (mp_search_edit_box, SIGNAL (textEdited (const QString &)), this, SLOT (search_edited_no_signal ())); connect (mp_search_edit_box, SIGNAL (esc_pressed ()), this, SLOT (search_editing_finished ())); connect (mp_search_edit_box, SIGNAL (tab_pressed ()), this, SLOT (search_next ())); connect (mp_search_edit_box, SIGNAL (backtab_pressed ()), this, SLOT (search_prev ())); @@ -1160,12 +1160,46 @@ LayerControlPanel::search_triggered (const QString &t) mp_search_frame->show (); mp_search_edit_box->setText (t); mp_search_edit_box->setFocus (); - search_edited (); + search_edited_no_signal (); + } +} + +void +LayerControlPanel::set_search_as_filter (bool f) +{ + if (f != search_as_filter ()) { + mp_filter->setChecked (f); + search_edited_no_signal (); + } +} + +void +LayerControlPanel::set_search_case_sensitive (bool f) +{ + if (f != search_case_sensitive ()) { + mp_case_sensitive->setChecked (f); + search_edited_no_signal (); + } +} + +void +LayerControlPanel::set_search_as_expression (bool f) +{ + if (f != search_as_expression ()) { + mp_use_regular_expressions->setChecked (f); + search_edited_no_signal (); } } void LayerControlPanel::search_edited () +{ + search_edited_no_signal (); + emit search_options_changed (); +} + +void +LayerControlPanel::search_edited_no_signal () { if (! mp_model) { return; diff --git a/src/layui/layui/layLayerControlPanel.h b/src/layui/layui/layLayerControlPanel.h index fe2f6d920..32e6c0cd6 100644 --- a/src/layui/layui/layLayerControlPanel.h +++ b/src/layui/layui/layLayerControlPanel.h @@ -215,6 +215,51 @@ public: return mp_model->get_test_shapes_in_view (); } + /** + * @brief Sets the "as_filter" flag for the search feature + * + * If this flag is set, search expressions are applied as filter + */ + void set_search_as_filter (bool f); + + /** + * @brief Gets the "search_as_filter" flag + */ + bool search_as_filter () + { + return mp_filter->isChecked (); + } + + /** + * @brief Sets the "case_sensitive" flag for the search feature + * + * If this flag is set, search expressions are case sensitive + */ + void set_search_case_sensitive (bool f); + + /** + * @brief Gets the "case_sensitive" flag for the search feature + */ + bool search_case_sensitive () + { + return mp_case_sensitive->isChecked (); + } + + /** + * @brief Sets the "as_expression" flag for the search feature + * + * If this flag is set, search expressions are handled as glob expressions + */ + void set_search_as_expression (bool f); + + /** + * @brief Gets the "as_expression" flag for the search feature + */ + bool search_as_expression () + { + return mp_use_regular_expressions->isChecked (); + } + /** * @brief Set the animation phase */ @@ -297,6 +342,7 @@ signals: void tab_changed (); void current_layer_changed (const lay::LayerPropertiesConstIterator &iter); void selected_layers_changed (); + void search_options_changed (); public slots: void cm_new_tab (); @@ -344,6 +390,7 @@ public slots: void downdown_clicked (); void search_triggered (const QString &t); void search_edited (); + void search_edited_no_signal (); void search_editing_finished (); void search_next (); void search_prev (); diff --git a/src/layui/layui/layWidgets.cc b/src/layui/layui/layWidgets.cc index fc18882b1..93d61ec15 100644 --- a/src/layui/layui/layWidgets.cc +++ b/src/layui/layui/layWidgets.cc @@ -848,6 +848,12 @@ LayerSelectionComboBox::is_no_layer_selected () const return currentIndex () < 0; } +int +LayerSelectionComboBox::cv_index () const +{ + return mp_private->cv_index; +} + int LayerSelectionComboBox::current_layer () const { diff --git a/src/layui/layui/layWidgets.h b/src/layui/layui/layWidgets.h index e812f2fbf..5a1eac17e 100644 --- a/src/layui/layui/layWidgets.h +++ b/src/layui/layui/layWidgets.h @@ -307,6 +307,14 @@ public: */ bool is_no_layer_selected () const; + /** + * @brief Gets the cellview index + * + * NOTE: this methods returns -1 if the widget is not + * associated with a cellview index. + */ + int cv_index () const; + /** * @brief Get the current layer (index) * diff --git a/src/layview/layview/layEditorOptionsPages.cc b/src/layview/layview/layEditorOptionsPages.cc index 0db1877de..6e3dd2d6a 100644 --- a/src/layview/layview/layEditorOptionsPages.cc +++ b/src/layview/layview/layEditorOptionsPages.cc @@ -120,8 +120,13 @@ EditorOptionsPages::editor_options_pages () bool EditorOptionsPages::has_content () const { + lay::Plugin *plugin = mp_view->active_plugin (); + if (! plugin) { + return false; + } + for (auto p = m_pages.begin (); p != m_pages.end (); ++p) { - if (p->active () && ! p->is_modal_page () && ! p->is_toolbox_widget ()) { + if (! p->is_modal_page () && ! p->is_toolbox_widget () && p->for_plugin_declaration (plugin->plugin_declaration ())) { return true; } } @@ -131,8 +136,13 @@ EditorOptionsPages::has_content () const bool EditorOptionsPages::has_modal_content () const { + lay::Plugin *plugin = mp_view->active_plugin (); + if (! plugin) { + return false; + } + for (auto p = m_pages.begin (); p != m_pages.end (); ++p) { - if (p->active () && p->is_modal_page () && ! p->is_toolbox_widget ()) { + if (p->is_modal_page () && ! p->is_toolbox_widget () && p->for_plugin_declaration (plugin->plugin_declaration ())) { return true; } } @@ -173,7 +183,7 @@ EditorOptionsPages::activate (const lay::Plugin *plugin) bool is_active = plugin && op->for_plugin_declaration (plugin->plugin_declaration ()); // The zero order page is picked as the initial one - if (is_active && ! op->active () && op->order () == 0 && page == 0) { + if (is_active && ! op->active () && (op->order () == 0 || page == 0)) { page = op.operator-> (); } diff --git a/src/layview/layview/layLayoutView_qt.cc b/src/layview/layview/layLayoutView_qt.cc index 3045f4881..6e51d4b28 100644 --- a/src/layview/layview/layLayoutView_qt.cc +++ b/src/layview/layview/layLayoutView_qt.cc @@ -404,6 +404,16 @@ void LayoutViewSignalConnector::layer_order_changed () mp_view->layer_order_changed (); } +void LayoutViewSignalConnector::layer_search_options_edited () +{ + mp_view->layer_search_options_edited (); +} + +void LayoutViewSignalConnector::cell_search_options_edited () +{ + mp_view->cell_search_options_edited (); +} + void LayoutViewSignalConnector::min_hier_changed (int i) { mp_view->min_hier_changed (i); @@ -573,6 +583,7 @@ LayoutView::init_ui (db::Manager *mgr) mp_hierarchy_panel = new lay::HierarchyControlPanel (this, hierarchy_frame, "hcp"); left_frame_ly->addWidget (mp_hierarchy_panel, 1 /*stretch*/); + QObject::connect (mp_hierarchy_panel, SIGNAL (search_options_changed ()), mp_connector, SLOT (cell_search_options_edited ())); QObject::connect (mp_hierarchy_panel, SIGNAL (cell_selected (cell_path_type, int)), mp_connector, SLOT (select_cell_dispatch (cell_path_type, int))); QObject::connect (mp_hierarchy_panel, SIGNAL (active_cellview_changed (int)), mp_connector, SLOT (active_cellview_changed (int))); QObject::connect (mp_hierarchy_frame, SIGNAL (destroyed ()), mp_connector, SLOT (side_panel_destroyed ())); @@ -655,6 +666,7 @@ LayoutView::init_ui (db::Manager *mgr) mp_control_frame = mp_control_panel; QObject::connect (mp_control_frame, SIGNAL (destroyed ()), mp_connector, SLOT (side_panel_destroyed ())); + QObject::connect (mp_control_frame, SIGNAL (search_options_changed ()), mp_connector, SLOT (layer_search_options_edited ())); QObject::connect (mp_control_panel, SIGNAL (tab_changed ()), mp_connector, SLOT (layer_tab_changed ())); QObject::connect (mp_control_panel, SIGNAL (order_changed ()), mp_connector, SLOT (layer_order_changed ())); QObject::connect (mp_control_panel, SIGNAL (current_layer_changed (const lay::LayerPropertiesConstIterator &)), mp_connector, SLOT (current_layer_changed_slot (const lay::LayerPropertiesConstIterator &))); @@ -1041,6 +1053,60 @@ LayoutView::configure (const std::string &name, const std::string &value) } return true; + } else if (name == cfg_layer_search_as_expressions) { + + bool f; + tl::from_string (value, f); + if (mp_control_panel) { + mp_control_panel->set_search_as_expression (f); + } + return true; + + } else if (name == cfg_layer_search_as_filter) { + + bool f; + tl::from_string (value, f); + if (mp_control_panel) { + mp_control_panel->set_search_as_filter (f); + } + return true; + + } else if (name == cfg_layer_search_case_sensitive) { + + bool f; + tl::from_string (value, f); + if (mp_control_panel) { + mp_control_panel->set_search_case_sensitive (f); + } + return true; + + } else if (name == cfg_cell_search_as_expressions) { + + bool f; + tl::from_string (value, f); + if (mp_hierarchy_panel) { + mp_hierarchy_panel->set_search_as_expression (f); + } + return true; + + } else if (name == cfg_cell_search_as_filter) { + + bool f; + tl::from_string (value, f); + if (mp_hierarchy_panel) { + mp_hierarchy_panel->set_search_as_filter (f); + } + return true; + + } else if (name == cfg_cell_search_case_sensitive) { + + bool f; + tl::from_string (value, f); + if (mp_hierarchy_panel) { + mp_hierarchy_panel->set_search_case_sensitive (f); + } + return true; + } else if (name == cfg_layers_always_show_source) { bool a = false; @@ -1299,6 +1365,26 @@ LayoutView::max_hier_changed (int i) set_hier_levels (std::make_pair (get_hier_levels ().first, i)); } +void +LayoutView::layer_search_options_edited () +{ + if (mp_control_panel) { + dispatcher ()->config_set (cfg_layer_search_as_expressions, mp_control_panel->search_as_expression ()); + dispatcher ()->config_set (cfg_layer_search_case_sensitive, mp_control_panel->search_case_sensitive ()); + dispatcher ()->config_set (cfg_layer_search_as_filter, mp_control_panel->search_as_filter ()); + } +} + +void +LayoutView::cell_search_options_edited () +{ + if (mp_hierarchy_panel) { + dispatcher ()->config_set (cfg_cell_search_as_expressions, mp_hierarchy_panel->search_as_expression ()); + dispatcher ()->config_set (cfg_cell_search_case_sensitive, mp_hierarchy_panel->search_case_sensitive ()); + dispatcher ()->config_set (cfg_cell_search_as_filter, mp_hierarchy_panel->search_as_filter ()); + } +} + tl::Color LayoutView::default_background_color () { diff --git a/src/layview/layview/layLayoutView_qt.h b/src/layview/layview/layLayoutView_qt.h index 5b9df8062..b7387aec3 100644 --- a/src/layview/layview/layLayoutView_qt.h +++ b/src/layview/layview/layLayoutView_qt.h @@ -114,6 +114,8 @@ public slots: void layer_tab_changed (); void layer_order_changed (); void select_cell_dispatch (const cell_path_type &path, int cellview_index); + void layer_search_options_edited (); + void cell_search_options_edited (); void min_hier_changed (int i); void max_hier_changed (int i); void app_terminated (); @@ -654,6 +656,8 @@ private: void layer_order_changed (); void min_hier_changed (int i); void max_hier_changed (int i); + void layer_search_options_edited (); + void cell_search_options_edited (); bool event_filter (QObject *obj, QEvent *ev, bool &taken); QSize size_hint () const; diff --git a/src/lvs/unit_tests/lvsSimpleTests.cc b/src/lvs/unit_tests/lvsSimpleTests.cc index 53222d344..e248cee48 100644 --- a/src/lvs/unit_tests/lvsSimpleTests.cc +++ b/src/lvs/unit_tests/lvsSimpleTests.cc @@ -362,3 +362,9 @@ TEST(63_FlagMissingPorts) run_test (_this, "flag_missing_ports", "flag_missing_ports.gds", false, true, "TOP"); } +// Split substrate - marker and global connection (issue #2345) +TEST(64_SplitSubstrate) +{ + run_test (_this, "split_substrate", "split_substrate.gds", true, false /*no LVS*/); +} + diff --git a/src/plugins/tools/density_map/density_map.pro b/src/plugins/tools/density_map/density_map.pro new file mode 100644 index 000000000..f1dd4434b --- /dev/null +++ b/src/plugins/tools/density_map/density_map.pro @@ -0,0 +1,6 @@ + +TEMPLATE = subdirs + +!equals(HAVE_QT, "0") { + SUBDIRS = lay_plugin +} diff --git a/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui new file mode 100644 index 000000000..ded9b81ce --- /dev/null +++ b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui @@ -0,0 +1,600 @@ + + + DensityMapDialog + + + + 0 + 0 + 657 + 480 + + + + Density Map + + + + 6 + + + 9 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + + + + Density map parameters + + + + + + + 0 + 0 + + + + 100 + + + + + + + Pixel size + + + + + + + + 0 + 0 + + + + + + + + µm + + + + + + + + 0 + 0 + + + + 1 + + + + + + + Averaging window size + + + + + + + Threads + + + + + + + Qt::Horizontal + + + + 353 + 20 + + + + + + + + µm (a multiple of the pixel size or empty) + + + + + + + Boundary mode + + + + + + + + 0 + 0 + + + + QComboBox::AdjustToContents + + + + Periodic (repeat region seamlessly) + + + + + Use zero density outside + + + + + Use 100% density outside + + + + + Use average density outside + + + + + + + + + + + Compute density of shapes on + + + + + + 1 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + QComboBox::AdjustToContents + + + + + + + Qt::Horizontal + + + + 385 + 20 + + + + + + + + + + + + + + + + Layer + + + + + Visible layers + + + + + Selected layers + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 8 + 20 + + + + + + + + + + + Compute density map of region + + + + 6 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + + + + 2 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + QComboBox::AdjustToContents + + + + + + + Qt::Horizontal + + + + 351 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + Box Boundaries (all values in µm) + + + + 9 + + + 6 + + + + + y = + + + + + + + 2nd corner + + + + + + + x = + + + + + + + 1st corner + + + + + + + x = + + + + + + + y = + + + + + + + + + + + + + + + + + + + + + + + + + + + + Global bounding box + + + + + Bounding box of layer ... + + + + + Single box with .. + + + + + Visible region + + + + + Defined by rulers + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 8 + 20 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Ok + + + + + + + + lay::LayerSelectionComboBox + QComboBox +
layWidgets.h
+
+
+ + le_pixel_size + sb_threads + layer_cb + cb_source_layer + region_cb + cb_box_layer + le_x1 + le_y1 + le_x2 + le_y2 + + + + + button_box + accepted() + DensityMapDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + button_box + rejected() + DensityMapDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc new file mode 100644 index 000000000..b6e280797 --- /dev/null +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc @@ -0,0 +1,765 @@ + +/* + + 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 "layDensityMapDialog.h" + +#include "dbClip.h" +#include "dbTilingProcessor.h" +#include "antService.h" +#include "tlException.h" +#include "tlString.h" +#include "tlExceptions.h" +#include "layUtils.h" +#include "imgObject.h" +#include "imgService.h" + +namespace lay +{ + +static const std::string layer_mode_specific ("layer-mode-specific"); +static const std::string layer_mode_visible ("layer-mode-visible"); +static const std::string layer_mode_selected ("layer-mode-selected"); + +// items in layer_cb +static const std::vector layer_modes = { layer_mode_specific, layer_mode_visible, layer_mode_selected }; + +static const std::string region_mode_global_bbox ("region-mode-global-bbox"); +static const std::string region_mode_layer_bbox ("region-mode-layer-bbox"); +static const std::string region_mode_single_box ("region-mode-single-box"); +static const std::string region_mode_visible_region ("region-mode-visible-region"); +static const std::string region_mode_by_rulers ("region-mode-by-rulers"); + +// items in region_cb +static const std::vector region_modes = { + region_mode_global_bbox, + region_mode_layer_bbox, + region_mode_single_box, + region_mode_visible_region, + region_mode_by_rulers +}; + +static const std::string boundary_mode_periodic ("boundary-mode-periodic"); +static const std::string boundary_mode_zero ("boundary-mode-zero"); +static const std::string boundary_mode_one ("boundary-mode-one"); +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 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_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"); +static const std::string cfg_density_map_box_layer ("density-map-box-layer"); +static const std::string cfg_density_map_single_box ("density-map-single-box"); + +// ------------------------------------------------------------ +// Declaration of the configuration options + +class DensityMapDialogPluginDeclaration + : public lay::PluginDeclaration +{ +public: + virtual void get_options (std::vector < std::pair > &options) const + { + options.push_back (std::make_pair (cfg_density_map_layer_mode, layer_mode_specific)); + options.push_back (std::make_pair (cfg_density_map_region_mode, region_mode_global_bbox)); + 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_threads, "1")); + } + + virtual lay::ConfigPage *config_page (QWidget * /*parent*/, std::string & /*title*/) const + { + return 0; // .. no config page yet .. + } + + virtual void get_menu_entries (std::vector &menu_entries) const + { + lay::PluginDeclaration::get_menu_entries (menu_entries); + menu_entries.push_back (lay::menu_item ("density_map::show", "density_map_tool:edit", "tools_menu.post_verification_group", tl::to_string (QObject::tr ("Density Map")))); + } + + virtual lay::Plugin *create_plugin (db::Manager *, lay::Dispatcher *root, lay::LayoutViewBase *view) const + { + if (lay::has_gui ()) { + return new DensityMapDialog (root, view); + } else { + return 0; + } + } +}; + +static tl::RegisteredClass config_decl (new DensityMapDialogPluginDeclaration (), 3002, "lay::DensityMapPlugin"); + + +// ------------------------------------------------------------ + +DensityMapDialog::DensityMapDialog (lay::Dispatcher *root, LayoutViewBase *vw) + : lay::Browser (root, vw), + Ui::DensityMapDialog () +{ + Ui::DensityMapDialog::setupUi (this); + + connect (layer_cb, SIGNAL (activated (int)), SLOT (layer_mode_changed (int))); + connect (region_cb, SIGNAL (activated (int)), SLOT (region_mode_changed (int))); + + connect (button_box->button (QDialogButtonBox::Apply), SIGNAL (clicked ()), this, SLOT (apply ())); + + region_mode_changed (region_cb->currentIndex ()); + layer_mode_changed (layer_cb->currentIndex ()); +} + +void +DensityMapDialog::region_mode_changed (int mode) +{ + if (mode < 0 || mode >= int (region_modes.size ())) { + return; + } + + const auto &rm = region_modes [mode]; + if (rm == region_mode_layer_bbox) { + region_stack->setCurrentIndex (0); + } else if (rm == region_mode_single_box) { + region_stack->setCurrentIndex (1); + } else { + region_stack->setCurrentIndex (2); + } +} + +void +DensityMapDialog::layer_mode_changed (int mode) +{ + if (mode < 0 || mode >= int (layer_modes.size ())) { + return; + } + + const auto &lm = layer_modes [mode]; + if (lm == layer_mode_specific) { + layer_stack->setCurrentIndex (0); + } else { + layer_stack->setCurrentIndex (1); + } +} + +void +DensityMapDialog::menu_activated (const std::string &symbol) +{ + if (symbol == "density_map::show") { + + int cv_index = view ()->active_cellview_index (); + + lay::CellView cv = view ()->cellview (cv_index); + if (cv.is_valid ()) { + cb_box_layer->set_view (view (), cv_index); + cb_source_layer->set_view (view (), cv_index); + show (); + activate (); + } + + } else { + lay::Browser::menu_activated (symbol); + } +} + +DensityMapDialog::~DensityMapDialog () +{ + // .. nothing yet .. +} + +namespace +{ + + class DensityMapTileReceiver + : public db::TileOutputReceiver + { + public: + DensityMapTileReceiver (img::Object *img) + : mp_img (img) + { + // .. nothing yet .. + } + + virtual void put (size_t ix, size_t iy, const db::Box &tile, size_t /*id*/, const tl::Variant &obj, double /*dbu*/, const db::ICplxTrans & /*trans*/, bool /*clip*/) + { + if (tl::verbosity () >= 30) { + tl::info << "Density map value: " << ix << "," << iy << " " << tile.to_string () << " -> " << obj.to_string (); + } + + mp_img->set_pixel (ix, iy, obj.to_double ()); + } + + private: + img::Object *mp_img; + }; + +} + +void +DensityMapDialog::apply () +{ +BEGIN_PROTECTED + + make_density_map (); + +END_PROTECTED +} + +void +DensityMapDialog::accept () +{ +BEGIN_PROTECTED + + make_density_map (); + + // close this dialog + QDialog::accept (); + +END_PROTECTED +} + +bool +DensityMapDialog::configure (const std::string &name, const std::string &value) +{ + if (name == cfg_density_map_layer_mode) { + + int mode = 0; + for (size_t i = 0; i < layer_modes.size (); ++i) { + if (layer_modes[i] == value) { + mode = int (i); + } + } + + layer_cb->setCurrentIndex (mode); + layer_mode_changed (mode); + return true; + + } else if (name == cfg_density_map_region_mode) { + + int mode = 0; + for (size_t i = 0; i < region_modes.size (); ++i) { + if (region_modes[i] == value) { + mode = int (i); + } + } + + region_cb->setCurrentIndex (mode); + region_mode_changed (mode); + return true; + + } else if (name == cfg_density_map_pixel_size) { + + double px = 100.0; + try { + tl::from_string (value, px); + le_pixel_size->setText (tl::to_qstring (tl::to_string (px))); + } catch (...) { + } + return true; + + } else if (name == cfg_density_map_window_size) { + + double ws = 0.0; + try { + tl::from_string (value, ws); + if (ws > 0) { + le_window_size->setText (tl::to_qstring (tl::to_string (ws))); + } else { + le_window_size->setText (QString ()); + } + } catch (...) { + } + return true; + + } else if (name == cfg_density_map_boundary_mode) { + + int mode = 0; + for (size_t i = 0; i < boundary_modes.size (); ++i) { + if (boundary_modes[i] == value) { + mode = int (i); + } + } + + cb_boundary_mode->setCurrentIndex (mode); + return true; + + } else if (name == cfg_density_map_threads) { + + int thr = 1; + try { + tl::from_string (value, thr); + sb_threads->setValue (thr); + } catch (...) { + } + return true; + + } else if (name == cfg_density_map_source_layer) { + + db::LayerProperties lp; + try { + tl::from_string (value, lp); + cb_source_layer->set_current_layer (lp); + } catch (...) { + } + return true; + + } else if (name == cfg_density_map_box_layer) { + + db::LayerProperties lp; + try { + tl::from_string (value, lp); + cb_box_layer->set_current_layer (lp); + } catch (...) { + } + return true; + + } else if (name == cfg_density_map_single_box) { + + db::DBox bx; + try { + tl::from_string (value, bx); + if (bx.empty ()) { + le_x1->setText (QString ()); + le_y1->setText (QString ()); + le_x2->setText (QString ()); + le_y2->setText (QString ()); + } else { + le_x1->setText (tl::to_qstring (tl::to_string (bx.left ()))); + le_y1->setText (tl::to_qstring (tl::to_string (bx.bottom ()))); + le_x2->setText (tl::to_qstring (tl::to_string (bx.right ()))); + le_y2->setText (tl::to_qstring (tl::to_string (bx.top ()))); + } + } catch (...) { + } + return true; + + } else { + return false; + } +} + +void +DensityMapDialog::make_density_map () +{ + DensityMapParameters par; + + par.threads = std::max (1, sb_threads->value ()); + + par.pixel_size = 0.0; + tl::from_string_ext (tl::to_string (le_pixel_size->text ()), par.pixel_size); + + if (par.pixel_size < 1e-6) { + throw tl::Exception (tl::to_string (QObject::tr ("The pixel size must be positive and not zero"))); + } + + par.window_size = 0.0; + if (! le_window_size->text ().simplified ().isEmpty ()) { + tl::from_string_ext (tl::to_string (le_window_size->text ()), par.window_size); + if (par.window_size < 1e-6) { + throw tl::Exception (tl::to_string (QObject::tr ("The window size must be positive and not zero or empty"))); + } + } + + int boundary_mode = cb_boundary_mode->currentIndex (); + if (boundary_mode < 0 || boundary_mode >= int (boundary_modes.size ())) { + return; + } + par.boundary_mode = boundary_modes [boundary_mode]; + + int region_mode = region_cb->currentIndex (); + if (region_mode < 0 || region_mode >= int (region_modes.size ())) { + return; + } + const auto &rm = region_modes [region_mode]; + + int layer_mode = layer_cb->currentIndex (); + if (layer_mode < 0 || layer_mode >= int (layer_modes.size ())) { + return; + } + const auto &lm = layer_modes [layer_mode]; + + db::LayerProperties region_layer; + db::LayerProperties source_layer; + + if (rm == region_mode_single_box) { + + if (le_x1->text ().isEmpty () || le_x2->text ().isEmpty () || + le_y1->text ().isEmpty () || le_y2->text ().isEmpty ()) { + throw tl::Exception (tl::to_string (QObject::tr ("All four coordinates of the clip box must be given"))); + } + + double x1 = 0.0, y1 = 0.0; + double x2 = 0.0, y2 = 0.0; + tl::from_string_ext (tl::to_string (le_x1->text ()), x1); + tl::from_string_ext (tl::to_string (le_x2->text ()), x2); + tl::from_string_ext (tl::to_string (le_y1->text ()), y1); + tl::from_string_ext (tl::to_string (le_y2->text ()), y2); + + par.region = db::DBox (db::DPoint (x1, y1), db::DPoint (x2, y2)); + + } else if (rm == region_mode_by_rulers) { + + ant::Service *ant_service = view ()->get_plugin (); + if (ant_service) { + ant::AnnotationIterator ant = ant_service->begin_annotations (); + while (! ant.at_end ()) { + par.region += db::DBox (ant->p1 (), ant->p2 ()); + ++ant; + } + } + + } else if (rm == region_mode_layer_bbox) { + + lay::CellView ccv = view ()->cellview (cb_box_layer->cv_index ()); + int sel_layer = cb_box_layer->current_layer (); + + region_layer = cb_box_layer->current_layer_props (); + + if (! ccv.is_valid () || sel_layer < 0 || ! ccv->layout ().is_valid_layer (sel_layer)) { + throw tl::Exception (tl::to_string (QObject::tr ("No valid layer selected to get clip boxes from"))); + } + + par.region = db::CplxTrans (ccv->layout ().dbu ()) * ccv->layout ().cell (ccv.cell_index ()).bbox (sel_layer); + + } else if (rm == region_mode_visible_region) { + + par.region = view ()->box (); + + } else { + + lay::CellView cv = view ()->cellview (view ()->active_cellview_index ()); + par.region = db::CplxTrans (cv->layout ().dbu ()) * cv->layout ().cell (cv.cell_index ()).bbox (); + + } + + if (lm == layer_mode_specific) { + + int cvi = cb_source_layer->cv_index (); + lay::CellView ccv = view ()->cellview (cvi); + int sel_layer = cb_source_layer->current_layer (); + + source_layer = cb_source_layer->current_layer_props (); + + if (! ccv.is_valid () || sel_layer < 0 || ! ccv->layout ().is_valid_layer (sel_layer)) { + throw tl::Exception (tl::to_string (QObject::tr ("No valid layer selected to get clip boxes from"))); + } + + par.input_layers.push_back (std::make_pair (cvi, sel_layer)); + + } else if (lm == layer_mode_visible) { + + for (auto l = view ()->begin_layers (); !l.at_end (); ++l) { + + if (! l->has_children () && l->visible (true)) { + + int cvi = (l->cellview_index () >= 0) ? l->cellview_index () : 0; + lay::CellView ccv = view ()->cellview (cvi); + int li = l->layer_index (); + + if (ccv.is_valid () || li >= 0 || ! ccv->layout ().is_valid_layer (li)) { + par.input_layers.push_back (std::make_pair (cvi, li)); + } + + } + + } + + } else if (lm == layer_mode_selected) { + + auto sel = view ()->selected_layers (); + for (auto s = sel.begin (); s != sel.end (); ++s) { + + auto l = *s; + if (! l->has_children ()) { + + int cvi = (l->cellview_index () >= 0) ? l->cellview_index () : 0; + lay::CellView ccv = view ()->cellview (cvi); + int li = l->layer_index (); + + if (ccv.is_valid () || li >= 0 || ! ccv->layout ().is_valid_layer (li)) { + par.input_layers.push_back (std::make_pair (cvi, li)); + } + + } + + } + + } + + // Commit the parameters + 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_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)); + + if (lm == layer_mode_specific) { + dispatcher ()->config_set (cfg_density_map_source_layer, source_layer.to_string ()); + } + + if (rm == region_mode_layer_bbox) { + dispatcher ()->config_set (cfg_density_map_box_layer, region_layer.to_string ()); + } else if (rm == region_mode_single_box) { + dispatcher ()->config_set (cfg_density_map_single_box, par.region.to_string ()); + } + + dispatcher ()->config_setup (); + + compute_density_map (par); +} + +void +DensityMapDialog::compute_density_map (const DensityMapParameters &par) +{ + if (par.region.empty ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Density map region is empty"))); + } + + if (par.input_layers.empty ()) { + throw tl::Exception (tl::to_string (QObject::tr ("No input layers given"))); + } + + // Compute the tile origins + + const double max_wh = 100000.0; + const int max_pixels = 100000000; + + double dnx = std::max (1.0, ceil (par.region.width () / par.pixel_size - db::epsilon)); + double dny = std::max (1.0, ceil (par.region.height () / par.pixel_size - db::epsilon)); + if (dnx > max_wh || dny > max_wh) { + throw tl::Exception (tl::sprintf (tl::to_string (QObject::tr ("Density map dimensions exceed maximum limit of %g pixels in one direction"))), max_wh); + } + + int nx = int (dnx); + int ny = int (dny); + if (dnx * dny > max_pixels) { + throw tl::Exception (tl::sprintf (tl::to_string (QObject::tr ("Density map array exceed maximum limit of %d pixels in total"))), max_pixels); + } + + double x0 = par.region.center ().x () - nx * 0.5 * par.pixel_size; + double y0 = par.region.center ().y () - ny * 0.5 * par.pixel_size; + + double dbu = view ()->cellview (par.input_layers.front ().first)->layout ().dbu (); + + // Set up the tiling processor + + db::TilingProcessor tp; + + tp.tiles (nx, ny); + tp.tile_origin (x0, y0); + tp.tile_size (par.pixel_size, par.pixel_size); + + tp.set_threads (par.threads); + tp.set_dbu (dbu); + + int ninput = 1; + std::string in_expr; + + for (auto i = par.input_layers.begin (); i != par.input_layers.end (); ++i, ++ninput) { + + const lay::CellView &cv = view ()->cellview (i->first); + const db::Layout &ly = cv->layout (); + const db::Cell &cell = ly.cell (cv.cell_index ()); + + std::string in_name = "in" + tl::to_string (ninput); + tp.input (in_name, db::RecursiveShapeIterator (ly, cell, i->second, true), db::ICplxTrans (ly.dbu () / dbu), db::TilingProcessor::TypeRegion, true); + + if (! in_expr.empty ()) { + in_expr += "+"; + } + in_expr += in_name; + + } + + tp.queue (std::string ("var inp = ") + in_expr + "; _tile && _output(dens, to_f(inp.area(_tile.bbox)) / to_f(_tile.bbox.area))"); + + // Prepare the Image for receiving + + img::Object *img_object = 0; + + img::Service *img_service = view ()->get_plugin (); + if (img_service) { + + const std::string img_tag = "density-map-image"; + + img::DataMapping dm; + dm.false_color_nodes.clear (); + + // default mapping blue -> red + // TODO: we could use that from a previous image ... + dm.false_color_nodes.push_back (std::make_pair (0.0, std::make_pair (0x0000ff, 0x0000ff))); + dm.false_color_nodes.push_back (std::make_pair (1.0, std::make_pair (0xff0000, 0xff0000))); + + for (auto i = img_service->begin_images (); ! i.at_end (); ++i) { + if (i->tag () == img_tag) { + // inherit data mapping from previous image + dm = i->data_mapping (); + img_service->erase_image_by_id (i->id ()); + break; + } + } + + img::Object img (nx, ny, db::DCplxTrans (par.pixel_size, 0.0, false, par.region.center () - db::DPoint ()), false, false); + img.set_data_mapping (dm); + img.set_tag (img_tag); + + img_object = img_service->insert_image (img); + + } + + tl_assert (img_object); + tp.output ("dens", 0, new DensityMapTileReceiver (img_object), db::ICplxTrans ()); + + // Execute the tiling processor + + tp.execute (tl::to_string (tr ("Computing density map"))); + + // Do the averaging if requested + + 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); + } +} + +inline int safe_mod (int a, int b) +{ + if (a < 0) { + return b - (-a % b); + } else { + return a % b; + } +} + +void +DensityMapDialog::average_window (img::Object &img_object, const std::string boundary_mode, int nw) +{ + bool periodic = (boundary_mode == boundary_mode_periodic); + + int nx = img_object.width (); + int ny = img_object.height (); + + // compute the outside value if not periodic + double outside = 0.0; + if (boundary_mode == boundary_mode_one) { + + outside = 1.0; + + } 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++; + } + outside /= double (nx) * double (ny); + + } + + std::vector vavg_data; + vavg_data.resize (size_t (nx) * size_t (ny), 0.0); + + // vertical sum + + 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; + + std::vector::iterator d = vavg_data.begin () + y * nx; + if (periodic || (y + dy >= 0 && y + dy < ny)) { + const float *s = img_object.float_data () + safe_mod (y + dy, ny) * nx; + for (int ix = 0; ix < nx; ++ix) { + *d++ += f * *s++; + } + } else { + for (int ix = 0; ix < nx; ++ix) { + *d++ += f * outside; + } + } + + } + + } + + // horizontal sum + + outside *= nw; // because we do normalization later + + // TODO: transposing the image would make things more efficient + + std::vector havg_data; + havg_data.resize (size_t (nx) * size_t (ny), 0.0); + + 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; + + std::vector::iterator d = havg_data.begin () + x; + + if (periodic || (x + dx >= 0 && x + dx < nx)) { + std::vector::const_iterator s = vavg_data.begin () + safe_mod (x + dx, nx); + for (int iy = 0; iy < ny; ++iy) { + *d += f * *s; + d += nx; + s += nx; + } + } else { + for (int iy = 0; iy < ny; ++iy) { + *d += f * outside; + d += nx; + } + } + + } + + } + + // take the average + double s = 1.0 / (double (nw) * double (nw)); + for (auto i = havg_data.begin (); i != havg_data.end (); ++i) { + *i *= s; + } + + img_object.set_data (nx, ny, havg_data); +} + +} + diff --git a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h new file mode 100644 index 000000000..bd99bbbe8 --- /dev/null +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h @@ -0,0 +1,98 @@ + +/* + + 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_layDensityMapDialog +#define HDR_layDensityMapDialog + +#include "ui_DensityMapDialog.h" + +#include "layLayoutView.h" +#include "layBrowser.h" +#include "layMarker.h" + +namespace img +{ + class Object; +} + +namespace lay +{ + +class DensityMapDialog + : public lay::Browser, + private Ui::DensityMapDialog +{ +Q_OBJECT + +public: + DensityMapDialog (lay::Dispatcher *root, lay::LayoutViewBase *view); + ~DensityMapDialog (); + +public slots: + void layer_mode_changed (int); + void region_mode_changed (int); + void accept (); + void apply (); + +private: + struct DensityMapParameters + { + DensityMapParameters () + : pixel_size (100.0), threads (1) + { } + + // Collects all cv_index/layer index pairs used for input + std::vector > input_layers; + + // The region to compute the density map from + db::DBox region; + + // The pixel size + double pixel_size; + + // The window size or zero for "no window" + double window_size; + + // The boundary mode + std::string boundary_mode; + + // The number of threads to use + int threads; + }; + + // implementation of the lay::Plugin interface + virtual bool configure (const std::string &name, const std::string &value); + + // implementation of the lay::Plugin interface + void menu_activated (const std::string &symbol); + + 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); + +}; + +} + +#endif + diff --git a/src/plugins/tools/density_map/lay_plugin/lay_plugin.pro b/src/plugins/tools/density_map/lay_plugin/lay_plugin.pro new file mode 100644 index 000000000..a448da1fe --- /dev/null +++ b/src/plugins/tools/density_map/lay_plugin/lay_plugin.pro @@ -0,0 +1,18 @@ + +TARGET = density_map_ui +DESTDIR = $$OUT_PWD/../../../../lay_plugins + +include($$PWD/../../../lay_plugin.pri) + +INCLUDEPATH += $$IMG_INC $$ANT_INC +DEPENDPATH += $$IMG_INC $$ANT_INC +LIBS += -L$$DESTDIR/.. -lklayout_img -lklayout_ant + +HEADERS = \ + layDensityMapDialog.h \ + +SOURCES = \ + layDensityMapDialog.cc \ + +FORMS = \ + DensityMapDialog.ui \ diff --git a/src/rdb/rdb/rdb.cc b/src/rdb/rdb/rdb.cc index 475f310e4..367b25f5d 100644 --- a/src/rdb/rdb/rdb.cc +++ b/src/rdb/rdb/rdb.cc @@ -1920,13 +1920,15 @@ static void map_databases (rdb::Database &self, const rdb::Database &other, std::map &rev_tag2tag, bool create_missing) { + std::list > new_cells; + for (auto c = other.cells ().begin (); c != other.cells ().end (); ++c) { // TODO: do we have a consistent scheme of naming variants? What requirements // exist towards detecting variant specific waivers rdb::Cell *this_cell = self.cell_by_qname_non_const (c->qname ()); if (! this_cell && create_missing) { this_cell = self.create_cell (c->name (), c->variant (), c->layout_name ()); - this_cell->import_references (c->references ()); + new_cells.push_back (std::make_pair (this_cell, c.operator-> ())); } if (this_cell) { cell2cell.insert (std::make_pair (this_cell->id (), c->id ())); @@ -1934,6 +1936,19 @@ static void map_databases (rdb::Database &self, const rdb::Database &other, } } + // import and map references for new cells + for (auto cp = new_cells.begin (); cp != new_cells.end (); ++cp) { + auto &new_refs = cp->first->references (); + for (auto r = cp->second->references ().begin (); r != cp->second->references ().end (); ++r) { + rdb::Reference rnew = *r; + auto cid = rev_cell2cell.find (rnew.parent_cell_id ()); + if (cid != rev_cell2cell.end ()) { + rnew.set_parent_cell_id (cid->second); + new_refs.insert (rnew); + } + } + } + for (auto c = other.categories ().begin (); c != other.categories ().end (); ++c) { map_category (*c, self, cat2cat, rev_cat2cat, create_missing, 0); } diff --git a/src/rdb/unit_tests/rdbTests.cc b/src/rdb/unit_tests/rdbTests.cc index 4ccc25a08..814e73573 100644 --- a/src/rdb/unit_tests/rdbTests.cc +++ b/src/rdb/unit_tests/rdbTests.cc @@ -979,16 +979,18 @@ TEST(22_MergeCells) } { + rdb::Cell *cell1, *cell2, *cell3; + cell1 = db2.create_cell ("B"); + cell2 = db2.create_cell ("A"); + cell3 = db2.create_cell ("A", "VAR2", "ALAY"); + + // NOTE: db2 parent is at a different position (issue #2339) rdb::Cell *parent; parent = db2.create_cell ("TOP"); - rdb::Cell *cell; - cell = db2.create_cell ("B"); - cell->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, 0.0)), parent->id ())); - cell = db2.create_cell ("A"); - cell->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, 2.5)), parent->id ())); // reference not taken! - cell = db2.create_cell ("A", "VAR2", "ALAY"); - cell->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, -1.0)), parent->id ())); + cell1->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, 0.0)), parent->id ())); + cell2->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, 2.5)), parent->id ())); // reference not taken as cell will be taken from db1 + cell3->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, -1.0)), parent->id ())); } db1.merge (db2); @@ -996,13 +998,13 @@ TEST(22_MergeCells) std::set cells; for (auto c = db1.cells ().begin (); c != db1.cells ().end (); ++c) { if (c->references ().begin () != c->references ().end ()) { - cells.insert (c->qname () + "[" + c->references ().begin ()->trans_str () + "]"); + cells.insert (c->qname () + "[" + c->references ().begin ()->trans_str () + ":" + db1.cell_by_id (c->references ().begin ()->parent_cell_id ())->qname () + "]"); } else { cells.insert (c->qname ()); } } - EXPECT_EQ (tl::join (cells.begin (), cells.end (), ";"), "A:1[r0 *1 1,2];A:VAR1[r0 *1 1,-2];A:VAR2[r0 *1 1,-1];B[r0 *1 1,0];TOP"); + EXPECT_EQ (tl::join (cells.begin (), cells.end (), ";"), "A:1[r0 *1 1,2:TOP];A:VAR1[r0 *1 1,-2:TOP];A:VAR2[r0 *1 1,-1:TOP];B[r0 *1 1,0:TOP];TOP"); } TEST(23_MergeTags) diff --git a/testdata/drc/drcSimpleTests_au121.gds b/testdata/drc/drcSimpleTests_au121.gds index 5476d6713..e873870f5 100644 Binary files a/testdata/drc/drcSimpleTests_au121.gds and b/testdata/drc/drcSimpleTests_au121.gds differ diff --git a/testdata/lvs/split_substrate.cir b/testdata/lvs/split_substrate.cir new file mode 100644 index 000000000..c6971c69e --- /dev/null +++ b/testdata/lvs/split_substrate.cir @@ -0,0 +1,13 @@ +* Extracted by KLayout + +.SUBCKT TOP A Q IOSUB SUBSTRATE +X$1 \$7 \$1 Q IOSUB IOSUB INV +X$2 \$7 A \$1 SUBSTRATE SUBSTRATE INV +.ENDS TOP + +.SUBCKT INV \$2 \$4 \$5 \$1 \$I11 +M$1 \$2 \$4 \$5 \$2 PMOS L=0.25U W=0.95U AS=0.73625P AD=0.73625P PS=3.45U ++ PD=3.45U +M$2 \$1 \$4 \$5 \$I11 NMOS L=0.25U W=0.95U AS=0.73625P AD=0.73625P PS=3.45U ++ PD=3.45U +.ENDS INV diff --git a/testdata/lvs/split_substrate.gds b/testdata/lvs/split_substrate.gds new file mode 100644 index 000000000..e33ad4d0b Binary files /dev/null and b/testdata/lvs/split_substrate.gds differ diff --git a/testdata/lvs/split_substrate.l2n b/testdata/lvs/split_substrate.l2n new file mode 100644 index 000000000..95cb99951 --- /dev/null +++ b/testdata/lvs/split_substrate.l2n @@ -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) + ) +) diff --git a/testdata/lvs/split_substrate.lvs b/testdata/lvs/split_substrate.lvs new file mode 100644 index 000000000..43204067d --- /dev/null +++ b/testdata/lvs/split_substrate.lvs @@ -0,0 +1,107 @@ + +$lvs_test_source && source($lvs_test_source) + +if $lvs_test_target_l2n + report_netlist($lvs_test_target_l2n) +else + report_netlist +end + +writer = write_spice(true, false) +$lvs_test_target_cir && target_netlist($lvs_test_target_cir, writer, "Extracted by KLayout") + +deep + +# Drawing layers + +nwell = input(1, 0) +active = input(2, 0) +nplus = input(2, 1) +pplus = input(2, 2) +poly = input(3, 0) +poly_lbl = input(3, 1) +diff_cont = input(4, 0) +poly_cont = input(5, 0) +metal1 = input(6, 0) +metal1_lbl = input(6, 1) +via1 = input(7, 0) +metal2 = input(8, 0) +metal2_lbl = input(8, 1) +iosub = input(10, 0) + +# Bulk layer for terminal provisioning + +bulk = polygon_layer + +psd = nil +nsd = nil + +# Computed layers + +active_in_nwell = active & nwell +pactive = active_in_nwell & pplus +ntie = active_in_nwell & nplus +pgate = pactive & poly +psd = pactive - pgate + +active_outside_nwell = active - nwell +nactive = active_outside_nwell & nplus +ptie = active_outside_nwell & pplus +ngate = nactive & poly +nsd = nactive - ngate + +# Device extraction + +# PMOS transistor device extraction +extract_devices(mos4("PMOS"), { "SD" => psd, "G" => pgate, "W" => nwell, + "tS" => psd, "tD" => psd, "tG" => poly }) + +# NMOS transistor device extraction +extract_devices(mos4("NMOS"), { "SD" => nsd, "G" => ngate, "W" => bulk, + "tS" => nsd, "tD" => nsd, "tG" => poly }) + +# Define connectivity for netlist extraction + +# Inter-layer + +soft_connect(diff_cont, psd) +soft_connect(diff_cont, nsd) +soft_connect(diff_cont, ptie) +soft_connect(diff_cont, ntie) +soft_connect(ntie, nwell) +soft_connect(poly_cont, poly) + +connect(diff_cont, metal1) +connect(poly_cont, metal1) +connect(metal1, via1) +connect(via1, metal2) + +# attach labels +connect(poly, poly_lbl) +connect(metal1, metal1_lbl) +connect(metal2, metal2_lbl) + +# Split bulk and ptie into inside and outside iosub +(bulk_io, bulk_reg) = bulk.andnot(iosub) +(ptie_io, ptie_reg) = ptie.andnot(iosub) + +# connect outside to "SUBSTRATE" globally +connect(bulk, bulk_reg) +connect(ptie, ptie_reg) +connect_global(bulk_reg, "SUBSTRATE") +soft_connect_global(ptie_reg, "SUBSTRATE") + +# connect inside to "IOSUB" via polygons +connect(bulk, bulk_io) +connect(ptie, ptie_io) +connect(bulk_io, iosub) +soft_connect(ptie_io, iosub) +connect_global(iosub, "IOSUB") + +# Netlist section (NOTE: we only check log here) +# for debugging: _make_soft_connection_diodes(true) +netlist + +netlist.simplify + +