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

commit3b78d01c0824c27ae8ee634e2d5e001d514744f6
parent35780a70dc
authorLucas Galante <[email protected]>
date2026-09-11 14:57
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.