Skip to content

Nuke

Dust motes expression

Basically noise, warped by noise, smoothstepped at the peaks to simulate dust/motes floating in space. Duplicate this a few times, alter the user controls to set different scales and speeds, and merge them all together (all the anim is in the alpha channel btw)

The above gif is 3 copies, mult-ed up to an intensity of 30, multed again against a section of a colour wheel, then convolved.

What? You don't have Nuke? Fine, have a quickie Houdini COPs version: cops_motes.hip

vex
set cut_paste_input [stack 0]
version 8.0 v5
push $cut_paste_input
Reformat {
 format "1024 1024 0 0 1024 1024 1 square_1K"
 name Reformat2
 selected true
 xpos -54
 ypos -220
}
Expression {
 expr3 smoothstep(low,high,noise((x*xscale)+noise(y/warpscale+(frame*speed)),(y*yscale)+noise(x/warpscale+(frame*speed)),((frame+offset)*evolvespeed)))
 name motes
 selected true
 xpos -62
 ypos -128
 addUserKnob {20 User}
 addUserKnob {7 offset}
 offset 0.444
 addUserKnob {7 warpscale}
 warpscale 20
 addUserKnob {7 low}
 low 0.524
 addUserKnob {7 high}
 high 1
 addUserKnob {7 speed}
 speed 0.03
 addUserKnob {7 xscale}
 xscale 0.27
 addUserKnob {7 yscale}
 yscale 0.365
 addUserKnob {7 evolvespeed}
 evolvespeed 0.001
}
set cut_paste_input [stack 0]
version 8.0 v5
push $cut_paste_input
Reformat {
 format "1024 1024 0 0 1024 1024 1 square_1K"
 name Reformat2
 selected true
 xpos -54
 ypos -220
}
Expression {
 expr3 smoothstep(low,high,noise((x*xscale)+noise(y/warpscale+(frame*speed)),(y*yscale)+noise(x/warpscale+(frame*speed)),((frame+offset)*evolvespeed)))
 name motes
 selected true
 xpos -62
 ypos -128
 addUserKnob {20 User}
 addUserKnob {7 offset}
 offset 0.444
 addUserKnob {7 warpscale}
 warpscale 20
 addUserKnob {7 low}
 low 0.524
 addUserKnob {7 high}
 high 1
 addUserKnob {7 speed}
 speed 0.03
 addUserKnob {7 xscale}
 xscale 0.27
 addUserKnob {7 yscale}
 yscale 0.365
 addUserKnob {7 evolvespeed}
 evolvespeed 0.001
}

Expression to generate random number between 5 and 15 each second

5+floor((noise(floor(frame/24)*0.1)+1*0.5)*10)

Text node to display image sequence name with tcl

This will find the topmost node above the text, assume its a read node, grab the name of the image, and strip the frame number and suffix.

vex
[lrange [split [basename [value [topnode].file]] .] 0 0 ]
[lrange [split [basename [value [topnode].file]] .] 0 0 ]

Working from inside to outside:

[value [topnode].file] -- get the value of the requested knob, in this case, the 'file' value of the topmost node ( I assume its a read node)

[basename ... ] -- given a full /big/path/to/image/sequence/beauty.1234.exr, return beauty.1234.exr

[ split .... .] -- take a string, and split it by the given character. in this case, split 'beauty.1234.exr' into 'beauty' '1234' 'exr'.

[lrange ... 0 0 ] -- given a list, return elements from x to x. Here, I ask from 0 to 0, ie, just the first element in the array, so here it'd return 'beauty'.

This page has been a great cheat sheet for all things tcl and nuke:

http://thoughtvfx.blogspot.com.au/2012/12/nuke-tcl-tips.html

Obligatory lazy copy/paste setup:

