import re, os, math import nuke, nukescripts class musterSubmit(KellerNukePlugin): def configurePlugin(self): menubar = nuke.menu("Nuke") self.m = menubar.addMenu("&Render") self.m.addCommand("MusterSubmit", 'musterSubmit.submit()', "ctrl+r") def unconfigurePlugin(self): self.m.removeItem("MusterSubmit") ############################# # GLOBALS ############################# mstr_server = "192.168.0.251" submission_user ="admin" password = "" mrtool = "\\\\hades\\p\\env\\muster9\\Mrtool.exe" username = "admin" pool = "NukeWin" template = "821" #nukePath="\\\\hades\\p\\env\\nuke\\_workgroup\\%s.%s\\" %(nuke.NUKE_VERSION_MAJOR,nuke.NUKE_VERSION_MINOR) nukePath="\\\\hades\\p\\env\\nuke\\_workgroup\\%s\\" %nuke.NUKE_VERSION_MAJOR # Nuke Version nukeVer = nuke.env['NukeVersionString'] nukeExe = "Nuke" + nukeVer[:-2] + ".exe" # Build env dict for the farm and ignore these entries... ignoreEnvEntries = ['PATH', 'NUKE_TEMP_DIR', 'TMP', 'TEMP', 'OCIO', 'ADSKFLEX_LICENSE_FILE', 'FPS', 'SOLIDANGLE_LICENSE', 'foundry_LICENSE', 'peregrinel_LICENSE', 'keller_LICENSE', 'QUALM_LICENSE', 'PROGRAMFILES', 'APPDATA', 'COMMONPROGRAMW6432', 'PATHEXT', 'ALLUSERSPROFILE', 'PSMODULEPATH', 'PROCESSOR_IDENTIFIER', 'PROCESSOR_ARCHITECTURE', 'LOCALAPPDATA', 'PROCESSOR', 'GUID', 'PYTHONPATH', 'IDEA_INITIAL_DIRECTORY', 'ONEDRIVE', 'PYCHARM', 'PYCHARM_HOSTED', 'NUMBER_OF_PROCESSORS', 'PROCESSOR', 'HOMEPATH' ,'COMPUTERNAME', 'USERDOMAIN', 'USERPROFILE','COMPILE_0_1DEV'] ############################ def get_broken_write_nodes(): """ Returns list of all write nodes that refer to non-existing paths (or an empty list). If nodes are selected only they will be checked. """ nodes = nuke.selectedNodes() if not nodes: nodes = nuke.allNodes() n = [i for i in nodes if i.Class() == "Write"] result = [] for curNode in n: fileKnob = curNode['file'] try: if not os.path.exists(os.path.dirname(fileKnob.evaluate())): result.append(curNode) except: pass return result def fix_broken_write_nodes(): """ Create paths for all (selected) write nodes in case they don't exist yet. """ nodes = get_broken_write_nodes() for curNode in nodes: thePath = os.path.dirname(curNode['file'].evaluate()) if thePath and not os.path.exists(thePath): try: os.makedirs(thePath) print ("musterSubmit: Info. Created directory "+thePath) except: nuke.message("musterSubmit: Error. Error creating "+thePath) def _replaceKnobPath(fileknob): infile = os.path.normpath(fileknob.getValue()).strip() infile = infile.replace("\\", "/") infile = infile.replace("p:/", "//hades/p/") infile = infile.replace("P:/", "//hades/p/") infile = infile.replace("o:/", "//calculon/o/") infile = infile.replace("O:/", "//calculon/o/") return infile def _replacePathInNodes(root): for node in root.nodes(): if isinstance(node, nuke.Group): _replacePathInNodes(node) elif node.Class() in ["Read", "Write"]: newFile = _replaceKnobPath(node["file"]) node["file"].setValue(newFile) print ("musterSubmit: Info. Checking file Path of node %s: %s" % (node.name(), newFile)) def replaceNodeFilePaths(): r = nuke.root() _replacePathInNodes(r) def submit(selectedNodes = None): """ Submits a script to Muster. Cross-Platform (Win and MacOS) """ replaceNodeFilePaths() # comp saved? if not nuke.knob("root.name") or nuke.modified(): if nuke.ask("Script needs to be saved before rendering.\nSave and continue?"): try: nuke.scriptSave() except RuntimeError: # if save dialog was cancelled and script is not untitled we could try to render anyways if not nuke.knob("root.name") or not nuke.ask("Could not save script. Try to render anyways?"): return else: return # get configs from scene # TODO: check with KEnv # ocio if nuke.root()['OCIO_config'].value() == 'custom': if nuke.root()['customOCIOConfigPath'].value() != "": ocioConfig = nuke.root()['customOCIOConfigPath'].value() ocioConfig = ocioConfig.replace("/","\\") else: ocioConfig = '\\env\\nuke\\_run\\Nuke\\' + nuke.NUKE_VERSION_STRING + '\\plugins\\OCIOConfigs\\configs\\' + \ nuke.root()['OCIO_config'].value() + '\\config.ocio' # Nuke Workgroup try: nukePath=os.environ['NUKE_PATH'] except: # fallback to gen workgroup pass # get default render range a = nuke.knob("first_frame") b = nuke.knob("last_frame") start = int(a) end = int(b) incr = 1 rangeStr = a+"-"+b # set packet size automatically to use all available nodes # set _max_instances to current size of render farm # OVERRIDING PACKET SIZE TO 1, OTHERWISE RENDER FAILS TOO OFTEN! #_max_instances = 6 #_max_psize = 10 psize = 4 # find packet size so that there is a small remainder #_smallest_remainder = 100 #for i in range(_max_psize+1, 1): # _remainder = (end-start+1) % (i * _max_instances) # if _remainder <= _smallest_remainder: # _smallest_remainder = _remainder # psize = i # packet size not smaller than 1/15th of frame range #psize = max( min(_max_psize, int((end-start+1)/15)), psize) #Priority: prio = 10 #Threads threads = 16 #Cache-Limit cachelimit = 8192 #Default Folder NukeWin parentID = 147 pool = "NukeWin" #One Job per Write parallel = False # build panel panel = nuke.Panel("Submit to Muster") panel.setWidth(500) panel.addSingleLineInput("Frames to render:", rangeStr) panel.addSingleLineInput("Packet size:", psize) panel.addSingleLineInput("Max. instances:", "") panel.addSingleLineInput("No. of threads:", threads) panel.addSingleLineInput("Ram-Cache Limit:", cachelimit) panel.addSingleLineInput("Priority:", prio) panel.addSingleLineInput("RenderPool:", pool) panel.addEnumerationPulldown("Muster Folder:","NukeWin HOT") panel.addSingleLineInput("Workgroup:", nukePath) panel.addBooleanCheckBox("Submit one job per Write Node", parallel) #panel.addSingleLineInput("OCIO Config:", ocioConfig) # panel.addButton("Cancel") panel.addButton("OK") if panel.show() == 0: return mfolder = panel.value("Muster Folder:") if mfolder == "HOT": parentID = 29037 elif mfolder == "NukeWin": parentID = 147 else: pass mpool = panel.value("RenderPool:") if mpool is not None: pool = mpool else: pass rangeStr = panel.value("Frames to render:") if rangeStr is not None: rangeStr = re.sub("[-|-|/]", " ", rangeStr) t = rangeStr.split() else: return if len(t) > 0: start = int(t[0]) end = start else: return if len(t) > 1: end = int(t[1]) if len(t) > 2: incr = int(t[2]) psize = panel.value("Packet size:") if psize is not None: try: psize = int(psize) psize = max(1, psize) except ValueError: psize = 4 else: psize = 4 maxi = panel.value("Max. instances:") try: maxistr = "-max %i" % (max(0, int(maxi))) except ValueError: maxistr = "" prio = panel.value("Priority:") if prio is not None: try: prio = int(prio) except ValueError: prio = 10 else: prio = 10 threads = panel.value("No. of threads:") if threads is not None: try: threads = int(threads) except ValueError: threads = 16 else: threads = 16 cache = panel.value("Ram-Cache Limit:") if cache is not None: try: cache = int(cache) except ValueError: cache = 20000 else: cache = 20000 parallel = panel.value("Submit one job per Write Node") if parallel is None: parallel is False currNk = nuke.value("root.name") nukePath = panel.value("Workgroup:") if nuke.env["WIN32"]: currNk = re.sub(r"^[pP]:", r"//hades/p", currNk) currNk = re.sub(r"^[oO]:", r"//calculon/o", currNk) currNk = os.path.normpath(currNk) currNk = currNk.replace("\\","/") # -s server # -n job name # -u user name # -sf start frame # -b batch submit # -ef end frame # -pk packet size # -bf by frame (frame stepping) # -e engine template ID # -st numbering step # -f file name # -pool destination pool # -pr priority # -max maximum nr of instances # -parent 39401 // folder ID # jobname for later use jobname = os.path.basename(nuke.value("root.name")).split(".nk")[0] jobtmp = "replaceMe" #envDict = {"horst":"hemke","peter":"wolf"} envDict = {key: val for key, val in os.environ.items() if key not in ignoreEnvEntries} cmd = '%s -s %s -u %s -b -e %s -n %s' % (mrtool, mstr_server, username, template, jobtmp) cmd += ' -sf %i -ef %i -bf 1 -st 1' % (int(start), int(end)) cmd += ' -pk %i -pr %i %s -pool "%s" -parent %i' % (int(psize), int(prio), maxistr, pool, int(parentID)) cmd += ' -attr "NUKEVERSION" %s 0 -attr "NUKEPATH" %s 0 -attr "NUKERENDERTHREADS" %s 0' % (nukeVer, nukePath, int(threads)) cmd += ' -attr "NUKECACHELIMIT" %s 0 -attr "NUKEEXEVERSION" %s 0' % (int(cache), nukeExe) cmd += ' -attr "environmental_variables" "%s" 0' % (';'.join('{}={}'.format(key, val.replace('/','\\').replace('\\','\\\\')) for key, val in sorted(envDict.items()))) cmd += ' -f "%s"' % currNk def appendWriteNodes(_nodes): writeNodes = list() if _nodes is not None: for n in _nodes: if n.Class() == "Write": writeNodes.append(n) elif n.Class() == "Group": writeNodes = writeNodes + appendWriteNodes(n.nodes()) return writeNodes if selectedNodes is None: selectedNodes = nuke.selectedNodes() writeNodes = appendWriteNodes(selectedNodes) # sort selWrites = '__'+'_'.join([node.fullName() for node in sorted(writeNodes)]) if writeNodes: if parallel == False: cmd += ' -add "-X '+','.join([node.fullName() for node in sorted(writeNodes)]) rep = jobname+selWrites cmd = cmd.replace("replaceMe",str(rep)) # submit print ("musterSubmit: Info. Submitting...") print (cmd) print (os.system(cmd)) else: for no in sorted(writeNodes): cmd += ' -add "-X ' + no.fullName() + '"' # change job name cmd = cmd.replace("replaceMe", jobname + "__" + no.fullName()) print ("musterSubmit: Info. Submitting...") print (cmd) # submit print (os.system(cmd)) # change name back for next loop cmd = cmd.replace(jobname + "__" + no.fullName(),"replaceMe")