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