vex
set cut_paste_input [stack 0]
version 8.0 v5
Read {
 inputs 0
 file /big/path/to/image/sequence/beauty.1234.exr
 format "1024 1024 0 0 1024 1024 1 square_1K"
 origset true
 on_error black
 name Read1
 selected true
 xpos -347
 ypos -159

}
Text2 {
 font_size_toolbar 144
 font_width_toolbar 100
 font_height_toolbar 100
 message "\[lrange \[split \[basename \[value \[topnode].file]] .] 0 0]"
 box {0 733 {width 1813 x1060 446} {height 878 x1060 878}}
 hidden_bbox {0 733 446 878}
 transforms {{0 2}
   }
 cursor_position 6
 font_size 144
 center {1440 1080}
 cursor_initialised true
 initial_cursor_position {{0 878}
   }
 group_animations {{0} imported: 0 selected: 0 items: "root transform/"}
 animation_layers {{1 11 1440 1080 0 0 1 1 0 0 0 0}
   }
 name Text1
 selected true
 xpos -347
 ypos -53
}
set cut_paste_input [stack 0]
version 8.0 v5
Read {
 inputs 0
 file /big/path/to/image/sequence/beauty.1234.exr
 format "1024 1024 0 0 1024 1024 1 square_1K"
 origset true
 on_error black
 name Read1
 selected true
 xpos -347
 ypos -159

}
Text2 {
 font_size_toolbar 144
 font_width_toolbar 100
 font_height_toolbar 100
 message "\[lrange \[split \[basename \[value \[topnode].file]] .] 0 0]"
 box {0 733 {width 1813 x1060 446} {height 878 x1060 878}}
 hidden_bbox {0 733 446 878}
 transforms {{0 2}
   }
 cursor_position 6
 font_size 144
 center {1440 1080}
 cursor_initialised true
 initial_cursor_position {{0 878}
   }
 group_animations {{0} imported: 0 selected: 0 items: "root transform/"}
 animation_layers {{1 11 1440 1080 0 0 1 1 0 0 0 0}
   }
 name Text1
 selected true
 xpos -347
 ypos -53
}

if/then syntax

I always forget this. Set a switch node to 1 if the frame is between 90 and 167, otherwise 0:

vex
(frame>90&&frame<167) ? 1 : 0
(frame>90&&frame<167) ? 1 : 0

basically

vex
thing_to_test ? result_if_true : result_if_false
thing_to_test ? result_if_true : result_if_false

Copy paste thing if you're lazy:

vex
set cut_paste_input [stack 0]
version 7.0 v9
push 0
push $cut_paste_input
Switch {
 inputs 2
 which {{(frame>90&&frame<167)?1:0}}
 name Switch2
 selected true
 xpos -495
 ypos -522
}
set cut_paste_input [stack 0]
version 7.0 v9
push 0
push $cut_paste_input
Switch {
 inputs 2
 which {{(frame>90&&frame<167)?1:0}}
 name Switch2
 selected true
 xpos -495
 ypos -522
}

Get digits in the name of a node aka opdigits

Oh TCL, you fickle mistress. Thanks for the tip Lorne!

vex
[ regexp -inline {\d+}  [knob name] ]
[ regexp -inline {\d+}  [knob name] ]

Nuke folk will look at that and be all like 'whaaa?', while Houdini folk will look at that and go 'Ah, opdigits, gotcha!'.

For you nuke squares, its a common thing in Houdini to have random behaviour be seeded from the name of a node. So if you have a cool noise thing in an expression, and you decide you want multiple copies of your thing, but have them all be different, as they all get named Expression1, Expression2, Expression63 etc, you can extract the 1, 2, 63, and feed that to your noise function.

Paste this into a nuke script, and keep pasting to see that each copy returns a different result:

vex
set cut_paste_input [stack 0]
version 9.0 v9
push $cut_paste_input
Expression {
 temp_name0 seed
 temp_expr0 "\[ regexp -inline \{\\d+\}  \[knob name] ]"
 temp_name1 scale
 temp_expr1 10*random(seed*10)
 temp_name2 offset
 temp_expr2 random(seed)*2
 expr0 noise((cx+offset)*scale,(cy+offset)*scale,0)
 name Expression1
 selected true
 xpos 207
 ypos -161
}
set cut_paste_input [stack 0]
version 9.0 v9
push $cut_paste_input
Expression {
 temp_name0 seed
 temp_expr0 "\[ regexp -inline \{\\d+\}  \[knob name] ]"
 temp_name1 scale
 temp_expr1 10*random(seed*10)
 temp_name2 offset
 temp_expr2 random(seed)*2
 expr0 noise((cx+offset)*scale,(cy+offset)*scale,0)
 name Expression1
 selected true
 xpos 207
 ypos -161
}

