Supporting Local Graph State in Parameter Evaluation

New in Katana 4.5v1

In order for Local Graph State to be queried in Parameter Expressions, Graph State must be placed on the Local Graph State stack prior to Parameter evaluation. This must be done while translating nodes to Ops (and may be done in any context in which Local Graph State is well-defined).

Custom node types built using NodeTypeBuilder, and using a standard interface, already perform this task. Where a custom node type directly extends from Nodes3DAPI.Node3D (or its base classes), and outside of the standard node interface, it is the node type author’s responsibility to stack the Local Graph State where necessary.

For example, if Node3D_geolib3.NodeGeolib3._getOp(self, port, graphState, visitedState, transaction) or NodegraphAPI.PythonNode.getInputPortAndGraphState(self, outputPort, graphState) are overridden, and parameters are queried in these overrides, the Local Graph State (the graphState argument) needs to be stacked before parameter values can be interrogated.

See the following functions as examples of functions that depend on the Local Graph State:

These functions source the Local Graph State from a global stack, and custom node types are required to maintain this stack themselves.

The Local Graph State can be pushed to the global stack using the NodegraphAPI.StackedLocalGraphState(graphState) context manager along with the with statement in Python.

Example of usage:

import textwrap

from Katana import (
    NodegraphAPI,
    Nodes3DAPI,
)


class CustomVariableSetNode(Nodes3DAPI.Node3D):
    """
    Custom node type that matches the built-in VariableSet node type in Katana.
    """
    def __init__(self):
        """
        Initializes an instance of the class.
        """
        super(CustomVariableSetNode, self).__init__()
        self.addInputPort('i0')
        self.addOutputPort('out')
        self.getParameters().parseXML(
            textwrap.dedent('''
            <group_parameter>
            <string_parameter name='name' value=''/>
            <string_parameter name='value' value=''/>
            </group_parameter>'''))

    def _getOpChain(self, interface):
        """
        Implementation of the :py:obj:`_getOpChain()` function that provides no Ops.
        """
        _ = interface
        # no-op

    def getInputPortAndGraphState(self, outputPort, graphState):
        """
        Overrides :py:obj:`getInputPortAndGraphState()` to extend the Local Graph
        State with one more Graph State Variable, name and value to be
        determined via the **name** and **value** parameters.
        """
        # Stack the Local Graph State, so that potential parameter expressions
        # can access it.
        with NodegraphAPI.StackedLocalGraphState(graphState):
            name = self.getParameter('name').getValue(graphState.getTime())
            value = self.getParameter('value').getValue(graphState.getTime())

        # Add a variable to the Local Graph State.
        newGraphState = graphState
        if name:
            graphStateBuilder = graphState.edit()
            graphStateBuilder.setDynamicEntry('var:' + name, value)
            newGraphState = graphStateBuilder.build()

        # Call through to the default implementation of this function with the
        # potentially modified Graph State.
        return super(CustomVariableSetNode,
                     self).getInputPortAndGraphState(outputPort, newGraphState)


NodegraphAPI.RegisterPythonNodeType('CustomVariableSet', CustomVariableSetNode)
NodegraphAPI.AddNodeFlavor("CustomVariableSet", "3d")

If the Node3D_geolib3.NodeGeolib3.ParamDependencyTracker context manager, defined in the standard interface, is already employed, NodegraphAPI.StackedLocalGraphState is not needed.