.. _ogn_tutorial_dynamic_attributes:

Tutorial 25 - Dynamic Attributes
================================

A dynamic attribute is like any other attribute on a node, except that it is added at runtime rather than being part
of the .ogn specification. These are added through the ABI function ``INode::createAttribute`` and removed from the
node through the ABI function ``INode::removeAttribute``.

Once a dynamic attribute is added it can be accessed through the same ABI and script functions as regular attributes.

.. warning::

    While the Python node database is able to handle the dynamic attributes through the same interface as regular
    attributes (e.g. ``db.inputs.dynAttr``), the C++ node database is not yet similarly flexible and access to
    dynamic attribute values must be done directly through the ABI calls.


OgnTutorialDynamicAttributes.ogn
--------------------------------
The *ogn* file shows the implementation of a node named "omni.graph.tutorials.DynamicAttributes", which has a simple
float input and output.

.. literalinclude:: OgnTutorialDynamicAttributes.ogn
    :linenos:
    :language: json

OgnTutorialDynamicAttributes.cpp
--------------------------------
The *cpp* file contains the implementation of the compute method. It passes the input directly to the output unless it
finds a dynamic attribute named "multiplier", in which case it multiplies by that amount instead.

.. literalinclude:: OgnTutorialDynamicAttributes.cpp
    :linenos:
    :language: c++

OgnTutorialDynamicAttributesPy.py
---------------------------------
The *py* file contains the same algorithm as the C++ node, with only the implementation language being different.

.. literalinclude:: OgnTutorialDynamicAttributesPy.py
    :linenos:
    :language: python

Adding And Removing Dynamic Attributes
--------------------------------------
In addition to the above ABI functions the Python ``og.Controller`` class provides the ability to add and remove
dynamic attributes from a script.

To create a dynamic attribute you would use this function:

.. literalinclude:: ../../../omni.graph/python/_impl/node_controller.py
    :language: python
    :start-after: begin-create-attribute-function
    :end-before: end-create-attribute-function

For example this is the code to create a `float[3]` input and a bundle output on an existing node:

.. code-block:: py

    import omni.graph.core as og
    new_input = og.Controller.create_attribute("/World/MyNode", "newInput", "float[3]")
    new_output = og.Controller.create_attribute("/World/MyNode", "newOutput", "bundle", og.AttributePortType.ATTRIBUTE_PORT_TYPE_OUTPUT)
    # The proper namespace will be added to the attribute, though you can also be explicit about it
    other_input = og.Controller.create_attribute("/World/MyNode", "inputs:otherInput", "float[3]")

When the node is deleted the dynamic attribute will also be deleted, and the attribute will be stored in the USD file.
If you want to remove the attribute from the node at any time you would use this function:

.. literalinclude:: ../../../omni.graph/python/_impl/node_controller.py
    :language: python
    :start-after: begin-remove-attribute-function
    :end-before: end-remove-attribute-function

The second optional parameter is only needed when the attribute is passed as a string. When passing an `og.Attribute`
the node is already known, being part of the attribute.

.. code-block:: py

    import omni.graph.core as og
    new_attr = og.Controller.create_attribute("/World/MyNode", "newInput", "float[3]")
    # When passing the attribute the node is not necessary
    og.Controller.remove_attribute(new_attr)
    # However if you don't have the attribute available you can still use the name, noting that the
    # namespace must be present.
    # og.Controller.remove_attribute("inputs:newInput", "/World/MyNode")

Adding More Information
+++++++++++++++++++++++

While the attribute name and type are sufficient to unambiguously create it there is other information you can add
that would normally be present in the .ogn file. It's a good idea to add some of the basic metadata for the UI.

.. code-block:: py

    import omni.graph.core as og
    new_attr = og.Controller.create_attribute("/World/MyNode", "newInput", "vectorf[3]")
    new_attr.set_metadata(og.MetadataKeys.DESCRIPTION, "This is a new input with a vector in it")
    new_attr.set_metadata(og.MetadataKeys.UI_NAME, "Input Vector")

While dynamic attributes don't have default values you can do the equivalent by setting a value as soon as you
create the attribute:

.. code-block:: py

    import omni.graph.core as og
    new_attr = og.Controller.create_attribute("/World/MyNode", "newInput", "vectorf[3]")
    og.Controller.set(new_attr, [1.0, 2.0, 3.0])

This default value can also be changed at any time (even when the attribute is already connected):

.. code-block:: py

    new_attr.set_default([1.0, 0.0, 0.0])
