top of page
python in nuke

Lesson 1 : Python in Nuke

In this course, we will learn how to use Python in Nuke to explore all possible potentials with the basics of Python in Nuke. We will learn how to create nodes, connect them, position them, and be able to use an IDE. We'll also learn how to interconnect the IDE with Nuke. Finally, we will explore four practical exercises to conclude the course.

Table of Contents

1. Why use Python in Nuke
2. First step
3. First command
4. First script
5. Working environment
6. Putting it into practice

7. Sources

1. Why use Python in Nuke

Nuke is a widely used compositing software in the film and television industry for creating visual effects. It allows for the combination of different image sources and the application of effects to produce final images.

​

​

Python is a versatile programming language that is

widelyused in the VFX industry. The integration of

Python into Nuke enables the automation of repetitive

tasks, the creationof custom scripts, and the extension

of Nuke's basic functionalities.

python nuke

2. First step

To start using Python in Nuke, open Nuke and go to the Python script window "Script Editor". It is usually located at the top of the user interface.

python in nuke

Now you will need to import the 'nuke' module in order to use any method that Nuke provides.

python nuke

The script editor interface in Nuke looks like this. At the bottom is where your code resides, and above is what would resemble your terminal, where you'll see the output of your commands. The small left-facing arrow allows you to execute your code. However, it will erase all your code written below. But if you select all of your code before executing it, it won't erase it.

import nuke

3. First command

To start, we'll learn how to create nodes, access their parameters, and connect nodes together. First, for example, let's say we want to create a Blur node to blur an image. For this, we can use the method nuke.createNode() and pass the name of the desired node as a parameter. For instance, to create a Blur node, you can use nuke.createNode('Blur'). Alternatively, you can write the method like this: nuke.nodes.Blur().

nuke.createNode()
nuke.nodes.blur()

There was no significant difference between nuke.nodes.Blur() and nuke.createNode('Blur') in terms of the final result.

 

The main difference lies in the final visualization. For example, with the createNode() method, the node will be loaded directly into the properties and selected, and if you have previously selected a mode, it will be automatically connected. Another difference is that with the method nuke.createNode('Blur'), you can only pass one parameter. I recommend using nuke.nodes.Blur() as it provides more convenience.

​

Now that we have created the node, we would like to be able to change the values inside it. For example, we want to change the 'size' of the Blur.

node blur

To do this, we need to use the following method to modify this value: node['size'].setValue(10).

If you pay attention, you can see that this command is quite specific and particular. In fact, in Nuke, we will work extensively with classes and objects to modify the parameters of nodes, because in Nuke, each node is a specific object of a class.

​

For example, if we take the example mentioned above, node = nuke.createNode('Blur'), it simply means that we are creating an object of the Blur class. You can easily verify this by printing the type of the variable node, and you'll see that the result is indeed a class.

node blur

This means that for every created node, we have an object, and within this object, we can change many things.

​

So, let's get back to what we wanted to do. If we execute the following command: node['size'].setValue(51), we change the size to 51.

cmds.createNode()
size blur nuke

It's possible to directly set a node's parameters during its creation using Nuke's createNode() function. Here's how to do it: nuke.createNode('Blur', 'size 10') or with the second recommended method nuke.nodes.Blur(size=10).

In this example, a Blur blur node is created and the 'size' parameter is set to 10 during its creation. This is equivalent to creating the node and then setting its size value with node['size'].setValue(10).

nuke python

Now, what we can also do is have the ability to connect nodes together. For this, nothing complicated. The method nuke.setInput() allows you to connect the input of the target node to the given source node. For example, if we want to connect our Blur node to the Color Bar node, you simply use this command: node.setInput(0, source_node).

​

The first parameter determines which input to choose, as some nodes may have multiple inputs. '0' is for the first input, '2' is for the third input, '4' is for the fifth input if the node has 5 inputs.

​

The second parameter will be the object of a class.

setInput()

To retrieve the instance of a selected node, you will need to use nuke.selectedNode().

For example, if we want to connect a shuffle node to the selected node 'colorbars', since we are going to use this input and this input requires an object of a class, an instance of a node, we cannot simply write setInput(0, 'ColorBars1') because this won't work. However, thanks to nuke.selectedNode(), we can retrieve the instance of the node.

setInput()

4. First Script

In this situation, we want to import an image in EXR format in order to break down the image using a shuffle node and retrieve, for example, the alpha channel of the image. To do this, we'll need to create a 'Read' node, create a 'Shuffle' node, connect them together, and set up the 'Shuffle' node to retrieve only the Alpha map and make it visible in the color channels.

