Python and TCL

Nuke Python Tutorial and Cheat Sheet

From your first line of Python to 106 snippets, tested in Nuke 17

Python is how you automate Nuke: create and connect nodes, change a knob on a hundred nodes at once, build your own tools, menus and panels. This page starts from zero, for those who have never written a line of Python in Nuke, and then becomes a cheat sheet of 106 snippets organised by what you want to do, ready to copy. Every snippet is Python 3 and I ran each one in Nuke 17 before publishing it.

Type in the box below to filter the snippets (try file, frame, select, callback), or use the index. The companion page with all the TCL is TCL for Nuke. There's also a free gizmo to try the commands directly inside Nuke:

Download the gizmo from Nukepedia





00. Nuke Python for beginners

Python is the language Nuke uses for tools, automation and pipelines: anything you can do with the mouse, you can do with a line of Python, on one node or on a thousand. You don't need to be a programmer to start. If you can read node['size'].setValue(10), you already understand most of it: take a node, take a knob, set a value.

Where you write Python in Nuke

  • The Script Editor (Windows > Script Editor, or the tab of the same name): write in the bottom half, press Ctrl+Enter to run what you selected (or everything), read the result in the top half. This is where you try every snippet of this page.
  • A Python button on a node (a PyScript knob, created with Edit > Manage User Knobs): the code runs when you click it, and nuke.thisNode() is the node that holds the button.
  • menu.py and init.py in your .nuke folder: Nuke runs them at start-up. Use them for your menus, shortcuts, defaults and callbacks (section menu.py and init.py).
  • Inside TCL, in a label or a Text node: [python {nuke.root().name()}].

Python 3 since Nuke 13

Nuke 13 moved from Python 2 to Python 3. I checked the versions installed on my computer: Nuke 12.2 runs Python 2.7, Nuke 13.0 runs Python 3.7, Nuke 15.1 Python 3.10, Nuke 16 and 17 Python 3.11. Many old snippets online are still written in Python 2 and fail with SyntaxError: Missing parentheses in call to 'print': in Python 3 print needs brackets. Every snippet on this page is Python 3 and I ran it in Nuke 17. Nuke 16 also moved the interface from PySide2 to PySide6: if an old tool breaks with No module named 'PySide2', read my page No module named 'PySide2' in Nuke 16+.

Your first script: create a Blur, set its size, print the result

blur = nuke.createNode('Blur')
blur['size'].setValue(25)
print(blur.name(), blur['size'].value())

The same idea on many nodes: every selected node with a size knob gets size 10

for node in nuke.selectedNodes():
    if 'size' in node.knobs():
        node['size'].setValue(10)

Don't know the name of a knob? Print all the knob names of the selected node

print(sorted(nuke.selectedNode().knobs()))

nuke.createNode() works like the Tab menu: it connects the new node to the selected one and opens its panel. nuke.nodes.Blur() creates the node silently, not connected and without panel: use it in scripts that build many nodes.

The three errors you'll see first

  • SyntaxError: Missing parentheses in call to 'print': Python 2 code. Write print(x) instead of print x.
  • AttributeError: 'NoneType' object has no attribute...: nuke.toNode('Blur1') found no node with that name and returned None. Names are case-sensitive: Read1 is not read1.
  • ValueError: no node selected: nuke.selectedNode() needs a selected node in the Node Graph.


01. Quick answers

The questions I'm asked most often, with the shortest answer in Python and in TCL.

File path of a Read node (Python)

path = nuke.toNode('Read1')['file'].value()
print(path)

The same path with the frame number of the current frame

print(nuke.toNode('Read1')['file'].evaluate())

File path of a Read node (TCL, in a label or a Text node). The node name is case-sensitive: [value read1.file] gives an error

[value Read1.file]

Only the file name, without the folders

[file tail [value Read1.file]]

Current frame, first and last frame of the script

print(nuke.frame(), nuke.root().firstFrame(), nuke.root().lastFrame())

Full path, name and folder of the script you have open (an unsaved script is called Root)

