def _beforeNukeScriptWrite(self, script):
        """
        Add ShotgunWriteNodePlaceholder Metadata nodes for tk-nuke-writenode to
        create full Tk WriteNodes in the Nuke environment
        """
        FnNukeShotExporter.NukeShotExporter._beforeNukeScriptWrite(
            self, script)

        # As the default script from Nuke Studio 11 > has a viewer node as the last node, it means that we
        # can't simply relying on our write nodes being linked up at the end. Instead we need to manually modify
        # the script by temporarily removing the viewer node and then adding it back in.
        nodeList = script.getNodes()

        currentLayoutContext = script._layoutContextStack[-1]

        # extract the current end Node from the script but keep hold of it so we can add it back on.
        oldScriptEnd = nodeList.pop()

        # now extract the last node's layout and keep hold of it so we can add it back on.
        oldLayoutEnd = currentLayoutContext.getNodes().pop()

        try:
            for toolkit_specifier in self._preset.properties(
            )["toolkitWriteNodes"]:
                # break down a string like 'Toolkit Node: Mono Dpx ("editorial")' into name and output
                match = re.match(
                    "^Toolkit Node: (?P<name>.+) \(\"(?P<output>.+)\"\)",
                    toolkit_specifier)

                metadata = match.groupdict()
                node = nuke.MetadataNode(metadatavalues=metadata.items())
                node.setName('ShotgunWriteNodePlaceholder')

                self.app.log_debug(
                    "Created ShotgunWriteNodePlaceholder Node: %s" %
                    node._knobValues)
                # rather than using the script.addNode, we append our node directly to the nodeList
                nodeList.append(node)

                # now add our new node to the layout
                currentLayoutContext.getNodes().append(node)
        except Exception:
            self.app.logger.exception("Failed to add SG writenodes")
        finally:
            # now put back the viewer nodes layout
            currentLayoutContext.getNodes().append(oldLayoutEnd)

            # put the old end Node back
            nodeList.append(oldScriptEnd)
Ejemplo n.º 2
0
    def _beforeNukeScriptWrite(self, script):
        """
        Add ShotgunWriteNodePlaceholder Metadata nodes for tk-nuke-writenode to 
        create full Tk WriteNodes in the Nuke environment
        """
        FnNukeShotExporter.NukeShotExporter._beforeNukeScriptWrite(self, script)

        for toolkit_specifier in self._preset.properties()["toolkitWriteNodes"]:
            # break down a string like 'Toolkit Node: Mono Dpx ("editorial")' into name and output
            match = re.match("^Toolkit Node: (?P<name>.+) \(\"(?P<output>.+)\"\)",
                toolkit_specifier)

            metadata = match.groupdict()
            node = nuke.MetadataNode(metadatavalues=metadata.items())
            node.setName('ShotgunWriteNodePlaceholder')

            self.app.log_debug("Created ShotgunWriteNodePlaceholder Node: %s" % node._knobValues)
            script.addNode(node)
