.. _omnigraph_controller_class:

OmniGraph Controller Class
==========================

.. _howto_omnigraph_controller:

The primary interface you can use for interacting with OmniGraph is the `Controller` class. It is in the main module
so you can access it like this:

.. code-block:: python

    import omni.graph.core as og
    keys = og.Controller.Keys
    controller = og.Controller()

.. note::

    For future examples these two lines will be assumed to be present.

Structure
---------

The `Controller` class is an amalgam of several other classes with specific subsets of functionality. It derives from
each of them, so that all of their functionality can be accessed through the one `Controller` class.

- **GraphController** handles operations that affect the structure of the graph
- **NodeController** handles operations that affect individual nodes
- **ObjectLookup** provides a generic set of interfaces for finding OmniGraph objects with a flexible set of inputs
- **DataView** lets you get and set attribute values

For the most part the controller functions can be accessed as static class members:

.. code-block:: python

    og.Controller.edit("/World/MyGraph", {keys.CREATE_NODES: ("MyNode", "omni.graph.tutorials.SimpleData")})
    og.Controller.edit("/World/MyGraph", {keys.SET_VALUES: ("/World/MyGraph/MyNode.inputs:a_bool", False)})

A second way to do it is to instantiate the controller so that the relative paths are all remembered:

.. code-block:: python

    controller.edit("/World/MyGraph", {keys.CREATE_NODES: ("MyNode", "omni.graph.tutorials.SimpleData")})
    controller.edit("/World/MyGraph", {keys.SET_VALUES: ("MyNode.inputs:a_bool", False)})

Or you can remember the return values of the first call yourself and reuse them:

