Using the nuke.math Python module to do Vector and Matrix operations
1. Creating Vector and Matrix objects:
Vector objects in the nuke.math module are convenience classes that store a set of attributes (x,y for a Vector2; x,y,z for a Vector3; x,y,z,w for a Vector4) and provide specific methods for each class to perform common vector operations (vector length, normalize, distance between vectors, dot product...) Matrix objects can be either of the Matrix3 or Matrix4 kind. Matrix3 defines a 3x3 transformation matrix, which can describe a translation, rotation and scale transforms. Matrix4 holds a 4x4 transformation matrix, most commonly used to represent perspective transforms. First, let's have a look at how to create new objects/instances of those classes. It's a good habit to check the documentation of any new Python class/method we want to use. So, let's pull the help for the Vector3 Class, for example:
help(nuke.math.Vector3)
And look under its constructor method, to see how an instance of the Vector2 class can be initialized (the constructor is the protected method called __init__):
 __init__(...)
····· __init__( (object)arg1) > None :
····· ...
····· __init__( (object)arg1, (float)arg2, (float)arg3, (float)arg4) > None :
····· ...
····· __init__( (object)arg1, (Vector3)arg2) > None :
····· ...
This is telling us there's 3 ways a Vector3 object can be constructed. If you ignore the first argument (object)arg1, which is just the object we're constructing or 'self', we are left with the following three possible syntaxes to create a new a Vector3. I'll use the Vector3 Class as an example to explain the different ways to create one, but the same methods apply to Vector2 and Vector4 classes. They also apply to Matrix classes, with a small exception that will be covered later.
Creating a new Vector3 instance with no arguments:
Syntax: v = nuke.math.Vector3()
This will create a Vector3 object with NON INITIALIZED values. The values/attributes of a Vector3 object are the x,y,z coordinates. Constructing a Vector 3 this way does NOT set x,y and z to 0. The short explanation is that the whole nuke.math is just a hook to the C++ Vector and Matrix Classes, and in C++, a variable that's not been initialized will be assigned a space in memory, but that space will not be set to any given value until the variable is initialized. So, until then, the value will be unpredictable (it will be the value previously stored at that memory position).
Let's see this in a practical example:
Vector3 with noninitialized values  
1 
v1 = nuke.math.Vector3() 
See how both new Vector3 instances have different values assigned to their x,y and z attributes. This is because they were not initialized in the constructor. You could easily fix that by using the 'set' method available for all Vector instances.
Like this:
Setting values of a Vector3 after creation  
1 
v1 = nuke.math.Vector3() 
So, generally, make sure you only use this method when you know you're going to need a Vector3, don't know what x, y and z values it needs to have yet, but you will set them yourself later.
Otherwise, if you want to avoid unexpected behaviour from noninitialized attributes, give the initial values yourself when creating a new Vector instance, as explained in the following method.
Creating a new Vector3 instance directly setting its arguments:
Syntax: v = nuke.math.Vector3(float, float, float)
This will create a Vector3 object with its 3 attributes (x, y and z) initialized to the value of our choice.
Example:
New Vector3 with initialised values  
1 
v1 = nuke.math.Vector3(1, 2, 3) 
Creating a new Vector3 instance from an existing Vector3:
Syntax: v = nuke.math.Vector3(nuke.math.Vector3)
The following code will create a new Vector3 instance (v2) taking the values from an existing instance of the same class (v1)
New Vector3 copying values from another Vector3  
1 
v1 = nuke.math.Vector3(0,0,0) 
Why would we want to do this, and not just do v2 = v1?
Well, we could, but then we would have two variables referencing the same Vector3 instance. This is because instances (objects) of the Classes defined in the nuke.math module are mutable objects, and therefore work in a similar way to python lists (which are also a mutable type, unlike numbers, strings or tuples) in that they can be modified 'in place'. Another important implication of this is the way we can copy a Vector or Matrix instance into a new variable/instance. Because this will become important later on when explaining some of the Vector and Matrix methods, let me open a parenthesis and show some of the differences between mutable and immutable types using some of the Python builtin types as an example. If you are already familiar with these concepts, feel free to skip the following framed section:
Immutable vs Mutable types in Python
Most python guides make a distinction between mutable and immutable types. Numbers, strings and tuples are immutable, meaning they cannot be modified (or at least not directly, inplace), whereas lists and dictionaries are mutable types, meaning they can be modified inplace. Here's a couple of practical examples to show the difference: "Modifying" an int (which is actually doing an assignment to a new int)
Modifying a list:

So, the method described above allows us to make a fully independent copy of an existing Vector3. If we had attempted to create v2 with just a simple assignment, here's what would have happened:
1 
v1 = nuke.math.Vector3(0,0,0) 
Keep this in mind every time you want to have a copy of your Vector or Matrix objects without altering the original.
Creating Matrix3 and Matrix4 objects:
Creating Matrix objects works the same way as creating Vector objects. The constructor also has the three different variants explained before, except in the case of the Matrix4 class. Matrix4 objects hold the definition of a 4x4 matrix, which is a total of 16 float attributes.
Problem is, the nuke.math module was written using Boost.Python, which has a limit of 15 arguments that can be passed to a function. For that reason, you cannot create a new Matrix4 instance by dirtectly passing it all its values. However, there's ways to work around that. Here's an example on how we can create a Matrix4 with all 16 values, having them stored in a list.
New Matrix object from values stored in a list  
1 
matrixValues = [3039.75, 176.404, 0.155272, 0.155268, 124.794, 2800.11, 0.121872, 0.121869, 547.587, 1113.77, 0.980346, 0.980326, 4755.08, 509.823, 8.00838, 8.20822] 
The code above is creating a new, empy Matrix4 instance (with no initialized values). Then we iterate over each value in our list and assign it to the same index of our Matrix4 instance. Like lists, Matrix4 objects can be treated as an array of values, and we can make assignments to single indices of that array. (ex. myMatrix[3] = 0.0).
Additionally, the nukescripts.snap3d module (Nuke 6.1 only) offers a very convenient function to create a Matrix4 from the knob values of any camera in Nuke. It takes a camera node object as the only argument (we could use the selected node, or nuke.toNode(nodename) to access a camera by name, or any other function that returns a camera node object). The function returns a Matrix4 object. Here's an example where we can create a Matrix4 object out of a selected camera node in Nuke, and assign it to a variable called myMatrix:
myMatrix = nukescripts.snap3d.cameraProjectionMatrix(nuke.selectedNode())
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