import os
script = nuke.root().name()
print(script, os.path.basename(script), os.path.dirname(script))

All the Read nodes of the script, also inside groups

for read in nuke.allNodes('Read', recurseGroups=True):
    print(read.fullName(), read['file'].value())

Run TCL from Python

print(nuke.tcl('value Read1.first'))

Run Python from TCL (in a label or a Text node)

[python {nuke.root().firstFrame()}]

Which Nuke version am I running?

print(nuke.NUKE_VERSION_STRING, nuke.NUKE_VERSION_MAJOR)


02. Python basics for compositors

The few pieces of Python you need for Nuke tools: variables, strings, lists and dictionaries, conditions and loops.

Variables, print and f-strings

name = 'Andrea'
size = 12.5
print('Hello', name)
print(f'The blur is {size} pixels')

Convert between numbers and text

frame = int('1001')
gain = float('1.25')
label = str(1001)
print(frame + 1, gain * 2, label + '_v001')

Strings: slice, replace, split

path = 'C:/shots/sh010/plate_v001.####.exr'
print(path[:8])
print(path.replace('v001', 'v002'))
print(path.split('/')[-1])

Lists and dictionaries

classes = ['Blur', 'Grade', 'Transform']
classes.append('Merge2')
values = {'size': 10, 'filter': 'gaussian'}
print(classes[0], len(classes), values['size'])

If, elif, else, and a condition in one line

size = nuke.toNode('Blur1')['size'].value()
if size > 100:
    print('big')
elif size > 10:
    print('medium')
else:
    print('small')
print('big' if size > 100 else 'small')

Is the answer in a list?

answer = 'Yes'
if answer.lower() in ['y', 'yes']:
    print('ok')

For and while loops

for i in range(3):
    print(i)
for node in nuke.allNodes():
    print(node.name(), node.Class())
count = 0
while count < 3:
    count += 1
print('count is', count)


03. Create nodes

Like the Tab menu: connected to the selected node, with its panel

nuke.createNode('Grade')

Silently, with knob values and a name

blur = nuke.nodes.Blur(size=20, name='Blur_big')

Three Blur nodes in a row, each connected to the previous one

previous = nuke.toNode('Read1')
for i in range(3):
    previous = nuke.nodes.Blur(inputs=[previous], size=5 * (i + 1))

Create a node at a position in the Node Graph

n = nuke.nodes.NoOp(name='Marker')
n.setXYpos(0, 200)

From a Python button inside a group: create a node outside the group, in the main script

with nuke.root():
    note = nuke.createNode('StickyNote', inpanel=False)
    note['label'].setValue('Created from ' + nuke.thisNode().name())


04. Select nodes

The selected node, and all the selected nodes

node = nuke.selectedNode()
nodes = nuke.selectedNodes()
print(node.name(), len(nodes))

Select a node by name

nuke.toNode('Blur1').setSelected(True)

Select all the nodes, then deselect them

for node in nuke.allNodes():
    node.setSelected(True)
for node in nuke.selectedNodes():
    node.setSelected(False)

Select the nodes whose name starts with "Blur"

for node in nuke.allNodes():
    node.setSelected(node.name().startswith('Blur'))

Select all the nodes of one class (here every Grade)

for node in nuke.allNodes('Grade'):
    node.setSelected(True)

A list with the names of the selected nodes

names = [node.name() for node in nuke.selectedNodes()]
print(names)

Nodes inside a group: two ways

group = nuke.toNode('Group1')
print([n.name() for n in group.nodes()])
with group:
    print([n.name() for n in nuke.allNodes()])

Use the nodes selected inside a group if the group is selected, otherwise the selection of the main script

sel = nuke.selectedNodes()
if len(sel) == 1 and sel[0].Class() == 'Group' and sel[0].selectedNodes():
    sel = sel[0].selectedNodes()
print([n.name() for n in sel])


05. Connect nodes

Connect the selected node to input 0 (B) and input 1 (A or mask) of another node

