Repository: KLayout/klayout (language: python)

Issue:
Version 0.29.9 crash
Hello @klayoutmatthias,

Thank you for making the maintenance release of 0.29.9.
Unfortunately, I'm facing a crash when opening a simple design. [gds.zip](https://github.com/user-attachments/files/17971564/gds.zip)

----

![KL0299-Win11](https://github.com/user-attachments/assets/237250fa-cc15-43e3-9187-282ab9cf4f6a)
![KL0299-Mint203](https://github.com/user-attachments/assets/0dcbd56d-158c-4a65-a44a-02cfcb154e2e)
* Ubuntu 20 binary package **klayout_0.29.9-1_amd64.deb (64bit)** also crashes in the same environment.
* **klayout_0.29.8-1_amd64.deb (64bit)** is OK.

----

**Valgrind on Linux Mint 20.3**
![KL0299-Mint203-Valgrind](https://github.com/user-attachments/assets/1f5d333a-c8cc-4dbe-9b20-26c460ebe9a6)
[KL0299-Mint203-Valgrind.zip](https://github.com/user-attachments/files/17971563/KL0299-Mint203-Valgrind.zip)

----

I also built the five DMGs as usual and found all crashed.
![KL0299-Sonoma](https://github.com/user-attachments/assets/7c7658d4-7ac3-4243-ac1b-5a3a5386bb1e)


Best regards,
Kazzz-S


PythonStandalone Package 0.29.9 stubs errors
Hi Matthias,

I think my request for the `Point.move(d)` confused your autogeneration  script for the stubs :face_with_peeking_eye: 

In the `dbcode.pyi` at line 704 there is a syntax error:


```python
    @overload
    def moved(self, dx, 0: int, dy: Optional[int] = ...) -> Box:
        r"""
        @brief Moves the box by a certain distance

        This is a convenience method which takes two values instead of a Point object.
        This method has been introduced in version 0.23.

        @return The moved box.
        """
        ...
```

The `dx, 0: int,`  should be `dx: int = 0` I think. Also, I don't think `dy: Optional[int] = ...` is valid, I would assume it should be  `moved(dx: int = 0, dy: Optional[int] = None)`

Hopefully this is just some minor tweak to fix it.

If you would want help with this, as always, I'd be happy to help.


Best,
Sebastian

(mypy found that error when I tried to type check kfactory as pre-commit :smile: )


Discussion / hints:
Hi Kazzz-S.You are right. I have no idea why the released version does that. I was working nicely for my builds.I will check what is wrong. I think I will create a hot fix for that problem.

Matthias
Looks like the problem is related to the navigator widget. If I close it, there is no crash.

Matthias
Yes!

![KL0299-Sonoma-NoNavigator](https://github.com/user-attachments/assets/0323431b-07be-44de-addd-6a96cdf1aaeb)

Kazzz-S



Fix patch:
diff --git a/Changelog b/Changelog
index 53c1953ad..210a64829 100644
--- a/Changelog
+++ b/Changelog
@@ -1,3 +1,13 @@
+0.29.10          (2024-12-03):
+* Bug: %GITHUB%/issues/1941 Crash with the navigator open
+* Bug: %GITHUB%/issues/1942 Syntax error in pyi stubs
+  As a bonus, added defaults for Box#enlarge and Box#enlarged (dx, dy)
+* Bugfix: Partial mode snapping now is object first, then grid
+* Bugfix: Key bindings have not been properly read from the configuration file
+  The change in the configuration string structure triggered an old bug:
+  Toolbar buttons had a twofold configuration and only the last one was
+  considered. Changing the order of the entries could spoil the configuration.
+
 0.29.9           (2024-12-01):
 * Bug: %GITHUB%/issues/1907 Locking layouts against modification during recursive iteration of instances and shapes
   This prevents crashes in write-white-iterating scenarios
diff --git a/Changelog.Debian b/Changelog.Debian
index 33fc15d0b..34f59cf70 100644
--- a/Changelog.Debian
+++ b/Changelog.Debian
@@ -1,3 +1,10 @@
+klayout (0.29.10-1) unstable; urgency=low
+
+  * New features and bugfixes
+    - See changelog
+
+ -- Matthias Köfferlein <matthias@koefferlein.de>  Mon, 02 Dec 2024 22:23:47 +0100
+
 klayout (0.29.9-1) unstable; urgency=low
 
   * New features and bugfixes
diff --git a/scripts/regenerate_stubs.sh b/scripts/regenerate_stubs.sh
index 644f594c8..65f441ab7 100755
--- a/scripts/regenerate_stubs.sh
+++ b/scripts/regenerate_stubs.sh
@@ -4,6 +4,7 @@
 
 # clean up
 rm -rf build dist
+rm -rf python3-venv-make_stubs
 
 python3 setup.py build
 python3 setup.py bdist_wheel
diff --git a/src/db/db/gsiDeclDbBox.cc b/src/db/db/gsiDeclDbBox.cc
index 6bf0601da..697cbb2a1 100644
--- a/src/db/db/gsiDeclDbBox.cc
+++ b/src/db/db/gsiDeclDbBox.cc
@@ -388,7 +388,7 @@ struct box_defs
       "\n"
       "@return A reference to this box.\n"
     ) +
-    method_ext ("moved", &box_defs<C>::moved, gsi::arg ("dx, 0"), gsi::arg ("dy", 0),
+    method_ext ("moved", &box_defs<C>::moved, gsi::arg ("dx", 0), gsi::arg ("dy", 0),
       "@brief Moves the box by a certain distance\n"
       "\n"
       "This is a convenience method which takes two values instead of a Point object.\n"
@@ -419,7 +419,7 @@ struct box_defs
       "\n"
       "@return The moved box.\n"
     ) +
-    method_ext ("enlarge", &box_defs<C>::enlarge, gsi::arg ("dx"), gsi::arg ("dy"),
+    method_ext ("enlarge", &box_defs<C>::enlarge, gsi::arg ("dx", 0), gsi::arg ("dy", 0),
       "@brief Enlarges the box by a certain amount.\n"
       "\n"
       "\n"
@@ -436,7 +436,7 @@ struct box_defs
       "\n"
       "@return A reference to this box.\n"
     ) +
-    method_ext ("enlarged", &box_defs<C>::enlarged, gsi::arg ("dx"), gsi::arg ("dy"),
+    method_ext ("enlarged", &box_defs<C>::enlarged, gsi::arg ("dx", 0), gsi::arg ("dy", 0),
       "@brief Enlarges the box by a certain amount.\n"
       "\n"
       "\n"
diff --git a/src/edt/edt/edtPartialService.cc b/src/edt/edt/edtPartialService.cc
index b57e5a3cd..4c35f62df 100644
--- a/src/edt/edt/edtPartialService.cc
+++ b/src/edt/edt/edtPartialService.cc
@@ -1808,11 +1808,38 @@ PartialService::mouse_move_event (const db::DPoint &p, unsigned int buttons, boo
       //  for a single selected point or edge, m_start is the original position and we snap the target -
       //  thus, we can bring the point on grid or to an object's edge or vertex
       snap_details = snap2 (p);
+
       if (snap_details.object_snap == lay::PointSnapToObjectResult::NoObject) {
+
         m_current = m_start + snap_move (p - m_start);
+
       } else {
-        m_current = m_start + snap_move (snap_details.snapped_point - m_start);
+
+        auto snapped_to_object = snap_details.snapped_point;
+        m_current = snapped_to_object;
+
+        if (snap_details.object_snap != lay::PointSnapToObjectResult::ObjectVertex) {
+          //  snap to grid on longer side of reference edge and to object on shorter
+          auto snapped_to_object_and_grid = m_start + snap_move (snapped_to_object - m_start);
+          if (std::abs (snap_details.object_ref.dx ()) > std::abs (snap_details.object_ref.dy ())) {
+            m_current.set_x (snapped_to_object_and_grid.x ());
+            //  project to edge, so we always hit it
+            auto cp = snap_details.object_ref.cut_point (db::DEdge (m_current, m_current + db::DVector (0, 1.0)));
+            if (cp.first) {
+              m_current.set_y (cp.second.y ());
+            }
+          } else if (std::abs (snap_details.object_ref.dy ()) > std::abs (snap_details.object_ref.dx ())) {
+            m_current.set_y (snapped_to_object_and_grid.y ());
+            //  project to edge, so we always hit it
+            auto cp = snap_details.object_ref.cut_point (db::DEdge (m_current, m_current + db::DVector (1.0, 0)));
+            if (cp.first) {
+              m_current.set_x (cp.second.x ());
+            }
+          }
+        }
+
         mouse_cursor_from_snap_details (snap_details);
+
       }
 
     } else {
diff --git a/src/lay/lay/layMacroController.cc b/src/lay/lay/layMacroController.cc
index 0b11f3215..1b4a5f52e 100644
--- a/src/lay/lay/layMacroController.cc
+++ b/src/lay/lay/layMacroController.cc
@@ -208,11 +208,6 @@ MacroController::uninitialize (lay::Dispatcher * /*root*/)
 bool
 MacroController::configure (const std::string &key, const std::string &value)
 {
-  if (key == cfg_key_bindings) {
-    m_key_bindings = unpack_key_binding (value);
-  } else if (key == cfg_menu_items_hidden) {
-    m_menu_items_hidden = unpack_menu_items_hidden (value);
-  }
   return false;
 }
 
@@ -810,21 +805,9 @@ MacroController::do_update_menu_with_macros ()
   add_macro_items_to_menu (m_temp_macros, used_names, groups, tech);
   add_macro_items_to_menu (lym::MacroCollection::root (), used_names, groups, tech);
 
-  //  apply the custom keyboard shortcuts
-  for (std::vector<std::pair<std::string, std::string> >::const_iterator kb = m_key_bindings.begin (); kb != m_key_bindings.end (); ++kb) {
-    if (mp_mw->menu ()->is_valid (kb->first)) {
-      lay::Action *a = mp_mw->menu ()->action (kb->first);
-      a->set_shortcut (kb->second);
-    }
-  }
-
-  //  apply the custom hidden flags
-  for (std::vector<std::pair<std::string, bool> >::const_iterator hf = m_menu_items_hidden.begin (); hf != m_menu_items_hidden.end (); ++hf) {
-    if (mp_mw->menu ()->is_valid (hf->first)) {
-      lay::Action *a = mp_mw->menu ()->action (hf->first);
-      a->set_hidden (hf->second);
-    }
-  }
+  //  apply the custom keyboard shortcuts and hidden flags
+  mp_mw->apply_key_bindings ();
+  mp_mw->apply_hidden ();
 }
 
 void
diff --git a/src/lay/lay/layMacroController.h b/src/lay/lay/layMacroController.h
index ad8517f2f..76ee94868 100644
--- a/src/lay/lay/layMacroController.h
+++ b/src/lay/lay/layMacroController.h
@@ -249,8 +249,6 @@ private slots:
   tl::DeferredMethod<MacroController> dm_do_sync_with_external_sources;
   tl::DeferredMethod<MacroController> dm_sync_file_watcher;
   tl::DeferredMethod<MacroController> dm_sync_files;
-  std::vector<std::pair<std::string, std::string> > m_key_bindings;
-  std::vector<std::pair<std::string, bool> > m_menu_items_hidden;
 
   void sync_implicit_macros (bool ask_before_autorun);
   void add_macro_items_to_menu (lym::MacroCollection &collection, std::set<std::string> &used_names, std::set<std::string> &groups, const db::Technology *tech);
diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc
index f7444453c..33814584a 100644
--- a/src/lay/lay/layMainWindow.cc
+++ b/src/lay/lay/layMainWindow.cc
@@ -1192,8 +1192,8 @@ MainWindow::configure (const std::string &name, const std::string &value)
 
   } else if (name == cfg_menu_items_hidden) {
 
-    std::vector<std::pair<std::string, bool> > hidden = unpack_menu_items_hidden (value);
-    apply_hidden (hidden);
+    m_hidden = unpack_menu_items_hidden (value);
+    apply_hidden ();
     return true;
 
   } else if (name == cfg_initial_technology) {
@@ -1213,12 +1213,15 @@ MainWindow::configure (const std::string &name, const std::string &value)
 }
 
 void
-MainWindow::apply_hidden (const std::vector<std::pair<std::string, bool> > &hidden)
+MainWindow::apply_hidden ()
 {
-  for (std::vector<std::pair<std::string, bool> >::const_iterator hf = hidden.begin (); hf != hidden.end (); ++hf) {
-    if (menu ()->is_valid (hf->first)) {
-      lay::Action *a = menu ()->action (hf->first);
-      a->set_hidden (hf->second);
+  for (std::vector<std::pair<std::string, bool> >::const_iterator hf = m_hidden.begin (); hf != m_hidden.end (); ++hf) {
+    lay::AbstractMenuItem *item = menu ()->find_item_exact (hf->first);
+    if (item && item->primary ()) {
+      lay::Action *a = item->action ();
+      if (a) {
+        a->set_hidden (hf->second);
+      }
     }
   }
 }
@@ -1227,9 +1230,12 @@ void
 MainWindow::apply_key_bindings ()
 {
   for (std::vector<std::pair<std::string, std::string> >::const_iterator kb = m_key_bindings.begin (); kb != m_key_bindings.end (); ++kb) {
-    if (menu ()->is_valid (kb->first)) {
-      lay::Action *a = menu ()->action (kb->first);
-      a->set_shortcut (kb->second);
+    lay::AbstractMenuItem *item = menu ()->find_item_exact (kb->first);
+    if (item && item->primary ()) {
+      lay::Action *a = item->action ();
+      if (a) {
+        a->set_shortcut (kb->second);
+      }
     }
   }
 }
diff --git a/src/lay/lay/layMainWindow.h b/src/lay/lay/layMainWindow.h
index 90e3f2693..f2f43d591 100644
--- a/src/lay/lay/layMainWindow.h
+++ b/src/lay/lay/layMainWindow.h
@@ -534,6 +534,16 @@ Q_OBJECT
    */
   static std::vector<std::string> menu_symbols ();
 
+  /**
+   *  @brief For internal use: apply current key bindings
+   */
+  void apply_key_bindings ();
+
+  /**
+   *  @brief For internal use: apply hidden menu flags
+   */
+  void apply_hidden ();
+
   /**
    *  @brief Open a new layout in mode 'mode'
    *
@@ -770,6 +780,7 @@ protected slots:
   double m_default_grid;
   bool m_default_grids_updated;
   std::vector<std::pair<std::string, std::string> > m_key_bindings;
+  std::vector<std::pair<std::string, bool> > m_hidden;
   bool m_new_layout_current_panel;
   bool m_synchronized_views;
   bool m_synchronous;
@@ -864,8 +875,6 @@ protected slots:
   void plugin_removed (lay::PluginDeclaration *cls);
 
   void libraries_changed ();
-  void apply_key_bindings ();
-  void apply_hidden (const std::vector<std::pair <std::string, bool> > &hidden);
 };
 
 }
diff --git a/src/laybasic/laybasic/layAbstractMenu.cc b/src/laybasic/laybasic/layAbstractMenu.cc
index 44de01ab0..a97e45e7c 100644
--- a/src/laybasic/laybasic/layAbstractMenu.cc
+++ b/src/laybasic/laybasic/layAbstractMenu.cc
@@ -220,20 +220,22 @@ parse_menu_title (const std::string &s, std::string &title, std::string &shortcu
 //  AbstractMenuItem implementation
 
 AbstractMenuItem::AbstractMenuItem (Dispatcher *dispatcher)
-  : mp_action (new Action ()), mp_dispatcher (dispatcher), m_has_submenu (false), m_remove_on_empty (false)
+  : mp_action (new Action ()), mp_dispatcher (dispatcher), m_has_submenu (false), m_remove_on_empty (false), m_primary (false)
 {
   //  ... nothing yet ..
 }
 
 AbstractMenuItem::AbstractMenuItem (const AbstractMenuItem &item)
-  : mp_action (new Action ()), mp_dispatcher (item.dispatcher ()), m_has_submenu (false), m_remove_on_empty (false)
+  : mp_action (new Action ()), mp_dispatcher (item.dispatcher ()), m_has_submenu (false), m_remove_on_empty (false), m_primary (false)
 {
   //  ... nothing yet ..
 }
 
 void
-AbstractMenuItem::setup_item (const std::string &pn, const std::string &s, Action *a)
+AbstractMenuItem::setup_item (const std::string &pn, const std::string &s, Action *a, bool primary)
 {
+  m_primary = primary;
+
   m_basename.clear ();
 
   tl::Extractor ex (s.c_str ());
@@ -1587,6 +1589,8 @@ AbstractMenu::items (const std::string &path) const
 void
 AbstractMenu::insert_item (const std::string &p, const std::string &name, Action *action)
 {
+  bool primary = true;
+
   tl::Extractor extr (p.c_str ());
   while (! extr.at_end ()) {
 
@@ -1601,7 +1605,8 @@ AbstractMenu::insert_item (const std::string &p, const std::string &name, Action
       parent->children.insert (iter, AbstractMenuItem (mp_dispatcher));
       --iter;
 
-      iter->setup_item (parent->name (), name, action);
+      iter->setup_item (parent->name (), name, action, primary);
+      primary = false;
 
       //  find any items with the same name and remove them
       for (std::list<AbstractMenuItem>::iterator existing = parent->children.begin (); existing != parent->children.end (); ) {
@@ -1635,7 +1640,7 @@ AbstractMenu::insert_separator (const std::string &p, const std::string &name)
     --iter;
     Action *action = new Action ();
     action->set_separator (true);
-    iter->setup_item (parent->name (), name, action);
+    iter->setup_item (parent->name (), name, action, true);
 
   }
 
@@ -1655,7 +1660,7 @@ AbstractMenu::insert_menu (const std::string &p, const std::string &name, Action
 
     parent->children.insert (iter, AbstractMenuItem (mp_dispatcher));
     --iter;
-    iter->setup_item (parent->name (), name, action);
+    iter->setup_item (parent->name (), name, action, true);
     iter->set_has_submenu ();
 
     //  find any items with the same name and remove them
@@ -1945,7 +1950,7 @@ AbstractMenu::find_item (tl::Extractor &extr)
           if (parent) {
             parent->children.insert (iter, AbstractMenuItem (mp_dispatcher));
             --iter;
-            iter->setup_item (parent->name (), n, new Action ());
+            iter->setup_item (parent->name (), n, new Action (), true);
             iter->set_has_submenu ();
             iter->set_remove_on_empty ();
             iter->set_action_title (ndesc.empty () ? n : ndesc);
@@ -2055,16 +2060,17 @@ AbstractMenu::get_shortcuts (const std::string &root, std::map<std::string, std:
   std::vector<std::string> items = this->items (root);
   for (std::vector<std::string>::const_iterator i = items.begin (); i != items.end (); ++i) {
     if (i->size () > 0) {
-      if (is_valid (*i) && action (*i)->is_visible ()) {
-        if (is_menu (*i)) {
+      const AbstractMenuItem *item = find_item_exact (*i);
+      if (item && item->action () && item->action ()->is_visible ()) {
+        if (item->has_submenu ()) {
           //  a menu must be listed (so it can be hidden), but does not have a shortcut
           //  but we don't include special menus
           if (i->at (0) != '@') {
             bindings.insert (std::make_pair (*i, std::string ()));
           }
           get_shortcuts (*i, bindings, with_defaults);
-        } else if (! is_separator (*i)) {
-          bindings.insert (std::make_pair (*i, with_defaults ? action (*i)->get_default_shortcut () : action (*i)->get_effective_shortcut ()));
+        } else if (! item->action ()->is_separator () && item->primary ()) {
+          bindings.insert (std::make_pair (*i, with_defaults ? item->action ()->get_default_shortcut () : item->action ()->get_effective_shortcut ()));
         }
       }
     }
diff --git a/src/laybasic/laybasic/layAbstractMenu.h b/src/laybasic/laybasic/layAbstractMenu.h
index aec72ac7a..d7908f5b7 100644
--- a/src/laybasic/laybasic/layAbstractMenu.h
+++ b/src/laybasic/laybasic/layAbstractMenu.h
@@ -559,7 +559,7 @@ struct LAYBASIC_PUBLIC AbstractMenuItem
   /**
    *  @brief Internal method used to set up the item
    */
-  void setup_item (const std::string &pn, const std::string &n, Action *a);
+  void setup_item (const std::string &pn, const std::string &n, Action *a, bool primary);
 
   Dispatcher *dispatcher () const
   {
@@ -616,6 +616,11 @@ struct LAYBASIC_PUBLIC AbstractMenuItem
     return m_remove_on_empty;
   }
 
+  bool primary () const
+  {
+    return m_primary;
+  }
+
   std::list <AbstractMenuItem> children;
 
 private:
@@ -623,6 +628,7 @@ struct LAYBASIC_PUBLIC AbstractMenuItem
   Dispatcher *mp_dispatcher;
   bool m_has_submenu;
   bool m_remove_on_empty;
+  bool m_primary;
   std::string m_name;
   std::string m_basename;
   std::set<std::string> m_groups;
@@ -906,6 +912,16 @@ Q_OBJECT
     return m_root;
   }
 
+  /**
+   *  @brief For internal use: gets the AbstractMenuItem for a given path or 0 if the path is not valid (const version)
+   */
+  const AbstractMenuItem *find_item_exact (const std::string &path) const;
+
+  /**
+   *  @brief For internal use: gets the AbstractMenuItem for a given path or 0 if the path is not valid
+   */
+  AbstractMenuItem *find_item_exact (const std::string &path);
+
 #if defined(HAVE_QT)
 signals:
   /**
@@ -918,8 +934,6 @@ Q_OBJECT
   friend class Action;
 
   std::vector<std::pair<AbstractMenuItem *, std::list<AbstractMenuItem>::iterator> > find_item (tl::Extractor &extr);
-  const AbstractMenuItem *find_item_exact (const std::string &path) const;
-  AbstractMenuItem *find_item_exact (const std::string &path);
   const AbstractMenuItem *find_item_for_action (const Action *action, const AbstractMenuItem *from = 0) const;
   AbstractMenuItem *find_item_for_action (const Action *action, AbstractMenuItem *from = 0);
 #if defined(HAVE_QT)
diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc
index 19e6ab7b0..9731df590 100644
--- a/src/laybasic/laybasic/layLayoutViewBase.cc
+++ b/src/laybasic/laybasic/layLayoutViewBase.cc
@@ -4084,7 +4084,9 @@ LayoutViewBase::cancel_edits ()
 
   //  the move service takes a special role here as it manages the
   //  transaction for the collective move operation.
-  mp_move_service->cancel ();
+  if (mp_move_service) {
+    mp_move_service->cancel ();
+  }
 
   //  cancel all drag and pending edit operations such as move operations.
   mp_canvas->drag_cancel ();
@@ -4099,7 +4101,9 @@ LayoutViewBase::finish_edits ()
 {
   //  the move service takes a special role here as it manages the
   //  transaction for the collective move operation.
-  mp_move_service->finish ();
+  if (mp_move_service) {
+    mp_move_service->finish ();
+  }
 
   //  cancel all drag operations
   mp_canvas->drag_cancel ();
@@ -5460,7 +5464,7 @@ LayoutViewBase::paste_interactive (bool transient_mode)
   //  operations.
   trans->close ();
 
-  if (mp_move_service->begin_move (trans.release (), transient_mode)) {
+  if (mp_move_service && mp_move_service->begin_move (trans.release (), transient_mode)) {
     switch_mode (-1);  //  move mode
   }
 }
diff --git a/src/pymod/distutils_src/klayout/dbcore.pyi b/src/pymod/distutils_src/klayout/dbcore.pyi
index 05e196ac5..b9c72d03c 100644
--- a/src/pymod/distutils_src/klayout/dbcore.pyi
+++ b/src/pymod/distutils_src/klayout/dbcore.pyi
@@ -553,7 +553,7 @@ class Box:
         """
         ...
     @overload
-    def enlarge(self, dx: int, dy: int) -> Box:
+    def enlarge(self, dx: Optional[int] = ..., dy: Optional[int] = ...) -> Box:
         r"""
         @brief Enlarges the box by a certain amount.
 
@@ -596,7 +596,7 @@ class Box:
         """
         ...
     @overload
-    def enlarged(self, dx: int, dy: int) -> Box:
+    def enlarged(self, dx: Optional[int] = ..., dy: Optional[int] = ...) -> Box:
         r"""
         @brief Enlarges the box by a certain amount.
 
@@ -701,7 +701,7 @@ class Box:
         """
         ...
     @overload
-    def moved(self, dx, 0: int, dy: Optional[int] = ...) -> Box:
+    def moved(self, dx: Optional[int] = ..., dy: Optional[int] = ...) -> Box:
         r"""
         @brief Moves the box by a certain distance
 
@@ -7407,7 +7407,7 @@ class DBox:
         """
         ...
     @overload
-    def enlarge(self, dx: float, dy: float) -> DBox:
+    def enlarge(self, dx: Optional[float] = ..., dy: Optional[float] = ...) -> DBox:
         r"""
         @brief Enlarges the box by a certain amount.
 
@@ -7450,7 +7450,7 @@ class DBox:
         """
         ...
     @overload
-    def enlarged(self, dx: float, dy: float) -> DBox:
+    def enlarged(self, dx: Optional[float] = ..., dy: Optional[float] = ...) -> DBox:
         r"""
         @brief Enlarges the box by a certain amount.
 
@@ -7555,7 +7555,7 @@ class DBox:
         """
         ...
     @overload
-    def moved(self, dx, 0: float, dy: Optional[float] = ...) -> DBox:
+    def moved(self, dx: Optional[float] = ..., dy: Optional[float] = ...) -> DBox:
         r"""
         @brief Moves the box by a certain distance
 
@@ -30277,10 +30277,9 @@ class Instance:
     @brief Gets the complex transformation of the instance or the first instance in the array
     This method is always valid compared to \trans, since simple transformations can be expressed as complex transformations as well.
     Setter:
-    @brief Sets the complex transformation of the instance or the first instance in the array (in micrometer units)
-    This method sets the transformation the same way as \cplx_trans=, but the displacement of this transformation is given in micrometer units. It is internally translated into database units.
+    @brief Sets the complex transformation of the instance or the first instance in the array
 
-    This method has been introduced in version 0.25.
+    This method has been introduced in version 0.23.
     """
     da: DVector
     r"""
@@ -41368,15 +41367,15 @@ class NetPinRef:
     @overload
     def net(self) -> Net:
         r"""
-        @brief Gets the net this pin reference is attached to (non-const version).
-
-        This constness variant has been introduced in version 0.26.8
+        @brief Gets the net this pin reference is attached to.
         """
         ...
     @overload
     def net(self) -> Net:
         r"""
-        @brief Gets the net this pin reference is attached to.
+        @brief Gets the net this pin reference is attached to (non-const version).
+
+        This constness variant has been introduced in version 0.26.8
         """
         ...
     def pin(self) -> Pin:
@@ -41666,17 +41665,17 @@ class NetTerminalRef:
     @overload
     def device(self) -> Device:
         r"""
-        @brief Gets the device reference.
+        @brief Gets the device reference (non-const version).
         Gets the device object that this connection is made to.
+
+        This constness variant has been introduced in version 0.26.8
         """
         ...
     @overload
     def device(self) -> Device:
         r"""
-        @brief Gets the device reference (non-const version).
+        @brief Gets the device reference.
         Gets the device object that this connection is made to.
-
-        This constness variant has been introduced in version 0.26.8
         """
         ...
     def device_class(self) -> DeviceClass:
@@ -42688,36 +42687,36 @@ class Netlist:
     @overload
     def circuit_by_name(self, name: str) -> Circuit:
         r"""
-        @brief Gets the circuit object for a given name (const version).
+        @brief Gets the circuit object for a given name.
         If the name is not a valid circuit name, nil is returned.
-
-        This constness variant has been introduced in version 0.26.8.
         """
         ...
     @overload
     def circuit_by_name(self, name: str) -> Circuit:
         r"""
-        @brief Gets the circuit object for a given name.
+        @brief Gets the circuit object for a given name (const version).
         If the name is not a valid circuit name, nil is returned.
+
+        This constness variant has been introduced in version 0.26.8.
         """
         ...
     @overload
     def circuits_by_name(self, name_pattern: str) -> List[Circuit]:
         r"""
-        @brief Gets the circuit objects for a given name filter.
+        @brief Gets the circuit objects for a given name filter (const version).
         The name filter is a glob pattern. This method will return all \Circuit objects matching the glob pattern.
 
-        This method has been introduced in version 0.26.4.
+
+        This constness variant has been introduced in version 0.26.8.
         """
         ...
     @overload
     def circuits_by_name(self, name_pattern: str) -> List[Circuit]:
         r"""
-        @brief Gets the circuit objects for a given name filter (const version).
+        @brief Gets the circuit objects for a given name filter.
         The name filter is a glob pattern. This method will return all \Circuit objects matching the glob pattern.
 
-
-        This constness variant has been introduced in version 0.26.8.
+        This method has been introduced in version 0.26.4.
         """
         ...
     def combine_devices(self) -> None:
@@ -56566,10 +56565,11 @@ class Shape:
 
     Starting with version 0.23, this method returns nil, if the shape does not represent a box.
     Setter:
-    @brief Replaces the shape by the given box (in micrometer units)
-    This method replaces the shape by the given box, like \box= with a \Box argument does. This version translates the box from micrometer units to database units internally.
+    @brief Replaces the shape by the given box
+    This method replaces the shape by the given box. This method can only be called for editable layouts. It does not change the user properties of the shape.
+    Calling this method will invalidate any iterators. It should not be called inside a loop iterating over shapes.
 
-    This method has been introduced in version 0.25.
+    This method has been introduced in version 0.22.
     """
     box_center: Point
     r"""
@@ -57238,10 +57238,10 @@ class Shape:
     Applies to texts only. Will throw an exception if the object is not a text.
 
     Setter:
-    @brief Sets the text transformation in micrometer units
+    @brief Sets the text transformation
     Applies to texts only. Will throw an exception if the object is not a text.
 
-    This method has been introduced in version 0.25.
+    This method has been introduced in version 0.23.
     """
     text_valign: int
     r"""
@@ -60854,16 +60854,16 @@ class SubCircuit(NetlistObject):
     @overload
     def circuit_ref(self) -> Circuit:
         r"""
-        @brief Gets the circuit referenced by the subcircuit (non-const version).
-
-
-        This constness variant has been introduced in version 0.26.8
+        @brief Gets the circuit referenced by the subcircuit.
         """
         ...
     @overload
     def circuit_ref(self) -> Circuit:
         r"""
-        @brief Gets the circuit referenced by the subcircuit.
+        @brief Gets the circuit referenced by the subcircuit (non-const version).
+
+
+        This constness variant has been introduced in version 0.26.8
         """
         ...
     @overload
@@ -61533,8 +61533,7 @@ class Text:
     Setter:
     @brief Sets the horizontal alignment
 
-    This property specifies how the text is aligned relative to the anchor point. 
-    This property has been introduced in version 0.22 and extended to enums in 0.28.
+    This is the version accepting integer values. It's provided for backward compatibility.
     """
     size: int
     r"""
@@ -61570,8 +61569,7 @@ class Text:
     Setter:
     @brief Sets the vertical alignment
 
-    This property specifies how the text is aligned relative to the anchor point. 
-    This property has been introduced in version 0.22 and extended to enums in 0.28.
+    This is the version accepting integer values. It's provided for backward compatibility.
     """
     x: int
     r"""
diff --git a/version.sh b/version.sh
index 18d074c60..51fecf2ff 100644
--- a/version.sh
+++ b/version.sh
@@ -2,10 +2,10 @@
 # This script is sourced to define the main version parameters
 
 # The main version
-KLAYOUT_VERSION="0.29.9"
+KLAYOUT_VERSION="0.29.10"
 
 # The version used for PyPI (don't use variables here!)
-KLAYOUT_PYPI_VERSION="0.29.9"
+KLAYOUT_PYPI_VERSION="0.29.10"
 
 # The build date
 KLAYOUT_VERSION_DATE=$(date "+%Y-%m-%d")


Test patch:
diff --git a/testdata/ruby/dbBoxTest.rb b/testdata/ruby/dbBoxTest.rb
index 1a207ef81..b86c5f2f2 100644
--- a/testdata/ruby/dbBoxTest.rb
+++ b/testdata/ruby/dbBoxTest.rb
@@ -271,6 +271,10 @@ def test_1_Box
     assert_equal( a.to_s, "(-9,18;12,22)" )
     a = b.moved( 1, -1 )
     assert_equal( a.to_s, "(-9,16;12,20)" )
+    a = b.moved( dy: 1 )
+    assert_equal( a.to_s, "(-10,18;11,22)" )
+    a = b.moved( dx: 1 )
+    assert_equal( a.to_s, "(-9,17;12,21)" )
 
     a = b.dup
     a.move( 1, -1 )
@@ -304,7 +308,12 @@ def test_1_Box
     aa = a.dup
     a.enlarge( -1, 1 )
     assert_equal( a.to_s, "(-10,21;11,25)" )
+    a.enlarge( 1, -1 )
+    a.enlarge( dy: 1 )
+    a.enlarge( dx: -1 )
+    assert_equal( a.to_s, "(-10,21;11,25)" )
     assert_equal( aa.enlarged( -1, 1 ).to_s, "(-10,21;11,25)" )
+    assert_equal( aa.enlarged( :dy => 1, :dx => -1 ).to_s, "(-10,21;11,25)" )
 
     a = a.enlarged( RBA::Point::new(1, -1) )
     assert_equal( a.to_s, "(-11,22;12,24)" )