Tile a uv map

Use an expression node, give it sliders for the number of repeats in x and y, and use modulus to find the remainder of (1/repeats) * repeats.

vex
set cut_paste_input [stack 0]
version 7.0 v9
push $cut_paste_input
Expression {
 temp_name0 rate
 temp_expr0 5
 expr0 r%(1/repeatx)*repeatx
 expr1 g%(1/repeaty)*repeaty
 name Expression2
 label "multiply uvs"
 selected true
 xpos 2359
 ypos -173
 addUserKnob {20 User}
 addUserKnob {7 repeatx R 0 10}
 repeatx 1.55
 addUserKnob {7 repeaty R 0 10}
 repeaty 15
}
set cut_paste_input [stack 0]
version 7.0 v9
push $cut_paste_input
Expression {
 temp_name0 rate
 temp_expr0 5
 expr0 r%(1/repeatx)*repeatx
 expr1 g%(1/repeaty)*repeaty
 name Expression2
 label "multiply uvs"
 selected true
 xpos 2359
 ypos -173
 addUserKnob {20 User}
 addUserKnob {7 repeatx R 0 10}
 repeatx 1.55
 addUserKnob {7 repeaty R 0 10}
 repeaty 15
}

Offset uv map with wraparound

I had a hacky version of this before, now less hacky. Can make it pan in any direction now, and the speed control works as you'd expect (low values = slow, high values = fast)

vex
set cut_paste_input [stack 0]
version 8.0 v1
push $cut_paste_input
Expression {
 temp_name0 shiftx
 temp_expr0 (time/24*ratex)%1
 temp_name1 shifty
 temp_expr1 (time/24*ratey)%1
 expr0 "( r+shiftx < 0) ? 1+r+shiftx :  (r+shiftx)%1"
 expr1 "( g+shifty < 0) ? 1+g+shifty :  (g+shifty)%1"
 name Expression7
 label "offset uvs"
 selected true
 xpos -322
 ypos -22
 addUserKnob {20 User}
 addUserKnob {7 ratex t "\t\t" R -10 10}
 ratex 1.35
 addUserKnob {7 ratey R -10 10}
 ratey -0.85
 addUserKnob {7 time R -1000 1000}
 time {{frame}}
}
set cut_paste_input [stack 0]
version 8.0 v1
push $cut_paste_input
Expression {
 temp_name0 shiftx
 temp_expr0 (time/24*ratex)%1
 temp_name1 shifty
 temp_expr1 (time/24*ratey)%1
 expr0 "( r+shiftx < 0) ? 1+r+shiftx :  (r+shiftx)%1"
 expr1 "( g+shifty < 0) ? 1+g+shifty :  (g+shifty)%1"
 name Expression7
 label "offset uvs"
 selected true
 xpos -322
 ypos -22
 addUserKnob {20 User}
 addUserKnob {7 ratex t "\t\t" R -10 10}
 ratex 1.35
 addUserKnob {7 ratey R -10 10}
 ratey -0.85
 addUserKnob {7 time R -1000 1000}
 time {{frame}}
}

Quick dummy uv map

Feeling lazy? Sure you are.

vex
set cut_paste_input [stack 0]
version 7.0 v9
push $cut_paste_input
Expression {
 expr0 x/width
 expr1 y/height
 name Expression8
 label "square uv map"
 selected true
 xpos 2444
 ypos -601
}
set cut_paste_input [stack 0]
version 7.0 v9
push $cut_paste_input
Expression {
 expr0 x/width
 expr1 y/height
 name Expression8
 label "square uv map"
 selected true
 xpos 2444
 ypos -601
}

Python

Cheaty cheat sheet, comments say what each line/batch of lines do:

python
# make a node
nuke.nodes.Blur()

#assign to a variable
b = nuke.toNode('Blur1')
c = nuke.toNode('Checkerboard4')

#set a value
b['size'].setValue(4)

#connect 2 nodes
b.setInput(2,c)

# get a list of all the attributes
[b.knob(x).name() for x in range(b.getNumKnobs()) ]
# set all write nodes to have their also_merge node to 'all'
nodes = [ n for n in nuke.allNodes() if 'Merge' in n.Class() ]
for n in nodes:
     print n['also_merge'].setValue('all')

