Nuke Python
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.pyhave 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
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
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
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
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
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()))")