SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git
session: Next Pane / Previous Pane warp the pointer between panes
Houdini has no keyboard focus for panes -- a pane-scoped hotkey goes to
whichever pane is under the pointer -- so cycling panes means moving the
pointer. The commands walk the visible panes in reading order (stowed
panes report a -1x-1 rectangle and are skipped; a maximized pane is the
only stop while it is up) and warp the cursor to the centre of the next
or previous one. Bound to alt+n / alt+shift+n, which takes alt+n away
from New File (ctrl+n still does it).
Co-Authored-By: Claude Fable 5.1 <[email protected]>
MainMenuCommon.xml | 10 +++++++++
hc_hotkeys.json | 2 ++
python3.13libs/hc/hcsession.py | 46 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 58 insertions(+)
diff --git a/MainMenuCommon.xml b/MainMenuCommon.xml
index a81f444..9ed2bf0 100755
--- a/MainMenuCommon.xml
+++ b/MainMenuCommon.xml
@@ -72,6 +72,16 @@
<scriptCode><![CDATA[from hc import HCSession; HCSession().toggleAllTabs()]]></scriptCode>
</scriptItem>
+ <scriptItem id="h.hc_session_nextpane">
+ <label>Next Pane</label>
+ <scriptCode><![CDATA[from hc import HCSession; HCSession().focusNextPane()]]></scriptCode>
+ </scriptItem>
+
+ <scriptItem id="h.hc_session_prevpane">
+ <label>Previous Pane</label>
+ <scriptCode><![CDATA[from hc import HCSession; HCSession().focusPrevPane()]]></scriptCode>
+ </scriptItem>
+
<scriptItem id="h.hc_session_restarthoudini">
<label>Restart Houdini</label>
<scriptCode><![CDATA[from hc import HCSession; HCSession().restartHoudini()]]></scriptCode>
diff --git a/hc_hotkeys.json b/hc_hotkeys.json
index cced0a6..37f8ce3 100644
--- a/hc_hotkeys.json
+++ b/hc_hotkeys.json
@@ -6,6 +6,8 @@
"h.hc_session_opensettings": "alt+,",
"h.hc_session_togglecurrenttabs": "alt+t",
"h.hc_session_toggletabs": "alt+shift+t",
+ "h.hc_session_nextpane": "alt+n",
+ "h.hc_session_prevpane": "alt+shift+n",
"h.hc_session_togglemainmenu": "alt+m",
"h.hc_session_togglestatusbar": "alt+b",
"h.pane.gview.state.obj.keycam.trans_up": "shift+k",
diff --git a/python3.13libs/hc/hcsession.py b/python3.13libs/hc/hcsession.py
index 5d74a8f..55b0029 100644
--- a/python3.13libs/hc/hcsession.py
+++ b/python3.13libs/hc/hcsession.py
@@ -1069,3 +1069,49 @@ class HCSession:
visible = self.isAnyPaneShowingTabs()
for pane in self.allPanes():
pane.showTabs(not visible)
+
+ def cyclablePanes(self):
+ """Every pane a warp could land in, in reading order.
+
+ Houdini has no keyboard focus for panes: a pane-scoped hotkey goes
+ to whichever pane is under the pointer, so "focusing" a pane means
+ putting the pointer there. A stowed or collapsed pane reports a
+ -1 by -1 rectangle and is left out; so is every other pane while
+ one is maximized, since their rectangles are stale then.
+ """
+ panes = []
+ for pane in self.allPanes():
+ rect = pane.qtScreenGeometry()
+ if rect.width() <= 0 or rect.height() <= 0:
+ continue
+ panes.append((rect.top(), rect.left(), pane))
+ maximized = [entry for entry in panes if entry[2].isMaximized()]
+ if maximized:
+ panes = maximized
+ panes.sort(key=lambda entry: entry[:2])
+ return [entry[2] for entry in panes]
+
+ def _cyclePane(self, step):
+ panes = self.cyclablePanes()
+ if not panes:
+ return None
+ current = self.currentPane()
+ ids = [pane.hou_pane.id() for pane in panes]
+ if current is not None and current.hou_pane.id() in ids:
+ index = (ids.index(current.hou_pane.id()) + step) % len(panes)
+ else:
+ index = 0 if step > 0 else len(panes) - 1
+ target = panes[index]
+ from PySide6.QtGui import QCursor
+ QCursor.setPos(target.qtScreenGeometry().center())
+ return target
+
+ @command("Next Pane")
+ def focusNextPane(self):
+ """Warp the pointer to the centre of the next pane in reading order."""
+ self._cyclePane(1)
+
+ @command("Previous Pane")
+ def focusPrevPane(self):
+ """Warp the pointer to the centre of the previous pane in reading order."""
+ self._cyclePane(-1)