Ejemplo n.º 3
0
    def writeSequenceToScript(self, script):
        # Get the range to set on the Root node. This is the range the nuke script will render by default.
        start, end = self.outputRange()
        log.debug("TranscodeExporter: rootNode range is %s %s", start, end)

        framerate = self._sequence.framerate()
        dropFrames = self._sequence.dropFrame()
        fps = framerate.toFloat()
        rootNode = self.makeRootNode(start, end, fps)
        script.addNode(rootNode)

        # Add Unconnected additional nodes
        if self._preset.properties()["additionalNodesEnabled"]:
            script.addNode(
                FnExternalRender.createAdditionalNodes(
                    FnExternalRender.kUnconnected,
                    self._preset.properties()["additionalNodesData"],
                    self._item))

        # Force track items to be reformatted to fit the sequence in this case
        reformatMethod = {
            "to_type": nuke.ReformatNode.kCompReformatToSequence,
            "filter": self._preset.properties()["reformat"].get("filter")
        }

        if self._preset.properties()["add_image"]:
            watermark_image = get_watermark_read(
                self._preset.properties()["watermark_path"],
                self._preset.properties()["watermark_premult"])
            premult_node = nuke.Node('Premult')
            watermark_transform = nuke.Node('Transform')
            watermark_transform.setKnob(
                'translate',
                self._preset.properties()["watermark_transform"])
            script.addNode(watermark_image)
            script.addNode(premult_node)
            script.addNode(watermark_transform)

        # Build out the sequence.
        scriptParams = FnNukeHelpersV2.ScriptWriteParameters(
            includeAnnotations=False,
            includeEffects=self.includeEffects(),
            retimeMethod=self._preset.properties()["method"],
            reformatMethod=reformatMethod,
            additionalNodesCallback=self._buildAdditionalNodes,
            views=self.views())

        script.pushLayoutContext("sequence",
                                 self._sequence.name(),
                                 disconnected=False)
        sequenceWriter = FnNukeHelpersV2.SequenceScriptWriter(
            self._sequence, scriptParams)
        sequenceWriter.writeToScript(script,
                                     offset=0,
                                     skipOffline=self._skipOffline,
                                     mediaToSkip=self._mediaToSkip,
                                     disconnected=False,
                                     masterTracks=None)
        script.popLayoutContext()
        script.pushLayoutContext("write", "%s_Render" % self._item.name())

        # Create metadata node
        metadataNode = nuke.MetadataNode(metadatavalues=[(
            "hiero/project",
            self._projectName), ("hiero/project_guid", self._project.guid())])

        # Add sequence Tags to metadata
        metadataNode.addMetadataFromTags(self._sequence.tags())

        # Apply timeline offset to nuke output
        script.addNode(
            nuke.AddTimeCodeNode(
                timecodeStart=self._sequence.timecodeStart(),
                fps=framerate,
                dropFrames=dropFrames,
                frame=0 if self._startFrame is None else self._startFrame))

        # 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())])

        # And next the Write.
        script.addNode(metadataNode)

        # Add Burnin group (if enabled)
        self.addBurninNodes(script)

        if self._preset.properties()["add_image"]:
            merge_node = nuke.MergeNode()
            script.addNode(merge_node)

        # Get the output format, either from the sequence or the preset,  and set it as the root format.
        # If a reformat is specified in the preset, add it immediately before the Write node.
        outputReformatNode = self._sequence.format().addToNukeScript(
            resize=nuke.ReformatNode.kResizeNone, black_outside=False)
        self._addReformatNode(script, rootNode, outputReformatNode)

        self.addWriteNodeToScript(script, rootNode, framerate)
        script.addNode(createViewerNode(self._projectSettings))
        script.popLayoutContext()
