git clone https://git.lucas.co/hou-control.git
python3.11libs/hc/hclayout.py (6.3K)
1 import hou
2 from .hcsession import HCSession
3
4
5 DEFAULT_FLOATING_PANE_TYPES = (
6 hou.paneTabType.Parm,
7 hou.paneTabType.NetworkEditor,
8 )
9
10
11 class HCLayout:
12 """Wraps the current Houdini desktop / pane / floating-panel state."""
13
14
15 """ Desktop """
16
17
18 def desktop(self):
19 return hou.ui.curDesktop()
20
21 def desktopName(self):
22 return self.desktop().name()
23
24 def mode(self):
25 return "detached" if self.desktopName() == "hc_detached" else "attached"
26
27 def setDesktop(self, name):
28 for d in hou.ui.desktops():
29 if d.name() == name:
30 d.setAsCurrent()
31 return True
32 return False
33
34
35 """ Panes """
36
37
38 def panes(self):
39 return self.desktop().panes()
40
41 def paneCount(self):
42 return len(self.panes())
43
44 def paneTabs(self):
45 tabs = []
46 for pane in self.panes():
47 tabs.extend(pane.tabs())
48 return tabs
49
50 def paneTabTypes(self):
51 return [tab.type() for tab in self.paneTabs()]
52
53
54 """ Floating """
55
56
57 def floatingPanels(self):
58 return [p for p in hou.ui.floatingPanels() if p.qtParentWindow() is not None]
59
60 def floatingPaneTabs(self):
61 tabs = []
62 for fp in self.floatingPanels():
63 tabs.extend(fp.paneTabs())
64 return tabs
65
66 def floatingPaneTabTypes(self):
67 return [tab.type() for tab in self.floatingPaneTabs()]
68
69 def launchFloating(self, pane_types=None):
70 """Open one floating panel per pane type, skipping any already present.
71
72 Each floating panel is a separate OS window the WM can tile. The main
73 Houdini window still exists alongside them. Each new pane tab gets
74 initialize() called on its HC* wrapper so per-type defaults can apply.
75 """
76 from .hcpane import HCPane
77 if pane_types is None:
78 pane_types = DEFAULT_FLOATING_PANE_TYPES
79 existing = set(self.floatingPaneTabTypes())
80 created = []
81 for pane_type in pane_types:
82 if pane_type in existing:
83 continue
84 panel = self.desktop().createFloatingPanel(pane_type, immediate=True)
85 panel.attachToDesktop(True)
86 created.append(panel)
87 initialized_panes = set()
88 for hou_tab in panel.paneTabs():
89 pane = hou_tab.pane()
90 if pane is None:
91 continue
92 hc_pane = HCPane(pane)
93 if pane.id() not in initialized_panes:
94 try:
95 hc_pane.initialize()
96 except Exception as e:
97 print(f"[HCLayout] HCPane.initialize() failed: {e!r}")
98 initialized_panes.add(pane.id())
99 hc_tab = hc_pane.convertTab(hou_tab)
100 try:
101 hc_tab.initialize()
102 except Exception as e:
103 print(f"[HCLayout] initialize() failed for {hou_tab.type().name()}: {e!r}")
104 return created
105
106
107 """ Snapshot """
108
109
110 def info(self):
111 return {
112 "desktop": self.desktopName(),
113 "mode": self.mode(),
114 "pane_count": self.paneCount(),
115 "pane_tab_types": [t.name() for t in self.paneTabTypes()],
116 "floating_pane_count": len(self.floatingPanels()),
117 "floating_pane_tab_types": [t.name() for t in self.floatingPaneTabTypes()],
118 }
119
120
121 def launchFloatingLayout(pane_types=None):
122 return HCLayout().launchFloating(pane_types)
123
124
125 """ Legacy free functions (broken — kept for reference) """
126
127
128 def setLayoutQuad(self):
129 session = HCSession()
130 session.clearLayout()
131 session.allTabs()[0].setType("PythonShell")
132 session.allPanes()[0].splitHorizontal()
133 session.allPanes()[0].splitVertical()
134 session.allPanes()[1].splitVertical()
135
136 def setLayoutRamp(self):
137 session = HCSession()
138 session.removeEventLoopCallbacks()
139 session.clearLayout()
140 session.allPanes()[0].splitVertical()
141 session.allTabs()[1].setType("Parm")
142 session.allPanes()[1].setSplitRatio(0.3)
143 session.allPanes()[1].createTab()
144
145 def setLayoutTriH(self):
146 session = HCSession()
147 session.removeEventLoopCallbacks()
148 session.clearLayout()
149 session.allTabs()[0].setType("PythonShell")
150 session.allPanes()[0].splitHorizontal()
151 session.allPanes()[1].splitHorizontal()
152 session.allPanes()[1].createTab("PythonShell")
153 session.allPanes()[1].allTabs()[0].setIsCurrentTab()
154 session.allPanes()[0].tabs()[0].setType("SceneViewer")
155 session.allPanes()[1].tabs()[0].setType("Parm")
156 session.allPanes()[1].tabs()[1].setType("DetailsView")
157 session.allPanes()[2].tabs()[0].setType("NetworkEditor")
158 session.allPanes()[0].setSplitFraction(0.5)
159 session.prev_pane = self.pane()
160 hou.ui.addEventLoopCallback(triHCallback)
161
162 def triHCallback(session):
163 panes = session.panes()
164 pane = session.pane()
165 if str(pane) != str(hou.session.lastPane):
166 hou.session.last_pane = pane
167 if str(pane) == str(panes[1]):
168 pane.setSplitFraction(0.6)
169 elif str(pane) == str(panes[2]):
170 pane.setSplitFraction(0.3)
171 return True
172
173 def setLayoutTriV(session):
174 session.removeEventLoopCallbacks()
175 session.clearLayout()
176 session.allPanes()[0].tabs()[0].setType("PythonShell")
177 session.allPanes()[0].splitHorizontal()
178 session.allPanes()[1].splitVertical()
179 session.allPanes()[1].createTab("PythonShell")
180 session.allPanes()[1].allTabs()[0].setIsCurrentTab()
181 session.allPanes()[0].tabs()[0].setType("SceneViewer")
182 session.allPanes()[1].tabs()[0].setType("Parm")
183 session.allPanes()[1].tabs()[1].setType("DetailsView")
184 session.allPanes()[2].tabs()[0].setType("NetworkEditor")
185 session.allPanes()[0].setSplitFraction(0.66)
186 hou.session.last_pane = self.pane()
187 hou.ui.addEventLoopCallback(self.triVCallback)
188
189 def triVCallback(session):
190 panes = session.allPanes()
191 pane = session.currentPane()
192 if str(pane) != str(hou.session.last_pane):
193 hou.session.last_pane = pane
194 if str(pane) == str(panes[0]):
195 pane.setSplitFraction(0.7)
196 elif str(pane) == str(panes[1]):
197 panes[0].setSplitFraction(0.4)
198 pane.setSplitFraction(0.33)
199 elif str(pane) == str(panes[2]):
200 panes[0].setSplitFraction(0.4)
201 pane.setSplitFraction(0.66)
202 return True