# Using the nuke.math Python module to do Vector and Matrix operations

Written by Ivan Busquets on .

# 4. Misc Examples

Even with the limitations mentioned in the introduction of this tutorial (basically the fact that Nuke doesn't provide a way to push pixel data into an image using Python), there's still plenty of situations where the nuke.math module can come in handy.  Here's a few examples, hoping they will shed one last bit of light into the uses and possibilities of the nuke.math module.

#### Using nuke.math in a gizmo

An example of a gizmo that uses the nuke.math module can be found here:

The gizmo generates tracking data from a world position pass and camera data stored in Renderman EXR renders.

To do so, it creates vector objects sampling the worldPosition pass for x,y,z coordinates. It then takes the projection matrix written by renderman into the exr metadata, and projects them into screen-space. It then outputs those screen coordinates to XY knobs so it can be used as tracking data.

#### Using nuke.math to create your own Python functions

You can use nuke.math inside your own functions.

Here's an example with a couple of function definitions. First one returns a Matrix4 object from any node that has a matrix knob, and the second one if a convenience function to get the distance from an Axis (or any node that has Axis-like knobs) to the plane defined by a card. This example works only for Nuke version 6.1 and above.

 Distance from an Axis to the plane defined by a card `1234567891011121314151617181920212223242526272829303132333435363738394041424344 ` `def getMatrixFromNode(node): ''' return a nuke.math.Matrix4 object with the transformations of a node "node" is the node object of any 3D node in Nuke that has a world or local matrix knob ''' try: nodeMatrix = nuke.math.Matrix4() if 'world_matrix' in [k for k in node.knobs()]: knobname = 'world_matrix' elif 'matrix' in [k for k in node.knobs()]: knobname = 'matrix' else: raise NameError  for x in range(node[knobname].width()): for y in range(node[knobname].height()): nodeMatrix[x*4+y] = node[knobname].value(y,x) except (NameError, AttributeError): nuke.message('Node %s does not have a valid world_matrix or local_matrix knobs.\nReturning the identity matrix instead' % node.name()) nodeMatrix.makeIdentity() return nodeMatrix  def distanceAxisToCard(axisNode, cardNode): ''' return the distance from an Axis, or any node with Axis-like knobs to the plane defined by a card ''' axisMatrix = getMatrixFromNode(axisNode) cardMatrix = getMatrixFromNode(cardNode)  v = nuke.math.Vector3(0,0,0)  axisPosition = axisMatrix.transform(v) cardPosition = cardMatrix.transform(v) cardNormal = nuke.math.Vector3() cardOrientation = cardNode['orientation'].value()  cardNormal.set(int('X' not in cardOrientation), int('Y' not in cardOrientation), int('Z' not in cardOrientation)) cardNormal = cardMatrix.inverse().ntransform(cardNormal) cardNormal.normalize()  return cardNormal.dot(axisPosition-cardPosition)`

#### Moving DAG art

Doubtful production value, but fun to play with, and a great way to impress your peers :)

2D art: A rotating ColorWheel

