git.lucas.co / hou-control
SideFX Houdini customization package
git clone https://git.lucas.co/hou-control.git

python3.13libs/hc/hccam.py (9.9K)

  1 import hou
  2 from .hcframing import orthographic_frame_width, perspective_frame_distance
  3 from .hcgeo import HCGeo
  4 from .hcsceneviewer import HCSceneViewer
  5 
  6 
  7 class HCCam:
  8     def __init__(self, cam_node, scene_viewer):
  9         self.cam_node = cam_node
 10         self.scene_viewer = scene_viewer
 11 
 12         self._t = hou.Vector3(0, 0, 0)
 13         self._r = hou.Vector3(0, 0, 0)
 14         self._p = hou.Vector3(0, 0, 0)
 15         self._ow = 10
 16         self._projection = 'perspective'
 17         self._resx = 1000
 18         self._resy = 1000
 19         self._aspect_ratio = 1
 20         self.local_x = hou.Vector3(1, 0, 0)
 21         self.local_y = hou.Vector3(0, 1, 0)
 22         self.local_z = hou.Vector3(0, 0, 1)
 23         self.global_x = hou.Vector3(1, 0, 0)
 24         self.global_y = hou.Vector3(0, 1, 0)
 25         self.global_z = hou.Vector3(0, 0, 1)
 26         # self.zoom = 10
 27         self.delta_t = 1
 28         self.delta_r = 15
 29         self.delta_zoom = 1
 30         self.target = None
 31 
 32         self.fitAspectRatio()
 33         self.lock()
 34         self.reset()
 35 
 36 
 37     """ Movement """
 38 
 39     def center(self):
 40         geo = self.scene_viewer.geo()
 41         centroid = geo.boundingBox().center() if geo else hou.Vector3(0, 0, 0)
 42         self.t = hou.Vector3(centroid)
 43         self.p = hou.Vector3(centroid)
 44 
 45     def frame(self):
 46         geo = self.geo()
 47         if not geo:
 48             return
 49 
 50         bbox = geo.boundingBox()
 51         centroid = bbox.center()
 52         size = bbox.sizevec()
 53 
 54         self.p = hou.Vector3(centroid)
 55         self._syncFromCameraNode()
 56 
 57         if self.projection == 'perspective':
 58             dist = perspective_frame_distance(
 59                 bbox_size=(size[0], size[1], size[2]),
 60                 aperture=self.cam_node.parm('aperture').eval(),
 61                 focal=self.cam_node.parm('focal').eval(),
 62                 aspect_ratio=self.aspect_ratio,
 63             )
 64             self.t = hou.Vector3(centroid) + (self.local_z * dist)
 65         else:
 66             self.ow = orthographic_frame_width((size[0], size[1], size[2]))
 67             self.t = hou.Vector3(centroid) + (self.local_z * max((self.t - self.p).length(), 0.001))
 68 
 69         self._syncFromCameraNode()
 70 
 71     def home(self):
 72         geo = self.geo()
 73         centroid = geo.boundingBox().center() if geo else hou.Vector3(0, 0, 0)
 74         self.t = centroid
 75         self.p = centroid
 76         # self.ow = 10
 77         # self.setZoom(6)
 78 
 79     # def movePivot(self):
 80         # If origin
 81         # if self.target == 0:
 82         #     self.t = [0, 0, self.zoom]
 83         #     self.r = [45, 45, 0]
 84         #     self.p = [0, 0, self.zoom * -1]
 85         #     self.ow = 10
 86 
 87     def rotateUp(self):
 88         self._syncFromCameraNode()
 89         delta = hou.Vector3(self.delta_r, 0, 0)
 90         self.r += delta
 91         m = hou.hmath.buildRotateAboutAxis(self.local_x, self.delta_r)
 92         self.t -= self.p
 93         self.t *= m
 94         self.t += self.p
 95         self.local_x *= m
 96         self.local_y *= m
 97         self.local_z *= m
 98 
 99     def rotateDown(self):
