Nuke Python

From bernie's
Jump to navigation Jump to search

Nuke find sequences on server and create a write node for each sequence

Not the most beautiful code but works for what I needed.

This one assumes there is a 0001.exr in each file sequence, creates a .png write. (yeah png bad, I know).

import nuke, glob
shots = glob.glob('D:/2022/projectPath/someFolder/SQ*/SQ*_SH*/moreFolders/exr/*0001.exr', recursive=True)
for shot in shots:
    frames = glob.glob(shot.replace('0001.exr','*.exr'))
    first = int(frames[0].split('.exr')[0][-4:])
    last = int(frames[-1].split('.exr')[0][-4:])
    shot = shot.replace("0001.exr","####.exr")
    shot = shot.replace("\\","/")
    read = nuke.nodes.Read (file=shot,first=first,last=last)
    expression = "[python {nuke.thisNode().input(0).knob('file').value().replace('exr','png')}]"
    write = nuke.nodes.Write(inputs=[read],channels='rgba',file=expression,file_type='png',create_directories='1',use_limit='1',first='this.input.first',last='this.input.last')
    debug = shot.split('/')[-1]
    print('{} {}-{}'.format(debug,first,last))

Natron fill write path with parent node path shenanigans

This is kinda hackish, finds the the oldest grandparent/readfile and uses it to generate the path of the write node. No error checking but a maxloop of 20 because While==crash

node = thisNode
a = 0
while True:
	a = a + 1
	try:
		parent = node.getInput(0)
	except:
		pass
	if a >= 20 or not parent  or node.getPluginID() == 'fr.inria.built-in.Read':
		break
	else:
		node = parent
#return(str(node.getScriptName()))
infile = node.getParam('filename').get()
filename = infile.rsplit('/',1)
basename = filename[1].rsplit('.',1)[0]
a = filename[0]+'/'+basename +'_jpg/'+basename +'.####.jpg'
return(str(a))

Write node file using topmost read node file name

if input is //path/to/myFile.mp4, output will be //prodX/folderwhereshitisgonnabesaved/220919/myFile_9x16.mov

Select write node(s) and run script

import os
prefix = '//prodX/folderwhereshitisgonnabesaved/220919/'
suffix = '_9x16.mov'

writes = nuke.selectedNodes()
for write in writes:
    n = write
    while n.inputs():
        n = n.input(0)    
    if n.Class() == 'Read':
        file = n.knob('file').value()
        filename = os.path.basename(file)
        filename = filename.split('.')[-2]
        write = write.knob('file').setValue( prefix + filename + suffix )
    else:
        print('Topmost node found for '+write.name()+ ' is not a read file but: '+n.Class()+' ('+n.name()+')')

Natron right click to create a PNG sequence from a folder

version a

The idea is that I have a .ntp setup that works but I want to change the in and out, and I can't manage to make the natron command line flags to work properly. So given a 'read' node called inRead and 'write' node called outWrite, here's what I do

  • Use a commandline in my shell:sendto: "C:\Program Files\Natron\bin\NatronRenderer.exe" "C:\Users\Me\Documents\natron\ocio.ntp" -l "C:\Users\Me\Documents\natron\ocio.py" -i inRead %1
  • In my ocio.py have code to figure out a proper naming scheme:
import sys, pathlib

file = sys.argv[-1]
file = file.replace('\\', '/')
filename = file.rsplit('/',1)
basename = filename[1].rsplit('.',1)[0]

a = filename[0]+'/'+basename +'_jpg/'+basename +'.####.jpg'

app.inRead.filename.set(file)
app.outWrite.filename.set(a)

version b

To set in shell:sendto folder


@echo off
"C:\Program Files\INRIA\Natron-2.3.14\bin\Natron.exe" -l "C:\Users\Me\Documents\natron\to_file.py" %1

to_file.py:

from NatronEngine import*
from NatronGui import *
import sys
import os
import re

pathArg = str(sys.argv[-1])
pathArg = pathArg.replace(os.sep, '/')

