git.lucas.co / hou-control
git clone https://git.lucas.co/hou-control.git

python3.11libs/hc/hccam.py (8.3K)

  1 import hou
  2 from .hcgeo import HCGeo
  3 from .hcsceneviewer import HCSceneViewer
  4 
  5 
  6 class HCCam:
  7     def __init__(self, cam_node, scene_viewer):
  8         self.cam_node = cam_node
  9         self.scene_viewer = scene_viewer
 10 
 11         self._t = hou.Vector3(0, 0, 0)
 12         self._r = hou.Vector3(0, 0, 0)
 13         self._p = hou.Vector3(0, 0, 0)
 14         self._ow = 10
 15         self._projection = 'perspective'
 16         self._resx = 1000
 17         self._resy = 1000
 18         self._aspect_ratio = 1
 19         self.local_x = hou.Vector3(1, 0, 0)
 20         self.local_y = hou.Vector3(0, 1, 0)
 21         self.local_z = hou.Vector3(0, 0, 1)
 22         self.global_x = hou.Vector3(1, 0, 0)
 23         self.global_y = hou.Vector3(0, 1, 0)
 24         self.global_z = hou.Vector3(0, 0, 1)
 25         # self.zoom = 10
 26         self.delta_t = 1
 27         self.delta_r = 15
 28         self.delta_zoom = 1
 29         self.target = None
 30 
 31         self.fitAspectRatio()
 32         self.lock()
 33         self.reset()
 34 
 35 
 36     """ Movement """
 37 
 38     def center(self):
 39         geo = self.scene_viewer.geo()
 40         centroid = geo.boundingBox().center() if geo else hou.Vector3(0, 0, 0)
 41         self.t = hou.Vector3(centroid)
 42         self.p = hou.Vector3(centroid)
 43 
 44     def frame(self):
 45         import math
 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         # Set pivot and basic target position to centroid
 55         self.p = hou.Vector3(centroid)
 56         self.t = hou.Vector3(centroid)
 57         
 58         margin = 1.2
 59         
 60         if self.projection == 'perspective':
 61             # Calculate FOV from camera parameters
 62             # Horizontal FOV: 2 * atan(aperture / (2 * focal))
 63             # Vertical FOV: 2 * atan((aperture/aspect) / (2 * focal))
 64             aperture = self.cam_node.parm('aperture').eval()
 65             focal = self.cam_node.parm('focal').eval()
 66             aspect = self.aspect_ratio
 67             
 68             fov_h = 2.0 * math.atan(aperture / (2.0 * focal))
 69             fov_v = 2.0 * math.atan((aperture / aspect) / (2.0 * focal))
 70             
 71             # Use the bounding sphere radius for a safe framing from any angle
 72             radius = size.length() * 0.5
 73             
 74             # Required distance to fit the sphere in both H and V FOV
 75             dist_h = (radius * margin) / math.sin(fov_h * 0.5)
 76             dist_v = (radius * margin) / math.sin(fov_v * 0.5)
 77             
 78             dist = max(dist_h, dist_v)
 79             self.setZoom(dist)
 80             
 81         else:
 82             # Orthographic framing
 83             # We need to fit the bounding box into the orthowidth
 84             # ow = max(width, height * aspect)
 85             width = max(size[0], size[1], size[2])
 86             self.ow = width * margin * 1.5 # Extra buffer for ortho
 87 
 88     def home(self):
 89         geo = self.geo()
 90         centroid = geo.boundingBox().center() if geo else hou.Vector3(0, 0, 0)
 91         self.t = centroid
 92         self.p = centroid
 93         # self.ow = 10
 94         # self.setZoom(6)
 95 
 96     # def movePivot(self):
 97         # If origin
 98         # if self.target == 0:
 99         #     self.t = [0, 0, self.zoom]