So, in this case, we will use the following methods: nuke.nodes.Shuffle2(in1 = "layer your want")  and nuke.setInput().

create node nuke

In1 modifies the shuffle input layer.

Everything is working correctly except for one thing. Indeed, the script has brought in my image, created the shuffle, and set the input layer to Alpha. But it's not exactly what I want. I would like the Alpha channel to be connected to the RGB outputs so that I can see the image in the RGB channels.

​

To do this, we need to use the 'mappings' parameter to map the inputs and outputs of the shuffle. We use the method knob() for this. This method allows us to set the elements we want.

layer nuke

import nuke

 

read = nuke.nodes.Read(file = 'D:/document/site/programme_cours/sphere.exr')

 

shuf = nuke.nodes.Shuffle2(in1 = "alpha")

shuf.setInput(0, read)

shuf.knob("mappings").setValue([    (0, 'rgba.alpha', 'rgba.red'),

                                                         (0, 'rgba.alpha', 'rgba.green'),

                                                         (0, 'rgba.alpha', 'rgba.blue')])

In this case, we use Knob() to indicate that we want to access the 'mapping' parameter, and we use setValue() to set the value. To set the mapping, it requires a list containing tuples.

To understand this, it's quite simple: for each tuple, the '0' will correspond to the shuffle input, whether it's input A or input B. '0' represents input B. Then, the second parameter, in the example 'rgba.alpha', will be the input we want to connect. Finally, in the example 'rgba.red', the last parameter will be the output we want to connect.

Then, we just need to repeat the action for each tuple. Keep in mind that one tuple represents one connection. So, if we want 4 connections, we'll need 4 tuples. If we want 2 connections, we'll need 2 tuples.

Thanks to the lines of code mentioned just above, we have the ability to create a lot of things and set up a lot of nodes. However, we will encounter a visual problem that is not related to the code itself. In fact, if we create a lot of shuffles that are all connected to the image, or if we decide to move around in the node graph and decide to create a node, it can become quite messy because the nodes will be placed somewhat randomly.

pyttho nuke
python nuke

To prevent this issue, we will use the methods setXpos() / setYpos() and the methods xpos() / ypos().

The setXpos() / setYpos() method allows you to move an element to the desired position.

The xpos() / ypos() method allows you to determine the position of a given node.

nuke node position
nuke node position

Thanks to these methods, we can now determine the positions of nodes and move them. The advantage now is that when creating a node, we can place it exactly where we want. So, if we want to create a multitude of nodes and arrange them in columns or rows, we can execute that.

import nuke

​

sel = nuke.selectedNode()

 

nmb_ligne = 20

count_ligne = 0

addPosX = 0

addPosY = 150

 

for i in range(50):

    shuf = nuke.nodes.Shuffle2()

    shuf.setInput(0, sel)

 

    if nmb_ligne == count_ligne:

        addPosY += 50

        addPosX = 0

        count_ligne = 0

 

    shuf.setXpos(sel.xpos() + addPosX)

    shuf.setYpos(sel.ypos() + addPosY)

​

    addPosX += 100

    count_ligne +=1

nuke python
nuke python

We will start by choosing the number of elements we want per row. Then, we simply create the nodes, connect them, and position the created shuffle node by retrieving information from the selected node and adding an offset.

In summary, this script creates 50 Shuffle2 nodes, connects them to the selected node, positions them in rows (with 20 nodes per row), and shifts them horizontally. When the maximum number of nodes per row is reached, the next nodes are placed on a new row.

5. Working environment

For practical reasons, we will not code directly, and instead, we will use an IDE called Visual Studio, which you can download here. While working in Visual Studio, we can also download a Nuke extension to have specialized auto-completions for Nuke methods. This will allow us to increase productivity and avoid any possible errors.

visual studio nuke
visual studio nuke

To be able to execute your program in Nuke, there are two solutions: either you copy-paste your code, or in Nuke, you need to assign it a path that will read the Python files you have placed in this path. To add a path, you need to use this command nuke.pluginAddPath() and provide the path as a parameter.

"C:\your\path\ ..\your_folder"

​

To ensure that the Python script works correctly, you need to create a Python folder in your path so that Nuke understands it's a Python script. Then simply put your script in the folder and import it as a module in Nuke.

pluginAddPath()
visual studio nuke

Next, you simply need to import the script and use the module reload function. module reload allows you to reset your module so that every time you save and make changes, they take effect. This is important because otherwise, you would need to shut down and restart Nuke each time, which can be quite inconvenient.

nuke script

6. Putting it into practice

