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