def addWriteNodeToScript(self, script, rootNode, framerate): """ Override the default addWriteNodeToScript functionality so that we can add a SetNode before the default writenodes get added to the script. The SetNode will allow us to later push our mov writenode to this point so that it get parented correctly. """ self.app.log_debug("Adding SetNode before base write node gets added") # Add the SetNode before the write nodes are added. set_command = nuke.SetNode(self._write_set_node_label, 0) script.addNode(set_command) super(ShotgunTranscodeExporter, self).addWriteNodeToScript(script, rootNode, framerate)
def _createWriteNodes(self, firstFrame, start, end, framerate, rootNode): # To add Write nodes, we get a task for the paths with the preset # (default is the "Nuke Write Node" preset) and ask it to generate the Write node for # us, since it knows all about codecs and extensions and can do the token # substitution properly for that particular item. # And doing it here rather than in taskStep out of threading paranoia. writeNodes = [] firstWriteNode = True # Create a stack to prevent multiple write nodes inputting into each other stackId = "ScriptEnd" writeNodes.append(nuke.SetNode(stackId, 0)) writePaths = self._preset.properties()["writePaths"] for (itemPath, itemPreset) in self._exportTemplate.flatten(): for writePath in writePaths: if writePath == itemPath: # Generate a task on same items as this one but swap in the shot path that goes with this preset. taskData = hiero.core.TaskData( itemPreset, self._item, self._exportRoot, itemPath, self._version, self._exportTemplate, project=self._project, cutHandles=self._cutHandles, retime=self._retime, startFrame=firstFrame, resolver=self._resolver, skipOffline=self._skipOffline, shotNameIndex=self._shotNameIndex) task = hiero.core.taskRegistry.createTaskFromPreset( itemPreset, taskData) if hasattr(task, "nukeWriteNode"): # Push to the stack before adding the write node writeNodes.append(nuke.PushNode(stackId)) try: reformatNode = reformatNodeFromPreset(itemPreset) if reformatNode: writeNodes.append(reformatNode) except Exception as e: self.setError(str(e)) # Add Burnin group (if enabled) burninGroup = task.addBurninNodes(script=None) if burninGroup is not None: writeNodes.append(burninGroup) try: writeNode = task.nukeWriteNode( framerate, project=self._project) writeNode.setKnob("first", start) writeNode.setKnob("last", end) if writeNode.knob('file_type') == 'mov': writeNode.setKnob( 'file', "[python writeNodeManager.setOutputPath('mov')]" ) writeNode.setKnob( 'afterRender', 'ftrackUpload.uploadToFtrack()') else: writeNode.setKnob( 'file', "[python writeNodeManager.setOutputPath('img')]" ) writeNodes.append(writeNode) # Set the first write node in the list as the one to be shown/rendered in the timeline if firstWriteNode: firstWriteNode = False rootNode.setKnob( nuke.RootNode.kTimelineWriteNodeKnobName, writeNode.knob("name")) except RuntimeError as e: # Failed to generate write node, set task error in export queue # Most likely because could not map default colourspace for format settings. self.setError(str(e)) return writeNodes
def _taskStep(self): hiero.core.TaskBase.taskStep(self) if self._nothingToDo: return False script = nuke.ScriptWriter() start, end = self.outputRange(ignoreRetimes=True, clampToSource=False) unclampedStart = start hiero.core.log.debug("rootNode range is %s %s %s", start, end, self._startFrame) firstFrame = start if self._startFrame is not None: firstFrame = self._startFrame # if startFrame is negative we can only assume this is intentional if start < 0 and (self._startFrame is None or self._startFrame >= 0): # We dont want to export an image sequence with negative frame numbers self.setWarning( "%i Frames of handles will result in a negative frame index.\nFirst frame clamped to 0." % self._cutHandles) start = 0 firstFrame = 0 framerate = self._sequence.framerate() dropFrames = self._sequence.dropFrame() if self._clip and self._clip.framerate().isValid(): framerate = self._clip.framerate() dropFrames = self._clip.dropFrame() fps = framerate.toFloat() showAnnotations = False # Create the root node, this specifies the global frame range and frame rate rootNode = nuke.RootNode(start, end, fps, showAnnotations) rootNode.addProjectSettings(self._projectSettings) dailiesScriptCheck = 'writeNodeManager.checkDailiesTab()' rootNode.setKnob("onScriptLoad", dailiesScriptCheck) script.addNode(rootNode) if isinstance(self._item, hiero.core.TrackItem): rootNode.addInputTextKnob( "shot_guid", value=hiero.core.FnNukeHelpers._guidFromCopyTag(self._item), tooltip= "This is used to identify the master track item within the script", visible=False) inHandle, outHandle = self.outputHandles(self._retime != True) rootNode.addInputTextKnob("in_handle", value=int(inHandle), visible=False) rootNode.addInputTextKnob("out_handle", value=int(outHandle), visible=False) # Set the format knob of the root node rootNode.setKnob("format", str(self.rootFormat())) # BUG 40367 - proxy_type should be set to 'scale' by default to reflect # the custom default set in Nuke. Sadly this value can't be queried, # as it's set by nuke.knobDefault, hence the hard coding. rootNode.setKnob("proxy_type", "scale") # Project setting for using OCIO nodes for colourspace transform useOCIONodes = self._project.lutUseOCIOForExport() # A dict of arguments which are used when calling addToNukeScript on any clip/sequence/trackitem addToScriptCommonArgs = {'useOCIO': useOCIONodes} writeNodes = self._createWriteNodes(firstFrame, start, end, framerate, rootNode) if not writeNodes: # Blank preset is valid, if preset has been set and doesn't exist, report as error self.setWarning( str("NukeShotExporter: No write node destination selected")) if self.writingSequence(): self.writeSequence(script, addToScriptCommonArgs) # Write out the single track item else: self.writeTrackItem(script, addToScriptCommonArgs, firstFrame) script.pushLayoutContext("write", "%s_Render" % self._item.name()) metadataNode = nuke.MetadataNode(metadatavalues=[( "hiero/project", self._projectName), ( "hiero/project_guid", self._project.guid()), ("hiero/shot_tag_guid", self._tag_guid)]) # Add sequence Tags to metadata metadataNode.addMetadataFromTags(self._sequence.tags()) metadataNode.addMetadata([("hiero/sequence", self._sequence.name())]) metadataNode.addMetadata([("hiero/shot", self._clip.name())]) # Apply timeline offset to nuke output if isinstance(self._item, hiero.core.TrackItem): if self._cutHandles is None: # Whole clip, so timecode start frame is first frame of clip timeCodeNodeStartFrame = unclampedStart else: startHandle, endHandle = self.outputHandles() timeCodeNodeStartFrame = trackItemTimeCodeNodeStartFrame( unclampedStart, self._item, startHandle, endHandle) timecodeStart = self._clip.timecodeStart() else: # Exporting whole sequence/clip timeCodeNodeStartFrame = unclampedStart timecodeStart = self._item.timecodeStart() script.addNode( nuke.AddTimeCodeNode(timecodeStart=timecodeStart, fps=framerate, dropFrames=dropFrames, frame=timeCodeNodeStartFrame)) # The AddTimeCode field will insert an integer framerate into the metadata, if the framerate is floating point, we need to correct this metadataNode.addMetadata([("input/frame_rate", framerate.toFloat())]) script.addNode(metadataNode) # Add the delivery gizmo deliveryNode = nuke.Node('delivery') writeNodes.append(nuke.PushNode("ScriptEnd")) writeNodes.append(deliveryNode) # Generate Write nodes for nuke renders. # Bug 45843 - Branch the viewer when using OCIO nodes so # the OCIOColorSpace node created for the writer doesn't # get used when rendering the viewer. branchViewer = useOCIONodes if branchViewer: branchStackId = "OCIOWriterBranch" script.addNode(nuke.SetNode(branchStackId, 0)) assert (isinstance(writeNodes[-1], nuke.WriteNode)) ocioNodes = [ node for node in writeNodes[-1].getNodes() if node.type() == "OCIOColorSpace" ] assert (len(ocioNodes)) ocioNode = ocioNodes[0] ocioNode.setAlignToNode(metadataNode) scriptFilename = self.resolvedExportPath() for node in writeNodes: if node.knobs().has_key('file_type'): if node.knob('file_type') == 'mov': slateNode = nuke.Node('slate') script.addNode(slateNode) node.setInputNode(0, metadataNode) script.addNode(node) if branchViewer: script.addNode(nuke.PushNode(branchStackId)) # add a viewer viewerNode = nuke.Node("Viewer") # Bug 45914: If the user has for some reason selected a custom OCIO config, but then set the 'Use OCIO nodes when export' option to False, # don't set the 'viewerProcess' knob, it's referencing a LUT in the OCIO config which Nuke doesn't know about setViewerProcess = True if not self._projectSettings[ 'lutUseOCIOForExport'] and self._projectSettings[ 'ocioConfigPath']: setViewerProcess = False if setViewerProcess: # Bug 45845 - default viewer lut should be set in the comp viewerLut = _toNukeViewerLutFormat( self._projectSettings['lutSettingViewer']) viewerNode.setKnob("viewerProcess", viewerLut) script.addNode(viewerNode) hiero.core.log.debug("Writing Script to: %s", scriptFilename) script.popLayoutContext() # Layout the script #hiero.exporters.FnScriptLayout.scriptLayout(script) script.writeToDisk(scriptFilename) # Nothing left to do, return False. return False
def taskStep(self): FnShotExporter.ShotTask.taskStep(self) if self._nothingToDo: return False eventManager = FnAssetAPI.Events.getEventManager() script = nuke.ScriptWriter() start, end = self.outputRange(ignoreRetimes=True, clampToSource=False) unclampedStart = start hiero.core.log.debug("rootNode range is %s %s %s", start, end, self._startFrame) firstFrame = start if self._startFrame is not None: firstFrame = self._startFrame # if startFrame is negative we can only assume this is intentional if start < 0 and (self._startFrame is None or self._startFrame >= 0): # We dont want to export an image sequence with negative frame numbers self.setWarning( "%i Frames of handles will result in a negative frame index.\nFirst frame clamped to 0." % self._cutHandles) start = 0 # Clip framerate may be invalid, then use parent sequence framerate framerate = self._sequence.framerate() dropFrames = self._sequence.dropFrame() if self._clip and self._clip.framerate().isValid(): framerate = self._clip.framerate() dropFrames = self._clip.dropFrame() fps = framerate.toFloat() # Create the root node, this specifies the global frame range and frame rate rootNode = nuke.RootNode(start, end, fps) rootNode.addProjectSettings(self._projectSettings) #rootNode.setKnob("project_directory", os.path.split(self.resolvedExportPath())[0]) script.addNode(rootNode) reformat = None # Set the root node format to default to source format if isinstance(self._item, hiero.core.Sequence) or self._collate: reformat = self._sequence.format().addToNukeScript(None) elif isinstance(self._item, hiero.core.TrackItem): reformat = self._clip.format().addToNukeScript(None) if isinstance(self._item, hiero.core.TrackItem): rootNode.addInputTextKnob( "shot_guid", value=hiero.core.FnNukeHelpers._guidFromCloneTag(self._item), tooltip= "This is used to identify the master track item within the script" ) inHandle, outHandle = self.outputHandles(self._retime != True) rootNode.addInputTextKnob("in_handle", value=int(inHandle)) rootNode.addInputTextKnob("out_handle", value=int(outHandle)) # This sets the format knob of the root node in the Nuke Script rootReformat = None if isinstance(self._item, hiero.core.Sequence) or self._collate: rootReformat = self._sequence.format().addToNukeScript(None) elif isinstance(self._item, hiero.core.TrackItem): rootReformat = self._item.parentSequence().format( ).addToNukeScript(None) rootNode.setKnob("format", rootReformat.knob("format")) # Add Unconnected additional nodes if self._preset.properties()["additionalNodesEnabled"]: script.addNode( FnExternalRender.createAdditionalNodes( FnExternalRender.kUnconnected, self._preset.properties()["additionalNodesData"], self._item)) # Project setting for using OCIO nodes for colourspace transform useOCIONodes = self._project.lutUseOCIOForExport() # To add Write nodes, we get a task for the paths with the preset # (default is the "Nuke Write Node" preset) and ask it to generate the Write node for # us, since it knows all about codecs and extensions and can do the token # substitution properly for that particular item. # And doing it here rather than in taskStep out of threading paranoia. self._writeNodes = [] stackId = "ScriptEnd" self._writeNodes.append(nuke.SetNode(stackId, 0)) writePathExists = False writePaths = self._preset.properties()["writePaths"] for (itemPath, itemPreset) in self._exportTemplate.flatten(): for writePath in writePaths: if writePath == itemPath: # Generate a task on same items as this one but swap in the shot path that goes with this preset. taskData = hiero.core.TaskData( itemPreset, self._item, self._exportRoot, itemPath, self._version, self._exportTemplate, project=self._project, cutHandles=self._cutHandles, retime=self._retime, startFrame=firstFrame, resolver=self._resolver, skipOffline=self._skipOffline) task = hiero.core.taskRegistry.createTaskFromPreset( itemPreset, taskData) if hasattr(task, "nukeWriteNode"): self._writeNodes.append(nuke.PushNode(stackId)) rf = itemPreset.properties()["reformat"] # If the reformat field has been set, create a reformat node immediately before the Write. if str(rf["to_type"]) == nuke.ReformatNode.kToFormat: if "width" in rf and "height" in rf and "pixelAspect" in rf and "name" in rf and "resize" in rf: format = hiero.core.Format( rf["width"], rf["height"], rf["pixelAspect"], rf["name"]) resize = rf["resize"] reformat = format.addToNukeScript( None, resize=resize) self._writeNodes.append(reformat) else: self.setError( "reformat mode set to kToFormat but preset properties do not contain required settings." ) # Add Burnin group (if enabled) burninGroup = task.addBurninNodes(script=None) if burninGroup is not None: self._writeNodes.append(burninGroup) try: writeNode = task.nukeWriteNode( framerate, projectsettings=self._projectSettings) writeNode.setKnob("first", start) writeNode.setKnob("last", end) self._writeNodes.append(writeNode) except RuntimeError as e: # Failed to generate write node, set task error in export queue # Most likely because could not map default colourspace for format settings. self.setError(str(e)) writePathExists = True # MPLEC TODO should enforce in UI that you can't pick things that won't work. if not writePaths: # Blank preset is valid, if preset has been set and doesn't exist, report as error self.setWarning( str("NukeShotExporter: No write node destination selected")) # If this flag is True, a read node pointing at the original media will be added # If read nodes which point at export items are selected, this flag will be set False originalMediaReadNode = True useEntityRefs = self._preset.properties().get("useAssets", False) for item in [self._item]: originalMediaReadNode = True if not self._collate: # Build read nodes for selected entries in the shot template readPaths = self._preset.properties()["readPaths"] for (itemPath, itemPreset) in self._exportTemplate.flatten(): for readPath in readPaths: if itemPath == readPath: # Generate a task on same items as this one but swap in the shot path that goes with this preset. taskData = hiero.core.TaskData( itemPreset, item, self._exportRoot, itemPath, self._version, self._exportTemplate, project=self._project, cutHandles=self._cutHandles, retime=self._retime, startFrame=self._startFrame, resolver=self._resolver, skipOffline=self._skipOffline) task = hiero.core.taskRegistry.createTaskFromPreset( itemPreset, taskData) readNodePath = task.resolvedExportPath() itemStart, itemEnd = task.outputRange() itemFirstFrame = firstFrame if self._startFrame: itemFirstFrame = self._startFrame if hiero.core.isVideoFileExtension( os.path.splitext(readNodePath)[1].lower()): # Don't specify frame range when media is single file newSource = hiero.core.MediaSource( readNodePath) itemEnd = itemEnd - itemStart itemStart = 0 else: # File is image sequence, so specify frame range newSource = hiero.core.MediaSource( readNodePath + (" %i-%i" % task.outputRange())) newClip = hiero.core.Clip(newSource, itemStart, itemEnd) originalMediaReadNode = False if self._cutHandles is None: newClip.addToNukeScript( script, firstFrame=itemFirstFrame, trimmed=True, useOCIO=useOCIONodes, additionalNodesCallback=self. _buildAdditionalNodes, nodeLabel=item.parent().name(), useEntityRefs=useEntityRefs) else: # Clone track item and replace source with new clip (which may be offline) newTrackItem = hiero.core.TrackItem( item.name(), item.mediaType()) for tag in self._item.tags(): newTrackItem.addTag(tag) # Handles may not be exactly what the user specified. They may be clamped to media range inHandle, outHandle = 0, 0 if self._cutHandles: # Get the output range without handles inHandle, outHandle = task.outputHandles() hiero.core.log.debug( "in/outHandle %s %s", inHandle, outHandle) newTrackItem.setSource(newClip) # Trackitem in/out newTrackItem.setTimelineIn(item.timelineIn()) newTrackItem.setTimelineOut(item.timelineOut()) # Source in/out is the clip range less the handles. newTrackItem.setSourceIn(inHandle * -1) newTrackItem.setSourceOut((newClip.duration() - 1) - outHandle) #print "New trackitem (src/dst/clip) ", newTrackItem.sourceDuration(), newTrackItem.duration(), newClip.duration() # Add track item to nuke script newTrackItem.addToNukeScript( script, firstFrame=itemFirstFrame, includeRetimes=self._retime, retimeMethod=self._preset.properties() ["method"], startHandle=self._cutHandles, endHandle=self._cutHandles, additionalNodesCallback=self. _buildAdditionalNodes, useOCIO=useOCIONodes, nodeLabel=item.parent().name(), useEntityRefs=useEntityRefs) if originalMediaReadNode: if isinstance(self._item, hiero.core.Sequence) or self._collate: # When building a collated sequence, everything is offset by 1000 # This gives head room for shots which may go negative when transposed to a # custom start frame. This offset is negated during script generation. offset = -1000 if self._collate else 0 self._sequence.addToNukeScript( script, additionalNodesCallback=self._buildAdditionalNodes, includeRetimes=True, offset=offset, useOCIO=useOCIONodes, skipOffline=self._skipOffline, useEntityRefs=useEntityRefs) elif isinstance(self._item, hiero.core.TrackItem): clip = item.source() # Add a Read Node for this Clip. if self._cutHandles is None: clip.addToNukeScript( script, firstFrame=firstFrame, trimmed=True, additionalNodesCallback=self._buildAdditionalNodes, useOCIO=useOCIONodes, nodeLabel=item.parent().name(), useEntityRefs=useEntityRefs) else: item.addToNukeScript( script, firstFrame=firstFrame, includeRetimes=self._retime, retimeMethod=self._preset.properties()["method"], startHandle=self._cutHandles, endHandle=self._cutHandles, additionalNodesCallback=self._buildAdditionalNodes, useOCIO=useOCIONodes, nodeLabel=item.parent().name(), useEntityRefs=useEntityRefs) metadataNode = nuke.MetadataNode(metadatavalues=[( "hiero/project", self._projectName), ( "hiero/project_guid", self._project.guid()), ("hiero/shot_tag_guid", self._tag_guid)]) # Add sequence Tags to metadata metadataNode.addMetadata([('"hiero/tags/' + tag.name() + '"', tag.name()) for tag in self._sequence.tags()]) metadataNode.addMetadata([('"hiero/tags/' + tag.name() + '/note"', tag.note()) for tag in self._sequence.tags()]) # Apply timeline offset to nuke output if isinstance(self._item, hiero.core.TrackItem): if self._cutHandles is None: # Whole clip, so timecode start frame is first frame of clip timeCodeNodeStartFrame = unclampedStart else: # Exporting shot with handles, adjust timecode start frame by shot trim and handle count timeCodeNodeStartFrame = ( unclampedStart - self._item.sourceIn()) + self._cutHandles timecodeStart = self._clip.timecodeStart() else: # Exporting whole sequence/clip timeCodeNodeStartFrame = unclampedStart timecodeStart = self._item.timecodeStart() script.addNode( nuke.AddTimeCodeNode(timecodeStart=timecodeStart, fps=framerate, dropFrames=dropFrames, frame=timeCodeNodeStartFrame)) # The AddTimeCode field will insert an integer framerate into the metadata, if the framerate is floating point, we need to correct this metadataNode.addMetadata([("input/frame_rate", framerate.toFloat())]) script.addNode(metadataNode) # Generate Write nodes for nuke renders. for node in self._writeNodes: script.addNode(node) # We do this here, rather than when its created so the event happens at a # suitable time, otherwise, it's called before the Read node is created. if node.type() == "Write" and self._preset.properties( )["useAssets"]: ## @todo Assuming that we're always on a thread for now, this should be verified eventManager.blockingEvent(False, 'hieroToNukeScriptAddWrite', self._item, node.knob("file"), node, script) # add a viewer viewerNode = nuke.Node("Viewer") script.addNode(viewerNode) self._writeScript(script) # Nothing left to do, return False. return False