TCL for Nuke

TCL for Nuke

Snippets for labels, Text nodes, file paths and expressions

Python is the language to build tools, but the little language you type every day in Nuke is TCL: inside the square brackets of a label, in the message of a Text node, in the file knob of a Write. It's short, it's live (it updates while you work) and it doesn't need any script editor.

This page is my collection of TCL snippets, organised by what you want to do, from the basics to path manipulation. It's the companion of my Python and TCL tips and tricks page. Type in the box below to filter the snippets.





00. TCL in Nuke: the basics

Nuke's own scripting language was TCL long before Python arrived, and the .nk files are still written in TCL. You meet it in three places:

- Text fields (label, Text node message, file paths): everything between [ ] is run and replaced by its result. size: [value size] becomes size: 12.
- Expressions (the = sign on a knob): they use Nuke's expression language, and you can still call TCL inside with [ ].
- The Script Editor: Python can run any TCL command with nuke.tcl().

Commands are words separated by spaces, and the brackets can be nested: the innermost runs first. That's all the grammar you need.

Nested brackets: the inner command runs first, its result feeds the outer one

[basename [value [topnode].file]]

Curly braces keep the text as it is: this returns the words [VALUE SIZE]

[string toupper {[value size]}]

Quotes let the brackets run: this returns the name of the node in capitals

[string toupper "[value name]"]


01. value vs knob

The two commands you will use the most. value returns what the knob is worth now, at the current frame: expressions and animation are already resolved. knob returns what is literally written in the knob (the expression itself, for example), and with a second argument it sets the knob.

Value of a knob of this node (in its own label)

[value size]

Value of a knob of another node

[value Blur1.size]

One channel of a multi-value knob

[value Transform1.translate.x]
[value Grade1.white.r]

The literal content: an animated or linked knob shows its expression

[knob Transform1.rotate]

Set a knob (careful: in a label it runs every time the label is drawn)

[knob Transform1.translate.x 30]

Default value if the knob doesn't exist

[value this.missing_knob "n/a"]


02. Which node? this, input, parent, topnode

Before the dot you say which node you are talking about: a name, or one of these relative words. They make your labels and gizmos work wherever you paste them.

This node, and its name

[value this.name]

The node connected to input 0, and to input 1

[value input.name]
[value input0.label]
[value input1.name]

Inside a group: the group itself, and the node connected to the group

[value parent.name]
[value this.parent.input.name]

The top node of the branch (usually the Read) and its file

[topnode]
[full_name [topnode]]
[value [topnode].file]

The project settings (Root): the path of the script, the fps, the frame range

[value root.name]
[value root.fps]
[value root.first_frame]-[value root.last_frame]

Does a node exist? (1 or 0)

[exists Read1]


03. Labels

Put these in the label knob (Node tab) and your Node Graph explains itself. More ideas in my Label and Autolabel tutorial.

Blur: size and filter

size: [value size]  [value filter]

Grade: the values that matter

gain: [value white]
gamma: [value gamma]

Transform: translate and rotate

t: [value translate.x], [value translate.y]
r: [value rotate]

Tracker or FrameHold: the reference frame

ref: [value reference_frame]
hold: [value first_frame]

Which channels the node works on, and the mix if it's not 1

[value channels]
[if {[value mix] != 1} {return "mix [value mix]"}]

Read node: just the file name, without folders

[file tail [value file]]

Switch: words instead of numbers

[if {[value which] == 0} {return "plate"} elseif {[value which] == 1} {return "CG"} else {return "other"}]

Show if the node is in error

[if {[value error]} {return "ERROR"}]


04. Text node and burn-ins

The message of a Text node accepts the same brackets. Perfect for slates, dailies and review burn-ins.

Script name, frame and date

[file rootname [file tail [value root.name]]]   frame [frame]   [date %d/%m/%Y]

The plate's file name and its frame range

[basename [value [topnode].file]]
[value [topnode].first]-[value [topnode].last]

