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 Tutorial 2 - Simple Data Node 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.)
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.
1{
2 "SimpleDataPy" : {
3 "version": 1,
4 "categories": "tutorials",
5 "description": [
6 "This is a tutorial node. It creates both an input and output attribute of every simple ",
7 "supported data type. The values are modified in a simple way so that the compute modifies values. ",
8 "It is the same as node omni.graph.tutorials.SimpleData, except it is implemented in Python instead of C++."
9 ],
10 "language": "python",
11 "$iconOverride": [
12 "If for some reason the default name or colors for the icon are inappropriate you can override like this.",
13 "This gives an alternative icon path, a shape color of Green, a border color of Red, and a background",
14 "color of half-opaque Blue. If you just want to override the icon path then instead of a dictionary you",
15 "can just use the string path, as in \"icon\": \"Tutorial10Icon.svg\"."
16 ],
17 "icon": {
18 "path": "Tutorial10Icon.svg",
19 "color": "#FF00FF00",
20 "borderColor": [255, 0, 0, 255],
21 "backgroundColor": "#7FFF0000"
22 },
23 "metadata":
24 {
25 "uiName": "Tutorial Python Node: Attributes With Simple Data"
26 },
27 "inputs": {
28 "a_bool": {
29 "type": "bool",
30 "metadata": {
31 "uiName": "Simple Boolean Input"
32 },
33 "description": ["This is an attribute of type boolean"],
34 "default": true,
35 "$optional": "When this is set there is no checking for validity before calling compute()",
36 "optional": true
37 },
38 "a_half": {
39 "type": "half",
40 "description": ["This is an attribute of type 16 bit float"],
41 "$comment": "0 is used as the decimal portion due to reduced precision of this type",
42 "default": 0.0
43 },
44 "a_int": {
45 "type": "int",
46 "description": ["This is an attribute of type 32 bit integer"],
47 "default": 0
48 },
49 "a_int64": {
50 "type": "int64",
51 "description": ["This is an attribute of type 64 bit integer"],
52 "default": 0
53 },
54 "a_float": {
55 "type": "float",
56 "description": ["This is an attribute of type 32 bit floating point"],
57 "default": 0
58 },
59 "a_double": {
60 "type": "double",
61 "description": ["This is an attribute of type 64 bit floating point"],
62 "default": 0
63 },
64 "a_path": {
65 "type": "path",
66 "description": ["This is an attribute of type path"],
67 "default": ""
68 },
69 "a_string": {
70 "type": "string",
71 "description": ["This is an attribute of type string"],
72 "default": "helloString"
73 },
74 "a_token": {
75 "type": "token",
76 "description": ["This is an attribute of type interned string with fast comparison and hashing"],
77 "default": "helloToken"
78 },
79 "a_objectId": {
80 "type": "objectId",
81 "description": ["This is an attribute of type objectId"],
82 "default": 0
83 },
84 "a_uchar": {
85 "type": "uchar",
86 "description": ["This is an attribute of type unsigned 8 bit integer"],
87 "default": 0
88 },
89 "a_uint": {
90 "type": "uint",
91 "description": ["This is an attribute of type unsigned 32 bit integer"],
92 "default": 0
93 },
94 "a_uint64": {
95 "type": "uint64",
96 "description": ["This is an attribute of type unsigned 64 bit integer"],
97 "default": 0
98 },
99 "a_constant_input": {
100 "type": "int",
101 "description": ["This is an input attribute whose value can be set but can only be connected as a source."],
102 "metadata": {
103 "outputOnly": "1"
104 }
105 }
106 },
107 "outputs": {
108 "a_bool": {
109 "type": "bool",
110 "description": ["This is a computed attribute of type boolean"]
111 },
112 "a_half": {
113 "type": "half",
114 "description": ["This is a computed attribute of type 16 bit float"]
115 },
116 "a_int": {
117 "type": "int",
118 "description": ["This is a computed attribute of type 32 bit integer"]
119 },
120 "a_int64": {
121 "type": "int64",
122 "description": ["This is a computed attribute of type 64 bit integer"]
123 },
124 "a_float": {
125 "type": "float",
126 "description": ["This is a computed attribute of type 32 bit floating point"]
127 },
128 "a_double": {
129 "type": "double",
130 "description": ["This is a computed attribute of type 64 bit floating point"]
131 },
132 "a_path": {
133 "type": "path",
134 "description": ["This is a computed attribute of type path"],
135 "default": "/Child"
136 },
137 "a_string": {
138 "type": "string",
139 "description": ["This is a computed attribute of type string"],
140 "default": "This string is empty"
141 },
142 "a_token": {
143 "type": "token",
144 "description": ["This is a computed attribute of type interned string with fast comparison and hashing"]
145 },
146 "a_objectId": {
147 "type": "objectId",
148 "description": ["This is a computed attribute of type objectId"]
149 },
150 "a_uchar": {
151 "type": "uchar",
152 "description": ["This is a computed attribute of type unsigned 8 bit integer"]
153 },
154 "a_uint": {
155 "type": "uint",
156 "description": ["This is a computed attribute of type unsigned 32 bit integer"]
157 },
158 "a_uint64": {
159 "type": "uint64",
160 "description": ["This is a computed attribute of type unsigned 64 bit integer"]
161 },
162 "a_nodeTypeUiName": {
163 "type": "string",
164 "description": "Computed attribute containing the UI name of this node type"
165 },
166 "a_a_boolUiName": {
167 "type": "string",
168 "description": "Computed attribute containing the UI name of input a_bool"
169 }
170 },
171 "tests": [
172 {
173 "$comment": ["Each test has a description of the test and a set of input and output values. ",
174 "The test runs by setting all of the specified inputs on the node to their values, ",
175 "running the compute, then comparing the computed outputs against the values ",
176 "specified in the test. Only the inputs in the list are set; others will use their ",
177 "default values. Only the outputs in the list are checked; others are ignored."],
178 "description": "Check that false becomes true",
179 "inputs:a_bool": false,
180 "outputs:a_bool": true
181 },
182 {
183 "$comment": "This is a more verbose format of test data that provides a different grouping of values",
184 "description": "Check that true becomes false",
185 "inputs": {
186 "a_bool": true
187 },
188 "outputs": {
189 "a_bool": false,
190 "a_a_boolUiName": "Simple Boolean Input",
191 "a_nodeTypeUiName": "Tutorial Python Node: Attributes With Simple Data"
192 }
193 },
194 {
195 "$comment": "Make sure the path append does the right thing",
196 "inputs:a_path": "/World/Domination", "outputs:a_path": "/World/Domination/Child"
197 },
198 {
199 "$comment": "Even though these computations are all independent they can be checked in a single test.",
200 "description": "Check all attributes against their computed values",
201 "inputs:a_bool": false, "outputs:a_bool": true,
202 "inputs:a_double": 1.1, "outputs:a_double": 2.1,
203 "inputs:a_float": 3.3, "outputs:a_float": 4.3,
204 "inputs:a_half": 5.0, "outputs:a_half": 6.0,
205 "inputs:a_int": 7, "outputs:a_int": 8,
206 "inputs:a_int64": 9, "outputs:a_int64": 10,
207 "inputs:a_token": "helloToken", "outputs:a_token": "worldToken",
208 "inputs:a_string": "helloString", "outputs:a_string": "worldString",
209 "inputs:a_objectId": 10, "outputs:a_objectId": 11,
210 "inputs:a_uchar": 11, "outputs:a_uchar": 12,
211 "inputs:a_uint": 13, "outputs:a_uint": 14,
212 "inputs:a_uint64": 15, "outputs:a_uint64": 16
213 }
214 ]
215 }
216}
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.
1"""
2Implementation of the Python node accessing all of the simple data types.
3This class exercises access to the DataModel through the generated database class for all simple data types.
4It implements the same algorithm as the C++ node OgnTutorialSimpleData.cpp
5"""
6
7import omni.graph.tools.ogn as ogn
8
9
10class OgnTutorialSimpleDataPy:
11 """Exercise the simple data types through a Python OmniGraph node"""
12
13 @staticmethod
14 def compute(db) -> bool:
15 """Perform a trivial computation on all of the simple data types to make testing easy"""
16 # Inside the database the contained object "inputs" holds the data references for all input attributes and the
17 # contained object "outputs" holds the data references for all output attributes.
18
19 # Each of the attribute accessors are named for the name of the attribute, with the ":" replaced by "_".
20 # The colon is used in USD as a convention for creating namespaces so it's safe to replace it without
21 # modifying the meaning. The "inputs:" and "outputs:" prefixes in the generated attributes are matched
22 # by the container names.
23
24 # For example attribute "inputs:translate:x" would be accessible as "db.inputs.translate_x" and attribute
25 # "outputs:matrix" would be accessible as "db.outputs.matrix".
26
27 # The "compute" of this method modifies each attribute in a subtle way so that a test can be written
28 # to verify the operation of the node. See the .ogn file for a description of tests.
29 db.outputs.a_bool = not db.inputs.a_bool
30 db.outputs.a_half = 1.0 + db.inputs.a_half
31 db.outputs.a_int = 1 + db.inputs.a_int
32 db.outputs.a_int64 = 1 + db.inputs.a_int64
33 db.outputs.a_double = 1.0 + db.inputs.a_double
34 db.outputs.a_float = 1.0 + db.inputs.a_float
35 db.outputs.a_uchar = 1 + db.inputs.a_uchar
36 db.outputs.a_uint = 1 + db.inputs.a_uint
37 db.outputs.a_uint64 = 1 + db.inputs.a_uint64
38 db.outputs.a_string = db.inputs.a_string.replace("hello", "world")
39 db.outputs.a_objectId = 1 + db.inputs.a_objectId
40
41 # The token interface is made available in the database as well, for convenience.
42 # By calling "db.token" you can look up the token ID of a given string.
43 if db.inputs.a_token == "helloToken":
44 db.outputs.a_token = "worldToken"
45
46 # Path just gets a new child named "Child".
47 # In the implementation the string is manipulated directly, as it does not care if the SdfPath is valid or
48 # not. If you want to manipulate it using the pxr.Sdf.Path API this is how you could do it:
49 #
50 # from pxr import Sdf
51 # input_path Sdf.Path(db.inputs.a_path)
52 # if input_path.IsValid():
53 # db.outputs.a_path() = input_path.AppendChild("/Child").GetString();
54 #
55 db.outputs.a_path = db.inputs.a_path + "/Child"
56
57 # To access the metadata you have to go out to the ABI, though the hardcoded metadata tags are in the
58 # OmniGraph Python namespace
59 assert db.node.get_attribute("inputs:a_bool").get_metadata(ogn.MetadataKeys.UI_NAME) == "Simple Boolean Input"
60
61 # You can also use the database interface to get the same data
62 db.outputs.a_nodeTypeUiName = db.get_metadata(ogn.MetadataKeys.UI_NAME)
63 db.outputs.a_a_boolUiName = db.get_metadata(ogn.MetadataKeys.UI_NAME, db.attributes.inputs.a_bool)
64
65 return True
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 db.inputs.foo
.
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 Fabric, 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, db.abi_context
,
for accessing the underlying OmniGraph evaluation context and its interface.
There is also the OmniGraph node member, db.abi_node
, for accessing
the underlying OmniGraph node object and its interface.