Instructions: Copy-paste the code into the script editor on a new Nuke session. Play with the rotation slider

 A rotating ColorWheel in the DAG `12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788` `import math #### Variables #### pointSeparation = 30.0 #half the screen size of an Axis node (60)outRadius = 20.0 # number of Axis nodes from the center to the outer edgeinRadius = 0.0 # number of Axis nodes from the center to the inner edge  #### Helper function ####def HSVtoHEX(h, s, v): """Convert HSV color space to hex values to use on a node's tile_color @param h: Hue @param s: Saturation @param v: Value return hex value """  hi = math.floor(h / 60.0) % 6 f = (h / 60.0) - math.floor(h / 60.0) p = v * (1.0 - s) q = v * (1.0 - (f*s)) t = v * (1.0 - ((1.0 - f) * s))  rgb = { 0: (v, t, p), 1: (q, v, p), 2: (p, v, t), 3: (p, q, v), 4: (t, p, v), 5: (v, p, q), }[hi]  return int('%02x%02x%02x%02x' % (rgb[0]*255,rgb[1]*255,rgb[2]*255,1),16)   #### Main #### m = nuke.math.Matrix3() # Matrix that will drive the rotation of all nodesm.makeIdentity() # Initialize matrix to identity nuke.zoom(0.3,(0,0)) # Set an initial zoom level and center the DAG at 0,0 for i in range(inRadius,outRadius):  radius = pointSeparation * i long = math.pi*radius*2.0  v = nuke.math.Vector3(0,radius,0) points = math.floor(long/pointSeparation)  for x in range(points):  angle = 360*(x/float(points)) m.rotationZ(math.radians(angle)) vv = m.transform(v) d = nuke.nodes.Axis() d['hide_input'].setValue(True) d.setXYpos(vv.x, vv.y) d['translate'].setValue((vv.x,vv.y,0)) d['tile_color'].setValue(HSVtoHEX(angle, radius/(outRadius*pointSeparation),1))  ### code for knobChanged callback on controller node ### knobChangedCode = """ n = nuke.thisNode()k = nuke.thisKnob()if k.name() == 'rotate':  m = nuke.math.Matrix3() m.makeIdentity() m.rotateZ(k.value()*0.0174532925) # Rotation in radians for a in nuke.allNodes('Axis'): v = nuke.math.Vector3(a['translate'].getValue(0), a['translate'].getValue(1), 0.0) tv = m.transform(v) a.setXYpos(tv.x, tv.y) """ control = nuke.createNode('NoOp', 'name controller')control.setXYpos(outRadius * pointSeparation + 200,0)rotKnob = nuke.Double_Knob('rotate')rotKnob.setRange(-180, 180)control.addKnob(rotKnob)nukescripts.autoBackdrop()control['knobChanged'].setValue(knobChangedCode)`

