.. _nodetypebuilder:

`NodeTypeBuilder`
=================

.. module:: Nodes3DAPI.NodeTypeBuilder

.. autoclass:: NodeTypeBuilder

Op Chain Interfaces
-------------------

.. autoclass:: BaseOpChainInterface
    :exclude-members: FnAttribute, FnGeolib
.. autoclass:: OpChainInterface
.. autoclass:: ParametersOpChainInterface

Migration Guide: Modifying Graph State
--------------------------------------

Node types created with
:py:class:`NodeTypeBuilder<Nodes3DAPI.NodeTypeBuilder.NodeTypeBuilder>` could
update Local Graph State from within a :py:func:`buildOpChain` function
registered with
:py:meth:`setBuildOpChainFnc<Nodes3DAPI.NodeTypeBuilder.NodeTypeBuilder.setBuildOpChainFnc>`.

That mechanism, using
:py:meth:`setExplicitInputRequestsEnabled<Nodes3DAPI.NodeTypeBuilder.OpChainInterface.setExplicitInputRequestsEnabled>`,
is deprecated. Modifications to graph state made using explicit input requests
are not visible to all parts of Katana. Some features like dimming nodes
disabled due to local graph state; showing the local graph state in the UI;
etc - do not work well with explicit input requests.

Instead, nodes that make changes to local graph state should register a function
with :py:meth:`setGetInputPortAndGraphStateFnc<Nodes3DAPI.NodeTypeBuilder.NodeTypeBuilder.setGetInputPortAndGraphStateFnc>`.
This function will be called to determine which of the nodes input Ports are
active, and allows modifying the local graph state passed to the upstream node
connected to the active inputs.

For example, a node that sets a graph state variable like VariableSet, could
be implemented with the deprecated explicit input request mechanism:

.. code-block:: python

    # NOTE: Example using deprecated setExplicitInputRequestsEnabled
    from Katana import (
        Nodes3DAPI,
        FnGeolibServices,
        FnAttribute
    )

    def buildOpChain(self, interface):
        # Set an example attribute on all locations
        argsbuilder = FnGeolibServices.OpArgsBuilders.AttributeSet()
        argsbuilder.setCEL(FnAttribute.StringAttribute('//*'))
        argsbuilder.setAttr('info.example', FnAttribute.IntAttribute(1))

        interface.appendOp('AttributeSet', argsbuilder.build())

        # Get the graph state
        graphState = interface.getGraphState()

        # Get the parameters at the graphState's current time
        currentTime = graphState.getTime();
        name = self.getParameter('name').getValue(currentTime)
        value = self.getParameter('value').getValue(currentTime)

        # Add a variable to the local graph state
        graphStateBuilder = graphState.edit()
        graphStateBuilder.setDynamicEntry('var:' + name, value)
        modifiedGraphState = graphStateBuilder.build()

        # Enable explicit input requests. This circumvents normal input
        # processing but allows modifying the local graph state.
        # DEPRECATED!
        interface.setExplicitInputRequestsEnabled(True)

        # Add the explicit input request for our input port with the modified graph state
        interface.addInputRequest('in', modifiedGraphState)

    # Define the parameter interface for nodes of our custom node type through a
    # builder for GroupAttribute objects, which is turned into a GroupAttribute
    # that is passed to the `setParametersTemplateAttr()` function on the
    # NodeTypeBuilder instance created below
    parametersTemplateAttrGroupBuilder = FnAttribute.GroupBuilder()
    parametersTemplateAttrGroupBuilder.set('name', 'myVariable')
    parametersTemplateAttrGroupBuilder.set('value', 'myValue')
    parametersTemplateAttr = parametersTemplateAttrGroupBuilder.build()

    # Create the node type using NodeTypeBuilder
    ntb = Nodes3DAPI.NodeTypeBuilder('MyVariableSet_Deprecated')
    ntb.setInputPortNames(('in',))
    ntb.setParametersTemplateAttr(parametersTemplateAttr)
    ntb.setBuildOpChainFnc(buildOpChain)
    ntb.build()



The deprecated example can be updated to use
:py:meth:`setGetInputPortAndGraphStateFnc<Nodes3DAPI.NodeTypeBuilder.NodeTypeBuilder.setGetInputPortAndGraphStateFnc>`
with some minor changes:

* Extract the code for modifying the graph state into a new function
  `getInputPortAndGraphState(self, outputPort, graphState)`
* Remove the call to
  :py:meth:`interface.setExplicitInputRequestsEnabled<Nodes3DAPI.NodeTypeBuilder.OpChainInterface.setExplicitInputRequestsEnabled>`
* Replace the call to
  :py:meth:`interface.addInputRequest<Nodes3DAPI.NodeTypeBuilder.OpChainInterface.addInputRequest>`
  with a call through to the default implementation
  :py:meth:`Node3D.getInputPortAndGraphState<Nodes3DAPI.Node3D.getInputPortAndGraphState>`
* Add a call to
  :py:meth:`NodeTypeBuilder.setGetInputPortAndGraphStateFnc<Nodes3DAPI.NodeTypeBuilder.NodeTypeBuilder.setGetInputPortAndGraphStateFnc>`
  to register the new `getInputPortAndGraphState` function.

.. code-block:: python

    # NOTE: Example using new getInputPortAndGraphState
    from Katana import (
        Nodes3DAPI,
        FnGeolibServices,
        FnAttribute
    )

    def buildOpChain(self, interface):
        # Set an example attribute on all locations
        argsbuilder = FnGeolibServices.OpArgsBuilders.AttributeSet()
        argsbuilder.setCEL(FnAttribute.StringAttribute('//*'))
        argsbuilder.setAttr('info.example', FnAttribute.IntAttribute(1))

        interface.appendOp('AttributeSet', argsbuilder.build())

    def getInputPortAndGraphState(self, outputPort, graphState):
        # Get the parameters at the graphState's current time
        currentTime = graphState.getTime();
        name = self.getParameter('name').getValue(currentTime)
        value = self.getParameter('value').getValue(currentTime)

        # Add a variable to the local graph state
        graphStateBuilder = graphState.edit()
        graphStateBuilder.setDynamicEntry('var:' + name, value)
        modifiedGraphState = graphStateBuilder.build()

        # Call through to the default implementation of this function
        # with the modified graph state.
        return Nodes3DAPI.Node3D.getInputPortAndGraphState(self, outputPort, modifiedGraphState)

    # Define the parameter interface for nodes of our custom node type through a
    # builder for GroupAttribute objects, which is turned into a GroupAttribute
    # that is passed to the `setParametersTemplateAttr()` function on the
    # NodeTypeBuilder instance created below
    parametersTemplateAttrGroupBuilder = FnAttribute.GroupBuilder()
    parametersTemplateAttrGroupBuilder.set('name', 'myVariable')
    parametersTemplateAttrGroupBuilder.set('value', 'myValue')
    parametersTemplateAttr = parametersTemplateAttrGroupBuilder.build()

    # Create the node type using NodeTypeBuilder
    ntb = Nodes3DAPI.NodeTypeBuilder('MyVariableSet')
    ntb.setInputPortNames(('in',))
    ntb.setParametersTemplateAttr(parametersTemplateAttr)
    ntb.setBuildOpChainFnc(buildOpChain)
    ntb.setGetInputPortAndGraphStateFnc(getInputPortAndGraphState)
    ntb.build()
