git.lucas.co / hou-control
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