Instructions: Copy-paste the code into the script editor on a new Nuke session. Play with the controller node's knobs.

 A Phong-shaded Sphere in the DAG ```1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 ``` ```# Sphere variables segments = 100.0 theta = 0.0 phi = 0.0 r = 50.0   # Vector container for each of the Sphere's vertices v = nuke.math.Vector3()   # Build the virtual Sphere using Axis2 nodes for i in range(int(segments/2)): phi += (2*nukescripts.math.pi) / segments faces = int (segments * ( 1 - (abs(i-(segments/4))) / (segments/4)))   for j in range( faces): theta += (2*nukescripts.math.pi) / faces v.x = r * nukescripts.math.sin(phi) * nukescripts.math.cos(theta) v.y = r * nukescripts.math.sin(phi) * nukescripts.math.sin(theta) v.z = r * nukescripts.math.cos(phi) d = nuke.nodes.Axis2() d['hide_input'].setValue(True) d['note_font_size'].setValue(0) d['translate'].setValue((v.x,v.y,v.z))   theta = 0.0   ##############################################################################   # create controller node control = nuke.createNode('NoOp', 'name controller', inpanel = False) control.setXYpos(500,0)   # Add all the control knobs and set some initial values lightGroup = nuke.Text_Knob("lightPosition", "lightPosition", "") latKnob = nuke.Double_Knob('latitude') latKnob.setRange(-180, 180) latKnob.setValue(200) longKnob = nuke.Double_Knob('longitude') longKnob.setRange(-180, 180) longKnob.setValue(40)   materialGroup = nuke.Text_Knob("material", "material", "") colorKnob = nuke.Color_Knob('matColor') colorKnob.setValue((0.1, 0.3, 0.3)) ambientKnob = nuke.Double_Knob('ambient') ambientKnob.setValue(0.05) diffuseKnob = nuke.Double_Knob('diffuse') diffuseKnob.setValue(0.18) specularKnob = nuke.Double_Knob('specular') specularKnob.setValue(0.8) shininessKnob = nuke.Double_Knob('shininess') shininessKnob.setRange(1, 128) shininessKnob.setValue(20)   viewGroup = nuke.Text_Knob("viewControls", "viewControls", "") translateKnob = nuke.XYZ_Knob('translate') translateKnob.setValue((0,0,300)) rotateKnob = nuke.XYZ_Knob('rotate') rotateKnob.setValue((0,180,-90)) lensKnob = nuke.Double_Knob('lens') lensKnob.setValue(80)   for k in [lightGroup, latKnob, longKnob, materialGroup, colorKnob, ambientKnob, diffuseKnob, specularKnob, shininessKnob, viewGroup, translateKnob, rotateKnob, lensKnob ]: control.addKnob(k)   ##############################################################################   knobChangedCode = """ n = nuke.thisNode() k = nuke.thisKnob()   def getCameraMatrices(n):   # initialize view matrices p = nuke.math.Matrix4() p.makeIdentity() t = nuke.math.Matrix4() t.makeIdentity() r = nuke.math.Matrix4() r.makeIdentity() o = nuke.math.Matrix4() o.makeIdentity()   p.projection(n['lens'].value()*10, 1, 1000, True)   # translation from the translate knob t.translation(n['translate'].value()[0], n['translate'].value()[1], n['translate'].value()[2])   o.rotateY(nukescripts.math.radians(180)) # make it look towards -Z   # rotations from the rotate knob r.rotateY(nukescripts.math.radians(n['rotate'].value()[1])) r.rotateX(nukescripts.math.radians(n['rotate'].value()[0])) r.rotateZ(nukescripts.math.radians(n['rotate'].value()[2]))   m = r * t   camPosition = m.transform(nuke.math.Vector3(0,0,0))   return p, t, r, camPosition, o     def transformPoints():   p, t, r, camPos, o = getCameraMatrices(n) # set view_matrix from the separate projection, translation, rotation and orientation matrices view_matrix = p * t * o * r   pos = nuke.math.Vector4(0,0,0,1)   # Loop through all points   for node in nuke.allNodes('Axis2'):   pos.x, pos.y, pos.z = node['translate'].value() # Project point onto display coordinates dpos = view_matrix.transform(pos) node.setXYpos(int(dpos.x/dpos.w), int(dpos.y/dpos.w))     def updateShading():   # get color of surface material if n['matColor'].singleValue(): matR = matG = matB = n['matColor'].value() else: matR, matG, matB = n['matColor'].value()   matColor = nuke.math.Vector3(matR, matG, matB)   # get intensities for each light component diffuseIntensity = n['diffuse'].value() specularIntensity = n['specular'].value() ambientIntensity = n['ambient'].value()   # get Light vector latitude = n['latitude'].value() longitude = n['longitude'].value() lx = 200 * nukescripts.math.cos(math.radians(longitude)) * nukescripts.math.cos(math.radians(latitude)) ly = 200 * nukescripts.math.sin(math.radians(longitude)) * nukescripts.math.cos(math.radians(latitude)) lz = 200 * nukescripts.math.sin(math.radians(latitude))   lightPos = nuke.math.Vector3(lx, ly, lz)   # get camera position vector camPos = getCameraMatrices(n)[3]   # Placeholder vector for each one of the points' position P = nuke.math.Vector3(0,0,0)   # Loop through all points for node in nuke.allNodes('Axis2'):   # P = point position vector P.set(node['translate'].value()[0], node['translate'].value()[1], node['translate'].value()[2])   # get a normalized version of P PN = nuke.math.Vector3(P) PN.fast_normalize()   # get normalized vector from light to point point2light = lightPos - P point2light.fast_normalize()   # get diffuse component   D = max(0, PN.dot(point2light))   # get specular component if PN.dot(point2light) > 0.0: point2cam = camPos - P point2cam.fast_normalize() R = point2cam.reflect(PN) rDotV = max(0, R.dot(point2light))   S = pow(rDotV, n['shininess'].value())   else: S = 0.0   specular = nuke.math.Vector3(S,S,S)   # build shader from all components shadedColor = (matColor*ambientIntensity) + (matColor * D * diffuseIntensity) + (specular * specularIntensity)   # set the node color colorR = min(255,nuke.expression('to_sRGB(%f)' % shadedColor[0])) colorG = min(255,nuke.expression('to_sRGB(%f)' % shadedColor[1])) colorB = min(255,nuke.expression('to_sRGB(%f)' % shadedColor[2])) tileColor = int('%02x%02x%02x%02x' % (colorR,colorG,colorB,1),16) node['tile_color'].setValue(tileColor)       if k.name() in ['matColor', 'ambient', 'diffuse', 'specular', 'latitude', 'longitude', 'shininess']: updateShading()   elif k.name() in ['rotate' , 'translate']: updateShading() transformPoints()   elif k.name() == 'lens': transformPoints()   elif k.name() == 'showPanel': import math updateShading() transformPoints() """   # assign knobChanged code control['knobChanged'].setValue(knobChangedCode) # open panel to trigger transformation and shade for the first time nuke.show(control) # set initial zoom level and center of the DAG nuke.zoom(0.6,(0,0))```