In this situation, we have a render of an image generated with Renderman. We've output all the render passes that decompose the beauty render. We're going to create a script that will composite the beauty render. In this example, we'll use this image and all its passes.

renderman
avos
avos

indirect diffuse

​indirect spec

aovs

direct diffuse​​​

passe aovs

direct spec

For this, we'll first need to create a script that creates four shuffles and merges them together to reconstruct the beauty. Then, in the second step, we'll create a script that will automatically detect the different passes and any light groups if we have them.

nuke extention
python nuke

Thanks to this script, we've created four shuffles, established all the connections, and set up all the merges. However, it lacks some organization. Thanks to the script, we can easily arrange our workspace without wasting time. So, I take the opportunity to add some nodes and position them correctly.

extention nuke visual
nuke python

Now, it's much better with a clear structure. The code I wrote for you works perfectly. I've written it in the clearest way possible. However, it can be optimized. We can write it like this.

extention visual studio
nuke

import nuke as nk

 

sel = nk.selectedNode()

 

list_positionX_shuf = [150, 50, -150, -50]

list_shuf = []

for i in range(4):

    shuf = nk.nodes.Shuffle2()

    shuf.setInput(0, sel)

    shuf.setXpos(sel.xpos() + list_positionX_shuf[i])

    shuf.setYpos(sel.ypos() + 100)

    list_shuf.append(shuf)

 

list_position_dot = [184, 154, -116, 154, 84, 204]

list_position_merge = [50 ,150,-50,150,-50, 200]

 

for i in range(3):

    dot = nk.nodes.Dot()

    dot.setXpos(sel.xpos() + list_position_dot[i*2])

    dot.setYpos(sel.ypos() + list_position_dot[i*2+1])

 

    Mplus = nk.nodes.Merge(operation = 'plus')

    Mplus.setXpos(sel.xpos() + list_position_merge[i*2])

    Mplus.setYpos(sel.ypos() + list_position_merge[i*2+1])

    if i == 0: first_merge = Mplus

    if i == 2:

        Mplus.setInput(0, old_merge)

        dot.setInput(0, first_merge)

    else:

        dot.setInput(0, list_shuf[i*2])

        old_merge = Mplus

        Mplus.setInput(0, list_shuf[2*i+1])

    Mplus.setInput(1, dot)

Now, we need to create the function that will allow us to find the different light groups. For this, our program will simply look in the various available channels of the image for the names of the channels and then sort them.

We will then group them together in a dictionary, and for each light group, it will consist of an indirect diffuse, indirect specular, a direct diffuse, and a direct specular.

For example, for our image, we have the following passes available

picture

To detect the different passes of an image in Nuke, you need to use the channels() method. By using this, we'll be able to list all the channels available for this image.

channels()

Thanks to this, we retrieve each channel of each pass/layer. To get only the names of the layers, we just need to clean the list that is returned to us, modify the name by removing the various .red, .green, .blue, etc., and remove any duplicates in the list. So in the end, we'll have only this.

channels()

Now, all that's left is to manage the different layers to determine if it's a direct pass, an indirect pass, or something else, and to sort the different layers by light group. In this case, there's only one, which is 'Left'.

channels()

Test performed on the two images: one image has only one light group 'left', and the other image has two light groups 'left' and 'right'.

decompose

import nuke as nk

 

sel = nk.selectedNode()

all_cahnnels = sel.channels()

liste = [element.split('.')[0] for element in all_cahnnels]

all_layers = list(set(liste))

   

list_lightGrp = []

doc_layer_LightGRP = {}

for layer in all_layers:

    if 'indirectDiffuse' in layer:

        list_lightGrp += [layer]

           

for lightGRP in list_lightGrp:

    name_lightGRP = lightGRP.split('indirectDiffuse')[1]

 

    if 'directDiffuse' + name_lightGRP + '.red' in all_cahnnels:

        directdif = 'directDiffuse' + name_lightGRP

 

    if 'directSpecular' + name_lightGRP + '.red' in all_cahnnels:

         specdif = 'directSpecular' + name_lightGRP

 

    if 'indirectSpecular' + name_lightGRP + '.red' in all_cahnnels:

        specindif = 'indirectSpecular' + name_lightGRP

   

    doc_layer_LightGRP[name_lightGRP] = [directdif, lightGRP, specdif, specindif]

   

if not doc_layer_LightGRP: doc_layer_LightGRP['null'] = ["rgba", "rgba", "rgba", "rgba"]