merge = nuke.toNode('Merge1')
merge.setInput(0, nuke.toNode('Read1'))
merge.setInput(1, nuke.selectedNode())

Connect every selected node to the output of Grade1

for node in nuke.selectedNodes():
    node.setInput(0, nuke.toNode('Grade1'))

A Merge with all the selected nodes as inputs (input 2 is the mask, so skip it)

sel = nuke.selectedNodes()
merge = nuke.nodes.Merge2()
slot = 0
for node in sel:
    if slot == 2:
        slot = 3
    merge.setInput(slot, node)
    slot += 1

Disconnect one input, or all the inputs of the selected node

node = nuke.selectedNode()
node.setInput(1, None)
for i in range(node.inputs()):
    node.setInput(i, None)

What is connected: inputs (dependencies) and outputs (dependent)

node = nuke.toNode('Blur1')
print([n.name() for n in node.dependencies()])
print([n.name() for n in node.dependent()])

Link a knob of a node inside a group to a knob of the group

inner = nuke.toNode('Group1.Blur_inside')
inner['size'].setExpression('parent.blur_size')


06. Read from a knob

Value of a knob: three ways that give the same result

node = nuke.toNode('Blur1')
print(node['size'].value())
print(node['size'].getValue())
print(node.knob('size').value())

value() gives the text, evaluate() resolves expressions and frame numbers

read = nuke.toNode('Read1')
print(read['file'].value())
print(read['file'].evaluate())

Knobs with more values: get one at a time, or all of them

t = nuke.toNode('Transform1')
print(t['translate'].value(0), t['translate'].value(1))
print(t['translate'].value())

Value at another frame, and the default value

size = nuke.toNode('Blur1')['size']
print(size.getValueAt(1050), size.defaultValue())

Pulldown menus: the text and the index

f = nuke.toNode('Transform1')['filter']
print(f.value(), int(f.getValue()), f.values())

Is there an expression? Print it

size = nuke.toNode('Blur1')['size']
if size.hasExpression():
    print(size.toScript())

Width, height and frame range of a node

node = nuke.toNode('Read1')
print(node.width(), node.height(), node.firstFrame(), node.lastFrame())

Minimum and maximum of an animated knob over a frame range

knob = nuke.toNode('Blur1')['size']
values = [knob.getValueAt(f) for f in range(1001, 1101)]
print('min', min(values), 'max', max(values))

Metadata of a Read, and the value of a pixel

read = nuke.toNode('Constant1')
print(read.metadata())
print(read.sample('rgba.red', 10, 10))


07. Write into a knob

Set a value on a node by name, or on the selected node

nuke.toNode('Blur1')['size'].setValue(20)
nuke.selectedNode()['size'].setValue(30)

Knobs with more values: all together, or one at a time

t = nuke.toNode('Transform1')
t['translate'].setValue([100, 50])
t['scale'].setSingleValue(False)
t['scale'].setValue(2, 0)
t['scale'].setValue(1, 1)

A box: x, y, right, top

nuke.toNode('Crop1')['box'].setValue([0, 150, 4096, 2010])

Text, checkboxes, pulldowns

nuke.toNode('Read1')['file'].setValue('C:/shots/sh010/plate_v002.####.exr')
nuke.toNode('Transform1')['invert_matrix'].setValue(True)
nuke.toNode('Transform1')['filter'].setValue('Lanczos4')
nuke.toNode('Transform1')['filter'].setValue(2)

Back to the default value, disable a node, hide a knob

g = nuke.toNode('Grade1')
g['gamma'].setValue(g['gamma'].defaultValue())
nuke.toNode('Blur1')['disable'].setValue(True)
nuke.toNode('Blur1')['mix'].setVisible(False)

Fill a pulldown of your own

n = nuke.toNode('NoOp1')
n.addKnob(nuke.Enumeration_Knob('shot', 'shot', ['sh010', 'sh020']))
n['shot'].setValues(['sh010', 'sh020', 'sh030'])

New default values for every new node of a class