if os.path.isdir(pathArg):
	firstFile = os.listdir(pathArg)[0]
	pathArg = pathArg+'/'+firstFile


def sequence(file):
	'''given a file path, return a dictionnary with [directory, filename (without number and extension), start frame, end frame, padding, extension]'''
	# given  C:/path/the_sequence_0033.jpg
	# like so ['c:/path/','the_sequence_',1,100,4,'.jpg']

	if os.path.isfile(file):
		reg = r'^(.+?)([0-9]+)\.([.a-zA-Z]{1,7})$'
		match = re.match(reg, file,re.IGNORECASE)
		if match:
			#return target
			newReg = r'('+os.path.basename(match.groups()[0])+')(\d*)\.('+match.groups()[2]+')'
						
			#bit convoluted but it will help me pick the first image of sequence that matches selection
			filelist = []
			target = os.path.dirname(file)
			for f in os.listdir(target):
				match = re.match(newReg, f,re.IGNORECASE)
				if match:
					filelist.append(match.groups())
			return [ target , filelist[0][0] , int(filelist[0][1]) , int(filelist[-1][1]) , len(filelist[0][1]) , filelist[0][2] ]

filePath = sequence(pathArg)
inPath = filePath[0]+'/'+filePath[1]+('#' * filePath[4])+'.'+filePath[5]

app = natron.getGuiInstance(0)

reader = app.createReader(inPath)
reader.setScriptName("readin")
writer = app.createWriter("")
writer.getParam("formatType").setValue(0)
writer.connectInput(0,reader)

## i wanted to use an expression but apparently this breaks Natron
#expression = "import os;a = "+reader.getScriptName()+".filename.get();path = os.path.dirname(a)+'_png/'+os.path.basename(a)+'.png';return path;"
#writer.getParam("filename").setExpression(expression,1)

outFile = filePath[0]+'_png/'+filePath[1]+('#' * filePath[4])+'.'+filePath[5]+'.png'
writer.getParam("filename").setValue(outFile)
app.render(writer,app.timelineGetLeftBound(),app.timelineGetRightBound())
app.closeProject()


Nuke Text Font TCL Python

[python {nuke.thisNode().input(0).input(0).knob('file').value().split('/')[-1][:-9]}]

get gandparent value of knob

Natron Read File path to Text Overlay expression

R0Hvhkv.png

I have a bunch of looping 10 frame sequences and I want to view the in a sequence with their file locations shown,I've used a switch node to switch which read node it is reading from every ten frames using (frame-1)/10. Here's what I added to the text node expression as shown in screenshot (it basically gets the file path of whichever input of the switch it finds):

#multiline is turned on, and you need the 'ret' variable at the end
switchNode = thisNode.getInput(0)
currentReadNode = switchNode.getInput(switchNode.getParam('which').get())
ret = currentReadNode.getParam('filename').get().split('/')[-1]

Create dots from selected

s = nuke.selectedNodes()
counter = -1
pos = [0,0]
dots = []
#dots are super 'sticky' need to remove selection
for node in nuke.allNodes(recurseGroups=True):
    node['selected'].setValue(False)
for n in reversed(s):
    counter += 1
    dot=nuke.createNode('Dot')
    dot.setInput(0, n)
    if counter == 0:
        #dot.autoplace()
        pos = [dot['xpos'].value(),dot['ypos'].value()]
        x = int(pos[0])
        y = int(pos[1])
        dot.setXYpos(x,y)
    else:
        x = int(pos[0]+counter*30)
        y = int(pos[1])
        dot.setXYpos(x,y)

    dot.knob('label').setValue(n.name())
    dots.append(dot)

for d in dots:
    d['selected'].setValue(True)

switch with inputs according to y pos

def getKey(item):
    return item['ypos'].value()

sel = nuke.selectedNodes()
sorted(sel, key=getKey)

switch = nuke.nodes.Switch(inputs=sel)

Check MXI SLs with Nuke (maxwell render)

Reads metadata

fAIuyTt.png

