[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
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):
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()
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
712    '''
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
<!--
expandto(location.href);
// -->

```

 Generated by Epydoc 3.0.1 on Fri Jun 30 18:46:46 2017 http://epydoc.sourceforge.net