Package nukescripts :: Module snap3d
[hide private]
[frames] | no frames]

Source Code for Module nukescripts.snap3d

  1  # Copyright (c) 2010 The Foundry Visionmongers Ltd.  All Rights Reserved. 
  2   
  3  import math 
  4  import nuke, nuke.geo, _nukemath 
  5   
  6  # 
  7  # Predefined snapping functions. 
  8  # 
  9   
 10  # Lazy functions to call on "thisNode" 
11 -def translateThisNodeToPoints():
12 return translateToPoints(nuke.thisNode())
13
14 -def translateRotateThisNodeToPoints():
15 return translateRotateToPoints(nuke.thisNode())
16
17 -def translateRotateScaleThisNodeToPoints():
18 return translateRotateScaleToPoints(nuke.thisNode())
19 20 # Lazy functions to determine the vertex selection 21 # These are the external user functions 22 # Much hard work is done in obtaining the selection in these functions
23 -def translateToPoints(nodeToSnap):
24 ''' 25 Translate the specified node to the average position of the current vertex selection in the active viewer. 26 The nodeToSnap must contain a 'translate' knob and the transform order must be 'SRT'. 27 28 @type nodeToSnap: nuke.Node 29 @param nodeToSnap: Node to translate 30 ''' 31 return translateSelectionToPoints(nodeToSnap, getSelection())
32
33 -def translateRotateToPoints(nodeToSnap):
34 ''' 35 Translate the specified node to the average position of the current vertex selection in the active viewer 36 and rotate to the orientation of the (mean squares) best fit plane for the selection. 37 The nodeToSnap must contain 'translate' and 'rotate' knobs, the transform order must be 'SRT' and the 38 rotation order must be 'ZXY'. 39 40 @type nodeToSnap: nuke.Node 41 @param nodeToSnap: Node to translate and rotate 42 ''' 43 return translateRotateSelectionToPoints(nodeToSnap, getSelection())
44
45 -def translateRotateScaleToPoints(nodeToSnap):
46 ''' 47 Translate the specified node to the average position of the current vertex selection in the active viewer, 48 rotate to the orientation of the (mean squares) best fit plane for the selection 49 and scale to the extents of the selection. 50 The nodeToSnap must contain 'translate', 'rotate' and 'scale' knobs, the transform order must be 'SRT' and 51 the rotation order must be 'ZXY'. 52 53 @type nodeToSnap: nuke.Node 54 @param nodeToSnap: Node to translate, rotate and scale 55 ''' 56 return translateRotateScaleSelectionToPoints(nodeToSnap, getSelection())
57 58 # Verification wrappers
59 -def translateSelectionToPoints(nodeToSnap, vertexSelection):
60 try: 61 verifyNodeToSnap(nodeToSnap, ["translate", "xform_order", "rot_order"]) 62 verifyVertexSelection(vertexSelection, 1) 63 translateToPointsVerified(nodeToSnap, vertexSelection) 64 except ValueError, e: 65 nuke.message(str(e))
66
67 -def translateRotateSelectionToPoints(nodeToSnap, vertexSelection):
68 try: 69 verifyNodeToSnap(nodeToSnap, ["translate", "rotate", "xform_order", "rot_order"]) 70 verifyVertexSelection(vertexSelection, 1) # This can use the normal direction 71 translateRotateToPointsVerified(nodeToSnap, vertexSelection) 72 except ValueError, e: 73 nuke.message(str(e))
74
75 -def translateRotateScaleSelectionToPoints(nodeToSnap, vertexSelection):
76 try: 77 verifyNodeToSnap(nodeToSnap, ["translate", "rotate", "scaling", "xform_order", "rot_order"]) 78 verifyVertexSelection(vertexSelection, 3) 79 translateRotateScaleToPointsVerified(nodeToSnap, vertexSelection) 80 except ValueError, e: 81 nuke.message(str(e))
82
83 -def verifyVertexSelection(vertexSelection, minLen):
84 if len(vertexSelection) < minLen: 85 raise ValueError('Selection must be at least %d points' % minLen)
86 87 # Verification functions
88 -def verifyNodeToSnap(nodeToSnap, knobList):
89 # Check the knobs 90 nodeKnobs = nodeToSnap.knobs() 91 for knob in knobList: 92 if knob not in nodeKnobs: 93 raise ValueError('Snap requires "%s" knob' % knob) 94 # Verify the transform order 95 verifyNodeOrder(nodeToSnap, "xform_order", "SRT") 96 # Verify the rotation order as necessary 97 if "rotate" in knobList: 98 verifyNodeOrder(nodeToSnap, "rot_order", "ZXY")
99
100 -def verifyNodeOrder(node, knobName, orderName):
101 orderKnob = node.knob(knobName) 102 # Is there a better way than this? 103 order = orderKnob.enumName( int(orderKnob.getValue()) ) 104 if orderName != order: 105 raise ValueError('Snap requires "%s" %s' % (orderName, knobName))
106 107 # Info for each vertex
108 -class VertexInfo:
109 - def __init__(self, objnum, index, value, position):
110 self.objnum = objnum 111 self.index = index 112 self.value = value 113 self.position = position # This is updated on applying a transform
114 115 # Selection container
116 -class VertexSelection:
117 - def __init__(self):
118 self.vertexInfoSet = set() 119 self.length = 0
120
121 - def __len__(self):
122 return self.length
123 124 # Convenience function to allow direct iteration
125 - def __iter__(self):
126 # Since a set is not iterable use a generator function 127 # Don't change the set while iterating! 128 for info in self.vertexInfoSet: 129 yield info
130
131 - def add(self, vertexInfo):
132 self.vertexInfoSet.add(vertexInfo) 133 self.length += 1
134
135 - def points(self):
136 # Generate an iterable list of the positions 137 points = [] 138 for info in self.vertexInfoSet: 139 points += [info.position] 140 return points
141
142 - def indices(self):
143 # Generate a searchable dictionary of the positions 144 indices = {} 145 i = 0 146 for info in self.vertexInfoSet: 147 indices[info.index] = i 148 i += 1 149 return indices
150
151 - def translate(self, vector):
152 for info in self.vertexInfoSet: 153 info.position += vector
154
155 - def inverseRotate(self, vector, order):
156 #nuke.tprint(vector) 157 #nuke.tprint(order) 158 # Create a rotation matrix 159 m = _nukemath.Matrix3() 160 m.makeIdentity() 161 for axis in order: 162 if axis == "X": 163 # Apply X rotation 164 m.rotateX(vector[0]) 165 #nuke.tprint("rotateX: %f" % vector[0]) 166 elif axis == "Y": 167 # Apply Y rotation 168 m.rotateY(vector[1]) 169 #nuke.tprint("rotateY: %f" % vector[1]) 170 elif axis == "Z": 171 # Apply Z rotation 172 m.rotateZ(vector[2]) 173 #nuke.tprint("rotateZ: %f" % vector[2]) 174 else: 175 raise ValueError("Invalid rotation axis: %s" % axis) 176 177 # Now determine the inverse/transpose matrix 178 #nuke.tprint(m) 179 transpose(m) 180 #nuke.tprint(m) 181 182 # Apply the matrix to the vertices 183 for info in self.vertexInfoSet: 184 info.position = m * info.position
185
186 - def scale(self, vector):
187 for info in self.vertexInfoSet: 188 info.position[0] *= vector[0] 189 info.position[1] *= vector[1] 190 info.position[2] *= vector[2]
191 192 # Helper function since Matrix3 has no transpose operation
193 -def transpose(m):
194 t = m[0+3*1] 195 m[0+3*1] = m[1+3*0] 196 m[1+3*0] = t 197 198 t = m[0+3*2] 199 m[0+3*2] = m[2+3*0] 200 m[2+3*0] = t 201 202 t = m[1+3*2] 203 m[1+3*2] = m[2+3*1] 204 m[2+3*1] = t 205 206 return
207 208 # Core snapping functions
209 -def translateToPointsVerified(nodeToSnap, vertexSelection):
210 # Find the average position 211 centre = calcAveragePosition(vertexSelection) 212 # Move the nodeToSnap to the average position 213 nodeToSnap['translate'].setValue(centre) 214 # Subtract this translation from the vertexSelection 215 inverseTranslation = -centre 216 vertexSelection.translate(inverseTranslation)
217
218 -def scaleToPointsVerified(nodeToScale, vertexSelection):
219 # Scale the nodeToScale to fit the bounding box of the selected points 220 scale = calcBounds(vertexSelection) 221 nodeToScale['scaling'].setValue(scale) 222 # Apply the inverse scale to the points 223 inverseScale = _nukemath.Vector3(1/scale[0], 1/scale[1], 1/scale[2]) 224 vertexSelection.scale(inverseScale)
225
226 -def rotateToPointsVerified(nodeToSnap, vertexSelection):
227 # Get the normal of the points 228 norm = averageNormal(vertexSelection) 229 # Calculate the rotation vector 230 rotationVec = calcRotationVector(vertexSelection, norm) 231 # Convert to degrees 232 rotationDegrees = _nukemath.Vector3( math.degrees(rotationVec.x), math.degrees(rotationVec.y), math.degrees(rotationVec.z) ) 233 # Set the node transform 234 nodeToSnap['rotate'].setValue(rotationDegrees) 235 # Apply the reverse rotation to the points 236 vertexSelection.inverseRotate(rotationVec, "YXZ")
237
238 -def translateRotateToPointsVerified(nodeToSnap, vertexSelection):
239 # Note that the vertexSelection positions are updated as we go 240 translateToPointsVerified(nodeToSnap, vertexSelection) 241 rotateToPointsVerified(nodeToSnap, vertexSelection)
242
243 -def translateRotateScaleToPointsVerified(nodeToSnap, vertexSelection):
244 # Note that the vertexSelection positions are updated as we go 245 translateRotateToPointsVerified(nodeToSnap, vertexSelection) 246 scaleToPointsVerified(nodeToSnap, vertexSelection)
247 248 # Get the rotation vector
249 -def calcRotationVector(vertexSelection, norm):
250 # Collate a point set from the vertex selection 251 points = vertexSelection.points() 252 253 # Find a best fit plane with three or more points 254 if len(vertexSelection) >= 3: 255 planeTri = nuke.geo.bestFitPlane(*points) 256 257 rotationVec = planeRotation(planeTri, norm) 258 elif len(vertexSelection) == 2: 259 # Choose the axes dependent on the line direction 260 u = points[1] - points[0] 261 u.normalize() 262 263 w = norm 264 v = w.cross(u) 265 v.normalize() 266 # Update w 267 w = v.cross(u) 268 269 # Fabricate a tri (tuple) 270 planeTri = (_nukemath.Vector3(0.0, 0.0, 0.0), u, v) 271 272 rotationVec = planeRotation(planeTri, norm) 273 elif len(vertexSelection) == 1: 274 # Choose the axes dependent on the normal direction 275 w = norm 276 w.normalize() 277 278 if abs(w.x) < abs(w.y): 279 v = _nukemath.Vector3(0.0, 1.0, 0.0) 280 u = w.cross(v) 281 u.normalize() 282 # Update v 283 v = w.cross(u) 284 else: 285 u = _nukemath.Vector3(1.0, 0.0, 0.0) 286 v = w.cross(u) 287 v.normalize() 288 # Update v 289 u = w.cross(v) 290 291 # Fabricate a tri (tuple) 292 planeTri = (_nukemath.Vector3(0.0, 0.0, 0.0), u, v) 293 294 rotationVec = planeRotation(planeTri, norm) 295 296 # In fact this only handles ZXY (see planeRotation) 297 rotationVec.z = 0 298 299 return rotationVec
300 301 # 302 # Geometric functions 303 # 304
305 -def calcAveragePosition(vertexSelection):
306 ''' 307 Calculate the average position of all points. 308 309 @param points: An iterable sequence of _nukemath.Vector3 objects. 310 @return: A _nukemath.Vector3 containing the average of all the points. 311 ''' 312 pos = _nukemath.Vector3(0, 0, 0) 313 count = 0 314 for info in vertexSelection: 315 point = info.position 316 pos += point 317 count += 1 318 if count == 0: 319 return 320 pos /= count 321 return pos
322
323 -def calcBounds(vertexSelection):
324 # Get the bounding box of all the selected points 325 # Avoid zero size to allow inverse scaling (1/scale) 326 high = _nukemath.Vector3(1e-20, 1e-20, 1e-20) 327 low = _nukemath.Vector3(-1e-20, -1e-20, -1e-20) 328 for info in vertexSelection: 329 pos = info.position 330 for i in xrange(len(pos)): 331 if pos[i] < low[i]: 332 low[i] = pos[i] 333 elif pos[i] > high[i]: 334 high[i] = pos[i] 335 336 bounds = high - low 337 return bounds
338
339 -def planeRotation(tri, norm=None):
340 ''' 341 Calculate the rotations around the X, Y and Z axes that will align a plane 342 perpendicular to the Z axis with the given triangle. 343 344 @param tri: A list or tuple of 3 _nukemath.Vector3 objects. The 3 points must 345 describe the plane (i.e. they must not be collinear). 346 @return: A _nukemath.Vector3 object where the x coordinate is the angle of 347 rotation around the x axis and so on. 348 @raise ValueError: if the three points are collinear. 349 ''' 350 # Get the vectors along two edges of the triangle described by the three points. 351 a = tri[1] - tri[0] 352 b = tri[2] - tri[0] 353 354 # Calculate the basis vectors for an orthonormal basis, where: 355 # - u is parallel to a 356 # - v is perpendicular to u and lies in the plane defined by a and b 357 # - w is perpendicular to both u and v 358 u = _nukemath.Vector3(a) 359 u.normalize() 360 w = a.cross(b) 361 w.normalize() 362 v = w.cross(u) 363 364 # If a normal was passed in, check to make sure that the one we're generating 365 # is aligned close to the same way 366 if norm != None: 367 if w.dot(norm) < 0.0: 368 w.x = -w.x 369 w.y = -w.y 370 w.z = -w.z 371 # Don't mirror! 372 v.x = -v.x 373 v.y = -v.y 374 v.z = -v.z 375 376 # Now find the rotation angles necessary to align a card to the uv plane. 377 m = ( (u[0], v[0], w[0]), 378 (u[1], v[1], w[1]), 379 (u[2], v[2], w[2]) ) 380 if abs(m[1][2]) == 1.0: 381 ry = 0.0 382 rx = (math.pi / 2.0) * -m[1][2] 383 rz = math.atan2(-m[0][1], m[0][0]) 384 else: 385 cosx = math.sqrt(m[0][2] ** 2 + m[2][2] ** 2) 386 if cosx == 0: 387 cosx = 1.0 388 rx = math.atan2(-m[1][2], cosx) 389 rz = math.atan2(m[1][0] / cosx, m[1][1] / cosx) 390 ry = math.atan2(m[0][2] / cosx, m[2][2] / cosx) 391 392 return _nukemath.Vector3(rx, ry, rz)
393 394 # 395 # Helper functions 396 #
397 -def averageNormal(vertexSelection):
398 ''' 399 averageNormal(selectionThreshold -> _nukemath.Vector3 400 Return a _nukemath.Vector3 which is the average of the normals of all selected points 401 ''' 402 403 if not nuke.activeViewer(): 404 return None 405 406 # Put the indices in a dictionary for fast searching 407 fastIndices = vertexSelection.indices() 408 409 found = False 410 norm = _nukemath.Vector3(0.0, 0.0, 0.0) 411 412 for theNode in allNodesWithGeoSelectKnob(): 413 geoSelectKnob = theNode['geo_select'] 414 sel = geoSelectKnob.getSelection() 415 objs = geoSelectKnob.getGeometry() 416 417 for o in xrange(len(sel)): 418 objPrimitives = objs[o].primitives() 419 objTransform = objs[o].transform() 420 421 # Use a dictionary for fast searching 422 visitedPrimitives = {} 423 for prim in objPrimitives: 424 # This will be slow! 425 if prim not in visitedPrimitives: 426 for pt in prim.points(): 427 # This will be slow! 428 if pt in fastIndices: 429 found = True 430 n = prim.normal() 431 n = _nukemath.Vector3(n[0], n[1], n[2]) 432 n = objTransform.vtransform(n) 433 norm += n 434 visitedPrimitives[prim] = pt 435 break 436 437 if found == False: 438 return None 439 440 norm.normalize() 441 return norm
442 443
444 -def anySelectedPoint(selectionThreshold=0.5):
445 ''' 446 anySelectedPoint(selectionThreshold) -> _nukemath.Vector3 447 Return a selected point from the active viewer or the first viewer with a selection. 448 The selectionThreshold parameter is used when working with a soft selection. 449 Only points with a selection level >= the selection threshold will be returned by this function. 450 ''' 451 if not nuke.activeViewer(): 452 return None 453 454 for n in allNodesWithGeoSelectKnob(): 455 geoSelectKnob = n['geo_select'] 456 sel = geoSelectKnob.getSelection() 457 objs = geoSelectKnob.getGeometry() 458 for o in xrange(len(sel)): 459 objSelection = sel[o] 460 for p in xrange(len(objSelection)): 461 if objSelection[p] >= selectionThreshold: 462 pos = objs[o].points()[p] 463 tPos = objs[o].transform() * _nukemath.Vector4(pos.x, pos.y, pos.z, 1.0) 464 return _nukemath.Vector3(tPos.x, tPos.y, tPos.z) 465 return None
466 467
468 -def selectedPoints(selectionThreshold=0.5):
469 ''' 470 selectedPoints(selectionThreshold) -> iterator 471 472 Return an iterator which yields the position of every point currently 473 selected in the Viewer in turn. 474 475 The selectionThreshold parameter is used when working with a soft selection. 476 Only points with a selection level >= the selection threshold will be 477 returned by this function. 478 ''' 479 if not nuke.activeViewer(): 480 return 481 482 for info in selectedVertexInfos(selectionThreshold): 483 yield info.position
484 485
486 -def getSelection(selectionThreshold=0.5):
487 # Build a VertexSelection from VertexInfos 488 vertexSelection = VertexSelection() 489 for info in selectedVertexInfos(selectionThreshold): 490 vertexSelection.add(info) 491 return vertexSelection
492 493
494 -def selectedVertexInfos(selectionThreshold=0.5):
495 ''' 496 selectedVertexInfos(selectionThreshold) -> iterator 497 498 Return an iterator which yields a tuple of the index and position of each 499 point currently selected in the Viewer in turn. 500 501 The selectionThreshold parameter is used when working with a soft selection. 502 Only points with a selection level >= the selection threshold will be 503 returned by this function. 504 ''' 505 if not nuke.activeViewer(): 506 return 507 508 for n in allNodesWithGeoSelectKnob(): 509 geoSelectKnob = n['geo_select'] 510 sel = geoSelectKnob.getSelection() 511 objs = geoSelectKnob.getGeometry() 512 for o in xrange(len(sel)): 513 objSelection = sel[o] 514 objPoints = objs[o].points() 515 objTransform = objs[o].transform() 516 for p in xrange(len(objSelection)): 517 value = objSelection[p] 518 if value >= selectionThreshold: 519 pos = objPoints[p] 520 tPos = objTransform * _nukemath.Vector4(pos.x, pos.y, pos.z, 1.0) 521 yield VertexInfo(o, p, value, _nukemath.Vector3(tPos.x, tPos.y, tPos.z))
522 523
524 -def anySelectedVertexInfo(selectionThreshold=0.5):
525 ''' 526 anySelectedVertexInfo(selectionThreshold) -> VertexInfo 527 528 Returns a single VertexInfo for a selected point. If more than one point is 529 selected, one of them will be chosen arbitrarily. 530 531 The selectionThreshold parameter is used when working with a soft selection. 532 Only points with a selection level >= the selection threshold will be 533 returned by this function. 534 ''' 535 if not nuke.activeViewer(): 536 return None 537 538 for n in allNodesWithGeoSelectKnob(): 539 geoSelectKnob = n['geo_select'] 540 sel = geoSelectKnob.getSelection() 541 objs = geoSelectKnob.getGeometry() 542 for o in xrange(len(sel)): 543 objSelection = sel[o] 544 objPoints = objs[o].points() 545 objTransform = objs[o].transform() 546 for p in xrange(len(objSelection)): 547 value = objSelection[p] 548 if value >= selectionThreshold: 549 pos = objPoints[p] 550 tPos = objTransform * _nukemath.Vector4(pos.x, pos.y, pos.z, 1.0) 551 return VertexInfo(o, p, value, _nukemath.Vector3(tPos.x, tPos.y, tPos.z)) 552 return None
553 554
555 -def allNodes(node = nuke.root()):
556 ''' 557 allNodes() -> iterator 558 Return an iterator which yields all nodes in the current script. 559 560 This includes nodes inside groups. They will be returned in top-down, 561 depth-first order. 562 ''' 563 yield node 564 if hasattr(node, "nodes"): 565 for child in node.nodes(): 566 for n in allNodes(child): 567 yield n
568 569
570 -def allNodesWithGeoSelectKnob():
571 if nuke.activeViewer(): 572 preferred_nodes = [n for n in nuke.activeViewer().getGeometryNodes() if 'geo_select' in n.knobs()] 573 else: 574 preferred_nodes = [] 575 nodes = preferred_nodes + [n for n in allNodes() if 'geo_select' in n.knobs() and n not in preferred_nodes] 576 return nodes
577 578
579 -def cameraProjectionMatrix(cameraNode):
580 'Calculate the projection matrix for the camera based on its knob values.' 581 582 # Matrix to transform points into camera-relative coords. 583 camTransform = cameraNode['transform'].value().inverse() 584 585 # Matrix to take the camera projection knobs into account 586 roll = float(cameraNode['winroll'].getValue()) 587 scale_x, scale_y = [float(v) for v in cameraNode['win_scale'].getValue()] 588 translate_x, translate_y = [float(v) for v in cameraNode['win_translate'].getValue()] 589 m = _nukemath.Matrix4() 590 m.makeIdentity() 591 m.rotateZ(math.radians(roll)) 592 m.scale(1.0 / scale_x, 1.0 / scale_y, 1.0) 593 m.translate(-translate_x, -translate_y, 0.0) 594 595 # Projection matrix based on the focal length, aperture and clipping planes of the camera 596 focal_length = float(cameraNode['focal'].getValue()) 597 h_aperture = float(cameraNode['haperture'].getValue()) 598 near = float(cameraNode['near'].getValue()) 599 far = float(cameraNode['far'].getValue()) 600 projection_mode = int(cameraNode['projection_mode'].getValue()) 601 p = _nukemath.Matrix4() 602 p.projection(focal_length / h_aperture, near, far, projection_mode == 0) 603 604 # Matrix to translate the projected points into normalised pixel coords 605 format = nuke.root()['format'].value() 606 imageAspect = float(format.height()) / float(format.width()) 607 t = _nukemath.Matrix4() 608 t.makeIdentity() 609 t.translate( 1.0, 1.0 - (1.0 - imageAspect / float(format.pixelAspect())), 0.0 ) 610 611 # Matrix to scale normalised pixel coords into actual pixel coords. 612 x_scale = float(format.width()) / 2.0 613 y_scale = x_scale * format.pixelAspect() 614 s = _nukemath.Matrix4() 615 s.makeIdentity() 616 s.scale(x_scale, y_scale, 1.0) 617 618 # The projection matrix transforms points into camera coords, modifies based 619 # on the camera knob values, projects points into clip coords, translates the 620 # clip coords so that they lie in the range 0,0 - 2,2 instead of -1,-1 - 1,1, 621 # then scales the clip coords to proper pixel coords. 622 return s * t * p * m * camTransform
623 624
625 -def projectPoints(camera=None, points=None):
626 ''' 627 projectPoint(camera, points) -> list of nuke.math.Vector2 628 629 Project the given 3D point through the camera to get 2D pixel coordinates. 630 631 @param camera: The Camera node or name of the Camera node to use for projecting 632 the point. 633 @param points: A list or tuple of either nuke.math.Vector3 or of list/tuples of 634 three float values representing the 3D points. 635 @raise ValueError: If camera or point is invalid. 636 ''' 637 638 camNode = None 639 if isinstance(camera, nuke.Node): 640 camNode = camera 641 elif isinstance(camera, str): 642 camNode = nuke.toNode(camera) 643 else: 644 raise ValueError, "Argument camera must be a node or the name of a node." 645 646 camMatrix = cameraProjectionMatrix(camNode) 647 if camMatrix == None: 648 raise RuntimeError, "snap3d.cameraProjectionMatrix() returned None for camera." 649 650 if not ( isinstance(points, list) or isinstance(points, tuple) ): 651 raise ValueError, "Argument points must be a list or tuple." 652 653 for point in points: 654 # Would be nice to not do this for every item but since lists/tuples can 655 # containg anything... 656 if isinstance(point, nuke.math.Vector3): 657 pt = point 658 elif isinstance(point, list) or isinstance(point, tuple): 659 pt = nuke.math.Vector3(point[0], point[1], point[2]) 660 else: 661 raise ValueError, "All items in points must be nuke.math.Vector3 or list/tuple of 3 floats." 662 663 tPos = camMatrix * nuke.math.Vector4(pt.x, pt.y, pt.z, 1.0) 664 yield nuke.math.Vector2(tPos.x / tPos.w, tPos.y / tPos.w)
665 666 667
668 -def projectPoint(camera=None, point=None):
669 ''' 670 projectPoint(camera, point) -> nuke.math.Vector2 671 672 Project the given 3D point through the camera to get 2D pixel coordinates. 673 674 @param camera: The Camera node or name of the Camera node to use for projecting 675 the point. 676 @param point: A nuke.math.Vector3 or of list/tuple of three float values 677 representing the 3D point. 678 @raise ValueError: If camera or point is invalid. 679 ''' 680 681 return projectPoints( camera, (point,) ).next()
682 683 684
685 -def projectSelectedPoints(cameraName='Camera1'):
686 ''' 687 projectSelectedPoints(cameraName='Camera1') -> iterator yielding nuke.math.Vector2 688 689 Using the specified camera, project all of the selected points into 2D pixel 690 coordinates and return their locations. 691 692 @param cameraName: Optional name of the Camera node to use for projecting the 693 points. If omitted, will look for a node called Camera1. 694 ''' 695 camNode = nuke.toNode(cameraName) 696 camMatrix = cameraProjectionMatrix(camNode) 697 for pt in selectedPoints(): 698 tPos = camMatrix * nuke.math.Vector4(pt.x, pt.y, pt.z, 1.0) 699 yield nuke.math.Vector2(tPos.x / tPos.w, tPos.y / tPos.w)
700 701 702 # 703 # Managing the list of snapping functions. 704 # 705 706 # The list of snapping functions. Treat this as a read-only variable; if you 707 # want to add a new snapping function call addSnapFunc (below) 708 snapFuncs = [] 709 710
711 -def addSnapFunc(label, func):
712 ''' 713 addSnapFunc(label, func) -> None 714 Add a new snapping function to the list. 715 716 The label parameter is the text that will appear in (eg.) an Enumeration_Knob 717 for the function. It cannot be the same as any existing snap function label 718 (if it is, the function will abort without changing anything). 719 720 The func parameter is the snapping function. It must be a callable object 721 taking a single parameter: the node to perform the snapping on. 722 ''' 723 if label in dict(snapFuncs): 724 return 725 snapFuncs.append( (label, func) )
726 727
728 -def callSnapFunc(nodeToSnap=None):
729 ''' 730 callSnapFunc(nodeToSnap) -> None 731 Call the snapping function on a node. 732 733 The nodeToSnap parameter is optional. If it's not specified, or is None, we 734 use the result of nuke.thisNode() instead. 735 736 The node must have an Enumeration_Knob called "snapFunc" which selects the 737 snapping function to call. 738 ''' 739 if nodeToSnap is None: 740 nodeToSnap = nuke.thisNode() 741 742 # Make sure that the nodeToSnap has a snapFunc knob 743 if "snapFunc" not in nodeToSnap.knobs(): 744 # TODO: warn the user that we can't snap this node. 745 return 746 747 snapFunc = dict(snapFuncs)[nodeToSnap['snapFunc'].value()] 748 snapFunc(nodeToSnap)
749