100         #     self.r = [45, 45, 0]
101         #     self.p = [0, 0, self.zoom * -1]
102         #     self.ow = 10
103 
104     def rotateUp(self):
105         delta = hou.Vector3(self.delta_r, 0, 0)
106         self.r += delta
107         m = hou.hmath.buildRotateAboutAxis(self.local_x, self.delta_r)
108         self.t -= self.p
109         self.t *= m
110         self.t += self.p
111         self.local_x *= m
112         self.local_y *= m
113         self.local_z *= m
114 
115     def rotateDown(self):
116         delta = hou.Vector3(-self.delta_r, 0, 0)
117         self.r += delta
118         m = hou.hmath.buildRotateAboutAxis(self.local_x, -self.delta_r)
119         self.t -= self.p
120         self.t *= m
121         self.t += self.p
122         self.local_x *= m
123         self.local_y *= m
124         self.local_z *= m
125 
126     def rotateLeft(self):
127         delta = hou.Vector3(0, -self.delta_r, 0)
128         self.r += delta
129         m = hou.hmath.buildRotateAboutAxis(self.global_y, -self.delta_r)
130         self.t -= self.p
131         self.t *= m
132         self.t += self.p
133         self.local_x *= m
134         self.local_y *= m
135         self.local_z *= m
136 
137     def rotateRight(self):
138         delta = hou.Vector3(0, self.delta_r, 0)
139         self.r += delta
140         m = hou.hmath.buildRotateAboutAxis(self.global_y, self.delta_r)
141         self.t -= self.p
142         self.t *= m
143         self.t += self.p
144         self.local_x *= m
145         self.local_y *= m
146         self.local_z *= m
147 
148     def setZoom(self, amt):
149         move = self.local_z * amt
150         self.t += move
151 
152     def translateUp(self):
153         move = self.local_y * self.delta_t
154         self.t += move
155         self.p += move
156 
157     def translateDown(self):
158         move = self.local_y * self.delta_t * -1
159         self.t += move
160         self.p += move
161 
162     def translateLeft(self):
163         move = self.local_x * self.delta_t * -1
164         self.t += move
165         self.p += move
166 
167     def translateRight(self):
168         move = self.local_x * self.delta_t
169         self.t += move
170         self.p += move
171 
172     def zoom(self, dir):
173         dir_map = {'out': 1, 'in': -1}
174         move = self.local_z * self.delta_zoom * dir_map[dir]
175         self.t += move
176 
177     def zoomOrtho(self, dir):
178         dir_map = {'out': 1, 'in': -1}
179         self.ow += self.delta_zoom * dir_map[dir]
180 
181 
182     """ Util """
183 
184     def fitAspectRatio(self):
185         self.resx = 1000
186         self.resy = 1000
187         viewport = self.currentViewport()
188         ratio = viewport.size()[2] / viewport.size()[3]
189         self.aspect_ratio = ratio
190 
191     def geo(self):
192         return self.scene_viewer.geo()
193 
194     def lock(self):
195         viewport = self.currentViewport()
196         viewport.setCamera(self.cam_node)
197         viewport.lockCameraToView(1)
198 
199     def reset(self):
200         self.t = hou.Vector3(0, 0, 2)
201         self.r = hou.Vector3(0, 0, 0)
202         self.p = hou.Vector3(0, 0, 0)
203         # self.zoom = 10
204         self.ow = 10
205         self.delta_t = 1
206         self.delta_r = 15
207         self.delta_zoom = 1
208         self.local_x = hou.Vector3(1, 0, 0)
209         self.local_y = hou.Vector3(0, 1, 0)
210         self.local_z = hou.Vector3(0, 0, 1)
211         self.global_x = hou.Vector3(1, 0, 0)
212         self.global_y = hou.Vector3(0, 1, 0)
213         self.global_z = hou.Vector3(0, 0, 1)
214 
215     def setView(self):
216         view_map = {
217             'top':    hou.Vector3(270, 0, 0),
218             'bottom': hou.Vector3(90, 0, 0),
219             'front':  hou.Vector3(0, 180, 0),
220             'back':   hou.Vector3(0, 0, 0),
221             'right':  hou.Vector3(0, 90, 0),
222             'left':   hou.Vector3(0, 270, 0)
223         }
224         self.r = view_map[self.view]
225 
226     def toggleProjection(self):
227         projection_map = {
228             'perspective': 'ortho',
229             'ortho': 'perspective'
230         }
231         self.projection = projection_map[self.projection]
232 
233     def unlock(self):
234         self.viewport().lockCameraToView(0)
235 
236     def currentViewport(self):
237         return self.scene_viewer.allViewports()[3]
238 
239 
240     """ Props """
241 
242     @property
243     def t(self):
244         return self._t
245     @t.setter
246     def t(self, val):
247         self._t = val
248         self.cam_node.parmTuple('t').set(val)
249 
250     @property
251     def r(self):
252         return self._r
253     @r.setter
254     def r(self, val):
255         self._r = val
256         self.cam_node.parmTuple('r').set(val)
257 
258     @property
259     def p(self):
260         return self._p
261     @p.setter
262     def p(self, val):
263         self._p = val
264 
265     @property
266     def ow(self):
267         return self._ow
268     @ow.setter
269     def ow(self, val):
270         self._ow = val
271         self.cam_node.parm('orthowidth').set(val)
272 
273     @property
274     def projection(self):
275         return self._projection
276     @projection.setter
277     def projection(self, val):
278         self._projection = val
279         self.cam_node.parm('projection').set(val)
280 
281     @property
282     def resx(self):
283         return self._resx
284     @resx.setter
285     def resx(self, val):
286         self._resx = val
287         self.cam_node.parm('resx').set(val)
288 
289     @property
290     def resy(self):
291         return self._resy
292     @resy.setter
293     def resy(self, val):
294         self._resy = val
295         self.cam_node.parm('resy').set(val)
296 
297     @property
298     def aspect_ratio(self):
299         return self._aspect_ratio
300     @aspect_ratio.setter
301     def aspect_ratio(self, val):
302         self._aspect_ratio = val
303         self.cam_node.parm('aspect').set(val)
304