+1 # Frank Rueter 2010-09-14 15:09
awesome tutorial! Thanks so much for sharing and putting in that much effort!

0 # Carlos Trijueque 2010-09-16 00:55
Ivan, thanks for sharing this terrific piece of info.Very helpful and, as your other tutorials, very well written and explainied.

0 # Michael Garrett 2010-09-17 11:29
Brilliant! Good info on nuke.math is long overdue!

+1 # Ivan Busquets 2010-09-17 12:06
Thanks for all the comments, guys!

Yes, Michael, completely agree. It's one of those I had to learn the hard way, so I figured it would be useful to share. Plus, I learnt a bit more while writing it. Glad you liked it.

0 # Pete O'Connell 2010-09-20 19:33
Thanks Ivan, that is one of the most awesome tutorials I have ever read!

0 # Zach Lewis 2011-08-06 10:12
Wanted to chime in and say that this is awesome. A lot of care went into preparing this, and it's much appreciated.

0 # Diego Piccinato 2011-09-08 10:53
Awesome stuff, thanks Ivan!

0 # Marco Leone 2013-08-24 00:25
Hey Ivan, thanks a lot for the tutorial, it opened my mind!!!

one quick question thou, I was testing out some of those function on Matrix and I found a little problem.

What I am trying to do is to get the XYZ rotation( in degrees ) from the world matrix of a Camera.

Following you tutorial everythign works fine but the final result of the rotation is a bit different from the original rotation.

what I am doing is really simple :

########################################

import math

matrix = nuke.math.Matri x4() #creating a matrix

matrixValues = nuke.toNode('Ca mera1')['world_ matrix'].getVal ue() # getting the value from the camera world matrix

print "matrixValues :", matrixValues

### adding the value from the camera to the matrix just created ###

for VAL in matrixValues :
print "VAL",VAL
matrix[matrixVa lues.index(VAL) ] = VAL

matrix.rotationOnly() #getting sure I have only the rotation values

print math.degrees(ma trix.rotationsZ XY()[0])
print math.degrees(matrix.rotationsZXY()[1])
print math.degrees(matrix.rotationsZXY()[2])

### check if values r the same

if nuke.toNode('Ca mera1')['rotate '].value(0) == math.degrees(ma trix.rotationsZ XY()[0]) : print True
else: print False

########################################

as u may see it seems that there is an approximation and we loose the last 5 digits on every value when we are adding the worldMatrix of the camera to our Matrix4, and that may be causing the little difference in the result.

Do you know by chance if that is something I did wrong or maybe if there is a work around it ?