.. _ogn_code_samples_py:

OGN Code Samples - Python
=========================
This files contains a collection of examples for using the .ogn generated code from Python. There is no particular flow
to these examples, they are used as reference data for the :ref:`ogn_user_guide`.

In the examples below this import will be assumed when describing names from the OmniGraph API, in the spirit of
common usage for packages such as *numpy* or *pandas*:

.. code-block:: py

   import omni.graph.core as og

.. contents::

.. _ogn_python_generated_database:

Python Generated Database
-------------------------
When the .ogn files are processed and the implementation language is set to *python* it generates a database file
through which all of the attribute data can be accessed. It also generates some utility functions that are useful in
the context of a compute function. For the file **OgnMyNode.ogn** the database class will be named **OgnMyNodeDatabase**
and can be imported directly from the generated `ogn` module inside your Python module.

.. code-block:: py

   from omni.examples.ogn.OgnMyNodeDatabase import OgnMyNodeDatabase as database

Usually you will not need to import the file though as the compute method is passed an instance to it. The contents
of that database file will include these functions:

.. code-block:: py

   db.log_error("Explanation of error") # Log an error in the compute
   db.log_warning("Explanation of warning") # Log a warning in the compute
   db.log_warn("Explanation of warning") # An alias for log_warning
   db.inputs # Object containing accessors for all input attribute data
   db.outputs # Object containing accessors for all output attribute data
   db.state # Object containing accessors for all state attribute data
   database.per_node_internal_state(node) # Class method to get the internal state data attached to a specific node

The attribute members of `db.inputs`, `db.outputs`, and `db.state` are all properties. The input setter can only be
used during node initialization.

.. _ogn_minimal_node_py:

Minimal Python Node Implementation
----------------------------------
Every Python node must contain a node class definition with an implementation of the ``compute`` method that takes the
database as a parameter and returns a boolean indicating if the compute succeeded. To enforce more stringent type
checking on compute calls, import the database definition for the declaration.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-minimal
   :end-before: end-minimal

.. note::

    For simplicity, the import will be omitted from subsequent examples.

:ref:`[C++ Version]<ogn_minimal_node_cpp>`

.. _ogn_metadata_node_py:

Python Node Type Metadata Access
--------------------------------
When node types have metadata added to them they can be accessed through the Python bindings to the node ABI.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-node-metadata
   :end-before: end-node-metadata

:ref:`[C++ Version]<ogn_metadata_node_cpp>`

.. _ogn_node_with_icon_py:

Python Node Icon Location Access
--------------------------------
Specifying the icon location and color information creates consistently named pieces of metadata that the UI can use to
present a more customized visual appearance.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-node-icon
   :end-before: end-node-icon

:ref:`[C++ Version]<ogn_node_with_icon_cpp>`

.. _ogn_scheduling_node_python:

Python Node Type Scheduling Hints
---------------------------------
Specifying scheduling hints makes it easier for the OmniGraph scheduler to optimize the scheduling of node evaluation.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-node-scheduling
   :end-before: end-node-scheduling


:ref:`[C++ Version]<ogn_scheduling_node_cpp>`

.. _ogn_singleton_node_py:

Python Singleton Node Types
---------------------------
Specifying that a node type is a singleton creates a consistently named piece of metadata that can be checked to see
if multiple instances of that node type will be allowed in a graph or its child graphs. Attempting to create more than
one of such node types in the same graph or any of its child graphs will result in an error.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-node-singleton
   :end-before: end-node-singleton

:ref:`[C++ Version]<ogn_singleton_node_cpp>`

.. _ogn_tags_node_py:

Python Node Type Tags
---------------------
Specifying the node tags creates a consistently named piece of metadata that the UI can use to present a more
friendly grouping of the node types to the user.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-node-tags
   :end-before: end-node-tags

This example introduces a simple helper data structure ``og.MetadataKeys``, which contains strings set to the key
values of special internal metadata. These are metadata elements managed by the code generator. Using these names
ensures consistent access.

