.. _ogn_tutorial_simpleDataPy:


Tutorial 10 - Simple Data Node in Python
========================================

The simple data node creates one input attribute and one output attribute of each of the simple types, where "simple"
refers to data types that have a single component and are not arrays. (e.g. "float" is simple, "float[3]" is not, nor is
"float[]"). See also :ref:`ogn_tutorial_simpleData` for a similar example in C++.

Automatic Python Node Registration
----------------------------------
By implementing the standard Carbonite extension interfact in Python, OmniGraph will know to scan your Python import
path for to recursively scan the directory, import all Python node files it finds, and register those nodes.
It will also deregister those nodes when the extension shuts down. Here is an example of the directory structure for
an extension with a single node in it. (For extensions that have a `premake5.lua` build script this will be in the
build directory. For standalone extensions it is in your source directory.)

.. code-block:: text

    omni.my.extension/
        omni/
            my/
                extension/
                    nodes/
                        OgnMyNode.ogn
                        OgnMyNode.py



OgnTutorialSimpleDataPy.ogn
---------------------------
The *ogn* file shows the implementation of a node named "omni.graph.tutorials.SimpleDataPy", which has one
input and one output attribute of each simple type.

.. literalinclude:: OgnTutorialSimpleDataPy.ogn
    :linenos:
    :language: json

OgnTutorialSimpleDataPy.py
--------------------------
The *py* file contains the implementation of the compute method, which modifies
each of the inputs in a simple way to create outputs that have different values.

.. literalinclude:: OgnTutorialSimpleDataPy.py
    :linenos:
    :language: python

Note how the attribute values are available through the ``OgnTutorialSimpleDataPyDatabase`` class. The generated
interface creates access methods for every attribute, named for the attribute itself. They are all implemented as Python
properties, where inputs only have get methods and outputs have both get and set methods.

Pythonic Attribute Data
-----------------------
Three subsections are creating in the generated database class. The main section implements the node type ABI methods
and uses introspection on your node class to call any versions of the ABI methods you have defined (see later
tutorials for examples of how this works).

The other two subsections are classes containing attribute access properties for inputs and outputs. For naming
consistency the class members are called *inputs* and *outputs*. For example, you can access the value of the input
attribute named *foo* by referencing ``OgnTutorialSimpleDataPyDatabase.inputs.foo``.

For the curious, here is an example of how the boolean attribute access is set up on the simple tutorial node:

.. code-block:: python

    class OgnTutorialSimpleDataPyDatabase:
        class InputAttributes:
            def __init__(self, context_helper, node):
                self.context_helper = context_helper
                self.node = node
                # A lock is used to prevent inputs from inadvertently being modified during a compute.
                # This is equivalent to the "const" approach used in C++.
                self.setting_locked = False
                # Caching the Attribute object lets future data access run faster
                self._a_bool = node.get_attribute('inputs:a_bool')
            @property
            def a_bool(self):
                return self.context_helper.get_attr_value(self._a_bool)
            @a_bool.setter
            def a_bool(self, value):
                if self.setting_locked:
                    raise AttributeError('inputs:a_bool')
                self.context_helper.set_attr_value(value, self._a_bool)

        class OutputAttributes:
            def __init__(self, context_helper, node):
                self.context_helper = context_helper
                self.node = node
                self._a_bool = node.get_attribute('outputs:a_bool')
            @property
            def a_bool(self):
                return self.context_helper.get_attr_value(self._a_bool)
            @a_bool.setter
            def a_bool(self, value):
                self.context_helper.set_attr_value(value, self._a_bool)

        def __init__(self, context, node):
            self.context = context
            self.node = node
            self.context_helper = ContextHelper(context)
            self.inputs = OgnTutorialSimpleDataPyDatabase.InputAttributes(self.context_helper, node)
            self.outputs = OgnTutorialSimpleDataPyDatabase.OutputAttributes(self.context_helper, node)



Pythonic Attribute Access
-------------------------
In the USD file the attribute names are automatically namespaced as *inputs:FOO* or *outputs:BAR*. In the Python
interface the colon is illegal so the contained classes above are used to make use of the dot-separated equivalent,
as *inputs.FOO* or *outputs.BAR*.

While the underlying data types are stored in their exact form there is conversion when they are passed back to Python
as Python has a more limited set of data types, though they all have compatible ranges. For this class, these are the
types the properties provide:

+-------------------+---------------+
| Database Property | Returned Type |
+===================+===============+
| inputs.a_bool     | bool          |
+-------------------+---------------+
| inputs.a_half     | float         |
+-------------------+---------------+
| inputs.a_int      | int           |
+-------------------+---------------+
| inputs.a_int64    | int           |
+-------------------+---------------+
| inputs.a_float    | float         |
+-------------------+---------------+
| inputs.a_double   | float         |
+-------------------+---------------+
| inputs.a_token    | str           |
+-------------------+---------------+
| outputs.a_bool    | bool          |
+-------------------+---------------+
| outputs.a_half    | float         |
+-------------------+---------------+
| outputs.a_int     | int           |
+-------------------+---------------+
| outputs.a_int64   | int           |
+-------------------+---------------+
| outputs.a_float   | float         |
+-------------------+---------------+
| outputs.a_double  | float         |
+-------------------+---------------+
| outputs.a_token   | str           |
+-------------------+---------------+

The data returned are all references to the real data in the FlatCache, our managed memory store, pointed to the
correct location at evaluation time.

Python Helpers
--------------
A few helpers are provided in the database class definition to help make coding with it more natural.

Python logging
++++++++++++++
Two helper functions are providing in the database class to help provide more information when the compute method of
a node has failed. Two methods are provided, both taking a formatted string describing the problem.

``log_error(message)`` is used when the compute has run into some inconsistent or unexpected data, such as two
input arrays that are supposed to have the same size but do not, like the normals and vertexes on a mesh.

``log_warning(message)`` can be used when the compute has hit an unusual case but can still provide a consistent
output for it, for example the deformation of an empty mesh would result in an empty mesh and a warning since that is
not a typical use for the node.


Direct Pythonic ABI Access
++++++++++++++++++++++++++
All of the generated database classes provide access to the underlying *INodeType* ABI for those rare situations
where you want to access the ABI directly. There are two members provided, which correspond to the objects passed
in to the ABI compute method.

There is the graph evaluation context member, ``context: Py_GraphContext``, for accessing the underlying OmniGraph
evaluation context and its interface.

There is also the OmniGraph node member, ``node: Py_Node``, for accessing the underlying OmniGraph node
object and its interface.
