From 6bbe6976dd53a17989653f8695aba4e2be9dfce1 Mon Sep 17 00:00:00 2001 From: Kazunari Sekigawa Date: Fri, 17 Apr 2026 07:58:00 +0900 Subject: [PATCH 01/20] Use Ruby 3.4.9 from MacPorts --- macbuild/build4mac_env.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 9ad273d83454851d5ab112905db721d50554dcb6 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 20 Apr 2026 22:19:56 +0200 Subject: [PATCH 02/20] Fixing a segfault on 'xkill' - solution was to register an exit handler that cleanly shuts down --- src/lay/lay/layApplication.cc | 10 ++++++++++ src/lay/lay/layApplication.h | 10 +++++++++- src/lay/lay/layMainWindow.cc | 1 - src/lay/lay/layMainWindow.h | 1 - src/laybasic/laybasic/layDispatcher.h | 3 ++- 5 files changed, 21 insertions(+), 4 deletions(-) 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/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; }; } From 93090948eeb4f775e88ae4685987f73d3fd54ca0 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 20 Apr 2026 22:55:27 +0200 Subject: [PATCH 03/20] Fixed issue #2331 - assertion and crash with fractional scaling and oversampling --- src/laybasic/laybasic/layLayoutCanvas.cc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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); From 6ffb59200ef8a96a62d8eb3b35b2d4e650ae99eb Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 26 Apr 2026 17:46:39 +0200 Subject: [PATCH 04/20] Fixing issue #2339 Solution is to do proper cell mapping, including the parent references from source to target RDB. --- src/rdb/rdb/rdb.cc | 17 ++++++++++++++++- src/rdb/unit_tests/rdbTests.cc | 20 +++++++++++--------- 2 files changed, 27 insertions(+), 10 deletions(-) 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) From 423df1e7e239c2bc61b9bd04bf340705702d919c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 26 Apr 2026 22:43:13 +0200 Subject: [PATCH 05/20] Fixing issue #2335 Now, the editor options are also shown in selection mode. Also, the logic was enhanced that decides whether to show the editor options or not. --- src/laybasic/laybasic/layEditorOptionsPage.h | 2 +- src/laybasic/laybasic/layLayoutViewBase.h | 13 ++++++++----- src/laybasic/laybasic/laySelector.cc | 8 ++++++++ src/layview/layview/layEditorOptionsPages.cc | 16 +++++++++++++--- 4 files changed, 30 insertions(+), 9 deletions(-) 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/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/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/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-> (); } From 5d60cfe27dae1189088e80a3d7cef2b99fde6e38 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 30 Apr 2026 21:35:48 +0200 Subject: [PATCH 06/20] Persisting settings of search features (see discussion 2868) --- src/laybasic/laybasic/layLayoutViewConfig.cc | 6 ++ src/laybasic/laybasic/laybasicConfig.h | 7 ++ src/layui/layui/layHierarchyControlPanel.cc | 36 +++++++- src/layui/layui/layHierarchyControlPanel.h | 48 +++++++++++ src/layui/layui/layLayerControlPanel.cc | 38 ++++++++- src/layui/layui/layLayerControlPanel.h | 47 +++++++++++ src/layview/layview/layLayoutView_qt.cc | 86 ++++++++++++++++++++ src/layview/layview/layLayoutView_qt.h | 4 + 8 files changed, 269 insertions(+), 3 deletions(-) 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/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/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; From 39c95a11de689a16a5490c21b56d52d329e809a8 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 30 Apr 2026 23:42:40 +0200 Subject: [PATCH 07/20] Fixed issue #2343 (loss of collinear points in copy_tree and other places) --- src/db/db/dbLayoutUtils.cc | 1 + src/db/db/dbPolygon.h | 2 +- src/db/unit_tests/dbLayoutTests.cc | 31 ++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) 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/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/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"); +} + From 5596019eceedba230d03d3903593fd96177131c2 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 1 May 2026 00:03:11 +0200 Subject: [PATCH 08/20] Fixing issue #2344 (preserving properties on PCell instantiation with instances) --- src/db/db/dbLibraryProxy.cc | 6 ++- src/db/unit_tests/dbLibrariesTests.cc | 62 +++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) 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/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); +} + From 17f5b4f2da01fd1ea4154c2c4e1da499454f7fef Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 1 May 2026 18:57:03 +0200 Subject: [PATCH 09/20] Snapping of rulers: now all points snap when the ruler is moved --- src/ant/ant/antService.cc | 22 ++++++++++++++++++---- src/laybasic/laybasic/layMove.cc | 3 +++ 2 files changed, 21 insertions(+), 4 deletions(-) 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/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; + } } From a34a83a52b431675413df58bb459e77400313144 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 00:40:38 +0000 Subject: [PATCH 10/20] Bump pypa/cibuildwheel from 3.4.0 to 3.4.1 Bumps [pypa/cibuildwheel](https://github.com/pypa/cibuildwheel) from 3.4.0 to 3.4.1. - [Release notes](https://github.com/pypa/cibuildwheel/releases) - [Changelog](https://github.com/pypa/cibuildwheel/blob/main/docs/changelog.md) - [Commits](https://github.com/pypa/cibuildwheel/compare/v3.4.0...v3.4.1) --- updated-dependencies: - dependency-name: pypa/cibuildwheel dependency-version: 3.4.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d83d24aa..26def03be 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 }} From 2860e39de1cb6b304061474cd66c36b3fd02787f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 00:40:40 +0000 Subject: [PATCH 11/20] Bump pypa/gh-action-pypi-publish from 1.13.0 to 1.14.0 Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.13.0 to 1.14.0. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.13.0...v1.14.0) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-version: 1.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d83d24aa..85f5e1a40 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 }} From 9630bff24086f9b1fcddcb8282a42a7062275c1c Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 5 May 2026 23:06:44 +0200 Subject: [PATCH 12/20] A contribution to issue #2345 mitigation With this patch, empty layers can be used to place device terminals on and these shapes are visible on those layers. This allows splitting the terminal shapes and used those shapes to connect down to different substrates. The patch turns EmptyLayer into a DeepLayer when used as terminal layer for device extraction. --- src/db/db/dbEdgePairs.cc | 7 + src/db/db/dbEdgePairs.h | 5 + src/db/db/dbEdges.cc | 7 + src/db/db/dbEdges.h | 5 + src/db/db/dbNetlistDeviceExtractor.cc | 6 +- src/db/db/dbRegion.cc | 7 + src/db/db/dbRegion.h | 5 + src/db/db/dbShapeCollection.h | 5 + src/db/db/dbTexts.cc | 7 + src/db/db/dbTexts.h | 5 + src/lvs/unit_tests/lvsSimpleTests.cc | 6 + testdata/lvs/split_substrate.cir | 13 ++ testdata/lvs/split_substrate.gds | Bin 0 -> 3182 bytes testdata/lvs/split_substrate.l2n | 215 ++++++++++++++++++++++++++ testdata/lvs/split_substrate.lvs | 107 +++++++++++++ 15 files changed, 398 insertions(+), 2 deletions(-) create mode 100644 testdata/lvs/split_substrate.cir create mode 100644 testdata/lvs/split_substrate.gds create mode 100644 testdata/lvs/split_substrate.l2n create mode 100644 testdata/lvs/split_substrate.lvs diff --git a/src/db/db/dbEdgePairs.cc b/src/db/db/dbEdgePairs.cc index cd43514b8..0b5cc36d3 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 (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..1d6b1a72e 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 (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/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/dbRegion.cc b/src/db/db/dbRegion.cc index f290b87d3..e4d1e1983 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 (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..7f59715ff 100644 --- a/src/db/db/dbShapeCollection.h +++ b/src/db/db/dbShapeCollection.h @@ -102,6 +102,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 * diff --git a/src/db/db/dbTexts.cc b/src/db/db/dbTexts.cc index 902a5cf2e..47420fd39 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 (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/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/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 0000000000000000000000000000000000000000..e33ad4d0b0cd8968b263a8db891a33c5f469d51a GIT binary patch literal 3182 zcmbW3J!lj`6vyB0-RuEodf%i?>##20Fu>G)1|MN34bI()EP zgFl;4eIsyx}X& z{1I=Nzvqrmln*fm`gMGk@s%3ShtB--w*MdbbHLX;(Y#S=yd{4Q_`2tsH%g7SMX=NnK;vV ze<%NU2hoo6vkQ;&KDVvZI5W+c{AZu@o_!_fKgZMZpW8XlnVk3RD>dFe|1sC2nBNCs z^d7fM1#1vfn{7`B<$=^7Cocw2x&k2*y5Bo}ux11lnoz2%(G>7#L@SDSP@RdT( zlp1Hs{0~1r^_#ckgy0OAvt-UdX%(LZSL7KuJx<$+o}?{)KkIxD_08fio)PkMYE|5~ zF*Od_>+AUWZtBsGY zNYJhrQ!^PrdQAxjK>u#?u&J4?HiugzPIDP zkomc} 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 + + From 838b2409b0f7b3811d4cdae137e9ba7523d79957 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 7 May 2026 00:09:16 +0200 Subject: [PATCH 13/20] Maintaining data_id during modification of layer inside device extractor -> this way, the name-to-layer link is maintained inside the DRC engine --- src/db/db/dbEdgePairs.cc | 2 +- src/db/db/dbEdges.cc | 2 +- src/db/db/dbRegion.cc | 2 +- src/db/db/dbShapeCollection.h | 31 +++++++++++++++++++++- src/db/db/dbTexts.cc | 2 +- src/db/db/gsiDeclDbEdgePairs.cc | 6 ++--- src/db/db/gsiDeclDbEdges.cc | 6 ++--- src/db/db/gsiDeclDbRegion.cc | 6 ++--- src/db/db/gsiDeclDbTexts.cc | 6 ++--- src/drc/drc/built-in-macros/_drc_netter.rb | 4 ++- 10 files changed, 49 insertions(+), 18 deletions(-) diff --git a/src/db/db/dbEdgePairs.cc b/src/db/db/dbEdgePairs.cc index 0b5cc36d3..de4b3ab24 100644 --- a/src/db/db/dbEdgePairs.cc +++ b/src/db/db/dbEdgePairs.cc @@ -107,7 +107,7 @@ void EdgePairs::convert_to_deep (const db::DeepLayer &layer) { tl_assert (mp_delegate->deep () == 0); - set_delegate (new db::DeepEdgePairs (layer)); + set_delegate (copy_data_id (new db::DeepEdgePairs (layer))); } void diff --git a/src/db/db/dbEdges.cc b/src/db/db/dbEdges.cc index 1d6b1a72e..f64d23747 100644 --- a/src/db/db/dbEdges.cc +++ b/src/db/db/dbEdges.cc @@ -118,7 +118,7 @@ void Edges::convert_to_deep (const db::DeepLayer &layer) { tl_assert (mp_delegate->deep () == 0); - set_delegate (new db::DeepEdges (layer)); + set_delegate (copy_data_id (new db::DeepEdges (layer))); } const db::RecursiveShapeIterator & diff --git a/src/db/db/dbRegion.cc b/src/db/db/dbRegion.cc index e4d1e1983..8e40874fd 100644 --- a/src/db/db/dbRegion.cc +++ b/src/db/db/dbRegion.cc @@ -136,7 +136,7 @@ void Region::convert_to_deep (const db::DeepLayer &layer) { tl_assert (mp_delegate->deep () == 0); - set_delegate (new db::DeepRegion (layer)); + set_delegate (copy_data_id (new db::DeepRegion (layer))); } void diff --git a/src/db/db/dbShapeCollection.h b/src/db/db/dbShapeCollection.h index 7f59715ff..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; + } }; /** @@ -116,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 47420fd39..1e15801ed 100644 --- a/src/db/db/dbTexts.cc +++ b/src/db/db/dbTexts.cc @@ -103,7 +103,7 @@ void Texts::convert_to_deep (const db::DeepLayer &layer) { tl_assert (mp_delegate->deep () == 0); - set_delegate (new db::DeepTexts (layer)); + set_delegate (copy_data_id (new db::DeepTexts (layer))); } void 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/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). From 964083ba12acbbee6267a0dc927b4958a8923f36 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Thu, 7 May 2026 00:21:39 +0200 Subject: [PATCH 14/20] Updating test data --- testdata/lvs/split_substrate.l2n | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/testdata/lvs/split_substrate.l2n b/testdata/lvs/split_substrate.l2n index 4e9853fcb..95cb99951 100644 --- a/testdata/lvs/split_substrate.l2n +++ b/testdata/lvs/split_substrate.l2n @@ -11,16 +11,16 @@ L(l16 '6/1') L(l13 '7/0') L(l14 '8/0') L(l17) -L(l23 '10/0') -L(l18) +L(l22 '10/0') +L(l7) L(l10) L(l2) L(l9) L(l6) -L(l22) -L(l19) L(l21) +L(l18) L(l20) +L(l19) C(l3 l3 l10) C(l4 l4 l15 l11) C(l15 l4 l15) @@ -33,22 +33,22 @@ C(l16 l12 l16) C(l13 l12 l13 l14) C(l14 l13 l14 l17) C(l17 l14 l17) -C(l23 l23 l22 l21) -C(l18 l18 l19 l21) +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 l22 l20) +C(l9 l8 l9 l21 l19) C(l6 l8 l6) -C(l22 l23 l9 l22) -CS(l22 l23) -C(l19 l18 l19) -C(l21 l23 l18 l21) -C(l20 l9 l20) -G(l23 IOSUB) +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) -G(l20 SUBSTRATE) -GS(l20 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)')) @@ -79,7 +79,7 @@ D(D$NMOS NMOS R(l6 (125 -475) (775 950)) ) T(B - R(l18 (-125 -475) (250 950)) + R(l7 (-125 -475) (250 950)) ) ) X(INV @@ -183,16 +183,16 @@ X(TOP R(l16 (-400 -200) (0 0)) ) N(4 I(IOSUB) - R(l23 (1334 621) (4230 5073)) - R(l22 (-964 -4594) (400 1000)) - R(l21 (-2125 -975) (250 950)) + 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(l19 (7575 1125) (250 950)) - R(l20 (1475 -975) (400 1000)) + R(l18 (7575 1125) (250 950)) + R(l19 (1475 -975) (400 1000)) ) P(2 I(A)) P(3 I(Q)) From 8f1e432146d97a8e866d21ea9d54a403304a5be0 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 9 May 2026 19:04:19 +0200 Subject: [PATCH 15/20] Updating test data --- testdata/drc/drcSimpleTests_au121.gds | Bin 2944 -> 3476 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/testdata/drc/drcSimpleTests_au121.gds b/testdata/drc/drcSimpleTests_au121.gds index 5476d67136c05263e051eeabf2c75316b9fc1179..e873870f5935d3b06619399db50d893c97710a8a 100644 GIT binary patch delta 658 zcmZn=pCVn)z{bGD6u}_F$i)7Nft7)iL70JwK@yqGz`?}k3AI!kO!ok46#>>RO#lXhL#K6FyBf$Ls|JNJ;|Np2r z05KRC*e?J%KVUSF4HgH{3@n0J4FIa;0LueukUj=2P%eP>Mz8M83=ERf;NIN)m#KwuWA-$r&5bPo*#Jq_wFdwI delta 120 zcmbOt-5{RGz{bGD6u}_F$i)5v$Yx^@VX$J*LS{2?FtPb~Ix#SaFtge@cA9%1TmHe) pWrzNP*-;@3GOT#?v9X2t2W Date: Sat, 9 May 2026 23:09:11 +0200 Subject: [PATCH 16/20] Fixing a glitch in the data mapping widget (when editing the value, the node got deselected, but the colors boxes were still enabled) --- src/img/img/imgPropertiesPage.cc | 77 +++++++++++++++++--------------- src/img/img/imgPropertiesPage.h | 1 + 2 files changed, 41 insertions(+), 37 deletions(-) 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 (); }; } From 974a300bdcb468bad589160fc9c770c9277b53d4 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 11 May 2026 22:19:12 +0200 Subject: [PATCH 17/20] First draft for density map feature. --- src/lay/lay/layClipDialog.cc | 6 +- src/layui/layui/layWidgets.cc | 6 + src/layui/layui/layWidgets.h | 8 + src/plugins/tools/density_map/density_map.pro | 6 + .../lay_plugin/DensityMapDialog.ui | 450 ++++++++++++++++++ .../lay_plugin/layDensityMapDialog.cc | 368 ++++++++++++++ .../lay_plugin/layDensityMapDialog.h | 63 +++ .../density_map/lay_plugin/lay_plugin.pro | 18 + 8 files changed, 923 insertions(+), 2 deletions(-) create mode 100644 src/plugins/tools/density_map/density_map.pro create mode 100644 src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui create mode 100644 src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc create mode 100644 src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h create mode 100644 src/plugins/tools/density_map/lay_plugin/lay_plugin.pro 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/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/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..27e8005e8 --- /dev/null +++ b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui @@ -0,0 +1,450 @@ + + + DensityMapDialog + + + + 0 + 0 + 641 + 531 + + + + Density Map + + + + 6 + + + 9 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + + + + Density map region + + + + 9 + + + 6 + + + + + false + + + QComboBox::AdjustToContents + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + Single box with ... + + + false + + + false + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Qt::Horizontal + + + + 231 + 20 + + + + + + + + false + + + Box Boundaries + + + + 9 + + + 6 + + + + + y = + + + + + + + 2nd corner + + + + + + + x = + + + + + + + 1st corner + + + + + + + x = + + + + + + + y = + + + + + + + + + + + + + + + + + + + + + + From rulers / box rulers + + + false + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + 0 + + + + + Visible region + + + false + + + + + + + + + + Bounding box of layer + + + false + + + + + + + Bounding box of layouts + + + true + + + + + + + + + + Density map parameters + + + + + + + 0 + 0 + + + + 100 + + + + + + + Pixel size + + + + + + + µm + + + + + + + Qt::Horizontal + + + + 353 + 20 + + + + + + + + + 0 + 0 + + + + 1 + + + + + + + Threads + + + + + + + + + + Density of + + + + + + Layer + + + + + + + false + + + QComboBox::AdjustToContents + + + + + + + Qt::Horizontal + + + + 385 + 20 + + + + + + + + Visible layers + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + lay::LayerSelectionComboBox + QComboBox +
layWidgets.h
+
+
+ + rb_layer_bbox + rb_box1 + rb_visible + rb_rulers + cb_box_layer + le_x1 + le_y1 + le_x2 + le_y2 + button_box + + + + + 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..909ecc788 --- /dev/null +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc @@ -0,0 +1,368 @@ + +/* + + 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 +{ + +// ------------------------------------------------------------ +// Declaration of the configuration options + +class DensityMapDialogPluginDeclaration + : public lay::PluginDeclaration +{ +public: + virtual void get_options (std::vector < std::pair > & /*options*/) const + { + // .. no options yet .. + } + + 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 (rb_box1, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ())); + connect (rb_rulers, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ())); + connect (rb_whole_layout, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ())); + connect (rb_layer_bbox, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ())); + connect (rb_visible, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ())); + + connect (rb_visible_layers, SIGNAL (clicked ()), this, SLOT (source_selection_clicked ())); + connect (rb_of_layer, SIGNAL (clicked ()), this, SLOT (source_selection_clicked ())); +} + +void +DensityMapDialog::box_selection_clicked () +{ + rb_box1->setChecked (sender () == rb_box1); + grp_box1->setEnabled (sender () == rb_box1); + rb_rulers->setChecked (sender () == rb_rulers); + rb_whole_layout->setChecked (sender () == rb_whole_layout); + rb_layer_bbox->setChecked (sender () == rb_layer_bbox); + cb_box_layer->setEnabled (sender () == rb_layer_bbox); + rb_visible->setChecked (sender () == rb_visible); +} + +void +DensityMapDialog::source_selection_clicked () +{ + rb_visible_layers->setChecked (sender () == rb_visible_layers); + cb_source_layer->setEnabled (sender () == rb_of_layer); + rb_of_layer->setChecked (sender () == rb_of_layer); +} + +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 .. +} + +// @@@@ +class TileRec + : public db::TileOutputReceiver +{ +public: + TileRec (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*/) + { + tl::info << "@@@ " << ix << "," << iy << " -> " << obj.to_string () << " -- " << tile.to_string (); + mp_img->set_pixel (ix, iy, obj.to_double ()); + } + +private: + img::Object *mp_img; +}; +// @@@ + +void +DensityMapDialog::accept () +{ +BEGIN_PROTECTED + + // Collects all cv_index/layer index pairs used for input + std::vector > input_layers; + + int threads = std::max (1, sb_threads->value ()); + + double pixel_size = 0.0; + tl::from_string_ext (tl::to_string (le_pixel_size->text ()), pixel_size); + + if (pixel_size < 1e-6) { + throw tl::Exception (tl::to_string (QObject::tr ("Pixel size must be positive and not zero"))); + } + + db::DBox region; + + if (rb_box1->isChecked ()) { + + 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); + + region = db::DBox (db::DPoint (x1, y1), db::DPoint (x2, y2)); + + } else if (rb_rulers->isChecked ()) { + + ant::Service *ant_service = view ()->get_plugin (); + if (ant_service) { + ant::AnnotationIterator ant = ant_service->begin_annotations (); + while (! ant.at_end ()) { + region += db::DBox (ant->p1 (), ant->p2 ()); + ++ant; + } + } + + } else if (rb_layer_bbox->isChecked ()) { + + lay::CellView ccv = view ()->cellview (cb_box_layer->cv_index ()); + int sel_layer = cb_box_layer->current_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"))); + } + + region = db::CplxTrans (ccv->layout ().dbu ()) * ccv->layout ().cell (ccv.cell_index ()).bbox (sel_layer); + + } else if (rb_visible->isChecked ()) { + + region = view ()->box (); + + } else { + + lay::CellView cv = view ()->cellview (view ()->active_cellview_index ()); + region = db::CplxTrans (cv->layout ().dbu ()) * cv->layout ().cell (cv.cell_index ()).bbox (); + + } + + if (rb_of_layer->isChecked ()) { + + int cvi = cb_box_layer->cv_index (); + lay::CellView ccv = view ()->cellview (cvi); + int sel_layer = cb_box_layer->current_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"))); + } + + input_layers.push_back (std::make_pair (cvi, sel_layer)); + + } else { + + 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)) { + input_layers.push_back (std::make_pair (cvi, li)); + } + + } + + } + + } + + if (region.empty ()) { + throw tl::Exception (tl::to_string (QObject::tr ("Density map region is empty"))); + } + + if (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 (region.width () / pixel_size - db::epsilon)); + double dny = std::max (1.0, ceil (region.height () / 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 = region.center ().x () - nx * 0.5 * pixel_size; + double y0 = region.center ().y () - ny * 0.5 * pixel_size; + + double dbu = view ()->cellview (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 (pixel_size, pixel_size); + + tp.set_threads (threads); + tp.set_dbu (dbu); + + int ninput = 1; + std::string in_expr; + + for (auto i = input_layers.begin (); i != 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) { + + // TODO: we could selectively clear all images that are used for density maps (-> needs a property that identifies them) + img_service->clear_images (); + + 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))); + + img::Object img (nx, ny, db::DCplxTrans (pixel_size, 0.0, false, region.center () - db::DPoint ()), false, false); + img.set_data_mapping (dm); + + img_object = img_service->insert_image (img); + + } + + tl_assert (img_object); + tp.output ("dens", 0, new TileRec (img_object), db::ICplxTrans ()); + + // Execute the tiling processor + + tp.execute (tl::to_string (tr ("Computing density map"))); + + // close this dialog + QDialog::accept (); + +END_PROTECTED +} + +bool +DensityMapDialog::configure (const std::string & /*name*/, const std::string & /*value*/) +{ + // .. nothing yet .. + return false; +} + +} + 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..360ba48f8 --- /dev/null +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h @@ -0,0 +1,63 @@ + +/* + + 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 lay +{ + +class DensityMapDialog + : public lay::Browser, + private Ui::DensityMapDialog +{ +Q_OBJECT + +public: + DensityMapDialog (lay::Dispatcher *root, lay::LayoutViewBase *view); + ~DensityMapDialog (); + +public slots: + void box_selection_clicked (); + void source_selection_clicked (); + void accept (); + +private: + // 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); + +}; + +} + +#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 \ From ae46712f4582b8527432a738a9dcfe0cb4544082 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 11 May 2026 23:48:55 +0200 Subject: [PATCH 18/20] WIP on density map feature --- src/img/img/imgObject.cc | 8 +++ src/img/img/imgObject.h | 17 ++++++ .../lay_plugin/layDensityMapDialog.cc | 55 ++++++++++++------- 3 files changed, 60 insertions(+), 20 deletions(-) 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/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc index 909ecc788..8d926a011 100644 --- a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc @@ -135,27 +135,33 @@ DensityMapDialog::~DensityMapDialog () // .. nothing yet .. } -// @@@@ -class TileRec - : public db::TileOutputReceiver +namespace { -public: - TileRec (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*/) + class DensityMapTileReceiver + : public db::TileOutputReceiver { - tl::info << "@@@ " << ix << "," << iy << " -> " << obj.to_string () << " -- " << tile.to_string (); - mp_img->set_pixel (ix, iy, obj.to_double ()); - } + public: + DensityMapTileReceiver (img::Object *img) + : mp_img (img) + { + // .. nothing yet .. + } -private: - img::Object *mp_img; -}; -// @@@ + 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::accept () @@ -326,8 +332,7 @@ BEGIN_PROTECTED img::Service *img_service = view ()->get_plugin (); if (img_service) { - // TODO: we could selectively clear all images that are used for density maps (-> needs a property that identifies them) - img_service->clear_images (); + const std::string img_tag = "density-map-image"; img::DataMapping dm; dm.false_color_nodes.clear (); @@ -337,15 +342,25 @@ BEGIN_PROTECTED 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 (pixel_size, 0.0, false, 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 TileRec (img_object), db::ICplxTrans ()); + tp.output ("dens", 0, new DensityMapTileReceiver (img_object), db::ICplxTrans ()); // Execute the tiling processor From f2b1e389bd932ff0af65f266aef5067166582e42 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 18 May 2026 22:25:43 +0200 Subject: [PATCH 19/20] Enhancements to density map feature --- .../lay_plugin/DensityMapDialog.ui | 572 ++++++++++-------- .../lay_plugin/layDensityMapDialog.cc | 341 +++++++++-- .../lay_plugin/layDensityMapDialog.h | 27 +- 3 files changed, 628 insertions(+), 312 deletions(-) diff --git a/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui index 27e8005e8..ae4712e76 100644 --- a/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui +++ b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui @@ -6,8 +6,8 @@ 0 0 - 641 - 531 + 657 + 425 @@ -38,218 +38,6 @@ - - - - Density map region - - - - 9 - - - 6 - - - - - false - - - QComboBox::AdjustToContents - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 6 - - - 0 - - - - - Single box with ... - - - false - - - false - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - Qt::Horizontal - - - - 231 - 20 - - - - - - - - false - - - Box Boundaries - - - - 9 - - - 6 - - - - - y = - - - - - - - 2nd corner - - - - - - - x = - - - - - - - 1st corner - - - - - - - x = - - - - - - - y = - - - - - - - - - - - - - - - - - - - - - - From rulers / box rulers - - - false - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 6 - - - 0 - - - - - Visible region - - - false - - - - - - - - - - Bounding box of layer - - - false - - - - - - - Bounding box of layouts - - - true - - - - - - @@ -299,7 +87,7 @@ - + 0 0 @@ -322,49 +110,347 @@ - Density of + Compute density of shapes on - - - - Layer + + + + 1 + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + QComboBox::AdjustToContents + + + + + + + Qt::Horizontal + + + + 385 + 20 + + + + + + + + + - - - - false - - - QComboBox::AdjustToContents - + + + + + Layer + + + + + Visible layers + + + + + Selected layers + + - - + + Qt::Horizontal + + QSizePolicy::Fixed + - 385 + 8 20 - - - - Visible layers + + + + + + + Compute density map of region + + + + 6 + + + + + QFrame::NoFrame - - true + + 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 + + + + @@ -387,7 +473,7 @@ Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok @@ -401,16 +487,16 @@ - rb_layer_bbox - rb_box1 - rb_visible - rb_rulers + 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 diff --git a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc index 8d926a011..151706176 100644 --- a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc @@ -36,6 +36,36 @@ 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 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_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 @@ -43,9 +73,12 @@ class DensityMapDialogPluginDeclaration : public lay::PluginDeclaration { public: - virtual void get_options (std::vector < std::pair > & /*options*/) const + virtual void get_options (std::vector < std::pair > &options) const { - // .. no options yet .. + 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_threads, "1")); } virtual lay::ConfigPage *config_page (QWidget * /*parent*/, std::string & /*title*/) const @@ -80,34 +113,45 @@ DensityMapDialog::DensityMapDialog (lay::Dispatcher *root, LayoutViewBase *vw) { Ui::DensityMapDialog::setupUi (this); - connect (rb_box1, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ())); - connect (rb_rulers, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ())); - connect (rb_whole_layout, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ())); - connect (rb_layer_bbox, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ())); - connect (rb_visible, SIGNAL (clicked ()), this, SLOT (box_selection_clicked ())); + connect (layer_cb, SIGNAL (activated (int)), SLOT (layer_mode_changed (int))); + connect (region_cb, SIGNAL (activated (int)), SLOT (region_mode_changed (int))); - connect (rb_visible_layers, SIGNAL (clicked ()), this, SLOT (source_selection_clicked ())); - connect (rb_of_layer, SIGNAL (clicked ()), this, SLOT (source_selection_clicked ())); + 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::box_selection_clicked () +DensityMapDialog::region_mode_changed (int mode) { - rb_box1->setChecked (sender () == rb_box1); - grp_box1->setEnabled (sender () == rb_box1); - rb_rulers->setChecked (sender () == rb_rulers); - rb_whole_layout->setChecked (sender () == rb_whole_layout); - rb_layer_bbox->setChecked (sender () == rb_layer_bbox); - cb_box_layer->setEnabled (sender () == rb_layer_bbox); - rb_visible->setChecked (sender () == rb_visible); + 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::source_selection_clicked () +DensityMapDialog::layer_mode_changed (int mode) { - rb_visible_layers->setChecked (sender () == rb_visible_layers); - cb_source_layer->setEnabled (sender () == rb_of_layer); - rb_of_layer->setChecked (sender () == rb_of_layer); + 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 @@ -163,26 +207,153 @@ namespace } -void +void +DensityMapDialog::apply () +{ +BEGIN_PROTECTED + + make_density_map (); + +END_PROTECTED +} + +void DensityMapDialog::accept () { BEGIN_PROTECTED - // Collects all cv_index/layer index pairs used for input - std::vector > input_layers; + make_density_map (); - int threads = std::max (1, sb_threads->value ()); + // close this dialog + QDialog::accept (); - double pixel_size = 0.0; - tl::from_string_ext (tl::to_string (le_pixel_size->text ()), pixel_size); +END_PROTECTED +} - if (pixel_size < 1e-6) { +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_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 ("Pixel size must be positive and not zero"))); } - db::DBox region; + int region_mode = region_cb->currentIndex (); + if (region_mode < 0 || region_mode >= int (region_modes.size ())) { + return; + } + const auto &rm = region_modes [region_mode]; - if (rb_box1->isChecked ()) { + 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 ()) { @@ -196,54 +367,58 @@ BEGIN_PROTECTED tl::from_string_ext (tl::to_string (le_y1->text ()), y1); tl::from_string_ext (tl::to_string (le_y2->text ()), y2); - region = db::DBox (db::DPoint (x1, y1), db::DPoint (x2, y2)); + par.region = db::DBox (db::DPoint (x1, y1), db::DPoint (x2, y2)); - } else if (rb_rulers->isChecked ()) { + } 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 ()) { - region += db::DBox (ant->p1 (), ant->p2 ()); + par.region += db::DBox (ant->p1 (), ant->p2 ()); ++ant; } } - } else if (rb_layer_bbox->isChecked ()) { + } 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"))); } - region = db::CplxTrans (ccv->layout ().dbu ()) * ccv->layout ().cell (ccv.cell_index ()).bbox (sel_layer); + par.region = db::CplxTrans (ccv->layout ().dbu ()) * ccv->layout ().cell (ccv.cell_index ()).bbox (sel_layer); - } else if (rb_visible->isChecked ()) { + } else if (rm == region_mode_visible_region) { - region = view ()->box (); + par.region = view ()->box (); } else { lay::CellView cv = view ()->cellview (view ()->active_cellview_index ()); - region = db::CplxTrans (cv->layout ().dbu ()) * cv->layout ().cell (cv.cell_index ()).bbox (); + par.region = db::CplxTrans (cv->layout ().dbu ()) * cv->layout ().cell (cv.cell_index ()).bbox (); } - if (rb_of_layer->isChecked ()) { + if (lm == layer_mode_specific) { - int cvi = cb_box_layer->cv_index (); + int cvi = cb_source_layer->cv_index (); lay::CellView ccv = view ()->cellview (cvi); - int sel_layer = cb_box_layer->current_layer (); + 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"))); } - input_layers.push_back (std::make_pair (cvi, sel_layer)); + par.input_layers.push_back (std::make_pair (cvi, sel_layer)); - } else { + } else if (lm == layer_mode_visible) { for (auto l = view ()->begin_layers (); !l.at_end (); ++l) { @@ -254,7 +429,27 @@ BEGIN_PROTECTED int li = l->layer_index (); if (ccv.is_valid () || li >= 0 || ! ccv->layout ().is_valid_layer (li)) { - input_layers.push_back (std::make_pair (cvi, 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)); } } @@ -263,11 +458,35 @@ BEGIN_PROTECTED } - if (region.empty ()) { + // 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_threads, tl::to_string (par.threads)); + dispatcher ()->config_set (cfg_density_map_pixel_size, tl::to_string (par.pixel_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 (input_layers.empty ()) { + if (par.input_layers.empty ()) { throw tl::Exception (tl::to_string (QObject::tr ("No input layers given"))); } @@ -276,8 +495,8 @@ BEGIN_PROTECTED const double max_wh = 100000.0; const int max_pixels = 100000000; - double dnx = std::max (1.0, ceil (region.width () / pixel_size - db::epsilon)); - double dny = std::max (1.0, ceil (region.height () / pixel_size - db::epsilon)); + 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); } @@ -288,10 +507,10 @@ BEGIN_PROTECTED 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 = region.center ().x () - nx * 0.5 * pixel_size; - double y0 = region.center ().y () - ny * 0.5 * pixel_size; + 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 (input_layers.front ().first)->layout ().dbu (); + double dbu = view ()->cellview (par.input_layers.front ().first)->layout ().dbu (); // Set up the tiling processor @@ -299,15 +518,15 @@ BEGIN_PROTECTED tp.tiles (nx, ny); tp.tile_origin (x0, y0); - tp.tile_size (pixel_size, pixel_size); + tp.tile_size (par.pixel_size, par.pixel_size); - tp.set_threads (threads); + tp.set_threads (par.threads); tp.set_dbu (dbu); int ninput = 1; std::string in_expr; - for (auto i = input_layers.begin (); i != input_layers.end (); ++i, ++ninput) { + 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 (); @@ -351,7 +570,7 @@ BEGIN_PROTECTED } } - img::Object img (nx, ny, db::DCplxTrans (pixel_size, 0.0, false, region.center () - db::DPoint ()), false, false); + 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); @@ -365,18 +584,6 @@ BEGIN_PROTECTED // Execute the tiling processor tp.execute (tl::to_string (tr ("Computing density map"))); - - // close this dialog - QDialog::accept (); - -END_PROTECTED -} - -bool -DensityMapDialog::configure (const std::string & /*name*/, const std::string & /*value*/) -{ - // .. nothing yet .. - return false; } } diff --git a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h index 360ba48f8..b46f19539 100644 --- a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h @@ -44,17 +44,40 @@ public: ~DensityMapDialog (); public slots: - void box_selection_clicked (); - void source_selection_clicked (); + 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 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); + }; } From f74e71f7288a2fd0b1110d413bd3caaaae98b374 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 19 May 2026 00:04:06 +0200 Subject: [PATCH 20/20] Implementing averaging window for density map --- .../lay_plugin/DensityMapDialog.ui | 90 +++++++-- .../lay_plugin/layDensityMapDialog.cc | 177 +++++++++++++++++- .../lay_plugin/layDensityMapDialog.h | 12 ++ 3 files changed, 265 insertions(+), 14 deletions(-) diff --git a/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui index ae4712e76..ded9b81ce 100644 --- a/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui +++ b/src/plugins/tools/density_map/lay_plugin/DensityMapDialog.ui @@ -7,7 +7,7 @@ 0 0 657 - 425 + 480 @@ -64,6 +64,16 @@ + + + + + 0 + 0 + + + + @@ -71,6 +81,33 @@ + + + + + 0 + 0 + + + + 1 + + + + + + + Averaging window size + + + + + + + Threads + + + @@ -84,24 +121,51 @@ - - + + + + µm (a multiple of the pixel size or empty) + + + + + + + Boundary mode + + + + + 0 0 - - 1 - - - - - - - Threads + + QComboBox::AdjustToContents + + + Periodic (repeat region seamlessly) + + + + + Use zero density outside + + + + + Use 100% density outside + + + + + Use average density outside + + @@ -473,7 +537,7 @@ Qt::Horizontal - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Ok diff --git a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc index 151706176..b6e280797 100644 --- a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.cc @@ -58,9 +58,19 @@ static const std::vector region_modes = { 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"); @@ -78,6 +88,8 @@ public: 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")); } @@ -269,6 +281,32 @@ DensityMapDialog::configure (const std::string &name, const std::string &value) } 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; @@ -335,9 +373,23 @@ DensityMapDialog::make_density_map () 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 ("Pixel size must be positive and not zero"))); + 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; @@ -461,8 +513,10 @@ DensityMapDialog::make_density_map () // 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 ()); @@ -584,6 +638,127 @@ DensityMapDialog::compute_density_map (const DensityMapParameters &par) // 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 index b46f19539..bd99bbbe8 100644 --- a/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h +++ b/src/plugins/tools/density_map/lay_plugin/layDensityMapDialog.h @@ -30,6 +30,11 @@ #include "layBrowser.h" #include "layMarker.h" +namespace img +{ + class Object; +} + namespace lay { @@ -65,6 +70,12 @@ private: // 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; }; @@ -77,6 +88,7 @@ private: 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); };