def checkMXI_SL():
    '''takes mxi read node and checks its SLs metadata and outputs frames under certain treshold'''

    sl_threshold = 18.0
    txt = nuke.getInput('Print frames with SL below:', str(sl_threshold))
    if txt:
        sl_threshold = float(txt)
    output = ''

    task = nuke.ProgressTask("Checking MXI SLs") 
    task.setMessage("Progress")

    for node in nuke.selectedNodes():
        if node.Class() == 'Read':
            firstframe = int(node['first'].value())
            #lastframe = firstframe + 10
            lastframe = int(node['last'].value())
            for i in range(firstframe, lastframe):
                percentage = int(100.0*(i-firstframe)/(lastframe-firstframe))
                task.setProgress(percentage)
                if task.isCancelled(): 
                    break;

                sl = node.metadata('SAMPLING_LEVEL',i)
                if  sl < sl_threshold:
                        task.setMessage('Frame ['+str(i)+'] SL'+"{0:.1f}".format(sl))                        
                        logline = 'Frame ' + str(i) + ': ' + "{0:.2f}".format(sl) + '\n'
                        output = output + logline
            task.setProgress(100)
    del task

    p = nuke.Panel('Results')
    if output == '':
        output = 'All MXI are above or equal to '+str(sl_threshold)
    p.addNotepad('Results:',output)
    ret = p.show()

checkMXI_SL()

Rainbow colored gradient in nuke expression

dlg2rOF.png

red   : ( r * 6 >= 2 && r * 6 <= 4 ) ? 0 : ( r * 6 <= 1 || r * 6  >= 5) ? 1 : ( 1 - abs ( ( r * 6 ) % 2 - 1 ) )
green : ( r * 6 >= 4 && r * 6 <= 6 ) ? 0 : ( r * 6 >= 1 && r * 6  <= 3) ? 1 : ( 1 - abs ( ( r * 6 ) % 2 - 1 ) )
blue  : ( r * 6 >= 0 && r * 6 <= 2 ) ? 0 : ( r * 6 >= 3 && r * 6  <= 5) ? 1 : ( 1 - abs ( ( r * 6 ) % 2 - 1 ) )

But better:

r: from 0 to 1
g: 1
b: 1

colorspace in HSV out sRGB

Quick Tips

#knobs and values of a node
print(nuke.selectedNode())

Create proxy from File with reformat

exr to png

#creates RGBA8bit PNG with .5 scale and write next to file with .png extension. So /path/to/file.exr.png
for node in nuke.selectedNodes():
    if node.Class() == 'Read':
        readPath = node['file'].evaluate()
        reformat = nuke.nodes.Reformat(inputs=[node],type=2,scale=.5)
        write = nuke.nodes.Write(inputs=[reformat],channels='rgba',file=readPath+'.png',file_type='png')

        node['selected'].setValue(0)
        write['selected'].setValue(1)

png to exr

#reads to EXR
for node in nuke.selectedNodes():
    if node.Class() == 'Read':
        readPath = node['file'].evaluate()
        #reformat = nuke.nodes.Reformat(inputs=[node],type=2,scale=.5)
        write = nuke.nodes.Write(inputs=[node],channels='rgba',file=readPath+'.exr',file_type='exr')

        node['selected'].setValue(0)
        write['selected'].setValue(1)

Contact Sheet Ordered From Lasso-Selected

# takes a selection of read nodes, and places them in a contact sheet according to their position
# in the node graph
# first input of contact sheet determines text & text positions, it defaults to the read image name

# to do: auto number the Contact sheet

from random import random


def linkKnobs(sourceNode, destNode, knobs = []):
    for knob in knobs:
        for idx, item in enumerate(sourceNode[knob].array()):
            expression =  sourceNode.name()+'.'+str(knob)+'.'+str(idx)
            destNode[knob].setExpression(expression,idx)


nodes = nuke.selectedNodes()
nodeList = []
minmaxX = [1000000,-1000000]
minmaxY = [1000000,-1000000]
averagePosition = [0,0]
counter = 0

