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

Written by Ivan Busquets on .

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 non-initialized values
1
2
3
4
5
v1 = nuke.math.Vector3()
v2 = nuke.math.Vector3()
print v1, v2
 
# Result: {1.79998e-37, 1.78e-37, 2.73861e-26} {0, 3.7807e-11, 0}


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
2
3
4
5
v1 = nuke.math.Vector3()
v1.set(1,2,3)
print v1
 
# Result: {1,2,3}


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 non-initialized 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
2
3
4
v1 = nuke.math.Vector3(1, 2, 3)
print v1
 
# Result: {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
2
3
4
5
v1 = nuke.math.Vector3(0,0,0)
v2 = nuke.math.Vector2(v1)
print v1, v2
 
#Result: {0, 0, 0} {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 built-in 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, in-place), whereas lists and dictionaries are mutable types, meaning they can be modified in-place.
Here's a couple of practical examples to show the difference:

"Modifying" an int (which is actually doing an assignment to a new int)
1
2
3
4
5
6
a = 3
b = a
a += 1
print a, b
 
#Result: 4, 3


What happens when you do "a += 4" is a new assignment. We are not modifying the original number, because number types are immutable. Instead, python creates a new instance of type 'int' and assigns its value to the variable, effectively loosing the previous reference 'a' was pointing to. So, by the time we print "a" and "b", "a" and "b" already point to two different objects.

Modifying a list:
1
2
3
4
5
6
a = [1, 2, 3]
b = a
a.append(4)
print a, b
 
# Result: [1, 2, 3, 4] [1, 2, 3, 4] # both a and b are still pointing to the same list


Here, because lists are mutable, the result is different. Like in the previous example, an instance is created (of the type 'list') and assigned to a variable called "a". Then The same list is assigned to variable "b". Both a and b point to the same instance. Then a.append(4) tries to modify the list instance referenced by variable a. Because lists are mutable and can be modified in place, this is a valid operation and the original instance is modified (a new item '4' is appended to it). "a" and "b" are still pointing to the same list, therefore printing their content gets us the same list for both "a" and "b".


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
2
3
4
5
6
v1 = nuke.math.Vector3(0,0,0)
v2 = v1
v2.set(1,2,3)
print v1, v2
 
# Result: {1,2,3} {1,2,3} (v1 and v2 are actually pointing to the same Vector3 instance)


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
2
3
4
5
6
7
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]
myMatrix = nuke.math.Matrix4()
for val in matrixValues:
myMatrix[matrixValues.index(val)] = val
print myMatrix
 
# Result: {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   

 
+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 ?
thanks for your time
 

You have no rights to post comments

We have 4081 guests and 36 members online