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

python3.13libs/uiready.py (5.3K)

  1 import hou
  2 import hrpyc
  3 import sys
  4 import traceback
  5 
  6 from hc import HCSession, HCSettings
  7 
  8 # Start the agent bridge before anything else.
  9 #
 10 # It used to be the last statement in this file, behind every piece of startup
 11 # below, so one exception in hotkey loading or node recolouring or the status
 12 # bar aborted the module and took the bridge down with it -- silently, since
 13 # nothing here reports. That is exactly backwards: the bridge is how you
 14 # inspect a session whose startup went wrong, so it must not depend on that
 15 # startup having gone right.
 16 #
 17 # rpyc's ThreadedServer runs every request on a connection thread, and HOM is
 18 # not thread-safe -- a hou call off the main thread segfaults Houdini inside
 19 # SWIG. Install the main-thread pump before the server can accept anything;
 20 # see hc.hcmainthread and the runner in houdini-agent/bridge.py.
 21 from hc import hcmainthread
 22 
 23 hcmainthread.install()
 24 try:
 25     # use_thread=True by default, so this returns rather than blocking startup.
 26     hrpyc.start_server(port=18811)
 27 except OSError:
 28     pass  # already bound, e.g. a second Houdini on the same port
 29 
 30 
 31 def _step(label, fn, *args):
 32     """Run one startup step; report a failure instead of aborting the rest.
 33 
 34     These are independent -- a broken hotkey file should not cost you the
 35     status bar -- and a printed traceback beats a session that is half set up
 36     for no visible reason.
 37     """
 38     try:
 39         fn(*args)
 40         return True
 41     except Exception:
 42         print(f"[uiready] {label} failed:", file=sys.stderr)
 43         traceback.print_exc()
 44         return False
 45 
 46 
 47 hc_session = HCSession()
 48 
 49 # What this session started with, so the settings panel can say which
 50 # restart-only settings have since been saved with a different value. After
 51 # 123.py, whose startup dialog may have just written desktop_mode.
 52 _step("captureStartupSettings", HCSettings().captureStartupValues)
 53 _step("reloadHotkeys", hc_session.reloadHotkeys)
 54 _step("initializeNetworkEditorsDeferred", hc_session.initializeNetworkEditorsDeferred)
 55 _step("updateNodeColors", hc_session.updateNodeColors)
 56 
 57 
 58 def _startViewRegions():
 59     from hc.hcviewregions import HCViewRegions
 60     HCViewRegions().start()
 61 
 62 
 63 # Tells cce-fx which panes want a two-finger swipe as a view drag, so the
 64 # others scroll; see hc.hcviewregions. Inert without ccectl.
 65 _step("viewRegions", _startViewRegions)
 66 
 67 desktop_mode = HCSettings().desktopMode()
 68 
 69 
 70 def _applyMainMenuSetting():
 71     """Houdini's main menu bar is a persisted preference (showmenu.val), so
 72     without this the last session's choice would carry over regardless of
 73     the setting."""
 74     show = HCSettings().section("startup").get("show_main_menu", False)
 75     hc_session.showMainMenu(int(bool(show)))
 76 
 77 if desktop_mode == "detached":
 78     def _deferredInit():
 79         """Two-phase deferred init for detached mode.
 80 
 81         Phase 1 (tick 1): Desktop setup + initialize existing floating panels.
 82         Phase 2 (tick 5): Create any missing floating panels + initialize.
 83 
 84         ALL setup is deferred to avoid posting Qt events during
 85         uiready.py module-level execution, which triggers re-entrant
 86         event processing that SIGSEGVs in PySide::getWrapperForQObject
 87         (Houdini 21.0.700 bug).
 88 
 89         Floating panel initialization (showTabs, showNetworkControls, etc.)
 90         is safe at tick 1 because all initialize() methods are pure HOM
 91         calls -- no PySide6 event filter installation.
 92         """
 93         global _layout
 94         count = getattr(hou.session, '_hc_deferred_count', 0) + 1
 95         hou.session._hc_deferred_count = count
 96 
 97         if count == 1:
 98             # Desktop setup + initialize panels already loaded from .desk
 99             for d in hou.ui.desktops():
100                 if d.name() == "hc_detached":
101                     d.setAsCurrent()
102                     break
103             hc_session.toggleStowbars()
104             hc_session.hideShelf()
105             _applyMainMenuSetting()
106             from hc import HCStatusBar
107             HCStatusBar().show()
108             from hc.hclayout import HCLayout
109             _layout = HCLayout()
110             _layout.initializeFloatingPanels()
111 
112         if count == 5:
113             # Create any panels not already in .desk, initialize immediately
114             from hc.hclayout import launchFloatingLayout
115             launchFloatingLayout()
116             _layout.initializeFloatingPanels()
117             hou.session._hc_deferred_done = True
118             try:
119                 hou.ui.removeEventLoopCallback(_safeDeferredInit)
120             except Exception:
121                 pass
122 
123     def _safeDeferredInit():
124         if getattr(hou.session, '_hc_deferred_done', False):
125             return
126         _deferredInit()
127 
128     _layout = None
129     hou.session._hc_deferred_count = 0
130     hou.session._hc_deferred_done = False
131     hou.ui.addEventLoopCallback(_safeDeferredInit)
132 else:
133     # Attached mode runs at module level (has been stable)
134     def _setDesktop():
135         for d in hou.ui.desktops():
136             if d.name() == "hc_attached":
137                 d.setAsCurrent()
138                 return
139 
140     def _showStatusBar():
141         from hc import HCStatusBar
142         HCStatusBar().show()
143 
144     _step("set hc_attached desktop", _setDesktop)
145     _step("toggleStowbars", hc_session.toggleStowbars)
146     _step("mainMenu", _applyMainMenuSetting)
147     _step("splitHandles", lambda: hc_session.splitHandles().show())
148     _step("statusBar", _showStatusBar)