# set all pbw nodes for the aov-reanders to premultiplied
for node in nuke.allNodes():
    if node.Class() == 'Group' and 'aov' in node['name'].getValue():
        if 'pbw' in node['passName'].getValue():
            try:
                print node['name'].getValue()
                node['unpremultiplied'].setValue(0.0)
                print 'result', node['unpremultiplied'].getValue()
            except:
                print '================bad================='
                print node

# set selected reads to have error mode as 'nearest frame'
[ x['on_error'].setValue("nearest_frame") for x in nuke.selectedNodes() ]

# set knob default. in this case, make the exposure tool work in stops, and set the label to show you the stops value. similar thing for gamma nodes.
nuke.knobDefault('EXPTool.label','[value red]')
nuke.knobDefault('EXPTool.mode','0')
nuke.knobDefault('Gamma.label','[value value]')
# make a node
nuke.nodes.Blur()

#assign to a variable
b = nuke.toNode('Blur1')
c = nuke.toNode('Checkerboard4')

#set a value
b['size'].setValue(4)

#connect 2 nodes
b.setInput(2,c)

# get a list of all the attributes
[b.knob(x).name() for x in range(b.getNumKnobs()) ]
# set all write nodes to have their also_merge node to 'all'
nodes = [ n for n in nuke.allNodes() if 'Merge' in n.Class() ]
for n in nodes:
     print n['also_merge'].setValue('all')

# set all pbw nodes for the aov-reanders to premultiplied
for node in nuke.allNodes():
    if node.Class() == 'Group' and 'aov' in node['name'].getValue():
        if 'pbw' in node['passName'].getValue():
            try:
                print node['name'].getValue()
                node['unpremultiplied'].setValue(0.0)
                print 'result', node['unpremultiplied'].getValue()
            except:
                print '================bad================='
                print node

# set selected reads to have error mode as 'nearest frame'
[ x['on_error'].setValue("nearest_frame") for x in nuke.selectedNodes() ]

# set knob default. in this case, make the exposure tool work in stops, and set the label to show you the stops value. similar thing for gamma nodes.
nuke.knobDefault('EXPTool.label','[value red]')
nuke.knobDefault('EXPTool.mode','0')
nuke.knobDefault('Gamma.label','[value value]')

Command line python

http://docs.thefoundry.co.uk/nuke/63/pythondevguide/command_line.html

Start nuke command prompt. On linux, annoyingly, there's no cursor or backspace/delete support, can't get readlines/ncurses up n running yet.

python
nuke -t
nuke -t

Script that define functions to load a script, return a write node, and execute a write node:

python
#!/bin/python
import nuke

def do():
    inScript = '/depts/cg_treehouse/matthewes/mineral/gammafix.nk'
    nuke.scriptOpen( inScript )
    writes = [ n for n in nuke.allNodes() if 'Write' in n.Class() ]
    return writes[0]

def run(n):
    nuke.execute(n,1,200)
#!/bin/python
import nuke

def do():
    inScript = '/depts/cg_treehouse/matthewes/mineral/gammafix.nk'
    nuke.scriptOpen( inScript )
    writes = [ n for n in nuke.allNodes() if 'Write' in n.Class() ]
    return writes[0]

def run(n):
    nuke.execute(n,1,200)

Save that as 'batchnuke.py', call it from the nuke command line python prompt like this:

python
import batchnuke as bn
n = bn.do()
bn.run(n)
import batchnuke as bn
n = bn.do()
bn.run(n)

Can make chages to the python script and reload it with

python
reload(bn)
reload(bn)

Once this is run, the script should also be available live in the interactive prompt, eg

python
>>> nodes = nuke.allNodes()
>>> nodes
[<Viewer1 at 0x2b09a50>, <Write1 at 0x2b09a30>, <Grade1 at 0x2b09a10>, <Saturation1 at 0x2b099f0>, <Gamma1 at 0x2b099d0>, <Read1 at 0x2b099b0>]
>>> nodes = nuke.allNodes()
>>> nodes
[<Viewer1 at 0x2b09a50>, <Write1 at 0x2b09a30>, <Grade1 at 0x2b09a10>, <Saturation1 at 0x2b099f0>, <Gamma1 at 0x2b099d0>, <Read1 at 0x2b099b0>]

