Using the nuke.math Python module to do Vector and Matrix operations
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:
http://www.nukepedia.com/gizmos/gizmo-downloads/metadata/prmantracker/
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 | |
1 |
def getMatrixFromNode(node): |
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 | |
1 |
import math |
3D art: A Phong-shaded sphere
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)) |
- << Prev
- Next
Comments
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.
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 ?
thanks for your time
RSS feed for comments to this post