for node in nodes:
    counter = counter + 1
    nodePos = [node['xpos'].value(),node['ypos'].value()]
    averagePosition = [averagePosition[0]+nodePos[0],averagePosition[1]+nodePos[1]]
    minmaxX = [min(minmaxX[0],nodePos[0]),max(minmaxX[1],nodePos[0])]
    minmaxY = [min(minmaxY[0],nodePos[1]),max(minmaxY[1],nodePos[1])]
    nodeList.append([node,nodePos[0],nodePos[1]])

averagePosition = [averagePosition[0]/(counter*1.0),averagePosition[1]/(counter*1.0)]
horizontalSort = True

if minmaxX[1]-minmaxX[0] > minmaxY[1]-minmaxY[0]:
    horizontalSort = True
    nodeList.sort(key=lambda x: x[1])
else:
    horizontalSort = False
    nodeList.sort(key=lambda x: x[2])

textNodes = []
for array in nodeList:

    textNode = nuke.nodes.Text2(inputs=[array[0]],xjustify='left',yjustify='bottom',box='15 15 3000 1000')
    textNodes.append(textNode)

    if horizontalSort:
        textNode.setXpos( int(array[0]['xpos'].value()) )
        textNode.setYpos( int(array[0]['ypos'].value() + 200.0) )
    else:
        textNode.setXpos( int(array[0]['xpos'].value() + 200.0) )
        textNode.setYpos( int(array[0]['ypos'].value() + 20 ) )
    nuke.autoplaceSnap(textNode)

#ratio = nodeList[0][0].width()/nodeList[0][0].height()
#print ratio
#print len(nodeList)
cs = nuke.nodes.ContactSheet(inputs=textNodes)

if horizontalSort:
    cs.setXYpos(int(averagePosition[0] + 0),int(averagePosition[1] + 350))
else:
    cs.setXYpos(int(averagePosition[0] + 350),int(averagePosition[1] + 0))

nuke.autoplaceSnap(cs)

controlText = textNodes[0]
controlText.knob('message').setValue('[lindex [split [lindex [split [knob [topnode].file] .] 0] /] end]')
controlText.knob('tile_color').setValue(int('%02x%02x%02x%02x' % (random()*255,random()*255,random()*255,1),16))


for n in textNodes[1:]:

    n.knob('message').setValue('[expr [knob '+controlText.name()+'.message]]')
    linkKnobs(controlText,n,['box','global_font_scale','yjustify','xjustify','font_size','font_width','font_height','kerning','tracking','baseline_shift','leading','translate','rotate','scale','center'])

Change Read file locations with a text file

OyXUaft.png

Poor man's read node manager if you don't have Shotgun. Basically opens up a temp text file with the selected 'Read' nodes filepaths where you can easily search and replace paths. Non-read nodes won't be affected.

import tempfile, os

reads = []
for node in nuke.selectedNodes():
    if node.Class() == 'Read':
        npath = node['file'].getText()
        reads.append([node,npath])
paths = [item[1] for item in reads]
paths = "\n".join(paths)

fp = tempfile.NamedTemporaryFile(delete=False,suffix=".txt")

fp.write(paths)

fp.flush()

if os.name == 'nt':
    os.startfile(fp.name)
else:
    os.system('xdg-open '+fp.name)
if nuke.ask('Press No to cancel or \nPress Yes if you have finished editing and saved the temp file'):
    f = open(fp.name, "r")
    newpaths = f.read().split('\n')
    for i in range(len(reads)):
        if reads[i][1] != newpaths[i]:
            reads[i][0]['file'].setValue(newpaths[i])
        else:
            print(reads[i][0].name()+" no change detected.")

Automatically merge files into single channel

bE5OYQu.png

If you didn't toggle multi-channel exr you big dummy

This might bug around ' simplename = strpath.split("/").pop().split("_v0")[0].split("_").pop() ' since it was specific to my project (this line figures out the channel name)

