Tutorial 17 - Python State Attributes Node

This node illustrates how you can use state attributes. These are attributes that are not meant to be connected to other nodes as they maintain a node’s internal state, persistent from one evaluation to the next.

As they are persistent, care must be taken that they be initialized properly. This can take the form of a reset flag, as seen on this node, state flag values with known defaults that describe the validity of the state attribute data, or using a checksum on inputs, among other possibilities.

State attributes can be both read and written, like output attributes. The presence of state attributes will also inform the evaluators on what type of parallel scheduling is appropriate.

These attributes provide a similar functionality to those found in Tutorial 13 - Python State Node, except that being node attributes the structure is visible to the outside world, making it easier to construct UI and visualizers for it.

OgnTutorialStateAttributesPy.ogn

The .ogn file containing the implementation of a node named “omni.graph.tutorials.StateAttributesPy”, with a couple of state attributes that both read and write values during the compute.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
    "StateAttributesPy" : {
        "version": 1,
        "categories": "tutorials",
        "description": [
            "This is a tutorial node. It exercises state attributes to remember data from on evaluation to the next."
        ],
        "language": "python",
        "metadata":
        {
           "uiName": "Tutorial Python Node: State Attributes"
        },
        "inputs": { "ignored": { "type": "bool", "description": "Ignore me" } },
        "state": {
            "reset": {
                "type": "bool",
                "description": [
                    "If true then the inputs are ignored and outputs are set to default values, then this",
                    "flag is set to false for subsequent evaluations."
                ],
                "default": true
            },
            "monotonic": {
                "type": "int",
                "description": "The monotonically increasing output value, reset to 0 when the reset value is true"
            }
        },
        "$externalTest": [
            "This test infrastructure does not yet support multiple successive evaluations, so",
            "an external test script called 'test_tutorial_state_attributes_py.py' is used for that case.",
            "This test illustrates how a state value can be initialized before the test runs and checked after",
            "the test runs by using the _get and _set suffix on the state namespace. Having no suffix defaults to",
            "_get, where the expected state value is checked after running."
        ],
        "tests": [
            { "state_set:monotonic": 7, "state_get:reset": false, "state:monotonic": 8 }
        ]
    }
}

OgnTutorialStateAttributesPy.py

The .py file contains the compute method that uses the state attributes to run the algorithm.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"""
Implementation of a Python node that uses state attributes to maintain information between evaluations.

The node has a simple monotonically increasing state output "monotonic" that can be reset by setting the state
attribute "reset" to true.
"""


class OgnTutorialStateAttributesPy:
    """Use internal node state information in addition to inputs"""

    @staticmethod
    def compute(db) -> bool:
        """Compute the output based on inputs and internal state"""

        # State attributes are the only ones guaranteed to remember and modify their values.
        # Care must be taken to set proper defaults when the node initializes, otherwise you could be
        # starting in an unknown state.
        if db.state.reset:
            db.state.monotonic = 0
            db.state.reset = False
        else:
            db.state.monotonic = db.state.monotonic + 1

        return True

Test Script

The .ogn test infrastructure currently only supports single evaluation, which will not be sufficient to test state attribute manipulations. This test script runs multiple evaluations and verifies that the state information is updated as expected after each evaluation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
"""
Tests for the omnigraph.tutorial.stateAttributesPy node
"""
import omni.graph.core as og
import omni.graph.core.tests as ogts


class TestTutorialStateAttributesPy(ogts.OmniGraphTestCaseFailOnLogError):
    """.ogn tests only run once while state tests require multiple evaluations, handled here"""

    # ----------------------------------------------------------------------
    async def test_tutorial_state_attributes_py_node(self):
        """Test basic operation of the Python tutorial node containing state attributes"""
        keys = og.Controller.Keys
        (_, [state_node], _, _) = og.Controller.edit("/TestGraph", {
            keys.CREATE_NODES: ("StateNode", "omni.graph.tutorials.StateAttributesPy"),
            keys.SET_VALUES: ("StateNode.state:reset", True)
        })
        await og.Controller.evaluate()
        reset_attr = og.Controller.attribute("state:reset", state_node)
        monotonic_attr = og.Controller.attribute("state:monotonic", state_node)

        self.assertEqual(og.Controller.get(reset_attr), False, "Reset attribute set back to False")
        self.assertEqual(og.Controller.get(monotonic_attr), 0, "Monotonic attribute reset to start")

        await og.Controller.evaluate()
        self.assertEqual(og.Controller.get(reset_attr), False, "Reset attribute still False")
        self.assertEqual(og.Controller.get(monotonic_attr), 1, "Monotonic attribute incremented once")

        await og.Controller.evaluate()
        self.assertEqual(og.Controller.get(monotonic_attr), 2, "Monotonic attribute incremented twice")

        og.Controller.set(reset_attr, True)
        await og.Controller.evaluate()
        self.assertEqual(og.Controller.get(reset_attr), False, "Reset again set back to False")
        self.assertEqual(og.Controller.get(monotonic_attr), 0, "Monotonic attribute again reset to start")