nuke.knobDefault('Blur.size', '5')
nuke.knobDefault('Merge2.bbox', 'B')


08. Create a new knob

Create the knob, set it up, then add it to the node. The list of all knob types is in the Foundry Python reference (links at the end of the page).

A slider from 0 to 100, a checkbox, a text field, a pulldown

n = nuke.selectedNode()
slider = nuke.Double_Knob('amount', 'Amount')
slider.setRange(0, 100)
slider.setValue(10)
n.addKnob(slider)
n.addKnob(nuke.Boolean_Knob('use_mask', 'use mask'))
n.addKnob(nuke.String_Knob('note', 'Note'))
n.addKnob(nuke.Enumeration_Knob('mode', 'Mode', ['soft', 'hard']))

A tab, a divider line and a Python button

n = nuke.selectedNode()
n.addKnob(nuke.Tab_Knob('my_tools', 'My tools'))
n.addKnob(nuke.Text_Knob('divider', ''))
n.addKnob(nuke.PyScript_Knob('hello', 'Say hello', "print('Hello from', nuke.thisNode().name())"))

An integer knob, and a knob on the same line as the previous one

n = nuke.selectedNode()
steps = nuke.Int_Knob('steps', 'Steps')
steps.setValue(4)
n.addKnob(steps)
extra = nuke.Boolean_Knob('fast', 'fast')
extra.clearFlag(nuke.STARTLINE)
n.addKnob(extra)


09. Animation and curves

Current frame, and go to a frame

print(nuke.frame())
nuke.frame(1050)

Keyframes: animate a knob and set keys

size = nuke.toNode('Blur1')['size']
size.setAnimated()
size.setValueAt(5, 1001)
size.setValueAt(50, 1050)
print(size.isAnimated(), size.isKeyAt(1050), size.getValueAt(1025))

Keys on knobs with more values: index 0 is x, 1 is y

t = nuke.toNode('Transform1')['translate']
t.setAnimated()
t.setValueAt(100, 1001, 0)
t.setValueAt(20, 1001, 1)

Read every key of a curve

size = nuke.toNode('Blur1')['size']
size.setAnimated()
size.setValueAt(5, 1001)
size.setValueAt(50, 1050)
for key in size.animation(0).keys():
    print(key.x, key.y)

A curve from scratch, then copied into a knob

g = nuke.nodes.Grade()
curve = nuke.AnimationCurve(g['blackpoint'], 0, 'main')
curve.setKey(1001, 0.1)
curve.setKey(1010, 0.125)
g['blackpoint'].copyAnimation(0, curve)
print(g['blackpoint'].getValueAt(1005))

Remove a key, or all the animation of the selected nodes

size = nuke.toNode('Blur1')['size']
size.setAnimated()
size.setValueAt(5, 1001)
size.removeKeyAt(1001)
for node in nuke.selectedNodes():
    for knob in node.knobs().values():
        if knob.isAnimated():
            knob.clearAnimated()

A key at the current frame on the four "to" knobs of a CornerPin2D (in a Python button)

n = nuke.thisNode()
for name in ['to1', 'to2', 'to3', 'to4']:
    value = n[name].value()
    n[name].setAnimated()
    n[name].setValue(value)


10. Expressions

Nuke has three small languages that work together. Expressions are what you type after the = sign on a knob (size * 2, frame). TCL is what you write in square brackets in text fields. Python is what runs in the Script Editor and in buttons. From each one you can call the others:

  • Expression from Python: knob.setExpression('size * 2'), or evaluate one with nuke.expression().
  • TCL from Python: nuke.tcl().
  • Python from TCL or from an expression: [python {...}].

Set an expression on a knob, and on one channel of a knob

nuke.toNode('Blur1')['mix'].setExpression('size / 100')
nuke.toNode('Transform1')['scale'].setSingleValue(False)
nuke.toNode('Transform1')['scale'].setExpression('scale.w * 2', 1)

Evaluate an expression from Python, and remove an expression