Ejemplo n.º 4
0
    def writeClipOrTrackItemToScript(self, script):
        isMovieContainerFormat = self._preset.properties()["file_type"] in (
            "mov", "mov32", "mov64", "ffmpeg")

        start, end = self.outputRange(ignoreRetimes=True, clampToSource=False)
        unclampedStart = start
        log.debug("rootNode range is %s %s", start, end)

        firstFrame, lastFrame = start, end
        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
                          ) and not isMovieContainerFormat:
            # We dont want to export an image sequence with a 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

        # The clip framerate may be invalid, if so, use parent sequence framerate
        fps, framerate, dropFrames = None, None, False
        if self._sequence:
            framerate = self._sequence.framerate()
            dropFrames = self._sequence.dropFrame()
        if self._clip.framerate().isValid():
            framerate = self._clip.framerate()
            dropFrames = self._clip.dropFrame()
        if framerate:
            fps = framerate.toFloat()

        # Create root node, this defines global frame range and framerate
        rootNode = self.makeRootNode(start, end, fps)
        script.addNode(rootNode)

        if self._preset.properties()["add_image"]:
            watermark_image = get_watermark_read(
                self._preset.properties()["watermark_path"],
                self._preset.properties()["watermark_premult"])
            premult_node = nuke.Node('Premult')
            watermark_transform = nuke.Node('Transform')
            watermark_transform.setKnob(
                'translate',
                self._preset.properties()["watermark_transform"])
            script.addNode(watermark_image)
            script.addNode(premult_node)
            script.addNode(watermark_transform)

        # Add Unconnected additional nodes
        if self._preset.properties()["additionalNodesEnabled"]:
            script.addNode(
                FnExternalRender.createAdditionalNodes(
                    FnExternalRender.kUnconnected,
                    self._preset.properties()["additionalNodesData"],
                    self._item))

        # Now add the Read node.
        writingClip = isinstance(self._item, hiero.core.Clip)
        if writingClip:
            script.pushLayoutContext("clip", self._item.name())
            self._clip.addToNukeScript(
                script,
                additionalNodesCallback=self._buildAdditionalNodes,
                firstFrame=firstFrame,
                trimmed=True,
                includeEffects=self.includeEffects(),
                project=self._project
            )  # _clip has no project set, but one is needed by addToNukeScript to do colorpsace conversions
            script.popLayoutContext()
        else:
            # If there are separate track items for each view, write them out (in reverse
            # order so the inputs are correct) then add a JoinViews
            items = self._multiViewTrackItems if self._multiViewTrackItems else [
                self._item
            ]
            for item in reversed(items):
                script.pushLayoutContext("clip", item.name())
                # Construct a TrackItemExportScriptWriter and write the track item
                trackItemWriter = TrackItemExportScriptWriter(item)
                trackItemWriter.setAdditionalNodesCallback(
                    self._buildAdditionalNodes)
                # Find sequence level effects/annotations which apply to the track item.
                # Annotations are not currently included by the transcode exporter
                effects, annotations = FnEffectHelpers.findEffectsAnnotationsForTrackItems(
                    [item])
                trackItemWriter.setEffects(self.includeEffects(), effects)

                # TODO This is being done in both the NukeShotExporter and TranscodeExporter.
                # There should be fully shared code for doing the handles calculations.
                fullClipLength = (self._cutHandles is None)
                if fullClipLength:
                    trackItemWriter.setOutputClipLength()
                else:
                    trackItemWriter.setOutputHandles(*self.outputHandles())

                trackItemWriter.setIncludeRetimes(
                    self._retime,
                    self._preset.properties()["method"])
                trackItemWriter.setReformat(
                    self._preset.properties()["reformat"])
                trackItemWriter.setFirstFrame(firstFrame)
                trackItemWriter.writeToScript(script)

                if self._preset.properties()["add_image"]:
                    merge_node = nuke.MergeNode()
                    script.addNode(merge_node)

                script.popLayoutContext()

            if self._multiViewTrackItems:
                joinViewsNode = nuke.Node("JoinViews",
                                          inputs=len(self.views()))
                script.addNode(joinViewsNode)

        script.pushLayoutContext("write", "%s_Render" % self._item.name())

        metadataNode = nuke.MetadataNode(metadatavalues=[(
            "hiero/project",
            self._projectName), ("hiero/project_guid", self._project.guid())])

        # Add sequence Tags to metadata
        metadataNode.addMetadataFromTags(self._clip.tags())

        # Need a framerate inorder to create a timecode
        if framerate:
            # Apply timeline offset to nuke output
            if self._cutHandles is None:
                timeCodeNodeStartFrame = unclampedStart
            else:
                startHandle, endHandle = self.outputHandles()
                timeCodeNodeStartFrame = trackItemTimeCodeNodeStartFrame(
                    unclampedStart, self._item, startHandle, endHandle)

            script.addNode(
                nuke.AddTimeCodeNode(timecodeStart=self._clip.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 Burnin group (if enabled)
        self.addBurninNodes(script)

        # Get the output format, either from the clip or the preset,  and set it as the root format.
        # If a reformat is specified in the preset, add it immediately before the Write node.
        reformatNode = self._clip.format().addToNukeScript(None)
        self._addReformatNode(script, rootNode, reformatNode)
        self.addWriteNodeToScript(script, rootNode, framerate)
        script.addNode(createViewerNode(self._projectSettings))
        script.popLayoutContext()
    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
Ejemplo n.º 6
0
def _TrackItem_addToNukeScript(self,
                               script,
                               firstFrame=None,
                               additionalNodes=[],
                               additionalNodesCallback=None,
                               includeRetimes=False,
                               retimeMethod=None,
                               startHandle=None,
                               endHandle=None,
                               trimStart=None,
                               useOCIO=False,
                               colourTransform=None,
                               offset=0,
                               nodeLabel=None,
                               useEntityRefs=False):
    """This is a variation on the Clip.addToNukeScript() method that remaps the
  Read frame range to the range of the this TrackItem rather than the Clip's
  range. TrackItem retimes and reverses are applied via Retime and OFlow nodes
  if needed. The additionalNodes parameter takes a list of nodes to add before
  the source material is shifted to the TrackItem timeline time and trimmed to
  black outside of the cut. This means timing can be set in the original
  source range and adding channels, etc won't affect frames outside the cut
  length.

  @param retimeMethod: "Motion", "Blend", "Frame" - Knob setting for OFlow retime method
  @param offset: Optional, Global frame offset applied across whole script
  """

    # Check that we are on the right type of object, just to be safe.
    assert isinstance(
        self, TrackItem
    ), "This function can only be punched into a TrackItem object."

    hiero.core.log.debug(
        "Add TrackItem (%s) to script, startHandle = %s, endHandle = %s, firstFrame=%s"
        % (self.name(), str(startHandle), str(endHandle), str(firstFrame)))

    added_nodes = []

    retimeRate = 1.0
    if includeRetimes:
        retimeRate = self.playbackSpeed()

    # Compensate for retime in HandleLength!!
    if startHandle is None:
        startHandle = 0
    if endHandle is None:
        endHandle = 0

    # Check for transitions
    inTransition, outTransition = _TrackItem_getTransitions(self)
    inTransitionHandle, outTransitionHandle = 0, 0

    # Adjust the clips to cover dissolve transition
    if outTransition is not None:
        if outTransition.alignment() == Transition.kDissolve:
            # Calculate the delta required to move the end of the clip to cover the dissolve transition
            outTransitionHandle = (outTransition.timelineOut() -
                                   self.timelineOut())
    if inTransition is not None:
        if inTransition.alignment() == Transition.kDissolve:
            # Calculate the delta required to move the beginning of the clip to cover the dissolve transition
            inTransitionHandle = (self.timelineIn() -
                                  inTransition.timelineIn())

    # If the clip is reversed, we need to swap the start and end times
    start = min(self.sourceIn(), self.sourceOut())
    end = max(self.sourceIn(), self.sourceOut())

    # Extend handles to incorporate transitions
    # If clip is reversed, handles are swapped
    if retimeRate >= 0.0:
        inHandle = startHandle + inTransitionHandle
        outHandle = endHandle + outTransitionHandle
    else:
        inHandle = startHandle + outTransitionHandle
        outHandle = endHandle + inTransitionHandle

    clip = self.source()
    # Recalculate handles clamping to available media range
    readStartHandle = min(start, math.ceil(inHandle * abs(retimeRate)))
    readEndHandle = min((clip.duration() - 1) - end,
                        math.ceil(outHandle * abs(retimeRate)))

    hiero.core.log.debug("readStartHandle", readStartHandle, "readEndHandle",
                         readEndHandle)

    # Add handles to source range
    start -= readStartHandle
    end += readEndHandle

    # Read node frame range
    readStart, readEnd = start, end
    # First frame identifies the starting frame of the output. Defaults to timeline in time
    readNodeFirstFrame = firstFrame
    if readNodeFirstFrame is None:
        readNodeFirstFrame = self.timelineIn() - min(
            min(self.sourceIn(), self.sourceOut()), inHandle)
    else:
        # If we have trimmed the handles, bump the start frame up by the difference
        readNodeFirstFrame += round(
            inHandle * abs(retimeRate)) - readStartHandle

    # trim start is used to trim the beginning of the read when applying a fade in
    if trimStart:
        readStart += trimStart
        readNodeFirstFrame += trimStart

    # Apply global offset
    readNodeFirstFrame += offset

    # Create a metadata node
    metadataNode = nuke.MetadataNode()
    reformatNode = None

    # Add TrackItem metadata to node
    metadataNode.addMetadata([("hiero/shot", self.name()),
                              ("hiero/shot_guid", _guidFromCloneTag(self))])

    # Add Tags to metadata
    metadataNode.addMetadata([("\"hiero/tags/" + tag.name() + "\"", tag.name())
                              for tag in self.tags()])
    metadataNode.addMetadata([("\"hiero/tags/" + tag.name() + "/note\"",
                               tag.note()) for tag in self.tags()])

    # Add Track and Sequence here as these metadata nodes are going to be added per clip/track item. Not per sequence or track.
    if self.parent():
        metadataNode.addMetadata([("hiero/track", self.parent().name()),
                                  ("hiero/track_guid",
                                   _guidFromCloneTag(self.parent()))])
        if self.parentSequence():
            metadataNode.addMetadata([
                ("hiero/sequence", self.parentSequence().name()),
                ("hiero/sequence_guid",
                 _guidFromCloneTag(self.parentSequence()))
            ])

            # If we have clip and we're in a sequence then we output the reformat settings as another reformat node.
            reformat = self.reformatState()
            if reformat.type() != nuke.ReformatNode.kDisabled:
                seq = self.parent().parent()
                seqFormat = seq.format()

                formatString = "%i %i 0 0 %i %i %f %s" % (
                    seqFormat.width(), seqFormat.height(), seqFormat.width(),
                    seqFormat.height(), seqFormat.pixelAspect(),
                    seqFormat.name())

                reformatNode = nuke.ReformatNode(
                    resize=reformat.resizeType(),
                    center=reformat.resizeCenter(),
                    flip=reformat.resizeFlip(),
                    flop=reformat.resizeFlop(),
                    turn=reformat.resizeTurn(),
                    to_type=reformat.type(),
                    format=formatString,
                    scale=reformat.scale())

    # Capture the clip nodes without adding to the script, so that we can group them as necessary
    clip_nodes = clip.addToNukeScript(None,
                                      firstFrame=readNodeFirstFrame,
                                      trimmed=True,
                                      trimStart=readStart,
                                      trimEnd=readEnd,
                                      useOCIO=useOCIO,
                                      colourTransform=colourTransform,
                                      metadataNode=metadataNode,
                                      nodeLabel=nodeLabel,
                                      enabled=self.isEnabled(),
                                      useEntityRefs=useEntityRefs)

    # Add the read node to the script
    # This assumes the read node will be the first node
    read_node = clip_nodes[0]
    if script:
        script.addNode(read_node)
    added_nodes.append(read_node)

    # Create a group to encapsulate all of the additional nodes
    clip_group = nuke.GroupNode("HieroData")
    clip_group.addNode(nuke.Node("Input", inputs=0))
    if script:
        script.addNode(clip_group)
    added_nodes.append(clip_group)

    # Add all other clip nodes to the group
    for node in clip_nodes[1:]:
        clip_group.addNode(node)

    # Add reformat node
    if reformatNode is not None:
        clip_group.addNode(reformatNode)

    # Add metadata node
    clip_group.addNode(metadataNode)

    first_frame = start
    last_frame = end

    # Calculate the frame range, != read range as read range may be clamped to available media range
    if firstFrame is not None:
        # if firstFrame is specified
        last_frame = firstFrame + (startHandle + inTransitionHandle) + (
            self.duration() - 1) + (endHandle + outTransitionHandle)
        hiero.core.log.debug(
            "last_frame(%i) =  firstFrame(%i) + startHandle(%i) + (self.duration() -1)(%i) + endHandle(%i)"
            % (last_frame, firstFrame, startHandle + inTransitionHandle,
               (self.duration() - 1), endHandle + outTransitionHandle))
        first_frame = firstFrame
    else:
        # if firstFrame not specified, use timeline time
        last_frame = self.timelineIn() + (self.duration() - 1) + (
            endHandle + outTransitionHandle)
        first_frame = (self.timelineIn() - (startHandle + inTransitionHandle))
        hiero.core.log.debug(
            "first_frame(%i) =  self.timelineIn(%i) - (startHandle(%i) + inTransitionHandle(%i)"
            %
            (first_frame, self.timelineIn(), startHandle, inTransitionHandle))

    if trimStart:
        first_frame += trimStart

    # This parameter allow the whole nuke script to be shifted by a number of frames
    first_frame += offset
    last_frame += offset

    # Frame range is used to correct the range from OFlow
    framerange = nuke.Node("FrameRange",
                           first_frame=first_frame,
                           last_frame=last_frame)
    framerange.setKnob(
        'label', 'Set frame range to [knob first_frame] - [knob last_frame]')
    added_nodes.append(framerange)
    clip_group.addNode(framerange)

    # Add Additional nodes.
    postReadNodes = []
    if callable(additionalNodesCallback):
        postReadNodes.extend(additionalNodesCallback(self))
    if additionalNodes is not None:
        postReadNodes.extend(additionalNodes)

    # Add any additional nodes.
    for node in postReadNodes:
        if node is not None:
            node = copy.deepcopy(node)
            # Disable additional nodes too (in particular the Shuffle which controls the mask used to merge layers)
            if not self.isEnabled():
                node.setKnob("disable", True)

            added_nodes.append(node)
            clip_group.addNode(node)

    # If the clip is retimed we need to also add an OFlow node.
    if includeRetimes and retimeRate != 1 and retimeMethod != 'None':
        # Obtain keyFrames
        tIn, tOut = self.timelineIn(), self.timelineOut()
        sIn, sOut = self.sourceIn(), self.sourceOut()

        hiero.core.log.debug("sIn %f sOut %f tIn %i tOut %i" %
                             (sIn, sOut, tIn, tOut))
        # Offset keyFrames, so that they match the input range (source times) and produce expected output range (timeline times)
        # timeline values must start at first_frame
        tOffset = (first_frame + startHandle +
                   inTransitionHandle) - self.timelineIn()
        tIn += tOffset
        tOut += tOffset
        sOffset = readNodeFirstFrame - readStart
        sIn += sOffset
        sOut += sOffset

        hiero.core.log.debug("Creating OFlow:", tIn, sIn, tOut, sOut)
        # Create OFlow node for computed keyFrames
        keyFrames = "{{curve l x%d %f x%d %f}}" % (tIn, sIn, tOut, sOut)
        oflow = nuke.Node("OFXuk.co.thefoundry.time.oflow_v100",
                          method="Blend",
                          timing="Source Frame",
                          timingFrame=keyFrames)

        # Override Retime Method
        if retimeMethod is not None:
            oflow.setKnob('method', retimeMethod)
        oflow.setKnob('label', 'retime ' + str(retimeRate))
        added_nodes.append(oflow)
        clip_group.addNode(oflow)

    clip_group.addNode(nuke.Node("Output"))

    return added_nodes
Ejemplo n.º 7
0
def _Clip_addToNukeScript(self,
                          script,
                          additionalNodes=None,
                          additionalNodesCallback=None,
                          trimmed=True,
                          firstFrame=None,
                          trimStart=None,
                          trimEnd=None,
                          useOCIO=False,
                          colourTransform=None,
                          metadataNode=None,
                          nodeLabel=None,
                          enabled=True,
                          useEntityRefs=False):
    """addToNukeScript(self, script, trimmed=True, trimStart=None, trimEnd=None)

  Add a Read node to the Nuke script for each media sequence/file used in this clip. If there is no media, nothing is added.

  @param script: Nuke script object to add nodes

  @param additionalNodes: List of nodes to be added post read

  @param additionalNodesCallback: callback to allow custom additional node per item function([Clip|TrackItem|Track|Sequence])

  @param firstFrame: Custom offset to move start frame of clip

  @param trimmed: If True, a FrameRange node will be added to trim the range output by the Read node. The range defaults to the clip's soft trim range. If soft trims are not enabled on the clip, the range defaults to the clip range. The range can be overridden by passing trimStart and/or trimEnd values.

  @param trimStart: Override the trim range start with this value.

  @param trimEnd: Override the trim range end with this value.

  @param enabled: enabled status of the read node. True by default
  
  @param useOCIO: Use open colourIO nodes for colour space transform"""

    hiero.core.log.debug(
        "trimmed=%s, trimStart=%s, trimEnd=%s, firstFrame=%s" %
        (str(trimmed), str(trimStart), str(trimEnd), str(firstFrame)))
    # Check that we are on the right type of object, just to be safe.
    assert isinstance(
        self, Clip), "This function can only be punched into a Clip object."

    added_nodes = []

    source = self.mediaSource()

    if source is None:
        # TODO: Add a constant here so that offline media has some representation within the nuke scene.
        # For now just do nothing
        return added_nodes

    # MPLEC TODO
    # Currently, on ingest only one source media element is added to the Clip timeline.
    # However it is possible for the Clip timeline to contain multiple track items.
    # We don't currently allow other source media to be added (though stereo will do this)
    # but users can cut/trim/etc on the one that's there so this needs to be smarter.
    # It will need to do the same thing as the timeline build-out does, with multiple
    # tracks, AppendClips, gap filling -- the whole routine. Should be able to share that?
    for fi in source.fileinfos():
        quicktime = hiero.core.isQuickTimeFileExtension(
            os.path.splitext(fi.filename().lower())[1])

        # Get start frame. First frame of an image sequence. Zero if quicktime/r3d
        startFrame = self.sourceIn()
        hiero.core.log.debug("startFrame: " + str(startFrame))

        # Initialise to the source length, starting at the first frame of the media.
        start = startFrame
        end = start + self.duration() - 1
        # Trim if soft trims are available and requested or they specified a trim range.
        if trimmed:
            # Everything within Hiero is zero-based so add file start frame to get real frame numbers at end.
            if self.softTrimsEnabled():
                start = self.softTrimsInTime()
                end = self.softTrimsOutTime()

            # Offset the trim range by the source.startTime() since the user sees frame numbers
            # on Sequences (including Clips) in the UI start numbering from 0.
            if trimStart is not None:
                start = trimStart + startFrame
            if trimEnd is not None:
                end = trimEnd + startFrame

        # Quicktime files in Nuke must always start at frame 1.
        # Hiero Starts at frame 0.. so adjust if Read file has .mov or .MOV extension
        if quicktime:
            start += 1
            end += 1

        # Grab clip format
        format = self.format()
        clipMetadata = self.metadata()
        # Get the filename, or an entity ref if one is present
        filename = fi.filename()
        ## @todo Re-enable when Nuke supports entity refs again
        if False and useEntityRefs and hasattr(self, 'entityReference'):
            entityRef = self.entityReference()
            if entityRef:
                filename = entityRef

        hiero.core.log.debug("- adding Nuke node for:%s %s %s", filename,
                             start, end)

        isRead = False
        ## @todo This also needs updating when nuke supports refs, as we'd need to resolve the
        ## reference to see the ending, or some such...
        if filename.endswith('.nk'):
            read_node = nuke.PrecompNode(filename)
        else:
            read_node = nuke.ReadNode(filename,
                                      format.width(),
                                      format.height(),
                                      format.pixelAspect(),
                                      round(start),
                                      round(end),
                                      clipMetadata=clipMetadata)
            if firstFrame is not None:
                read_node.setKnob("frame_mode", 'start at')
                read_node.setKnob("frame", firstFrame)
            isRead = True

        # If a node name has been specified
        if nodeLabel is not None:
            read_node.setKnob("label", nodeLabel)

        if script is not None:
            script.addNode(read_node)

        if useEntityRefs:
            manager = FnAssetAPI.Events.getEventManager()
            ## @todo Assuming that we're always on a thread for now, this should be verified
            manager.blockingEvent(False, 'hieroToNukeScriptAddClip', self,
                                  filename, read_node, script)

        added_nodes.append(read_node)

        if not isRead and firstFrame is not None:

            timeClip = nuke.TimeClipNode(round(start), round(end),
                                         round(firstFrame))
            added_nodes.append(timeClip)

        # Pull the colourspace from the Clip Metadata, rather than the sourceMediaColourTransform() function
        # Because sourceMediaColourTransform()  will return the gammaspace for red/arri clips.
        if colourTransform is None and "clip.properties.colourspacename" in clipMetadata:
            colourTransform = clipMetadata["clip.properties.colourspacename"]
            # Don't set knob, if set to default.
            if colourTransform.startswith("default"):
                colourTransform = None

        if colourTransform is not None:
            # Don't add ocio nodes for these file types
            ocioExclusionList = ["r3d", "ari", "arri", "arriraw"]
            fileExtension = os.path.splitext(fi.filename())[1][1:].lower()
            # Handle OCIO colour transforms
            if useOCIO is True and fileExtension not in ocioExclusionList:

                # Make sure the read node is not applying a LUT
                read_node.setKnob("colorspace", "linear")

                colourTransformGroup = hiero.core.LUTGroup(colourTransform)
                if colourTransformGroup:
                    colourTransform = "%s/%s" % (colourTransformGroup,
                                                 colourTransform)

                # Add a OCIO colour
                ocioNode = nuke.Node("OCIOColorSpace",
                                     in_colorspace=colourTransform)
                if script is not None:
                    script.addNode(ocioNode)
                added_nodes.append(ocioNode)
            else:
                # Apply colour Transform override
                if isRead:
                    read_node.setKnob("colorspace", colourTransform)

        if not enabled:
            read_node.setKnob("disable", True)
            # If we disable the read node, we need to add some channels else the write node will error
            add_channels_node = nuke.Node("AddChannels", channels="rgb")
            if script is not None:
                script.addNode(add_channels_node)
            added_nodes.append(add_channels_node)

        if metadataNode is None:
            metadataNode = nuke.MetadataNode()
            if script is not None:
                script.addNode(metadataNode)
            added_nodes.append(metadataNode)
            metadataNode.setInputNode(0, read_node)

        metadataNode.addMetadata([("hiero/clip", self.name()),
                                  ("hiero/clip_guid", _guidFromCloneTag(self))
                                  ])
        # Also set the reel name (if any) on the metadata key the dpx writer expects for this.
        if Keys.kSourceReelId in clipMetadata:
            reel = clipMetadata[Keys.kSourceReelId]
            if len(reel):
                metadataNode.addMetadata([("hiero/reel", reel),
                                          ('dpx/input_device', reel),
                                          ('quicktime/reel', reel)])

        # Add Tags to metadata
        metadataNode.addMetadata([("\"hiero/tags/" + tag.name() + "\"",
                                   tag.name()) for tag in self.tags()])
        metadataNode.addMetadata([("\"hiero/tags/" + tag.name() + "/note\"",
                                   tag.note()) for tag in self.tags()])

        postReadNodes = []
        if callable(additionalNodesCallback):
            postReadNodes.extend(additionalNodesCallback(self))

        if additionalNodes is not None:
            postReadNodes.extend(additionalNodes)

        prevNode = metadataNode
        for node in postReadNodes:
            # Add additional nodes
            if node is not None:
                node = copy.deepcopy(node)
                node.setInputNode(0, prevNode)
                prevNode = node

                # Disable additional nodes too (in particular the Shuffle which controls the mask used to merge layers)
                if not enabled:
                    node.setKnob("disable", "true")

                added_nodes.append(node)
                if script is not None:
                    script.addNode(node)

    return added_nodes
    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