SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git
hc: let a launchFloating entry opt out of being tiled
An entry in launchFloating()'s pane_types may now be a
(pane_type, (width, height)) pair, which creates that panel at that size
and runs its toplevel through floatWindow() -- the same Qt.Dialog plus
transient parent that stops a tiling WM resizing HC Settings and the
spreadsheet to the main window's tile.
DEFAULT_FLOATING_PANE_TYPES keeps bare types, so detached mode still
hands the Parm and NetworkEditor panels to the WM as ordinary toplevels
for it to tile -- which is what detached mode is for. The pair form is
there to flip a pane type over when wanted.
_floatPanel swallows its own errors: this runs during the deferred
startup layout, where a tiled panel beats a traceback that stops the
rest of it. It also skips activateWindow() so startup does not steal
focus.
Co-Authored-By: Claude Opus 5 <[email protected]>
python3.13libs/hc/hclayout.py | 50 +++++++++++++++++++++++++++++++++++++++----
1 file changed, 46 insertions(+), 4 deletions(-)
diff --git a/python3.13libs/hc/hclayout.py b/python3.13libs/hc/hclayout.py
index 11def8d..88cc15f 100644
--- a/python3.13libs/hc/hclayout.py
+++ b/python3.13libs/hc/hclayout.py
@@ -1,5 +1,5 @@
import hou
-from .hcsession import HCSession
+from .hcsession import HCSession, floatWindow
DEFAULT_FLOATING_PANE_TYPES = (
@@ -99,7 +99,16 @@ class HCLayout:
"""Open one floating panel per pane type, skipping any already present.
Each floating panel is a separate OS window the WM can tile. The main
- Houdini window still exists alongside them.
+ Houdini window still exists alongside them -- that is the whole point
+ of detached mode, so these panels are deliberately left as ordinary
+ toplevels for the WM to lay out.
+
+ An entry may instead be a `(pane_type, (width, height))` pair, which
+ opts that one panel out of tiling: it is created at that size and its
+ toplevel is marked as a dialog (see hcsession.floatWindow), which is
+ what stops a tiling WM from resizing it to the main window's tile.
+ DEFAULT_FLOATING_PANE_TYPES uses bare types, so the default layout
+ still tiles.
NOTE: Tab initialization (_initializePanelTabs) is intentionally NOT
called here -- the PySide6/GL context isn't ready during startup and
@@ -111,14 +120,47 @@ class HCLayout:
pane_types = DEFAULT_FLOATING_PANE_TYPES
existing = set(self.floatingPaneTabTypes())
created = []
- for pane_type in pane_types:
+ for entry in pane_types:
+ pane_type, size = self._paneTypeAndSize(entry)
if pane_type in existing:
continue
- panel = self.desktop().createFloatingPanel(pane_type, immediate=True)
+ kwargs = {"immediate": True}
+ if size is not None:
+ kwargs["size"] = size
+ panel = self.desktop().createFloatingPanel(pane_type, **kwargs)
panel.attachToDesktop(True)
+ if size is not None:
+ self._floatPanel(panel, size)
created.append(panel)
return created
+ @staticmethod
+ def _paneTypeAndSize(entry):
+ """Split a launchFloating() entry into (pane_type, size or None)."""
+ if isinstance(entry, tuple):
+ pane_type, size = entry
+ return pane_type, tuple(size)
+ return entry, None
+
+ def _floatPanel(self, panel, size):
+ """Mark a freshly created panel's toplevel as a floating dialog.
+
+ Only ever called for an entry that asked for a size. Failures are
+ non-fatal: this runs during startup, and a panel that ends up tiled is
+ better than a traceback that stops the rest of the layout.
+ """
+ import sys
+ try:
+ tabs = panel.paneTabs()
+ window = tabs[0].qtParentWindow() if tabs else None
+ if window is None:
+ return
+ floatWindow(window, size=size)
+ window.show()
+ window.raise_()
+ except Exception as e:
+ print(f"[HCLayout] _floatPanel failed: {e!r}", file=sys.stderr, flush=True)
+
def initializeFloatingPanels(self):
"""Initialize (HC-wrap) all current floating panel tabs.