print(nuke.expression('Blur1.size * 2'))
nuke.toNode('Blur1')['mix'].clearAnimated()

Link a pulldown to another node's pulldown

nuke.toNode('Transform1')['filter'].setExpression('Transform2.filter')

A knob that changes value only on some frames (for example the "which" of a Switch)

frame == 1025 || frame == 1072 || frame == 1074

Python inside TCL: the metadata of the input

[python {nuke.thisNode().input(0).metadata('input/filename')}]

Different values in the GUI and on the render farm ($gui is 1 in the interface, 0 when rendering)

nuke.toNode('Blur1')['size'].setExpression('$gui ? 5 : 50')

Different code for different Nuke versions

if nuke.NUKE_VERSION_MAJOR >= 16:
    from PySide6 import QtWidgets
else:
    from PySide2 import QtWidgets


11. Maths and the wave generator

These are Nuke expressions: type them after the = sign on any knob. Add four user knobs to the node (offset, waveLength, minVal, maxVal) or replace them with numbers. The maths behind them is explained, with live graphs, in my page Expressions and Curves.

Random wave

random((frame+offset)/waveLength) * (maxVal-minVal) + minVal

Noise wave

(noise((frame+offset)/waveLength)+1)/2 * (maxVal-minVal) + minVal

Sine wave

(sin(2*pi*(frame+offset)/waveLength)+1)/2 * (maxVal-minVal) + minVal

Triangle wave

(asin(sin(2*pi*(frame+offset)/waveLength))/pi+0.5) * (maxVal-minVal) + minVal

Square wave (the classic int(sin+1) version returns 2 on the exact peak: this one gives only min or max)

sin(2*pi*(frame+offset)/waveLength) >= 0 ? maxVal : minVal

Sawtooth wave

((frame+offset) % waveLength)/waveLength * (maxVal-minVal) + minVal

Bounce wave

abs(sin(pi*(frame+offset)/waveLength)) * (maxVal-minVal) + minVal

Blip: maxVal for blipLength frames, then minVal (add a blipLength knob)

((frame+offset) % waveLength) < blipLength ? maxVal : minVal

Functions you can use in any expression

clamp(lerp(0, 10, smoothstep(1001, 1050, frame)), 0, 10)

The same maths in Python: import the math module

import math
print(math.sin(math.pi / 2), math.sqrt(16), math.degrees(math.atan2(1, 1)))


12. Functions

Write a function once, use it everywhere

def set_size(nodes, size=10):
    """Set the size knob on every node that has one."""
    changed = 0
    for node in nodes:
        if 'size' in node.knobs():
            node['size'].setValue(size)
            changed += 1
    return changed

print(set_size(nuke.allNodes('Blur'), 25), 'nodes changed')


13. Callbacks

A callback is code that Nuke runs by itself when something happens: a node is created, a knob changes, the script is saved. You can set them on one node (its knobChanged knob) or for every node of a class, from menu.py.

On one node: run code when a knob changes

code = '''
k = nuke.thisKnob()
if k.name() == 'size':
    nuke.thisNode()['label'].setValue('size ' + str(k.value()))
'''
nuke.toNode('Blur1')['knobChanged'].setValue(code)

For every Blur, in menu.py: a function called when a knob changes

def blur_changed():
    k = nuke.thisKnob()
    if k.name() == 'size':
        print(nuke.thisNode().name(), 'size is now', k.value())

nuke.addKnobChanged(blur_changed, nodeClass='Blur')

For every new Write: start from the first frame of the script

def write_created():
    w = nuke.thisNode()
    w['use_limit'].setValue(True)
    w['first'].setValue(nuke.root().firstFrame())

nuke.addOnCreate(write_created, nodeClass='Write')

When the script is saved

def saved():
    print('Saved:', nuke.root().name())

nuke.addOnScriptSave(saved)

Remove a callback (same function and class you added)

nuke.removeKnobChanged(blur_changed, nodeClass='Blur')


14. Panels and dialogs

A message, a yes/no question, a text input