Frame number with 4 digits (like 0012)

[format %04d [frame]]

A value with only 2 decimals

[format %.2f [value Transform1.rotate]]

Timecode from the plate's metadata

[metadata input/timecode]


05. Frames and time

Current frame

[frame]

Frame range of the script and of the Read

[value root.first_frame] - [value root.last_frame]
[value first_frame] - [value last_frame]

How many frames since the start of the plate

[expr {[frame] - [value [topnode].first]}]

Set a keyframe from TCL: knob, frame, value

[setkey Blur1.size 1001 10]


06. Format and bounding box

Width and height of the project and of the input

[value root.width] x [value root.height]
[value input.width] x [value input.height]

The format of the input (r = right = width, t = top = height)

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

Bounding box: left, bottom, right, top

[value bbox.x] [value bbox.y] [value bbox.r] [value bbox.t]

Centre of the format and centre of the bounding box (in an expression)

[value input.width]/2
([value bbox.x] + [value bbox.r]) / 2


07. File paths

Take the path of a Read (or of the script) and cut it the way you need. Here the example path is /jobs/show/sh010/plates/sh010_plate_v003.1001.exr.

Folder (/jobs/show/sh010/plates), file (sh010_plate_v003.1001.exr) and extension (.exr). Note: value gives the file name at the current frame, knob gives it with the padding (%04d or ####)

[file dirname [value [topnode].file]]
[file tail [value [topnode].file]]
[file extension [value [topnode].file]]

Without the extension, and the file name only

[file rootname [value [topnode].file]]
[basename [value [topnode].file]]
[basename [file rootname [value [topnode].file]]]

The folder of the Nuke script, to build relative paths

[file dirname [value root.name]]/renders/

Write node: name the render like the script, with the frame padding

[file dirname [value root.name]]/renders/[file rootname [file tail [value root.name]]].%04d.exr

Write node: same name as the top Read, in another format (cuts the frame number and the extension)

[join [lrange [split [value [topnode].file] .] 0 end-2] .].%04d.dpx

A folder some levels up: split the path, keep the first pieces, join them back

[join [lrange [file split [value [topnode].file]] 0 3] /]

The shot name, if it's the 3rd folder of the path

[lindex [split [value [topnode].file] /] 3]

The version of the script (v003)

[regexp -inline {v[0-9]+} [file tail [value root.name]]]


08. Strings

Upper case, lower case, length

[string toupper [value name]]
[string tolower [value name]]
[string length [value label]]

Part of a string: characters from 0 to 4, or the first character

[string range [value name] 0 4]
[string index [value name] 0]

Replace several words at once

[string map {"_v003" "_v004" "plates" "renders"} [value [topnode].file]]

Replace with a regular expression (every occurrence)

[regsub -all {v[0-9]+} [value [topnode].file] "v999"]

Compare two strings (1 if equal) and look for a word inside

[string equal [value Text1.message] "slate"]
[string match "*plate*" [value [topnode].file]]


09. Lists

Split a string into a list, then pick the pieces. end is the last element, end-1 the one before.

Split a path by "/" and take an element

[lindex [split [value [topnode].file] /] end]
[lindex [split [value [topnode].file] /] end-1]

How many pieces, and a range of them

[llength [split [value [topnode].file] /]]
[lrange [split [file tail [value [topnode].file]] _] 0 1]

Join a list back with a separator

[join [lrange [split [file tail [value [topnode].file]] _] 0 1] _]

Sort, search, replace an element

[lsort [split "c b a" " "]]
[lsearch [split [value [topnode].file] /] plates]
[lreplace [split [value [topnode].file] /] 4 4 renders]


10. Conditions and maths

if / elseif / else

[if {[value size] > 10} {return "big blur"} else {return "small blur"}]

Compare strings in a condition (use eq for text)

[if {[value filter] eq "gaussian"} {return "G"} {return "other"}]

Maths with expr (always in braces)

[expr {[value size] * 2}]
[expr {round([value Transform1.rotate])}]
[expr {[frame] % 2}]


11. Pixels and metadata

Read a pixel: node, channel, x, y

[sample [node input] red 960 540]

Read the pixel under a position knob (here the center of Transform1)

[sample [node input] red [value Transform1.center.x] [value Transform1.center.y]]

Metadata of the input

[metadata input/filename]
[metadata input/timecode]


12. Set values and variables

Save a value in a variable without printing it (end with return)

[set plate [basename [value [topnode].file]]; return]
plate: $plate

Set a knob of another node

[knob Read1.first 1001]


13. TCL and Python together

Python inside TCL: [python ...], with braces when there are spaces

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

An environment variable (through Python)

[python {__import__("os").getenv("SHOT", "no shot")}]

TCL inside Python: nuke.tcl() returns the result as a string

name = nuke.tcl("value root.name")
nuke.tcl("knob Blur1.size 5")
print(nuke.tcl("basename [value Read1.file]"))


14. Nuke expressions (not TCL)

When you right-click a knob and choose Add expression (or type =), you are not writing TCL but Nuke's expression language. The two mix well: TCL in brackets works inside an expression, and the expression language has its own useful functions.

Value of a knob at another frame: one frame before, a fixed frame

Transform1.translate.x(frame - 1)
Tracker1.translate.x(1001)

Offset an animation by 50 frames

Transform1.translate.x(frame + 50)

Condition: condition ? then : else

frame < 1050 ? 0 : 1

Wiggle, noise and waves

random(frame) * 10
noise(frame / 10) * 20
sin(frame / 5) * 30

Link to the parent group's knob

parent.size

More waves and maths in the Math functions section of the Python page and in the Expression Node Collection.



15. Recipes

Backdrop or StickyNote: the name of the shot from the script name (sh010_comp_v003.nk becomes sh010)

[lindex [split [file tail [value root.name]] _] 0]

Slate line for dailies

[lindex [split [file tail [value root.name]] _] 0]  |  [regexp -inline {v[0-9]+} [file tail [value root.name]]]  |  frame [format %04d [frame]]  |  [date %d/%m/%Y]

Read node label: file name and frame range at a glance

[file tail [knob file]]
[value first]-[value last]

Write node: version taken from the script name

[file dirname [value root.name]]/renders/[file rootname [file tail [value root.name]]]/[file rootname [file tail [value root.name]]].%04d.exr


16. Nodes to paste

Whole nodes with the TCL already inside: copy the box and paste it in the Node Graph. In a .nk text every [ is written \[, so Nuke keeps the expression instead of running it while pasting.

Blur that shows its size and filter

Blur {
 size 10
 name Blur_labelled
 label "size \[value size]  \[value filter]"
}

Grade that shows gain and gamma

Grade {
 name Grade_labelled
 label "gain \[value white]\ngamma \[value gamma]"
}

Switch that says plate or CG

Switch {
 name Switch_labelled
 label "\[if {\[value which] == 0} {return plate} {return CG}]"
}

Text node burn-in: shot, script name and frame

Text2 {
 message "\[lindex \[split \[file tail \[value root.name]] _] 0]   \[file rootname \[file tail \[value root.name]]]   frame \[format %04d \[frame]]"
 name Slate_burnin
}

Write node that names the render after the script, in a renders folder next to it

Write {
 file "\[file dirname \[value root.name]]/renders/\[file rootname \[file tail \[value root.name]]]/\[file rootname \[file tail \[value root.name]]].####.exr"
 file_type exr
 name Write_auto
}

Backdrop with the shot name taken from the script name

BackdropNode {
 inputs 0
 name Backdrop_shot
 label "\[lindex \[split \[file tail \[value root.name]] _] 0]"
 note_font_size 42
 bdwidth 420
 bdheight 300
}

Leave a comment