100         self._syncFromCameraNode()
101         delta = hou.Vector3(-self.delta_r, 0, 0)
102         self.r += delta
103         m = hou.hmath.buildRotateAboutAxis(self.local_x, -self.delta_r)
104         self.t -= self.p
105         self.t *= m
106         self.t += self.p
107         self.local_x *= m
108         self.local_y *= m
109         self.local_z *= m
110 
111     def rotateLeft(self):
112         self._syncFromCameraNode()
113         delta = hou.Vector3(0, -self.delta_r, 0)
114         self.r += delta
115         m = hou.hmath.buildRotateAboutAxis(self.global_y, -self.delta_r)
116         self.t -= self.p
117         self.t *= m
118         self.t += self.p
119         self.local_x *= m
120         self.local_y *= m
121         self.local_z *= m
122 
123     def rotateRight(self):
124         self._syncFromCameraNode()
125         delta = hou.Vector3(0, self.delta_r, 0)
126         self.r += delta
127         m = hou.hmath.buildRotateAboutAxis(self.global_y, self.delta_r)
128         self.t -= self.p
129         self.t *= m
130         self.t += self.p
131         self.local_x *= m
132         self.local_y *= m
133         self.local_z *= m
134 
135     def setZoom(self, amt):
136         self._syncFromCameraNode()
137         move = self.local_z * amt
138         self.t += move
139 
140     def translateUp(self):
141         self._syncFromCameraNode()
142         move = self.local_y * self.delta_t
143         self.t += move
144         self.p += move
145 
146     def translateDown(self):
147         self._syncFromCameraNode()
148         move = self.local_y * self.delta_t * -1
149         self.t += move
150         self.p += move
151 
152     def translateLeft(self):
153         self._syncFromCameraNode()
154         move = self.local_x * self.delta_t * -1
155         self.t += move
156         self.p += move
157 
158     def translateRight(self):
159         self._syncFromCameraNode()
160         move = self.local_x * self.delta_t
161         self.t += move
162         self.p += move
163 
164     def zoom(self, dir):
165         self._syncFromCameraNode()
166         dir_map = {'out': 1, 'in': -1}
167         move = self.local_z * self.delta_zoom * dir_map[dir]
168         self.t += move
169 
170     def zoomOrtho(self, dir):
171         self._syncFromCameraNode()
172         dir_map = {'out': 1, 'in': -1}
173         self.ow += self.delta_zoom * dir_map[dir]
174 
175 
176     """ Util """
177 
178     def fitAspectRatio(self):
179         self.resx = 1000
180         self.resy = 1000
181         viewport = self.currentViewport()
182         ratio = viewport.size()[2] / viewport.size()[3]
183         self.aspect_ratio = ratio
184 
185     def resetAspectRatio(self):
186         self.aspect_ratio = 1.0
187 
188     @staticmethod
189     def captureViewportState(viewport):
190         """Capture the current viewport's camera state before keycam takes over."""
191         xform = viewport.viewTransform()
192         is_persp = viewport.type() == hou.geometryViewportType.Perspective
193         parts = xform.explode(transform_order='srt', rotate_order='xyz')
194         return {
195             't': hou.Vector3(parts['translate']),
196             'r': hou.Vector3(parts['rotate']),  # XYZ order to match keycam xOrd=0
197             'p': hou.Vector3(viewport.viewPivot()),
198             'projection': 'perspective' if is_persp else 'ortho',
199         }
200 
201     def initFromState(self, state):
202         """Initialize camera from a previously captured viewport state."""
203         self.t = state['t']
204         self.r = state['r']
205         self._p = state['p']
206         if state['projection'] != self._projection:
207             self.projection = state['projection']
208         self._syncFromCameraNode()
209 
210     def geo(self):
211         return self.scene_viewer.geo()
212 
213     def lock(self):
214         viewport = self.currentViewport()
215         viewport.setCamera(self.cam_node)
216         viewport.lockCameraToView(1)
217 
218     def reset(self):
219         self.t = hou.Vector3(0, 0, 2)
220         self.r = hou.Vector3(0, 0, 0)
221         self.p = hou.Vector3(0, 0, 0)
222         # self.zoom = 10
223         self.ow = 10
224         self.delta_t = 1
225         self.delta_r = 15
226         self.delta_zoom = 1
227         self.local_x = hou.Vector3(1, 0, 0)
228         self.local_y = hou.Vector3(0, 1, 0)
229         self.local_z = hou.Vector3(0, 0, 1)
230         self.global_x = hou.Vector3(1, 0, 0)
231         self.global_y = hou.Vector3(0, 1, 0)
232         self.global_z = hou.Vector3(0, 0, 1)
233 
234     def _syncFromCameraNode(self):
235         self._t = hou.Vector3(self.cam_node.parmTuple('t').eval())
236         self._r = hou.Vector3(self.cam_node.parmTuple('r').eval())
237         self._ow = self.cam_node.parm('orthowidth').eval()
238         self._projection = self.cam_node.parm('projection').evalAsString()
239 
240         world_xform = self.cam_node.worldTransform()
241         self.local_x = hou.Vector3(world_xform.at(0, 0), world_xform.at(0, 1), world_xform.at(0, 2)).normalized()
242         self.local_y = hou.Vector3(world_xform.at(1, 0), world_xform.at(1, 1), world_xform.at(1, 2)).normalized()
243         self.local_z = hou.Vector3(world_xform.at(2, 0), world_xform.at(2, 1), world_xform.at(2, 2)).normalized()
244 
245     def setView(self):
246         view_map = {
247             'top':    hou.Vector3(270, 0, 0),
248             'bottom': hou.Vector3(90, 0, 0),
249             'front':  hou.Vector3(0, 180, 0),
250             'back':   hou.Vector3(0, 0, 0),
251             'right':  hou.Vector3(0, 90, 0),
252             'left':   hou.Vector3(0, 270, 0)
253         }
254         self._syncFromCameraNode()
255         distance = max((self.t - self.p).length(), 0.001)
256         self.r = view_map[self.view]
257         self._syncFromCameraNode()
258         self.t = self.p + (self.local_z * distance)
259         self._syncFromCameraNode()
260 
261     def toggleProjection(self):
262         projection_map = {
263             'perspective': 'ortho',
264             'ortho': 'perspective'
265         }
266         self.projection = projection_map[self.projection]
267 
268     def unlock(self):
269         self.viewport().lockCameraToView(0)
270 
271     def currentViewport(self):
272         return self.scene_viewer.currentViewport()
273 
274 
275     """ Props """
276 
277     @property
278     def t(self):
279         return self._t
280     @t.setter
281     def t(self, val):
282         self._t = val
283         self.cam_node.parmTuple('t').set(val)
284 
285     @property
286     def r(self):
287         return self._r
288     @r.setter
289     def r(self, val):
290         self._r = val
291         self.cam_node.parmTuple('r').set(val)
292 
293     @property
294     def p(self):
295         return self._p
296     @p.setter
297     def p(self, val):
298         self._p = val
299 
300     @property
301     def ow(self):
302         return self._ow
303     @ow.setter
304     def ow(self, val):
305         self._ow = val
306         self.cam_node.parm('orthowidth').set(val)
307 
308     @property
309     def projection(self):
310         return self._projection
311     @projection.setter
312     def projection(self, val):
313         self._projection = val
314         self.cam_node.parm('projection').set(val)
315 
316     @property
317     def resx(self):
318         return self._resx
319     @resx.setter
320     def resx(self, val):
321         self._resx = val
322         self.cam_node.parm('resx').set(val)
323 
324     @property
325     def resy(self):
326         return self._resy
327     @resy.setter
328     def resy(self, val):
329         self._resy = val
330         self.cam_node.parm('resy').set(val)
331 
332     @property
333     def aspect_ratio(self):
334         return self._aspect_ratio
335     @aspect_ratio.setter
336     def aspect_ratio(self, val):
337         self._aspect_ratio = val
338         self.cam_node.parm('aspect').set(val)