.. code-block:: py

   og.MetadataKeys.ALLOWED_TOKENS       # On attributes of type token, a CSV formatted comma-separated list of potential legal values for the UI
   og.MetadataKeys.CATEGORIES           # On node types, contains a comma-separated list of categories to which the node type belongs
   og.MetadataKeys.DESCRIPTION          # On attributes and node types, contains their description from the .ogn file
   og.MetadataKeys.EXTENSION            # On node types, contains the extension that owns this node type
   og.MetadataKeys.HIDDEN               # On attributes and node types, indicating to the UI that they should not be shown
   og.MetadataKeys.ICON_PATH            # On node types, contains the file path to the node's icon representation in the editor
   og.MetadataKeys.ICON_BACKROUND_COLOR # On node types, overrides the background color of the node's icon
   og.MetadataKeys.ICON_BORDER_COLOR    # On node types, overrides the border color of the node's icon
   og.MetadataKeys.SINGLETON            # On node types its presence indicates that only one of the node type may be created in a graph
   og.MetadataKeys.TAGS                 # On node types, a comma-separated list of tags for the type
   og.MetadataKeys.UI_NAME              # On attributes and node types, user-friendly name specified in the .ogn file

:ref:`[C++ Version]<ogn_tags_node_cpp>`

.. _ogn_tokens_node_py:

Python Token Access
-------------------
Python properties are used for convenience in accessing the predefined token values. As tokens are represented
directly as strings in Python there is no need to support translation between strings and tokens as there is in C++.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-tokens
   :end-before: end-tokens

:ref:`[C++ Version]<ogn_tokens_node_cpp>`

.. _ogn_uiName_node_py:

Python Node Type UI Name Access
-------------------------------
Specifying the node UI name creates a consistently named piece of metadata that the UI can use to present a more
friendly name of the node type to the user.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-node-uiName
   :end-before: end-node-uiName

:ref:`[C++ Version]<ogn_uiName_node_cpp>`

.. _ogn_simple_node_py:

Simple Python Attribute Data Type
---------------------------------
Accessors are created on the generated database class that return Python accessor objects that wrap the underlying
attribute data, which lives in Fabric. As Python does not have the same flexibility with numeric data types there
is some conversion performed. i.e. a Python number is always 64 bits so it must truncate when dealing with smaller
attributes, such as **int** or **uchar**.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-simple
   :end-before: end-simple

:ref:`[C++ Version]<ogn_simple_node_cpp>`

.. _ogn_tuple_node_py:

Tuple Python Attribute Data Type
--------------------------------
Tuples, arrays, and combinations of these all use the ``numpy`` array types as return values as opposed to a plain
Python list such as ``List[float, float, float]``. This plays a big part in efficiency as the ``numpy`` arrays can
point directly to the Fabric data to minimize data copying.

Values are returned through the same kind of accessor as for simple data types, only differing in the returned data
types.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-tuple
   :end-before: end-tuple

:ref:`[C++ Version]<ogn_tuple_node_cpp>`

.. _ogn_role_node_py:

Role Python Attribute Data Type
-------------------------------
Roles are stored in a parallel structure to the attributes as properties. For example ``db.inputs.color`` will have a
corresponding property ``db.role.inputs.color``. For convenience, the legal role names are provided as constants in
the database class. The list of role names corresponds to the role values in the omni.graph.core.AttributeRole enum:

- ROLE_COLOR
- ROLE_EXECUTION
- ROLE_FRAME
- ROLE_NORMAL
- ROLE_POINT
- ROLE_QUATERNION
- ROLE_TEXCOORD
- ROLE_TIMECODE
- ROLE_TRANSFORM
- ROLE_VECTOR

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-role
   :end-before: end-role

:ref:`[C++ Version]<ogn_role_node_cpp>`

.. _ogn_array_node_py:

Array Python Attribute Data Type
--------------------------------
As with tuple values, all array values in Python are represented as ``numpy.array`` types.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-array
   :end-before: end-array

:ref:`[C++ Version]<ogn_array_node_cpp>`

.. _ogn_tuple_array_node_py:

Tuple-Array Python Attribute Data Type
--------------------------------------
As with simple tuple values and array values the tuple-array values are also represented as ``numpy.array`` types.
The numpy objects returned use the Fabric memory as their storage so they can be modified directly when computing
outputs. As with regular arrays, you must first set the size required so that the right amount of memory can be
allocated by Fabric.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-tuple-array
   :end-before: end-tuple-array

:ref:`[C++ Version]<ogn_tuple_array_node_cpp>`

.. _ogn_string_node_py:

String Python Attribute Data Type
---------------------------------
String attributes are a bit unusual in Python. In Fabric they are implemented as arrays of characters but they are
exposed in Python as plain old ``str`` types. The best approach is to manipulate local copies of the string and then
assign it to the result when you are finished.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-string
   :end-before: end-string

.. important::

   Although strings are implemented in Fabric as arrays the fact that strings are immutable in Python means you
   don't want to use the array method of resizing (i.e. setting the `db.outputs.stringAttribute_size` property).
   You can allocate it, but string elements cannot be assigned so there is no way to set the individual values.

:ref:`[C++ Version]<ogn_string_node_cpp>`

.. _ogn_any_node_py:

Extended Python Attribute Data Type - Any
-----------------------------------------
Extended attribute types have extra information that identifies the type they were resolved to at runtime. The access
to this information is achieved by wrapping the attribute value in the same way as :ref:`ogn_bundle_node_py`.

The Python property for the attribute returns an accessor rather than the value itself. This accessor has the
properties **".value"**, **".name"**, and **".type"** so that the type resolution information can be accessed directly.
In addition, variations of the **".value"** method specific to each memory space are provided as the properties
**".cpu_value"** and **".gpu_value"**.

For example, the value for the input named **a** can be found at ``db.inputs.a.value``, and its resolved type is at
``db.inputs.a.type``.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-union
   :end-before: end-union

:ref:`[C++ Version]<ogn_any_node_cpp>`

The extended data types must all be resolved before calling into the compute method. The generated code
handles that for you, executing the equivalent of these calls for extended inputs **a** and **b**, and extended
output **sum**, preventing the call to ``compute()`` if any of the types are unresolved.

.. code-block:: python

   if db.inputs.a.type.base_type == og.BaseDataType.UNKNOWN:
       return False
   if db.inputs.b.type.base_type == og.BaseDataType.UNKNOWN:
       return False
   if db.outputs.sum.type.base_type == og.BaseDataType.UNKNOWN:
       return False

.. _ogn_union_node_py:

Extended Python Attribute Data Type - Union
-------------------------------------------
The generated interface for union types is exactly the same as for **any** types. There is just a tacit agreement that
the resolved types will always be one of the ones listed in the union type description.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-union
   :end-before: end-union

:ref:`[C++ Version]<ogn_union_node_cpp>`

.. _ogn_bundle_node_py:

Bundle Python Attribute Data Type
---------------------------------
Bundle attribute information is accessed the same way as information for any other attribute type. As an aggregate,
the bundle can be treated as a container for attributes, without any data itself.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-bundle
   :end-before: end-bundle

:ref:`[C++ Version]<ogn_bundle_node_cpp>`

.. _ogn_bundle_data_py:

When you want to get at the actual data, you use the bundle API to extract the runtime attribute accessors from the
bundle for those attributes you wish to process.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-bundle-data
   :end-before: end-bundle-data

.. tip::

   Although you access them in completely different ways the attributes that are bundle members use the same accessors
   as the extended attribute types. See further information in :ref:`ogn_any_node_cpp`

This documentation for bundle access is pulled directly from the code. It removes the extra complication in the
accessors required to provide proper typing information for bundle members and shows the appropriate calls in the
bundle attribute API.

.. literalinclude:: ../../../../source/extensions/omni.graph/python/_impl/bundles.py
   :language: cpp
   :start-after: begin-bundle-interface-description
   :end-before: end-bundle-interface-description

:ref:`[C++ Version]<ogn_bundle_data_cpp>`

.. _ogn_attribute_memory_type_py:

Python Attribute Memory Location
--------------------------------

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-memory-type
   :end-before: end-memory-type

:ref:`[C++ Version]<ogn_attribute_memory_type_cpp>`

.. _ogn_node_categories_py:

Node Type Categories
--------------------
Categories are added as metadata to the node and can be accessed through the standard metadata interface.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-node-categories
   :end-before: end-node-categories

:ref:`[C++ Version]<ogn_node_categories_cpp>`

.. _ogn_node_cudaPointers_py:

Python Attribute CPU Pointers to GPU Data
-----------------------------------------
.. note::

   Although this value takes effect at the attribute level the keyword is only valid at the node level. All
   attributes in a node will use the same type of CUDA array pointer referencing.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-cuda-pointers
   :end-before: end-cuda-pointers

:ref:`[C++ Version]<ogn_node_cudaPointers_cpp>`

.. _ogn_metadata_attribute_py:

Python Attribute Metadata Access
--------------------------------
When attributes have metadata added to them they can be accessed through the ABI attribute interface.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-attribute-metadata
   :end-before: end-attribute-metadata

:ref:`[C++ Version]<ogn_metadata_attribute_cpp>`

.. _ogn_optional_py:

Optional Python Attributes
--------------------------
Since Python values are extracted through the C++ ABI bindings they don't have a direct validity check so the validity
of optional attributes must be checked indirectly. If a Python attribute value returns the special **None** value then
the attribute is not valid. It may also raise a *TypeError* or *ValueError* exception, indicating there was a mismatch
between the data available and the type expected.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-optional
   :end-before: end-optional

:ref:`[C++ Version]<ogn_optional_cpp>`

.. _ogn_uiName_attribute_py:

Python Attribute UI Name Access
-------------------------------
Specifying the attribute **uiName** creates a consistently named piece of metadata that the UI can use to present a more
friendly version of the attribute name to the user. It can be accessed through the regular metadata ABI, with some
constants provided for easier access.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-attribute-uiName
   :end-before: end-attribute-uiName

:ref:`[C++ Version]<ogn_uiName_attribute_cpp>`

.. _ogn_uiType_attribute_py:

Python Attribute UI Type Access
-------------------------------
Specifying the attribute **uiType** tells the property panel that this attribute should be shown with custom widgets.
- For path, string, and token attributes, a ui type of "filePath" will show file browser widgets
- For 3- and 4-component numeric tuples, a ui type of "color" will show the color picker widget

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-attribute-uiType
   :end-before: end-attribute-uiType

:ref:`[C++ Version]<ogn_uiType_attribute_cpp>`

.. _ogn_unvalidated_py:

Unvalidated Python Attributes
-----------------------------
For most attributes the generated code will check to see if the attribute is valid before it calls the `compute()`
function. unvalidated attributes will not have this check made. If you end up using their value then you must make the
call to the `is_valid()` method yourself first and react appropriately if invalid values are found. Further, for
attributes with extended types you must verify that they have successfully resolved to a legal type.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-unvalidated
   :end-before: end-unvalidated

:ref:`[C++ Version]<ogn_unvalidated_cpp>`

.. _ogn_dynamic_attributes_py:

Dynamic Python Attributes
-------------------------
In addition to attributes statically defined through a .ogn file, you can also dynamically add attributes to a single
node by using the ABI call ``og.Node.create_attribute(...)``. When you do so, the Python database interface will
automatically pick up these new attributes and provide access to their data in exactly the same way as it does for
regular attributes. (i.e. ``db.inputs.X`` for the value, ``db.attributes.input.X`` for the underlying `og.Attribute`,
``db.roles.inputs.X`` for the attribute role, etc.)

The way you test for such an attribute's existence inside a ``compute()`` method is to capture the `AttributeError`
exception.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-dynamic
   :end-before: end-dynamic

.. note::

   There is no C++ equivalent to this feature. Dynamic attributes will be available on the Python accessors to the
   C++ node but the C++ code can only access the attribute data by using the low level ABI.

.. _ogn_state_node_py:

Python Nodes With Internal State
--------------------------------

Unlike C++ classes it is not as easy to determine if a Python class contains data members that should be interpreted as
state information. Instead, the Python node class will look for a method called `internal_state()`, which should return
an object containing state information to be attached to a node. Once the internal state has been constructed it is not
modified by OmniGraph until the node is released, it is entirely up to the node how and when to modify the data.

That information will be in turn made accessible through the database class using the property `db.internal_state`.


.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-state-node
   :end-before: end-state-node

:ref:`[C++ Version]<ogn_state_node_cpp>`

.. _ogn_versioned_node_py:

Python Nodes With Version Upgrades
----------------------------------

To provide code to upgrade a node from a previous version to the current version you must override the ABI function
`update_node_version()`. The current context and node to be upgraded are passed in, as well as the old version at which
the node was created and the new version to which it should be upgraded. Passing both values allows you to upgrade
nodes at multiple versions in the same code.

This example shows how a new attribute is added using the *og.Node* ABI interface.

.. literalinclude:: ogn_code_samples_python.py
   :language: python
   :start-after: begin-versioned-node
   :end-before: end-versioned-node

:ref:`[C++ Version]<ogn_versioned_node_cpp>`