This script searches among all the layers for the layers that have 'indirect diffuse' in their name and stores them. By doing this, we retrieve all the names of the light groups (in this case, 'indirectDiffuse_left'). Then, once we have all the light groups, we check if there are RGB channels for the direct diffuses, direct speculars, etc. This allows us to know if the passes actually exist. Then, once this is done, we add, for each light group, its corresponding passes to the dictionary. In this case, for the 'left' groups, the program does this:

​

doc_layer_LightGRP[_left] = [directDiffuse_left, indirectDiffuse_left, directSpecular_left, indirectSpecular_left]

​

The last line is just there in case it doesn't find any groups. It will create a default structure.

Total assembly

Perfect, now with this, we have organized the different passes by light group. This will allow us to set up the shuffles with the new passes. Thanks to the first script, we have the structure to compose the beauty, and the second script helps us sort and organize the different light groups and passes in the image. All that's left is to combine the two, and everything will be perfect.

We first run the program that helps us sort and organize the different passes, and then we just need to create the structure for each light group and set up the shuffles.

comositing nuke
shuffles nuke

base picture Beauty

copositing

Light Groupe Left

copositing

Light Groupe Rgiht

copositing

script nuke

script nuke

The script is quite dense for a first script, that's true. However, it will always be very useful because it covers the fundamentals of Python in Nuke. These basics will remain valuable for various situations.

​

We've learned the basics of scripting in Nuke, which allowed us to understand the main methods that enable us to optimize and save a tremendous amount of time in Nuke. Thanks to this, we were able to connect the initial nodes, set up the nodes, and also understand how to interact with the different nodes and manipulate their values. This enabled us to create a script that detects layers in an image, manipulates them, stores them, and composites an image from passes without any human intervention

import nuke as nk

 

sel = nk.selectedNode()

def composeBeauty():

    doc_layer_LightGRP = detectpasse()

    offset = 0

    for lightGRP in doc_layer_LightGRP:

        list_positionX_shuf = [150 + offset, 50 + offset, -150 + offset, -50 + offset]

        list_shuf = []

        for i in range(4):

            shuf = nk.nodes.Shuffle2(in1 = doc_layer_LightGRP[lightGRP][i])

            shuf.setInput(0, sel)

            shuf.setXpos(sel.xpos() + list_positionX_shuf[i])

            shuf.setYpos(sel.ypos() + 100)

            list_shuf.append(shuf)

 

        list_position_dot = [184 + offset, 154, -116 + offset, 154, 84 + offset, 204]

        list_position_merge = [50 + offset ,150,-50 + offset,150,-50 + offset, 200]

 

    for i in range(3):

        dot = nk.nodes.Dot()

        dot.setXpos(sel.xpos() + list_position_dot[i*2])

        dot.setYpos(sel.ypos() + list_position_dot[i*2+1])

​

        Mplus = nk.nodes.Merge(operation = 'plus')

        Mplus.setXpos(sel.xpos() + list_position_merge[i*2])

        Mplus.setYpos(sel.ypos() + list_position_merge[i*2+1])

        if i == 0: first_merge = Mplus

        if i == 2:

            Mplus.setInput(0, old_merge)

            dot.setInput(0, first_merge)

        else:

            dot.setInput(0, list_shuf[i*2])

            old_merge = Mplus

            Mplus.setInput(0, list_shuf[2*i+1])

        Mplus.setInput(1, dot)

        offset += 400

 

def detectpasse():

    all_cahnnels = sel.channels()

    liste = [element.split('.')[0] for element in all_cahnnels]

    all_layers = list(set(liste))

   

    list_lightGrp = []

    doc_layer_LightGRP = {}

    for layer in all_layers:

        if 'indirectDiffuse' in layer:

            list_lightGrp += [layer]

           

    for lightGRP in list_lightGrp:

        name_lightGRP = lightGRP.split('indirectDiffuse')[1]

​

        if 'directDiffuse' + name_lightGRP + '.red' in all_cahnnels:

            directdif = 'directDiffuse' + name_lightGRP

​

        if 'directSpecular' + name_lightGRP + '.red' in all_cahnnels:

            specdif = 'directSpecular' + name_lightGRP

​

        if 'indirectSpecular' + name_lightGRP + '.red' in all_cahnnels:

            specindif = 'indirectSpecular' + name_lightGRP

   

        doc_layer_LightGRP[name_lightGRP] = [directdif, lightGRP, specdif, specindif]

   

        if not doc_layer_LightGRP: doc_layer_LightGRP['null'] = ["rgba", "rgba", "rgba", "rgba"]

        return doc_layer_LightGRP

I hope this lesson has served you well !

7. Sources

bottom of page