From ca61d8d4afe11527c6a36d5593a6526a676aa12e Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 18 Jul 2026 15:44:25 +0200 Subject: [PATCH 1/6] Fixing issue #2392 (PCell rebinding fails aber technology change) The bug was that the rebinding by name did not happen because the name was looked up in the wrong layout. A testcase has been added to test proper PCell rebinding in case of technology switches. --- src/db/db/dbLayout.cc | 11 +- src/rba/unit_tests/rbaTests.cc | 1 + testdata/ruby/dbPCellsRebind.rb | 202 ++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+), 6 deletions(-) create mode 100644 testdata/ruby/dbPCellsRebind.rb diff --git a/src/db/db/dbLayout.cc b/src/db/db/dbLayout.cc index a69e8b4bb..56d650b1e 100644 --- a/src/db/db/dbLayout.cc +++ b/src/db/db/dbLayout.cc @@ -707,14 +707,12 @@ Layout::set_technology_name (const std::string &tech) for (db::Layout::iterator c = begin (); c != end (); ++c) { - std::map::const_iterator m; - db::LibraryProxy *lib_proxy = dynamic_cast (&*c); if (! lib_proxy) { continue; } - if ((m = mapping.find (lib_proxy->lib_id ())) != mapping.end ()) { + if (mapping.find (lib_proxy->lib_id ()) != mapping.end ()) { db::Library *lib = db::LibraryManager::instance ().lib (lib_proxy->lib_id ()); db::Cell *lib_cell = &lib->layout ().cell (lib_proxy->library_cell_index ()); @@ -745,7 +743,10 @@ Layout::set_technology_name (const std::string &tech) db::cell_index_type ci = lp->first->Cell::cell_index (); db::PCellVariant *lib_pcell = lp->second; - std::pair pn = lib_pcell->layout ()->pcell_by_name (lp->first->get_basic_name ().c_str ()); + db::Library *new_lib = db::LibraryManager::instance ().lib (mapping [lp->first->lib_id ()]); + tl_assert (new_lib != 0); + + std::pair pn = new_lib->layout ().pcell_by_name (lp->first->get_basic_name ().c_str ()); if (! pn.first) { @@ -756,8 +757,6 @@ Layout::set_technology_name (const std::string &tech) } else { - db::Library *new_lib = db::LibraryManager::instance ().lib (mapping [lp->first->lib_id ()]); - const db::PCellDeclaration *old_pcell_decl = lib_pcell->layout ()->pcell_declaration (lib_pcell->pcell_id ()); const db::PCellDeclaration *new_pcell_decl = new_lib->layout ().pcell_declaration (pn.second); if (! old_pcell_decl || ! new_pcell_decl) { diff --git a/src/rba/unit_tests/rbaTests.cc b/src/rba/unit_tests/rbaTests.cc index 5a1cfa730..0fe2b1d0d 100644 --- a/src/rba/unit_tests/rbaTests.cc +++ b/src/rba/unit_tests/rbaTests.cc @@ -129,6 +129,7 @@ RUBYTEST (dbNetlistReaderTests, "dbNetlistReaderTests.rb") RUBYTEST (dbNetlistCompare, "dbNetlistCompare.rb") RUBYTEST (dbPathTest, "dbPathTest.rb") RUBYTEST (dbPCells, "dbPCells.rb") +RUBYTEST (dbPCellsRebind, "dbPCellsRebind.rb") RUBYTEST (dbPointTest, "dbPointTest.rb") RUBYTEST (dbPolygonTest, "dbPolygonTest.rb") RUBYTEST (dbRegionTest, "dbRegionTest.rb") diff --git a/testdata/ruby/dbPCellsRebind.rb b/testdata/ruby/dbPCellsRebind.rb new file mode 100644 index 000000000..88150ac78 --- /dev/null +++ b/testdata/ruby/dbPCellsRebind.rb @@ -0,0 +1,202 @@ +# encoding: UTF-8 + +# 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 + +if !$:.member?(File::dirname($0)) + $:.push(File::dirname($0)) +end + +load("test_prologue.rb") + +class PCellRebindVar < RBA::PCellDeclaration + + def initialize(name, layer, w_name, w_default) + @name = name + @layer = layer + @w_name = w_name + @w_default = w_default + end + + def display_text(parameters) + return "#{@name}(L=#{@layer},#{@w_name}=#{parameters[0].to_s},H=#{parameters[1].to_s})" + end + + def get_parameters + + # prepare a set of parameter declarations + param = [] + + param.push(RBA::PCellParameterDeclaration.new(@w_name, RBA::PCellParameterDeclaration::TypeDouble, "Width", @w_default)) + param.push(RBA::PCellParameterDeclaration.new("H", RBA::PCellParameterDeclaration::TypeDouble, "Height", 1.0)) + + return param + + end + + def get_layers(parameters) + return [ RBA::LayerInfo::new(@layer, 0) ] + end + + def produce(layout, layers, parameters, cell) + + # fetch the parameters + w = parameters[0] + h = parameters[1] + + # create the shape + cell.shapes(layout.layer(@layer, 0)).insert(RBA::DBox.new(-w / 2, -h / 2, w / 2, h / 2)) + + end + +end + +class RebindTestLib < RBA::Library + + def initialize(var) + + # set the description + self.description = "PCell rebind test lib" + + # create the PCell declarations + if var == 0 + layout.register_pcell("Box0", PCellRebindVar::new("Box0", 0, "W", 3.0)) + layout.register_pcell("Box1", PCellRebindVar::new("Box1", 1, "W", 2.0)) + layout.register_pcell("Box2A", PCellRebindVar::new("Box2A", 2, "W", 1.0)) + else + layout.register_pcell("Box2B", PCellRebindVar::new("Box2B", 12, "W", 1.5)) + layout.register_pcell("Box1", PCellRebindVar::new("Box1", 11, "WW", 2.5)) + layout.register_pcell("Box0", PCellRebindVar::new("Box0", 10, "W", 3.5)) + end + + self.technology = (var == 0 ? "T0" : "T1") + + register("RebindTestLib") + + end + +end + +def c2s(cell) + ly = cell.layout + s = [] + ly.layer_indexes.collect { |li| ly.get_info(li) }.sort { |a,b| [ a.layer, a.datatype ] <=> [ b.layer, b.datatype ] }.each do |lp| + bbox = cell.dbbox(ly.layer(lp)) + bbox.empty? || (s << lp.to_s + ":" + bbox.to_s) + end + s.join(";") +end + +class DBPCellRebind_TestClass < TestBase + + def test_1 + + tl0 = RebindTestLib::new(0) + tl1 = RebindTestLib::new(1) + + assert_equal(tl0.is_for_technology("T0"), true) + assert_equal(tl0.is_for_technology("T1"), false) + assert_equal(tl1.is_for_technology("T0"), false) + assert_equal(tl1.is_for_technology("T1"), true) + + ly = RBA::Layout::new + ly.technology_name = "T0" + + top = ly.create_cell("TOP") + + # in T0: + # Box0 -> on layer 0, w parameter is "W" with default 3.0 + # Box1 -> on layer 1, w parameter is "W" with default 2.0 + # Box2A -> on layer 2, w parameter is "W" with default 1.0 + # Box2B does not exist + + c0 = ly.create_cell("Box0", "RebindTestLib", { "W" => 0.3, "H" => 0.5 }) + c1 = ly.create_cell("Box1", "RebindTestLib", { "W" => 0.4, "H" => 0.6 }) + c2 = ly.create_cell("Box2A", "RebindTestLib", { "W" => 0.5, "H" => 0.7 }) + + top.insert(RBA::CellInstArray::new(c0.cell_index(), RBA::Trans::new)) + top.insert(RBA::CellInstArray::new(c1.cell_index(), RBA::Trans::new)) + top.insert(RBA::CellInstArray::new(c2.cell_index(), RBA::Trans::new)) + + assert_equal(c0.display_title, "RebindTestLib.Box0(L=0,W=0.3,H=0.5)") + assert_equal(c2s(c0), "0/0:(-0.15,-0.25;0.15,0.25)") + + assert_equal(c1.display_title, "RebindTestLib.Box1(L=1,W=0.4,H=0.6)") + assert_equal(c2s(c1), "1/0:(-0.2,-0.3;0.2,0.3)") + + assert_equal(c2.display_title, "RebindTestLib.Box2A(L=2,W=0.5,H=0.7)") + assert_equal(c2s(c2), "2/0:(-0.25,-0.35;0.25,0.35)") + + # in T0: + # Box0 -> on layer 0, w parameter is "W" with default 3.5 + # Box1 -> on layer 1, w parameter is "WW" with default 2.5 + # Box2A does not exist + # Box2B -> on layer 2, w parameter is "W" with default 1.5 + + ly.technology_name = "T1" + + assert_equal(c0.destroyed, false) + assert_equal(c1.destroyed, false) + assert_equal(c2.destroyed, true) # becomes a cold proxy + + c0 = ly.cell("Box0") + c1 = ly.cell("Box1") + c2 = ly.cell("Box2A") + + # layer changed, but parameters can be translated + assert_equal(c0.display_title, "RebindTestLib.Box0(L=10,W=0.3,H=0.5)") + assert_equal(c2s(c0), "10/0:(-0.15,-0.25;0.15,0.25)") + + # Layer changed and "W" parameter can't be translated as the name changed to "WW" + assert_equal(c1.display_title, "RebindTestLib.Box1(L=11,WW=2.5,H=0.6)") + assert_equal(c2s(c1), "11/0:(-1.25,-0.3;1.25,0.3)") + + # Box2A does no longer exist -> cold proxy + assert_equal(c2.display_title, "RebindTestLib.Box2A") + assert_equal(c2s(c2), "2/0:(-0.25,-0.35;0.25,0.35)") + + ly.technology_name = "T0" + + assert_equal(c0.destroyed, false) + assert_equal(c1.destroyed, false) + assert_equal(c2.destroyed, true) # became an active PCell variant again + + c0 = ly.cell("Box0") + c1 = ly.cell("Box1") + c2 = ly.cell("Box2A") + + # layer changed, but parameters can be translated + assert_equal(c0.display_title, "RebindTestLib.Box0(L=0,W=0.3,H=0.5)") + assert_equal(c2s(c0), "0/0:(-0.15,-0.25;0.15,0.25)") + + # Layer changed and "WW" parameter can't be translated as the name changed back to "W" + assert_equal(c1.display_title, "RebindTestLib.Box1(L=1,W=2.0,H=0.6)") + assert_equal(c2s(c1), "1/0:(-1,-0.3;1,0.3)") + + # Box2A is back to normal as the cold proxy kept the information + assert_equal(c2.display_title, "RebindTestLib.Box2A(L=2,W=0.5,H=0.7)") + assert_equal(c2s(c2), "2/0:(-0.25,-0.35;0.25,0.35)") + + tl0._destroy + tl1._destroy + + end + +end + +load("test_epilogue.rb") + From 68e7b271c395fb4e8c248be7986c83bcf55e61ad Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 18 Jul 2026 17:49:16 +0200 Subject: [PATCH 2/6] Implemented "cm_grid_increase" and "cm_grid_decrease" Default key bindings are "G" for finer grid and "Shift+G" for coarser grid. Two more enhancements: * The key binding merge in the config page was improved, so key bindings will appear in the config page before the configuration gets updated. * The current grid is shown in the status bar when the grid is changed. --- src/lay/lay/layMainConfigPages.cc | 23 ++++++- src/lay/lay/layMainWindow.cc | 76 ++++++++++++++++++++--- src/lay/lay/layMainWindow.h | 12 ++++ src/layui/layui/layLayoutViewFunctions.cc | 2 + src/layview/layview/layGridNet.cc | 12 ++++ 5 files changed, 112 insertions(+), 13 deletions(-) diff --git a/src/lay/lay/layMainConfigPages.cc b/src/lay/lay/layMainConfigPages.cc index ffe99975b..67f8734a8 100644 --- a/src/lay/lay/layMainConfigPages.cc +++ b/src/lay/lay/layMainConfigPages.cc @@ -27,6 +27,7 @@ #include "layStream.h" #include "layAbstractMenu.h" #include "layMainWindow.h" +#include "tlLog.h" #include "ui_MainConfigPage.h" #include "ui_MainConfigPage2.h" #include "ui_MainConfigPage3.h" @@ -412,15 +413,31 @@ CustomizeMenuConfigPage::apply (const std::vectormenu ()->get_shortcuts (false); + // retained bindings, even if not configured + std::vector::iterator> retained; + std::set shortcuts; + + // initialize configured bindings, clear others (for now) and remember std::map b; b.insert (key_bindings.begin (), key_bindings.end ()); - for (std::map::iterator kb = m_current_bindings.begin (); kb != m_current_bindings.end (); ++kb) { + for (auto kb = m_current_bindings.begin (); kb != m_current_bindings.end (); ++kb) { std::map::iterator bb = b.find (kb->first); if (bb != b.end ()) { lay::Action *a = mp_dispatcher->menu ()->action (kb->first); kb->second = a->get_effective_shortcut_for (bb->second); - } else { - kb->second.clear (); + if (! kb->second.empty ()) { + shortcuts.insert (kb->second); + } + } else if (! kb->second.empty ()) { + retained.push_back (kb); + } + } + + // retain key bindings which don't conflict + for (auto i = retained.begin (); i != retained.end (); ++i) { + if (shortcuts.find ((*i)->second) != shortcuts.end ()) { + tl::warn << tl::sprintf (tl::to_string (tr ("Resetting key binding for '%s' (was '%s') because of conflicts")), (*i)->first, (*i)->second); + (*i)->second.clear (); } } diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index 6a2582643..b112544e7 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -1970,6 +1970,18 @@ MainWindow::redraw () } } +void +MainWindow::cm_grid_decrease () +{ + change_grid (-1); +} + +void +MainWindow::cm_grid_increase () +{ + change_grid (1); +} + void MainWindow::cm_cancel () { @@ -4041,6 +4053,10 @@ MainWindow::menu_activated (const std::string &symbol) cm_bookmark_view (); } else if (symbol == "cm_cancel") { cm_cancel (); + } else if (symbol == "cm_grid_decrease") { + cm_grid_decrease (); + } else if (symbol == "cm_grid_increase") { + cm_grid_increase (); } else if (symbol == "cm_save_layer_props") { cm_save_layer_props (); } else if (symbol == "cm_load_layer_props") { @@ -4137,6 +4153,54 @@ MainWindow::menu_changed () dm_do_update_menu (); } +std::vector +MainWindow::default_grids () const +{ + lay::TechnologyController *tc = lay::TechnologyController::instance (); + if (tc && tc->active_technology ()) { + std::vector tech_grids = tc->active_technology ()->default_grid_list (); + if (! tech_grids.empty ()) { + return tech_grids; + } + } + + return m_default_grids; +} + +void +MainWindow::change_grid (int dir) +{ + std::vector grids = default_grids (); + if (grids.empty ()) { + return; + } + + std::sort (grids.begin (), grids.end ()); + + size_t i = 0; + for (std::vector::const_iterator g = grids.begin (); g != grids.end (); ++g, ++i) { + if (db::coord_traits::equals (*g, m_grid_micron)) { + break; + } + } + + if (i == grids.size ()) { + i = (dir > 0 ? grids.size () - 1 : 0); + } else { + if (dir > 0) { + if (i + 1 < grids.size ()) { + ++i; + } + } else if (dir < 0) { + if (i > 0) { + --i; + } + } + } + + dispatcher ()->config_set (cfg_grid, grids [i]); +} + void MainWindow::do_update_grids () { @@ -4176,15 +4240,7 @@ MainWindow::do_update_menu () m_default_grids_updated = false; - const std::vector *grids = &m_default_grids; - std::vector tech_grids; - lay::TechnologyController *tc = lay::TechnologyController::instance (); - if (tc && tc->active_technology ()) { - tech_grids = tc->active_technology ()->default_grid_list (); - if (! tech_grids.empty ()) { - grids = &tech_grids; - } - } + std::vector grids = default_grids (); std::vector group = menu ()->group ("default_grids_group"); @@ -4196,7 +4252,7 @@ MainWindow::do_update_menu () } int i = 1; - for (std::vector::const_iterator g = grids->begin (); g != grids->end (); ++g, ++i) { + for (std::vector::const_iterator g = grids.begin (); g != grids.end (); ++g, ++i) { std::string name = "default_grid_" + tl::to_string (i); diff --git a/src/lay/lay/layMainWindow.h b/src/lay/lay/layMainWindow.h index ade27bd24..c0d00b5f7 100644 --- a/src/lay/lay/layMainWindow.h +++ b/src/lay/lay/layMainWindow.h @@ -277,6 +277,14 @@ public: */ double grid_micron () const; + /** + * @brief Gets a list of the default grids + * + * The default grids are globally defined, but may change depending on the technology + * selected in the current view. + */ + std::vector default_grids () const; + /** * @brief Hierarchy level selection setter */ @@ -818,6 +826,8 @@ private: void cm_manage_bookmarks (); void cm_bookmark_view (); void cm_cancel (); + void cm_grid_decrease (); + void cm_grid_increase (); void cm_save_layer_props (); void cm_load_layer_props (); void cm_save_session (); @@ -863,6 +873,8 @@ private: void update_tab_title (int i); void add_view (LayoutViewWidget *view); + void change_grid (int dir); + bool can_close (); lay::CellViewRef create_or_load_layout (const std::string *filename, const db::LoadLayoutOptions *options, const std::string &tech, const int mode); int do_create_view (); diff --git a/src/layui/layui/layLayoutViewFunctions.cc b/src/layui/layui/layLayoutViewFunctions.cc index f965ed152..9511947ac 100644 --- a/src/layui/layui/layLayoutViewFunctions.cc +++ b/src/layui/layui/layLayoutViewFunctions.cc @@ -2154,6 +2154,8 @@ public: menu_entries.push_back (lay::menu_item ("cm_sel_move_interactive", "sel_move_interactive:edit", at, tl::to_string (tr ("Move Interactive")))); menu_entries.push_back (lay::menu_item ("cm_select_next_item", "select_next_item:edit", at, tl::to_string (tr ("Select Next Item(Space)")))); menu_entries.push_back (lay::menu_item ("cm_select_next_item_add", "select_next_item_add:edit", at, tl::to_string (tr ("Select Next Item too(Shift+Space)")))); + menu_entries.push_back (lay::menu_item ("cm_grid_decrease", "grid_decrease:edit", at, tl::to_string (tr ("Select Finer Grid(G)")))); + menu_entries.push_back (lay::menu_item ("cm_grid_increase", "grid_increase:edit", at, tl::to_string (tr ("Select Coarser Grid(Shift+G)")))); at = "edit_menu.edit_options_group"; menu_entries.push_back (lay::menu_item ("cm_undo", "undo:edit", at, tl::to_string (tr ("Undo(Ctrl+Z)")))); diff --git a/src/layview/layview/layGridNet.cc b/src/layview/layview/layGridNet.cc index 8a1e1c4b1..dabe4cb05 100644 --- a/src/layview/layview/layGridNet.cc +++ b/src/layview/layview/layGridNet.cc @@ -216,9 +216,21 @@ GridNet::configure (const std::string &name, const std::string &value) double g = 0; tl::from_string (value, g); if (fabs (g - m_grid) > 1e-6) { + m_grid = g; need_update = true; + + std::string gs; + if (m_grid < 0.4) { + // pick nm units below 400nm + gs = tl::to_string (m_grid * 1000.0) + tl::to_string (QObject::tr (" nm")); + } else { + gs = tl::to_string (m_grid) + tl::to_string (QObject::tr (" um")); + } + mp_view->message (tl::sprintf (tl::to_string (tr ("Grid: %s")), gs)); + } + taken = false; // to let others use the grid too. } else { From 1a4d94355641146033366e9bf84c034e9b38b5cb Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sat, 18 Jul 2026 18:34:13 +0200 Subject: [PATCH 3/6] Pushing the new functions to the grid menu and adding the 'default' indicator as proposed --- src/lay/lay/layMainWindow.cc | 73 +++++++++++++++++------ src/lay/lay/layMainWindow.h | 14 +++++ src/layui/layui/layLayoutViewFunctions.cc | 2 - 3 files changed, 70 insertions(+), 19 deletions(-) diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index b112544e7..7371b9a44 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -4167,6 +4167,20 @@ MainWindow::default_grids () const return m_default_grids; } +double +MainWindow::default_grid () const +{ + lay::TechnologyController *tc = lay::TechnologyController::instance (); + if (tc && tc->active_technology ()) { + auto tech_grids = tc->active_technology ()->default_grid_list (); + if (! tech_grids.empty ()) { + return tc->active_technology ()->default_grid (); + } + } + + return m_default_grid; +} + void MainWindow::change_grid (int dir) { @@ -4204,35 +4218,49 @@ MainWindow::change_grid (int dir) void MainWindow::do_update_grids () { - const std::vector *grids = &m_default_grids; - double default_grid = m_default_grid; + std::vector grids = default_grids (); + double def_grid = default_grid (); - std::vector tech_grids; - lay::TechnologyController *tc = lay::TechnologyController::instance (); - if (tc && tc->active_technology ()) { - tech_grids = tc->active_technology ()->default_grid_list (); - if (! tech_grids.empty ()) { - grids = &tech_grids; - default_grid = tc->active_technology ()->default_grid (); - } - } - - if (default_grid > db::epsilon) { - for (auto g = grids->begin (); g != grids->end (); ++g) { + if (def_grid > db::epsilon) { + for (auto g = grids.begin (); g != grids.end (); ++g) { if (db::coord_traits::equals (*g, m_grid_micron)) { - default_grid = 0.0; + def_grid = 0.0; break; } } } - if (default_grid > db::epsilon) { - dispatcher ()->config_set (cfg_grid, default_grid); + if (def_grid > db::epsilon) { + dispatcher ()->config_set (cfg_grid, def_grid); } do_update_menu (); } +namespace { + +class GenericMenuAction + : public Action +{ +public: + GenericMenuAction (lay::Dispatcher *dispatcher, const std::string &title, const std::string &symbol) + : Action (title), mp_dispatcher (dispatcher), m_symbol (symbol) + { } + + void triggered () + { + if (mp_dispatcher) { + mp_dispatcher->menu_activated (m_symbol); + } + } + +private: + Dispatcher *mp_dispatcher; + std::string m_symbol; +}; + +} + void MainWindow::do_update_menu () { @@ -4241,14 +4269,21 @@ MainWindow::do_update_menu () m_default_grids_updated = false; std::vector grids = default_grids (); + double def_grid = default_grid (); std::vector group = menu ()->group ("default_grids_group"); for (std::vector::const_iterator t = group.begin (); t != group.end (); ++t) { + std::vector items = menu ()->items (*t); for (std::vector::const_iterator i = items.begin (); i != items.end (); ++i) { menu ()->delete_item (*i); } + + menu ()->insert_item (*t + ".end", "finer_grid", new GenericMenuAction (dispatcher (), tl::to_string (tr ("Finer Grid(G)")), "cm_grid_decrease")); + menu ()->insert_item (*t + ".end", "coarser_grid", new GenericMenuAction (dispatcher (), tl::to_string (tr ("Coarser Grid(Shift+G)")), "cm_grid_increase")); + menu ()->insert_separator (*t + ".end", "default_grids_group_separator"); + } int i = 1; @@ -4264,6 +4299,10 @@ MainWindow::do_update_menu () gs = tl::to_string (*g) + tl::to_string (QObject::tr (" um")); } + if (def_grid > 0 && db::coord_traits::equals (*g, def_grid)) { + gs += tl::to_string (tr (" \\(default)")); + } + lay::Action *ga = new lay::ConfigureAction (gs, cfg_grid, tl::to_string (*g)); ga->set_checkable (true); ga->set_checked (db::coord_traits::equals (*g, m_grid_micron)); diff --git a/src/lay/lay/layMainWindow.h b/src/lay/lay/layMainWindow.h index c0d00b5f7..7e26be752 100644 --- a/src/lay/lay/layMainWindow.h +++ b/src/lay/lay/layMainWindow.h @@ -285,6 +285,20 @@ public: */ std::vector default_grids () const; + /** + * @brief Gets the default grid + * + * The default (fallback) grid is the one marked with "!" in the grid list + * and it is used, when the current grid is not one of the provided grids + * in the default grid list. + * + * The default grid is globally defined, but my change depending on the technology + * selected in the current view. + * + * If no such grid exists, 0 is returned. + */ + double default_grid () const; + /** * @brief Hierarchy level selection setter */ diff --git a/src/layui/layui/layLayoutViewFunctions.cc b/src/layui/layui/layLayoutViewFunctions.cc index 9511947ac..f965ed152 100644 --- a/src/layui/layui/layLayoutViewFunctions.cc +++ b/src/layui/layui/layLayoutViewFunctions.cc @@ -2154,8 +2154,6 @@ public: menu_entries.push_back (lay::menu_item ("cm_sel_move_interactive", "sel_move_interactive:edit", at, tl::to_string (tr ("Move Interactive")))); menu_entries.push_back (lay::menu_item ("cm_select_next_item", "select_next_item:edit", at, tl::to_string (tr ("Select Next Item(Space)")))); menu_entries.push_back (lay::menu_item ("cm_select_next_item_add", "select_next_item_add:edit", at, tl::to_string (tr ("Select Next Item too(Shift+Space)")))); - menu_entries.push_back (lay::menu_item ("cm_grid_decrease", "grid_decrease:edit", at, tl::to_string (tr ("Select Finer Grid(G)")))); - menu_entries.push_back (lay::menu_item ("cm_grid_increase", "grid_increase:edit", at, tl::to_string (tr ("Select Coarser Grid(Shift+G)")))); at = "edit_menu.edit_options_group"; menu_entries.push_back (lay::menu_item ("cm_undo", "undo:edit", at, tl::to_string (tr ("Undo(Ctrl+Z)")))); From 6253f04966bd41767718498f5f195ddc1089a3de Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 19 Jul 2026 10:45:51 +0200 Subject: [PATCH 4/6] Fixed issue #2396 (support weak refs in Python) --- src/pya/pya/pyaCallables.cc | 5 +++++ src/pya/pya/pyaModule.cc | 5 +++++ testdata/python/basic.py | 15 +++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/src/pya/pya/pyaCallables.cc b/src/pya/pya/pyaCallables.cc index 6cf02316b..8e8108ca9 100644 --- a/src/pya/pya/pyaCallables.cc +++ b/src/pya/pya/pyaCallables.cc @@ -43,6 +43,11 @@ namespace pya void pya_object_deallocate (PyObject *self) { + // Clear weak refs - needed for signal binding and other purposes +#if PY_VERSION_HEX > 0x03020000 + PyObject_ClearWeakRefs (self); +#endif + // This avoids an assertion in debug builds (Python, gcmodule.c - update_refs). // In short, the GC expects not to see objects with refcount 0 and asserts. // However, due to triggering of signals or similar, the destructor call below diff --git a/src/pya/pya/pyaModule.cc b/src/pya/pya/pyaModule.cc index b895f6a01..14a275c58 100644 --- a/src/pya/pya/pyaModule.cc +++ b/src/pya/pya/pyaModule.cc @@ -337,6 +337,11 @@ public: #if PY_VERSION_HEX >= 0x030D0000 // crashes with this option set type->tp_flags &= ~Py_TPFLAGS_INLINE_VALUES; +#endif +#if PY_VERSION_HEX < 0x03000000 + type->tp_flags |= Py_TPFLAGS_HAVE_WEAKREFS; + type->tp_weaklistoffset = type->tp_basicsize; + type->tp_basicsize += sizeof (PyObject *); #endif type->tp_basicsize += sizeof (PYAObjectBase); type->tp_init = &pya_object_init; diff --git a/testdata/python/basic.py b/testdata/python/basic.py index 4b13e65f2..61a0f6db7 100644 --- a/testdata/python/basic.py +++ b/testdata/python/basic.py @@ -22,6 +22,7 @@ import os import sys import gc import copy +import weakref # Set this to True to disable some tests involving exceptions leak_check = "TEST_LEAK_CHECK" in os.environ @@ -3352,6 +3353,20 @@ class BasicTest(unittest.TestCase): self.assertEqual(bc.str(), "xyz") self.assertEqual(bnc.str(), "xyz") + # weak refs + def test_94(self): + + b = pya.B() + b.set_str("abc") + + r = weakref.ref(b) + self.assertEqual(r() is None, False) + self.assertEqual(r().str(), "abc") + + b = None + self.assertEqual(r() is None, True) + + # run unit tests if __name__ == '__main__': suite = unittest.TestSuite() From 4e6834d92d7b038d79bce585db22418375a84682 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 19 Jul 2026 15:13:13 +0200 Subject: [PATCH 5/6] Trying to fix build errors for Python modules --- src/layview/layview/layGridNet.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/layview/layview/layGridNet.cc b/src/layview/layview/layGridNet.cc index dabe4cb05..7646f7113 100644 --- a/src/layview/layview/layGridNet.cc +++ b/src/layview/layview/layGridNet.cc @@ -29,6 +29,7 @@ #include "layPixelBufferPainter.h" #include "laySnap.h" #include "tlColor.h" +#include "tlInternational.h" #include "dbTrans.h" #if defined(HAVE_QT) From efe12377acd53bfd091c38a7e2f736fca9551a95 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 19 Jul 2026 16:01:04 +0200 Subject: [PATCH 6/6] Fixing non-Qt builds --- src/layview/layview/layGridNet.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/layview/layview/layGridNet.cc b/src/layview/layview/layGridNet.cc index 7646f7113..b5c479f1d 100644 --- a/src/layview/layview/layGridNet.cc +++ b/src/layview/layview/layGridNet.cc @@ -119,7 +119,7 @@ GridNetPluginDeclaration::get_options (std::vector < std::pairmessage (tl::sprintf (tl::to_string (tr ("Grid: %s")), gs));