SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git
python3.13libs/hc/hcguides.py (10.6K)
1 import hou
2 import math
3
4 class HCGuides:
5 def __init__(self, state):
6 self.state = state
7 self.cam_node = state.cam_node
8 self.cam = state.cam
9 self.scene_viewer = state.scene_viewer
10
11 self.axis_size = None
12 self.tie_axis_to_radius = None
13 self.cam_axis = None
14 self.pivot_axis = None
15 self.bbox = None
16 self.perim = None
17 self.pivot_2d = None
18 self.pivot_3d = None
19 self.ray = None
20
21 self.cam_axis_drawable = hou.GeometryDrawable(
22 scene_viewer=self.scene_viewer.hou_tab,
23 geo_type=hou.drawableGeometryType.Line,
24 name='cam_axis'
25 )
26
27 self.pivot_axis_drawable = hou.GeometryDrawable(
28 scene_viewer=self.scene_viewer.hou_tab,
29 geo_type=hou.drawableGeometryType.Line,
30 name='pivot_axis',
31 params={
32 'color1': hou.Vector4((1, 1, 1, 1)),
33 'color2': hou.Vector4((0.5, 0.5, 0.5, 0.5)),
34 'glow_width': 2,
35 'fade_factor': 0.0
36 }
37 )
38
39 self.bbox_drawable = hou.GeometryDrawable(
40 scene_viewer=self.scene_viewer.hou_tab,
41 geo_type=hou.drawableGeometryType.Line,
42 name='bbox',
43 params={
44 'color1': hou.Vector4((1, 1, 1, 0.3)),
45 'fade_factor': 0.0
46 }
47 )
48
49 self.perim_drawable = hou.GeometryDrawable(
50 scene_viewer=self.scene_viewer.hou_tab,
51 geo_type=hou.drawableGeometryType.Line,
52 name='perim'
53 )
54
55 self.pivot_2d_drawable = hou.GeometryDrawable(
56 scene_viewer=self.scene_viewer.hou_tab,
57 geo_type=hou.drawableGeometryType.Line,
58 name='pivot_2d'
59 )
60
61 self.pivot_3d_drawable = hou.GeometryDrawable(
62 scene_viewer=self.scene_viewer.hou_tab,
63 geo_type=hou.drawableGeometryType.Face,
64 name='pivot_3d',
65 params={
66 'color1': hou.Vector4(0.2, 0.8, 0.2, 0.6),
67 'fade_factor': 0.2
68 }
69 )
70
71 self.ray_drawable = hou.GeometryDrawable(
72 scene_viewer=self.scene_viewer.hou_tab,
73 geo_type=hou.drawableGeometryType.Line,
74 name='ray',
75 params={
76 'color1': hou.Vector4((1, 0.8, 1, 0.5))
77 }
78 )
79
80 def draw(self, kwargs):
81 draw_handle = kwargs['draw_handle']
82 if self.bbox: self.bbox_drawable.draw(draw_handle, {})
83 if self.cam_axis: self.cam_axis_drawable.draw(draw_handle, {})
84 if self.pivot_axis: self.pivot_axis_drawable.draw(draw_handle, {})
85 if self.perim: self.perim_drawable.draw(draw_handle, {})
86 if self.pivot_2d: self.pivot_2d_drawable.draw(draw_handle, {})
87 if self.pivot_3d: self.pivot_3d_drawable.draw(draw_handle, {})
88 if self.ray: self.ray_drawable.draw(draw_handle, {})
89
90 def update(self):
91 from hc import HCSettings
92 prefs = HCSettings().keycam('guides')
93 self.axis_size = prefs.get('axis_size')
94 self.tie_axis_to_radius = prefs.get('tie_axis_to_radius')
95 self.bbox = prefs.get('bbox')
96 self.cam_axis = prefs.get('cam_axis')
97 self.cam_geo = prefs.get('cam_geo')
98 self.pivot_axis = prefs.get('pivot_axis')
99 self.pivot_2d = prefs.get('pivot_2d')
100 self.pivot_3d = prefs.get('pivot_3d')
101 self.perim = prefs.get('perim')
102 self.ray = prefs.get('ray')
103
104 if self.bbox: self.makeBbox()
105 self.bbox_drawable.show(self.bbox)
106
107 if self.cam_axis: self.makeCamAxis()
108 self.cam_axis_drawable.show(self.cam_axis)
109
110 if self.pivot_axis: self.makePivotAxis()
111 self.pivot_axis_drawable.show(self.pivot_axis)
112
113 if self.perim: self.makePerim()
114 self.perim_drawable.show(self.perim)
115
116 if self.pivot_2d: self.makePivot2d()
117 self.pivot_2d_drawable.show(self.pivot_2d)
118
119 if self.pivot_3d: self.makePivot3d()
120 self.pivot_3d_drawable.show(self.pivot_3d)
121
122 if self.ray: self.makeRay()
123 self.ray_drawable.show(self.ray)
124
125
126 """ Construction """
127
128 def makeCamAxis(self):
129 axes = (
130 self.cam.local_x,
131 self.cam.local_y,
132 self.cam.local_z
133 )
134 geo = hou.Geometry()
135 for i in range(3):
136 P0 = self.cam.t + axes[i]
137 P1 = self.cam.t + axes[i] * -1
138 pts = geo.createPoints((P0, P1))
139 poly = geo.createPolygon(is_closed=False)
140 poly.addVertex(pts[0])
141 poly.addVertex(pts[1])
142 self.cam_axis_drawable.setGeometry(geo)
143
144 def makePivotAxis(self):
145 scale = self.axis_size
146 axes = (
147 self.cam.local_x,
148 self.cam.local_y,
149 self.cam.local_z
150 )
151 # Saturated pure RGB for maximum visibility
152 colors = (
153 [1.0, 0.2, 0.2], # X = red
154 [0.2, 1.0, 0.2], # Y = green
155 [0.4, 0.6, 1.0], # Z = blue (slightly softened for contrast on dark bg)
156 )
157 geo = hou.Geometry()
158
159 cone_r = scale * 0.08 # arrowhead radius
160 cone_h = scale * 0.25 # arrowhead height
161 n_sides = 6 # arrowhead facets
162
163 for i in range(3):
164 P_pos = self.cam.p + axes[i] * scale
165 P_neg = self.cam.p + axes[i] * -scale
166 pts = geo.createPoints((P_pos, P_neg))
167 poly = geo.createPolygon(is_closed=False)
168 poly.addVertex(pts[0])
169 poly.addVertex(pts[1])
170
171 # Arrowhead: pyramid at positive end pointing outward
172 # Apex is at P_pos; base is set back by cone_h along the axis
173 apex_pt = geo.createPoint()
174 apex_pt.setPosition(P_pos)
175
176 # Pick two perpendicular vectors for the base ring
177 perp1 = axes[(i + 1) % 3].normalized()
178 perp2 = axes[(i + 2) % 3].normalized()
179 base_center = P_pos - axes[i] * cone_h
180
181 ring_pts = []
182 for s in range(n_sides):
183 angle = 2.0 * math.pi * s / n_sides
184 pos = base_center + perp1 * (cone_r * math.cos(angle)) \
185 + perp2 * (cone_r * math.sin(angle))
186 rp = geo.createPoint()
187 rp.setPosition(pos)
188 ring_pts.append(rp)
189
190 # Build triangular faces from apex around the ring
191 for s in range(n_sides):
192 face = geo.createPolygon()
193 face.addVertex(apex_pt)
194 face.addVertex(ring_pts[s])
195 face.addVertex(ring_pts[(s + 1) % n_sides])
196
197 # Base cap polygon
198 cap = geo.createPolygon()
199 for rp in ring_pts:
200 cap.addVertex(rp)
201
202 # Add Cd after all geometry is built
203 cd_attrib = geo.addAttrib(hou.attribType.Point, 'Cd', (1.0, 1.0, 1.0))
204 pt_list = list(geo.iterPoints())
205 # Each axis contributes: 2 line pts + 1 apex + n_sides ring pts
206 pts_per_axis = 2 + 1 + n_sides
207 running = 0
208 for i in range(3):
209 color = tuple(colors[i])
210 for j in range(pts_per_axis):
211 if running + j < len(pt_list):
212 pt_list[running + j].setAttribValue(cd_attrib, color)
213 running += pts_per_axis
214
215 self.pivot_axis_drawable.setGeometry(geo)
216 self.pivot_axis_drawable.setParams(
217 {
218 'color1': hou.Vector4((1, 1, 1, 1)),
219 'color2': hou.Vector4((0.5, 0.5, 0.5, 0.4)),
220 'glow_width': 2,
221 'fade_factor': 0.0,
222 }
223 )
224
225 def makeBbox(self):
226 guide_geo = hou.Geometry()
227 target_geo = self.scene_viewer.geo()
228 if target_geo is None:
229 self.bbox_drawable.setGeometry(guide_geo)
230 return
231
232 bbox = target_geo.boundingBox()
233 mn = bbox.minvec()
234 mx = bbox.maxvec()
235
236 corners = (
237 hou.Vector3(mn[0], mn[1], mn[2]),
238 hou.Vector3(mx[0], mn[1], mn[2]),
239 hou.Vector3(mx[0], mx[1], mn[2]),
240 hou.Vector3(mn[0], mx[1], mn[2]),
241 hou.Vector3(mn[0], mn[1], mx[2]),
242 hou.Vector3(mx[0], mn[1], mx[2]),
243 hou.Vector3(mx[0], mx[1], mx[2]),
244 hou.Vector3(mn[0], mx[1], mx[2]),
245 )
246 pts = guide_geo.createPoints(corners)
247 edges = (
248 (0, 1), (1, 2), (2, 3), (3, 0),
249 (4, 5), (5, 6), (6, 7), (7, 4),
250 (0, 4), (1, 5), (2, 6), (3, 7),
251 )
252 for a, b in edges:
253 poly = guide_geo.createPolygon(is_closed=False)
254 poly.addVertex(pts[a])
255 poly.addVertex(pts[b])
256
257 self.bbox_drawable.setGeometry(guide_geo)
258
259 def makePerim(self):
260 guide_geo = hou.Geometry()
261 circle = hou.sopNodeTypeCategory().nodeVerb('circle')
262 circle.setParms(
263 {
264 'divs': 128,
265 'type': 1,
266 't': self.cam.p,
267 'scale': self.cam.p.distanceTo(self.cam.t),
268 'orient': 2
269 }
270 )
271 circle.execute(guide_geo, [])
272 self.perim_drawable.setParams(
273 {
274 'color1': hou.Vector4(1.0, 1.0, 1.0, 0.25),
275 'fade_factor': 1.0
276 }
277 )
278 self.perim_drawable.setGeometry(guide_geo)
279
280 def makePivot2d(self):
281 guide_geo = hou.Geometry()
282 circle = hou.sopNodeTypeCategory().nodeVerb('circle')
283 circle.setParms(
284 {
285 'type': 1,
286 'r': self.cam.r,
287 't': self.cam.p,
288 'scale': self.cam.ow * 0.0075
289 }
290 )
291 circle.execute(guide_geo, [])
292 self.pivot_2d_drawable.setParams(
293 {
294 'color1': hou.Vector4(0.0, 0.0, 1, 1),
295 'fade_factor': 1.0
296 }
297 )
298 self.pivot_2d_drawable.setGeometry(guide_geo)
299
300 def makePivot3d(self):
301 guide_geo = hou.Geometry()
302 sphere = hou.sopNodeTypeCategory().nodeVerb('sphere')
303 scale = self.cam.t.distanceTo(self.cam.p) * 0.01
304 sphere.setParms(
305 {
306 'freq': 7,
307 'scale': scale,
308 'type': 1,
309 't': self.cam.p
310 }
311 )
312 sphere.execute(guide_geo, [])
313 self.pivot_3d_drawable.setGeometry(guide_geo)
314
315 def makeRay(self):
316 guide_geo = hou.Geometry()
317 guide_geo.addAttrib(hou.attribType.Point, 'Cd', (1, 0, 0))
318 pts = guide_geo.createPoints((self.cam.p, self.cam.t))
319 poly = guide_geo.createPolygon()
320 poly.addVertex(pts[0])
321 poly.addVertex(pts[1])
322 self.ray_drawable.setGeometry(guide_geo)