git.lucas.co / hou-control
SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git

commit151fe131ad629a82638bd47b4be4ddfbbbd76c07
parent4b9d124ddc
authorLucas Galante <[email protected]>
date2026-09-18 15:12
keycam: refit the aspect ratio on LayoutChanged

Houdini's own ctrl+1..4 layout hotkeys raise SizeChanged and
LayoutChanged while the viewports still have their old size; they are
resized one event-loop tick later with no further event. Keycam only
refitted on SizeChanged, so it fitted the stale size and was left one
layout behind. Defer the fit from LayoutChanged instead; keycam's own
layout actions ride on the same event and lose their private deferrals.

Co-Authored-By: Claude Opus 5 <[email protected]>

 viewer_states/keycam.py | 37 ++++++++++++++++---------------------
 1 file changed, 16 insertions(+), 21 deletions(-)

diff --git a/viewer_states/keycam.py b/viewer_states/keycam.py
index 441d853..38888d5 100644
--- a/viewer_states/keycam.py
+++ b/viewer_states/keycam.py
@@ -110,6 +110,20 @@ class State(object):
         if event_type == hou.sceneViewerEvent.SizeChanged:
             self.cam.fitAspectRatio()
             self.guides.update()
+        elif event_type == hou.sceneViewerEvent.LayoutChanged:
+            # A layout change (ctrl+1..4, or keycam's own layout actions)
+            # raises SizeChanged and LayoutChanged while the viewports still
+            # have their old size; they are resized one event-loop tick later
+            # with no further event, so fit from there.
+            state = self
+
+            def _deferredFit():
+                state.cam.fitAspectRatio()
+                state.guides.update()
+                hou.ui.removeEventLoopCallback(_deferredFit)
+                return False
+
+            hou.ui.addEventLoopCallback(_deferredFit)
 
     def onKeyEvent(self, kwargs):
         """Keys are now handled via PluginHotkeyDefinitions and onMenuAction."""
@@ -218,7 +232,7 @@ class State(object):
             'trans_up':       self.cam.translateUp,
             'trans_down':     self.cam.translateDown,
             'toggle_proj':    self.cam.toggleProjection,
-            'cycle_layout':   self._cycleLayoutWithAspectFit,
+            'cycle_layout':   self.scene_viewer.nextLayout,
             'set_layout_single':           lambda: self._setLayout('Single'),
             'set_layout_doubleside':       lambda: self._setLayout('DoubleSide'),
             'set_layout_doublestack':      lambda: self._setLayout('DoubleStack'),
@@ -260,28 +274,9 @@ class State(object):
         self.cam.setView()
         return True
 
-    def _cycleLayoutWithAspectFit(self):
-        self.scene_viewer.nextLayout()
-        state = self
-
-        def _deferredFit():
-            state.cam.fitAspectRatio()
-            hou.ui.removeEventLoopCallback(_deferredFit)
-            return False
-
-        hou.ui.addEventLoopCallback(_deferredFit)
-        return True
-
     def _setLayout(self, layout_name):
+        # The aspect refit rides on the LayoutChanged event (_onViewerEvent).
         self.scene_viewer.setLayout(layout_name)
-        state = self
-
-        def _deferredFit():
-            state.cam.fitAspectRatio()
-            hou.ui.removeEventLoopCallback(_deferredFit)
-            return False
-
-        hou.ui.addEventLoopCallback(_deferredFit)
         return True
 
     def onMenuPreOpen(self, kwargs):