nuke.message('Render finished')
if nuke.ask('Delete the selected nodes?'):
    print('yes')
name = nuke.getInput('Shot name', 'sh010')

A small panel with a text field and a pulldown

p = nuke.Panel('Rename')
p.addSingleLineInput('Prefix', 'comp_')
p.addEnumerationPulldown('Case', 'lower upper')
if p.show():
    print(p.value('Prefix'), p.value('Case'))

A real panel with any knob, with nukescripts.PythonPanel

import nukescripts

class ShotPanel(nukescripts.PythonPanel):
    def __init__(self):
        nukescripts.PythonPanel.__init__(self, 'Shot settings')
        self.shot = nuke.String_Knob('shot', 'Shot')
        self.frames = nuke.Int_Knob('frames', 'Frames')
        self.addKnob(self.shot)
        self.addKnob(self.frames)

panel = ShotPanel()
if panel.showModalDialog():
    print(panel.shot.value(), panel.frames.value())

For full Qt windows, see my pages Custom Panel in QtPy, QtPy: what it is and how to install it and Color Panel.



15. menu.py and init.py: your own menus and shortcuts

Both files live in your .nuke folder (in your user folder: C:\Users\you\.nuke on Windows, ~/.nuke on macOS and Linux). init.py runs first, also when Nuke renders from the command line: put there plugin paths and formats. menu.py runs only with the interface: put there menus, shortcuts and callbacks.

init.py: a folder of your gizmos, and a format

nuke.pluginAddPath('./gizmos')
nuke.addFormat('2048 1152 1.0 2K_16x9')

menu.py: a menu in the toolbar with your nodes and a shortcut

toolbar = nuke.menu('Nodes')
my_menu = toolbar.addMenu('My Tools')
my_menu.addCommand('Blur 25', "nuke.createNode('Blur')['size'].setValue(25)", 'shift+b')
my_menu.addCommand('My Gizmo', "nuke.createNode('MyGizmo')")

menu.py: a command in the main menu bar that runs a function

def print_selected():
    for n in nuke.selectedNodes():
        print(n.name())

nuke.menu('Nuke').addCommand('My Tools/Print selected', print_selected)


16. Tricks

Autolabel: show a knob value under the name of every Blur

def blur_label():
    n = nuke.thisNode()
    return n.name() + '\n(' + str(n['size'].value()) + ')'

nuke.addAutolabel(blur_label, nodeClass='Blur')

Change the colour of the selected nodes in the Node Graph (0xRRGGBBAA)

for node in nuke.selectedNodes():
    node['tile_color'].setValue(0xFF8800FF)

Rename the selected nodes with a prefix

for i, node in enumerate(nuke.selectedNodes()):
    node.setName('comp_' + node.Class() + str(i + 1))

Every Read of the script that points to a missing file

import os, glob
for read in nuke.allNodes('Read', recurseGroups=True):
    pattern = read['file'].value().replace('####', '*').replace('%04d', '*')
    if not glob.glob(pattern):
        print('Missing:', read.fullName(), read['file'].value())

Render a Write node from Python (frames 1001 to 1010)

nuke.execute('Write1', 1001, 1010)

A relative path: a file in the same folder as the script (in the file knob of a Read)

[python {nuke.script_directory()}]/render.exr


17. TCL essentials

The TCL you'll use most, in labels, Text nodes and file paths. There's a whole page with many more snippets and a live filter: TCL for Nuke.

Value of a knob of this node, of another node, of the input

[value size] [value Blur1.size] [value input.name]

First frame of the Read above (topnode), and the name of the script

[value [topnode].first] [value root.name]

Width and height of the input format

[value input.format.r] x [value input.format.t]

Condition: show a word only when the node is disabled

[if {[value disable]} {return OFF} {return ""}]

Replace text in a path

[regsub -all "v001" [value Read1.file] "v002"]

The version number from the file name

[regexp -inline {v[0-9]{3}} [value Read1.file]]

Questions about this tutorial?

Write me directly: I read every message. You can also find me on LinkedIn.