Batch process, full example

Here's a thing that loads a script, finds every mp4 in the current folder, and for each file creates a new read node, swaps the input on the first node after the initial read (hardcoded to a gamma node for now), updates the write path, renders with new framerange.

Had to create a new read node rather than updating in-place, nuke never updates the last frame on the existing read node for some reason.

python
import nuke
import os

def load():
    inScript = '/path/to/nukescript/gammafix.nk'
    nuke.scriptOpen( inScript )
    writes = [ n for n in nuke.allNodes() if 'Write' in n.Class() ]
    return writes[0]


def swap():
    files = os.listdir('/location/of/video/folder/')
    files = [f for f in files if f.endswith('.mp4')]

    for f in files:
        r = nuke.allNodes('Read')[0]
        g = nuke.allNodes('Gamma')[0]
        print 'old file = ', r['file'].value()
        print 'old range = ', r['last'].getValue()


        mov=nuke.nodes.Read(name='foo')
        mov['file'].fromUserText(f)
        end = int(mov['last'].getValue())
        print 'new range = ', end


        g.setInput(0,mov)

        w = nuke.allNodes('Write')[0]

        print 'old write = ', w['file'].value()
        newwrite = f.replace('thumbnail','cc')
        w['file'].setValue(newwrite)
        print 'new write = ', w['file'].value()

        if (os.path.exists(newwrite)):
                print 'skipping file', newwrite
        else:
            nuke.execute(w,1,end)


"""
usage:

> nuke -t

import batchnuke as bn; bn.load();
reload(bn); bn.swap()

"""
import nuke
import os

def load():
    inScript = '/path/to/nukescript/gammafix.nk'
    nuke.scriptOpen( inScript )
    writes = [ n for n in nuke.allNodes() if 'Write' in n.Class() ]
    return writes[0]


def swap():
    files = os.listdir('/location/of/video/folder/')
    files = [f for f in files if f.endswith('.mp4')]

    for f in files:
        r = nuke.allNodes('Read')[0]
        g = nuke.allNodes('Gamma')[0]
        print 'old file = ', r['file'].value()
        print 'old range = ', r['last'].getValue()


        mov=nuke.nodes.Read(name='foo')
        mov['file'].fromUserText(f)
        end = int(mov['last'].getValue())
        print 'new range = ', end


        g.setInput(0,mov)

        w = nuke.allNodes('Write')[0]

        print 'old write = ', w['file'].value()
        newwrite = f.replace('thumbnail','cc')
        w['file'].setValue(newwrite)
        print 'new write = ', w['file'].value()

        if (os.path.exists(newwrite)):
                print 'skipping file', newwrite
        else:
            nuke.execute(w,1,end)


"""
usage:

> nuke -t

import batchnuke as bn; bn.load();
reload(bn); bn.swap()

"""

How do you interactively move the pivot for a 2d transform?

Hold control and drag the handle.

How do you use the rototpaint node?

Why can I never remember this?

  1. Connect it to your source
  2. Hit v to get into 'make a mask' node (its also on the side of the viewport, 3rd button, the bezier handle icon)
  3. Click down points, click-drag to set a point and drag it out to make smooth
  4. Click on the first point to complete the shape

Once made, can choose the output option to only make an alpha channel, and use the color slider to set what you want. Making a black alpha? You got it. Need to change the blending mode? Do that in the source options.

Cheaty paint-out-lights-from-hdr trick

Idea is to key the bright bits, make those black holes, blur everything massively to in-fill those holes, invert the alpha, and comp those little blurred regions back over your hdr.

  1. Luminance key to get the bright bits of your hdr
  2. rotopaint on alpha with black shapes to mask anything you don't want painted out
  3. filter erode, negative size in gausian mode to expand the alpha shape a bit
  4. invert the alpha
  5. premult
  6. split here, blur the rgba a lot, blur just the alpha not so much
  7. unpremult the rgba
  8. copy the less blurred alpha to the big blurred one
  9. invert the alpha
  10. grade the alpha a bit to get the center of the alpha blobs solid white again
  11. premult
  12. merge on top of your original hdr