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