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:
Index
- 00. Nuke Python for beginners
- 01. Quick answers
- 02. Python basics for compositors
- 03. Create nodes
- 04. Select nodes
- 05. Connect nodes
- 06. Read from a knob
- 07. Write into a knob
- 08. Create a new knob
- 09. Animation and curves
- 10. Expressions
- 11. Maths and the wave generator
- 12. Functions
- 13. Callbacks
- 14. Panels and dialogs
- 15. menu.py and init.py: your own menus and shortcuts
- 16. Tricks
- 17. TCL essentials
- Useful links
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 += 1Disconnect 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 == 1074Python 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 QtWidgets11. 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) + minValNoise wave
(noise((frame+offset)/waveLength)+1)/2 * (maxVal-minVal) + minValSine wave
(sin(2*pi*(frame+offset)/waveLength)+1)/2 * (maxVal-minVal) + minValTriangle wave
(asin(sin(2*pi*(frame+offset)/waveLength))/pi+0.5) * (maxVal-minVal) + minValSquare 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 : minValSawtooth wave
((frame+offset) % waveLength)/waveLength * (maxVal-minVal) + minValBounce wave
abs(sin(pi*(frame+offset)/waveLength)) * (maxVal-minVal) + minValBlip: maxVal for blipLength frames, then minVal (add a blipLength knob)
((frame+offset) % waveLength) < blipLength ? maxVal : minValFunctions 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.
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.exr17. 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]]No snippet matches. Try a shorter word.
Useful links
- Foundry: Nuke Python Developer's Guide
- Foundry: the nuke module, every function
- Foundry: nuke.Knob, every method of a knob
- Foundry: callbacks
- Foundry: custom panels
- The Python 3 tutorial
- TCL for Nuke: the full page
- No module named 'PySide2' in Nuke 16+
- QtPy: what it is and how to install it
- Custom Panel in QtPy
- Color Panel: PySide & QtPy
- Label and Autolabel
- Expressions and Curves: the maths