.. code-block:: python

    (graph, nodes, _, _) = og.Controller.edit("/World/MyGraph", {keys.CREATE_NODES: ("MyNode", "omni.graph.tutorials.SimpleData")})
    og.Controller.edit(graph, {keys.SET_VALUES: (("inputs:a_bool", nodes[0]), False))

The exceptions are when using the :py:class:`omni.graph.core.DataView` functions, as that class requires an attribute
in order to perform its operations. For those you can pass an `Attribute` or `AttributeValue` identifier to construct
a controller that can handle those operations:

.. code-block:: python

    controller = og.Controller(og.Controller.attribute("/World/MyGraph/MyNode.inputs:myInput"))
    print(f"My attribute value is {controller.get()}")

.. literalinclude:: ../../../../source/extensions/omni.graph/python/_impl/controller.py
    :language: python
    :start-after: begin-controller-docs
    :end-before: end-controller-docs


Controller Functions
--------------------

In addition to the functions inherited from the other classes the `Controller` has a couple of key functions itself.
There is a coroutine to evaluate one or more graphs:

.. code-block:: python

    async def my_function():
        await og.Controller.evaluate()

The :py:meth:`evaluate()<omni.graph.core.Controller.evaluate>` method takes an optional parameter
with a graph or list of graphs to be evaluated. By default it evaluates all graphs in the scene.

If you want to evaluate but are not in a coroutine then you can use the synchronous version `evaluate_sync()`

.. code-block:: python

    def my_normal_function():
        og.Controller.evaluate_sync()

The :py:meth:`evaluate_sync()<omni.graph.core.Controller.evaluate_sync>` method also takes an optional parameter
with a graph or list of graphs to be evaluated. By default it evaluates all graphs in the scene.

The workhorse of the controller class is the :py:meth:`edit()<omni.graph.core.Controller.edit>` method. It provides
simple access to a lot of the underlying functionality for manipulating the contents of OmniGraph.

It should be noted here that this method can be accessed as both a class method and an object method. The reason you
would create an instance of a `Controller` object is if you wish to preserve the internal mapping of node names to
instantiated node paths for future use.

For example, this code sequence could be used to create a node and then set a value on one of its attributes:

.. code-block:: python

    og.Controller.edit("/World/PushGraph",
        {
            og.Controller.Keys.CREATE_NODES: ("NewNode", "omni.graph.nodes.Add"),
            og.Controller.Keys.SET_VALUES: ("NewNode.inputs:a", {"type": "float", "value": 1.0})
        }
    )

In this example the extended version of value setting is used, where both the type and value are provided so that
the extended attribute type in `omni.graph.nodes.Add` can resolve its type.

ObjectLookup
------------

This class contains the functions you will probably use the most. It provides an extremely flexible method for looking
up OmniGraph objects from the information you have on hand. The specs it accepts as arguments can be seen in the
:py:class:`class documentation<omni.graph.core.ObjectLookup>`.

Here's a quick summary of the most useful methods on this class:

- :py:meth:`graph()<omni.graph.core.ObjectLookup.graph>` gets an **og.Graph** object
- :py:meth:`node()<omni.graph.core.ObjectLookup.node>` gets an **og.Node** object
- :py:meth:`attribute()<omni.graph.core.ObjectLookup.attribute>` gets an **og.Attribute** object
- :py:meth:`attribute_type()<omni.graph.core.ObjectLookup.attribute_type>` gets an **og.Type** object
- :py:meth:`node_type()<omni.graph.core.ObjectLookup.node_type>` gets an **og.NodeType** object
- :py:meth:`prim()<omni.graph.core.ObjectLookup.prim>` gets a **Usd.Prim** object
- :py:meth:`usd_attribute()<omni.graph.core.ObjectLookup.usd_attribute>` gets a **Usd.Attribute** object
- :py:meth:`variable()<omni.graph.core.ObjectLookup.variable>` gets a **og.IVariable** object

Other methods will be added as they become useful.

GraphController
---------------

This class contains the functions that manipulate the structure of the graph, including creating a graph. The
:py:class:`class documentation<omni.graph.core.GraphController>` describe the details of what it can do.

Here's a quick summary of the most useful methods in the class. All but the last of them can be accessed through
different keywords in the dictionary passed to :py:meth:`og.Controller.edit()<omni.graph.core.Controller.edit>`.

- :py:meth:`create_graph()<omni.graph.core.GraphController.create_graph>` creates a new **og.Graph**
- :py:meth:`create_node()<omni.graph.core.GraphController.create_node>` creates a new **og.Node**
- :py:meth:`create_prim()<omni.graph.core.GraphController.create_prim>` creates a new **Usd.Prim**
- :py:meth:`create_variable()<omni.graph.core.GraphController.create_variable>` creates a new **og.IVariable**
- :py:meth:`delete_node()<omni.graph.core.GraphController.delete_node>` deletes an existing **og.Node**
- :py:meth:`expose_prim()<omni.graph.core.GraphController.expose_prim>` makes a **Usd.Prim** visible to OmniGraph through a read node
- :py:meth:`connect()<omni.graph.core.GraphController.connect>` connects two **og.Attributes** together
- :py:meth:`disconnect()<omni.graph.core.GraphController.disconnect>` breaks an existing connection between two **og.Attributes**
- :py:meth:`disconnect_all()<omni.graph.core.GraphController.disconnect_all>` disconnects everything from an existing **og.Attribute**
- :py:meth:`set_variable_default_value()<omni.graph.core.GraphController.set_variable_default_value>` sets the default value of an **og.IVariable**
- :py:meth:`get_variable_default_value()<omni.graph.core.GraphController.get_variable_default_value>` gets the default value of an **og.IVariable**

NodeController
--------------

This class contains the functions that manipulate the contents of a node. It only has a few functions. The
:py:class:`class documentation<omni.graph.core.NodeController>` outlines its areas of control.

Here's a quick summary of the most useful methods in the class:

- :py:meth:`create_attribute()<omni.graph.core.NodeController.create_attribute>` creates a new dynamic **og.Attribute**
- :py:meth:`remove_attribute()<omni.graph.core.NodeController.remove_attribute>` removes an existing dynamic **og.Attribute**
- :py:meth:`safe_node_name()<omni.graph.core.NodeController.safe_node_name>` returns a node name based on an **og.NodeType** that is USD-safe

.. note::

    A **dynamic** attribute in this context means an attribute that can be added to the node that does not exist in the
    node's .ogn file.

DataView
--------

This class contains the functions to get and set attribute values. It has a flexible **__init__** function that can
optionally take an "attribute" parameter to specify either an **og.Attribute** or **og.AttributeData** to which the
data operations will apply. The :py:class:`class documentation<omni.graph.core.DataView>` shows the available
functionality.

Here's a quick summary of the most useful methods in the class:

- :py:meth:`get()<omni.graph.core.DataView.get>` gets the current value of the attribute
- :py:meth:`get_array_size()<omni.graph.core.DataView.get_array_size>` gets the number of elements in an array attribute
- :py:meth:`set()<omni.graph.core.DataView.set>` sets a new value on the attribute

All of these methods are set up to work either as class methods or as object methods. The difference is that when
called as a class method you have add an extra parameter at the beginning that is the attribute whose value is being
processed.

These two calls, for example, are equivalent:

.. code-block:: python

    attribute = og.Controller.attribute("/World/PushGraph/Add:inputs:a")

    # As a class method
    value = og.DataView.get(attribute)
    # or value = og.Controller.get(attribute)

    # As an object method
    value = og.DataView(attribute=attribute).get()
    # or value = og.Controller(attribute=attribute).get()
