git clone https://git.lucas.co/hou-control.git
python3.11libs/hc/hcsession.py (16K)
1 import hou
2 from .hcbindings import HCBindings
3 from .hcnetworkeditor import HCNetworkEditor
4 from .hcsceneviewer import HCSceneViewer
5 from .hcpane import HCPane
6 from .hcpathtab import HCPathTab
7 from .hctab import HCTab
8 from .hcwidgets import HCWidgets
9
10
11 class HCSession:
12 def __init__(self):
13 return
14
15
16 """ Return HC Objects """
17
18 def allNetworkEditors(self):
19 network_editors = []
20 for tab in self.allTabs():
21 if tab.type() == 'HCNetworkEditor':
22 network_editors.append(tab)
23 return network_editors
24
25 def allPanes(self):
26 panes = []
27 seen = set()
28 for pane in self.desktop().panes():
29 if pane.id() not in seen:
30 panes.append(HCPane(pane))
31 seen.add(pane.id())
32 for fp in hou.ui.floatingPanels():
33 if fp.qtParentWindow() is None:
34 continue
35 for pane in fp.panes():
36 if pane.id() not in seen:
37 panes.append(HCPane(pane))
38 seen.add(pane.id())
39 return panes
40
41 def allSceneViewers(self):
42 scene_viewers = []
43 for tab in self.allTabs():
44 if tab.type() == 'HCSceneViewer':
45 scene_viewers.append(tab)
46 return scene_viewers
47
48 def allTabs(self):
49 tabs = []
50 for pane in self.allPanes():
51 for hou_tab in pane.hou_pane.tabs():
52 tabs.append(pane.convertTab(hou_tab))
53 return tabs
54
55 def allViewports(self):
56 viewports = []
57 scene_viewers = self.allSceneViewers
58 for scene_viewer in scene_viewers:
59 for viewport in scene_viewer.allViewports():
60 viewports.append(viewport)
61 return viewports
62
63 def currentPane(self):
64 hou_pane = hou.ui.paneUnderCursor()
65 if hou_pane is None:
66 hou_pane = self._floatingPaneUnderCursor()
67 return HCPane(hou_pane) if hou_pane is not None else None
68
69 def _floatingPaneUnderCursor(self):
70 from PySide6.QtGui import QCursor
71 pos = QCursor.pos()
72 for fp in hou.ui.floatingPanels():
73 qw = fp.qtParentWindow()
74 if qw is None or not qw.geometry().contains(pos):
75 continue
76 panes = fp.panes()
77 if panes:
78 return panes[0]
79 return None
80
81 def currentTab(self):
82 pane = self.currentPane()
83 return pane.currentTab() if pane is not None else None
84
85
86 """ Windows """
87
88 def colorEditor(self):
89 hou.ui.selectColor()
90
91 def floatingParameterEditor(self):
92 tab = self.currentTab()
93 if tab.type() == 'HCNetworkEditor':
94 hou.ui.showFloatingParameterEditor(self.currentNode())
95 else:
96 hou.ui.setStatusMessage('Not a network editor', hou.severityType.Error)
97
98 def hcPanel(self):
99 from .hcmaps import HCMaps
100
101 pane = self.currentPane()
102 tab = self.currentTab()
103 tab_type = tab.type()
104
105 maps = HCMaps()
106 list_dict = maps.tab_map_base(self, pane, tab)
107 if tab_type in ('HCPathTab', 'HCParameterTab'):
108 list_dict = list_dict | maps.tab_map_path(self, pane, tab)
109 elif tab_type == 'HCNetworkEditor':
110 list_dict = list_dict | maps.tab_map_network_editor(self, pane, tab)
111 elif tab_type == 'HCSceneViewer':
112 list_dict = list_dict | maps.tab_map_scene_viewer(self, pane, tab)
113
114
115 panel = HCWidgets.SelectionDialog('HC Panel', list_dict)
116 panel.show()
117 panel.raise_()
118 panel.activateWindow()
119
120 def openFile(self):
121 file = hou.ui.selectFile()
122 hou.hipFile.load(file)
123
124 def openPreferences(self):
125 hou.ui.openPreferences('ui', '')
126
127 def toggleStatus(self):
128 from .hcstatus import HCStatus
129 existing = hou.qt.mainWindow().findChild(HCStatus, HCStatus.OBJECT_NAME)
130 if existing is None:
131 existing = HCStatus()
132 if existing.isVisible():
133 existing.hide()
134 else:
135 existing.show()
136 existing.raise_()
137
138 def openSettings(self):
139 from .hcsettings import HCSettingsPanel
140 existing = hou.qt.mainWindow().findChild(HCSettingsPanel, HCSettingsPanel.OBJECT_NAME)
141 if existing is None:
142 existing = HCSettingsPanel()
143 existing.show()
144 existing.raise_()
145 existing.activateWindow()
146
147 def splitHandles(self):
148 from .hcsplithandles import HCSplitHandles
149 if not hasattr(hou.session, "_hc_split_handles"):
150 hou.session._hc_split_handles = HCSplitHandles()
151 return hou.session._hc_split_handles
152
153 def toggleSplitHandles(self):
154 self.splitHandles().toggle()
155
156 def refreshSplitHandles(self):
157 self.splitHandles().refresh()
158
159 def rotateSplit(self):
160 pane = hou.ui.paneUnderCursor()
161 if pane is None:
162 return
163 parent = pane.getSplitParent()
164 if parent is not None:
165 parent.splitRotate()
166
167 def windowWatcher(self):
168 from .hcwindowwatcher import HCWindowWatcher
169 if not hasattr(hou.session, "_hc_window_watcher"):
170 hou.session._hc_window_watcher = HCWindowWatcher()
171 return hou.session._hc_window_watcher
172
173 def toggleWindowWatcher(self):
174 self.windowWatcher().toggle()
175
176
177 """ Settings """
178
179 def isAutoSave(self):
180 return hou.getPreference('autoSave')
181
182 def reloadColorSchemes(self):
183 hou.ui.reloadColorScheme()
184 hou.ui.reloadViewportColorSchemes()
185
186 def reloadHC(self):
187 import sys
188 from PySide6.QtWidgets import QWidget
189 # Close any HC-owned Qt widgets that would dangle with stale class refs
190 main = hou.qt.mainWindow()
191 for name in ("hc_status_dialog", "hc_status_overlay"):
192 for w in main.findChildren(QWidget, name):
193 w.hide()
194 w.setParent(None)
195 w.deleteLater()
196 # Drop every hc.* module so the next import reloads from disk
197 removed = [m for m in list(sys.modules) if m == "hc" or m.startswith("hc.")]
198 for m in removed:
199 del sys.modules[m]
200 import hc
201 hou.ui.setStatusMessage(f"Reloaded hc ({len(removed)} modules)")
202
203 def reloadHotkeys(self):
204 HCBindings().load()
205
206 def updateNodeColors(self):
207 from .hcsettings import HCSettings
208 prefs = HCSettings().prefs()
209 hex_color = prefs.get('node_graph').get('node_color').lstrip('#')
210 r = int(hex_color[0:2], 16) / 255.0
211 g = int(hex_color[2:4], 16) / 255.0
212 b = int(hex_color[4:6], 16) / 255.0
213 new_color = hou.Color(r, g, b)
214
215 count = 0
216 for node in hou.node("/").allSubChildren():
217 # Check for the custom tag we just added to OnCreated.py
218 if node.userData("hc_custom_color") == "1":
219 node.setColor(new_color)
220 count += 1
221
222 hou.ui.setStatusMessage(f"Updated {count} custom-colored nodes to {hex_color}")
223
224 def keycam(self):
225 tab = self.currentTab()
226 if tab is not None and tab.type() == 'HCSceneViewer':
227 tab.keycam()
228 return
229 scene_viewers = self.allSceneViewers()
230 if scene_viewers:
231 scene_viewers[0].keycam()
232 return
233 hou.ui.setStatusMessage('No scene viewer available', hou.severityType.Error)
234
235 def reloadKeycam(self):
236 hou.ui.reloadViewerState('keycam')
237
238 def setUpdateModeAuto(self):
239 hou.setUpdateMode(hou.updateMode.AutoUpdate)
240
241 def setUpdateModeManual(self):
242 hou.setUpdateMode(hou.updateMode.Manual)
243
244 def triggerUpdate(self):
245 hou.ui.triggerUpdate()
246
247 def toggleAutoSave(self):
248 map = {'0': '1', '1': '0'}
249 hou.setPreference('autoSave', map[hou.getPreference('autoSave')])
250 from .hcstatusbar import HCStatusBar
251 HCStatusBar().updateAutosave()
252
253 def toggleUpdateMode(self):
254 mode_map = {
255 'updateMode.AutoUpdate': hou.updateMode.Manual,
256 'updateMode.Manual': hou.updateMode.AutoUpdate
257 }
258 hou.setUpdateMode(mode_map(str(hou.updateModeSetting)))
259
260 def updateMainMenuBar(self):
261 hou.ui.updateMainMenuBar()
262
263
264 """ Utils """
265
266 def projectPath(self):
267 return hou.hipFile.path()
268
269 def removeEventLoopCallbacks(self):
270 callbacks = hou.ui.eventLoopCallbacks()
271 for callback in callbacks:
272 hou.ui.removeEventLoopCallback(callback)
273
274 def restartHoudini(self):
275 import os
276 import subprocess
277 import sys
278
279 # Save current state
280 current_hip = hou.hipFile.path()
281 # Houdini usually names untitled files "untitled.hip" (or .hiplc / .hipnc)
282 if "untitled.hip" not in current_hip.lower():
283 try:
284 hou.hipFile.save()
285 except hou.Error:
286 pass # Continue with restart even if save fails
287
288 # Get the path to the houdini executable
289 # On Linux, this is usually in $HFS/bin/houdini
290 hfs = os.environ.get("HFS")
291 if hfs:
292 executable = os.path.join(hfs, "bin", "houdini")
293 else:
294 executable = "houdini" # Fallback to path
295
296 # Launch new process
297 # start_new_session=True detaches the new process from the current one
298 args = [executable, current_hip]
299 subprocess.Popen(args, close_fds=True, start_new_session=True)
300
301 # Force an immediate low-level exit to bypass any hangs
302 import os
303 os._exit(0)
304
305 def save(self):
306 hou.hipFile.save()
307
308
309 """ Layout """
310
311 # def clearLayout(self):
312 # tabs = self.allTabs()
313 # for tab in tabs:
314 # if tab != tabs[0]:
315 # tab.close()
316
317 def desktop(self):
318 return hou.ui.curDesktop()
319
320 # def layoutRoot(self):
321 # root = hou.ui.panes()[0].getSplitParent().getSplitParent()
322
323 # def printLayout(self):
324 # panes = hou.ui.panes()
325 # root = panes[0].getSplitParent()
326 # lefts = []
327 # tops = []
328 # for pane in self.panes():
329 # geo = pane.qtScreenGeometry()
330 # lefts.append(geo.left())
331 # tops.append(geo.top())
332 # print(lefts)
333 # print(tops)
334
335 # def renameTabs(self):
336 # tabs = hou.ui.paneTabs()
337 # i = 0
338 # for tab in tabs:
339 # tab.setName('placeholder' + str(i))
340 # i += 1
341 # i = 0
342 # for tab in tabs:
343 # tab.setName('panetab' + str(i))
344 # i += 1
345
346
347 """ Visibility """
348
349 def isVisibleMainMenu(self):
350 value = hou.getPreference('showmenu.val')
351 return int(value)
352
353 def isVisibleMenus(self):
354 visible = 0
355 panes = self.allPanes()
356 tabs = self.allTabs()
357 # Main menu
358 if self.isVisibleMainMenu():
359 visible = 1
360 # Tabs
361 for tab in tabs:
362 if tab.type() == 'HCNetworkEditor':
363 if tab.isMenuOpen():
364 visible = 1
365 elif tab.isShowingNetworkControls():
366 visible = 1
367 elif tab.type() in ('HCPathTab', 'HCParameterTab'):
368 if tab.isShowingNetworkControls():
369 visible = 1
370 elif tab.type() == 'HCSceneViewer':
371 if tab.isShowingNetworkControls():
372 visible = 1
373 elif tab.isVisibleOperationBar():
374 visible = 1
375 elif tab.isVisibleDisplayBar():
376 visible = 1
377 elif tab.isVisibleSelectionBar():
378 visible = 1
379 # Panes
380 for pane in panes:
381 if pane.isShowingTabs():
382 visible = 1
383 return visible
384
385 def hideShelf(self):
386 self.desktop().shelfDock().show(0)
387
388 def showMainMenu(self, value):
389 hou.setPreference('showmenu.val', str(value))
390
391 def showShelf(self):
392 self.shelfDock().show(1)
393
394 def toggleMainMenu(self):
395 value = (self.isVisibleMainMenu()+1) % 2
396 self.showMainMenu(value)
397
398 def toggleMenus(self):
399 visible = self.isVisibleMenus()
400 panes = self.allPanes()
401 tabs = self.allTabs()
402 # Set state
403 self.showMainMenu((visible+1) % 2)
404 for tab in tabs:
405 if tab.type() == 'HCNetworkEditor':
406 tab.showNetworkControls(not visible)
407 tab.setMenuOpen(0)
408 elif tab.type() in ('HCPathTab', 'HCParameterTab'):
409 tab.showNetworkControls(not visible)
410 elif tab.type() == 'HCSceneViewer':
411 tab.showNetworkControls(not visible)
412 tab.showOperationBar(not visible)
413 tab.showDisplayBar(not visible)
414 tab.showSelectionBar(not visible)
415 for pane in panes:
416 pane.showTabs(not visible)
417 # Apply (needs to be called twice for some reason)
418 # hou.ui.setHideAllMinimizedStowbars(visible)
419 # hou.ui.setHideAllMinimizedStowbars(visible)
420
421 def toggleAllNetworkControls(self):
422 visible = 0
423 tabs = self.allTabs()
424 for tab in tabs:
425 if tab.isShowingNetworkControls():
426 visible = 1
427 for tab in tabs:
428 tab.showNetworkControls(not visible)
429
430 def toggleStowbars(self):
431 if hou.ui.hideAllMinimizedStowbars():
432 hou.ui.setHideAllMinimizedStowbars(False)
433 else:
434 hou.ui.setHideAllMinimizedStowbars(True)
435 hou.ui.setHideAllMinimizedStowbars(True)
436
437 def toggleCurrentPaneTabs(self):
438 pane = self.currentPane()
439 if pane is not None:
440 pane.toggleTabs()
441
442 def newTabDialog(self):
443 pane = self.currentPane()
444 if pane is None:
445 hou.ui.setStatusMessage("No pane under cursor", hou.severityType.Warning)
446 return
447 type_map = {
448 "Apex Editor": hou.paneTabType.ApexEditor,
449 "Asset Browser": hou.paneTabType.AssetBrowser,
450 "Bundle List": hou.paneTabType.BundleList,
451 "Channel Editor": hou.paneTabType.ChannelEditor,
452 "Channel List": hou.paneTabType.ChannelList,
453 "Channel Viewer": hou.paneTabType.ChannelViewer,
454 "Compositor Viewer": hou.paneTabType.CompositorViewer,
455 "Context Viewer": hou.paneTabType.ContextViewer,
456 "Data Tree": hou.paneTabType.DataTree,
457 "Details View": hou.paneTabType.DetailsView,
458 "Handle List": hou.paneTabType.HandleList,
459 "Help Browser": hou.paneTabType.HelpBrowser,
460 "IPR Viewer": hou.paneTabType.IPRViewer,
461 "Light Linker": hou.paneTabType.LightLinker,
462 "Material Palette": hou.paneTabType.MaterialPalette,
463 "Network Editor": hou.paneTabType.NetworkEditor,
464 "Output Viewer": hou.paneTabType.OutputViewer,
465 "Parameter Editor": hou.paneTabType.Parm,
466 "Parameter Spreadsheet": hou.paneTabType.ParmSpreadsheet,
467 "Performance Monitor": hou.paneTabType.PerformanceMonitor,
468 "Python Panel": hou.paneTabType.PythonPanel,
469 "Python Shell": hou.paneTabType.PythonShell,
470 "Render Gallery": hou.paneTabType.RenderGallery,
471 "Scene Viewer": hou.paneTabType.SceneViewer,
472 "Shader Viewer": hou.paneTabType.ShaderViewer,
473 "Take List": hou.paneTabType.TakeList,
474 "Textport": hou.paneTabType.Textport,
475 "Tree View": hou.paneTabType.TreeView,
476 }
477 list_dict = {name: (lambda t=pt: pane.newTab(t)) for name, pt in type_map.items()}
478 from .hcwidgets import HCWidgets
479 dialog = HCWidgets.SelectionDialog("New Pane Tab", list_dict)
480 dialog.show()
481 dialog.raise_()
482 dialog.activateWindow()
483
484 def toggleAllTabs(self):
485 visible = 0
486 panes = self.allPanes()
487 for pane in panes:
488 if pane.isShowingTabs():
489 visible = 1
490 for pane in panes:
491 pane.showTabs(not visible)
492