def sortAccordingToNodeGraphPosition(nodes):
    '''sorts a node list according to their graph position. Hack-ish but works (?)'''
    nodeList = []
    minmaxX = [1000000,-1000000]
    minmaxY = [1000000,-1000000]
    averagePosition = [0,0]
    counter = 0
    
    for node in nodes:
        counter = counter + 1
        nodePos = [node['xpos'].value(),node['ypos'].value()]
        averagePosition = [averagePosition[0]+nodePos[0],averagePosition[1]+nodePos[1]]
        minmaxX = [min(minmaxX[0],nodePos[0]),max(minmaxX[1],nodePos[0])]
        minmaxY = [min(minmaxY[0],nodePos[1]),max(minmaxY[1],nodePos[1])]
        nodeList.append([node,nodePos[0],nodePos[1]])
    
    averagePosition = [averagePosition[0]/(counter*1.0),averagePosition[1]/(counter*1.0)]
    horizontalSort = True
    
    if minmaxX[1]-minmaxX[0] > minmaxY[1]-minmaxY[0]:
        horizontalSort = True
        nodeList.sort(key=lambda x: x[1])
    else:
        horizontalSort = False
        nodeList.sort(key=lambda x: x[2])
    
    sortedList = [node for node, x,y in nodeList]
    return sortedList


selReads = nuke.selectedNodes()
selReads = sortAccordingToNodeGraphPosition(selReads)
shuffleInputs = []
selReadslen = len(selReads)
for i in range(selReadslen):

    #curRead = selReads[selReadslen-1-i]
    curRead = selReads[i]
    strpath = curRead['file'].value()
    simplename = strpath.split("/").pop().split("_v0")[0].split("_").pop()
    #goes from 'D:/longpath/folder/v006/superlongname_360_beauty_v006.%04d.exr' to  'beauty'


    if i > 0:
        shuffle = nuke.nodes.ShuffleCopy()
        shuffleInputs.append(shuffle)
        shuffle.setInput(0, shuffleInputs[i-1])
        shuffle.setInput(1, curRead)


        cmd = "add_layer {%s " % simplename
        for each in ["r", "g", "b", "a"]:
            cmd += "%s.%s " % (simplename, each)
        
        cmd += "}"
        
        #set using tcl can't figure out python for this
        nuke.tcl(cmd)
        shuffle["label"].setValue("[knob this.out]")
        shuffle["out"].setValue(simplename)
        shuffle["out2"].setValue('rgba')
        shuffle["red"].setValue("red")
        shuffle["green"].setValue("green")
        shuffle["blue"].setValue("blue")
        shuffle["alpha"].setValue("alpha")
        shuffle["black"].setValue("red2")
        shuffle["white"].setValue("green2")
        shuffle["red2"].setValue("blue2")
        shuffle["green2"].setValue("alpha2")

    
    else:
        shuffleInputs.append(curRead)
    
    

Automatically extract channels

source: http://www.muttsy.net/blog/2011/10/20/shuffle-out-all-channels/

import os

createWrite = False
nodes = nuke.selectedNodes()
for node in nodes:
    if node.Class() == 'Read':
        channels = node.channels()
        layers = list( set([channel.split('.')[0] for channel in channels]) )
        layers.sort()
        #readPath = node['file'].evaluate()
        readPath = os.path.splitext(readPath)[0]
        print(readPath)
        if 'rgba' in layers:
            layers.remove('rgba')
        for layer in layers:
            shuffleNode = nuke.nodes.Shuffle(label=layer,inputs=[node])
            shuffleNode['in'].setValue( layer )
            shuffleNode['postage_stamp'].setValue(True)
            if createWrite:
                writeNode = nuke.nodes.Write(label=layer,inputs=[shuffleNode])
                writeNode['file'].value = readPath+layer+'.png'
                print(readPath+layer+'.png')
    else:
        pass



Auto write new folders

for w in nuke.allNodes("Write"):
w.knob("beforeRender").setValue("import os\nif not os.path.isdir(os.path.dirname(nuke.thisNode()['file'].evaluate())):\n  os.makedirs(os.path.dirname(nuke.thisNode()['file'].evaluate()))")