Compound Nodes

Compound nodes are nodes whose execution is defined by the evaluation of an OmniGraph. Compound nodes can be used to encapsulate complex functionality into a single node, or to create a node that can be reused. The existing release of OmniGraph supports Compound Subgraphs, which allow the user to collapse a subnetwork of nodes into a separate OmniGraph that is represented by a node in the owning graph.

USD Representation of Compound Nodes

Similar to non-compound nodes, Compound Nodes are represented in USD using a prim with the schema type OmniGraphSchema.OmniGraphNode. A Compound Node also contains attributes and a node type. When a Compound Node represents a Compound Subgraph, the node type is fixed to omni.graph.nodes.CompoundSubgraph.

For the prim to be recognized by OmniGraph as a compound, a USD schemaAPI is applied: OmniGraphSchema.OmniGraphCompoundNodeAPI. This allows the compound node to inherit an attribute representing the compound node type (currently only “subgraph” is supported), and a USD relationship attribute (omni:graph:compoundGraph) that references the location of the graph Prim on the stage that represents the definition of the Compound Nodes execution.

The Prim representing the graph is a standard OmniGraph Prim. The Prim is inserted as a child of the Compound Node. Connections are made between the Compound Node and the nodes in the Compound Graph. The attributes of the Compound Node generally act as a passthrough, moving data between the owning graph and Compound Graph.

def OmniGraphNode "compound" (
    prepend apiSchemas = ["OmniGraphCompoundNodeAPI"]
)
{
    custom token inputs:a
    custom token inputs:b
    token node:type = "omni.graph.nodes.CompoundSubgraph"
    int node:typeVersion = 1
    rel omni:graph:compoundGraph = </World/PushGraph/compound/Subgraph>
    token omni:graph:compoundType = "subgraph"
    custom token outputs:sum
    prepend token outputs:sum.connect = </World/PushGraph/compound/Subgraph/add.outputs:sum>

    def OmniGraph "Subgraph"
    {
        def OmniGraphNode "add"
        {
            custom token inputs:a
            prepend token inputs:a.connect = </World/PushGraph/compound.inputs:a>
            custom token inputs:b
            prepend token inputs:b.connect = </World/PushGraph/compound.inputs:b>
            token node:type = "omni.graph.nodes.Add"
            int node:typeVersion = 2
            custom token outputs:sum
        }
    }
}

Using the og.Controller Class to Create Compound Nodes in Scripts

Creating Compound Nodes

The og.Controller class can be used to create Compound Nodes in scripts. The og.Controller class provides a set of methods useful in defining OmniGraphs using python scripting. The most straightforward way to create a compound node using the og.Controller.edit function is to define the Compound Subgraph when defining the Compound Node by using a recursive definion, as demonstrated in the following sample:

keys = og.Controller.Keys
controller = og.Controller()
(graph, nodes, _, name_to_object_map) = controller.edit(
    "/World/MyGraph1",
    {
        keys.CREATE_NODES: [
            (
                "CompoundNode",
                {  # Creates the compound nodes
                    # Defines the subgraph of the compound node
                    keys.CREATE_NODES: [
                        ("Constant1", "omni.graph.nodes.ConstantDouble"),
                        ("Constant2", "omni.graph.nodes.ConstantDouble"),
                        ("Add", "omni.graph.nodes.Add"),
                    ],
                    keys.CONNECT: [
                        ("Constant1.inputs:value", "Add.inputs:a"),
                        ("Constant2.inputs:value", "Add.inputs:b"),
                    ],
                },
            ),
        ],
    },
)

This will generate a graph that contains a Compound Node containing a Subgraph with two Constant nodes and an Add Node. Note that in the returned values from the edit function, the list represented by the nodes variable will only contain the Compound Node, and not the nodes defined inside the Compound Subgraph. In other words, the list of returned nodes only contains nodes in the top-level graph. However, the nodes inside the Compound Subgraph can be accessed via the returned name_to_object_map dictionary using the defined names of each of the nodes in the Subgraph. For example, to access the Constant1 node, use name_to_object_map["Constant1"]. This does mean that when creating complex graph structures, all node names need to be unique, even if they are defined in different Compound Subgraphs.

Promoting Compound Node Attributes

In the example above, the generated compound node has neither input nor output attributes. In practice, a Compound Node will use data from, and produce data for, adjacent nodes in the graph. This is accomplished using the og.Controller.keys.PROMOTE_ATTRIBUTES key. The following example demonstrates how to promote attributes:

keys = og.Controller.Keys
controller = og.Controller()
(graph, nodes, _, name_to_object_map) = controller.edit(
    "/World/MyGraph2",
    {
        keys.CREATE_NODES: [
            (
                "CompoundNode",
                {
                    keys.CREATE_NODES: [
                        ("Add", "omni.graph.nodes.Add"),
                    ],
                    keys.PROMOTE_ATTRIBUTES: [
                        ("Add.inputs:a", "inputs:one"),
                        ("Add.inputs:b", "inputs:two"),
                        ("Add.outputs:sum", "outputs:result"),
                        ("Add.outputs:sum", "outputs:alt_result"),
                    ],
                },
            ),
            ("Constant1", "omni.graph.nodes.ConstantDouble"),
            ("Constant2", "omni.graph.nodes.ConstantDouble"),
            ("Consumer", "omni.graph.nodes.Add"),
        ],
        keys.CONNECT: [
            # Connect to the promoted attributes
            ("Constant1.inputs:value", "CompoundNode.inputs:one"),
            ("Constant2.inputs:value", "CompoundNode.inputs:two"),
            ("CompoundNode.outputs:result", "Consumer.inputs:a"),
            ("CompoundNode.outputs:alt_result", "Consumer.inputs:b"),
        ],
    },
)

While the sample above does not demonstrate a particularly useful Compound Node, it does demonstrates a few important concepts about promotion. When promoting an attribute, the source attribute and the compound attribute name are supplied as a tuple. The source attribute is a path to an attribute in the Compound Subgraph. The second element of the tuple specifies the promoted attribute name in the compound. Note the name does not have to match the source attribute path.

The promoted attribute can then be accessed in the owning graph using the name specified in the promotion in the same manner as one would use any other node attributes. If required, an attribute can be promoted multiple times, as demonstrated by the promotion of the Add.outputs:sum. Attribute promotion can also be accomplished using the og.NodeController.promote_attribute function.

The inputs and outputs prefix is optional in the compound attribute name. This means that the type of promoted attribute is determined solely by the port type of the source attribute, and not the prefix of the compound attribute name. The is demonstrated in the following example:

keys = og.Controller.Keys
controller = og.Controller()
(graph, nodes, _, name_to_object_map) = controller.edit(
    "/World/MyGraph3",
    {
        keys.CREATE_NODES: [
            (
                "CompoundNode",
                {
                    keys.CREATE_NODES: [
                        ("Add", "omni.graph.nodes.Add"),
                        ("Constant1", "omni.graph.nodes.ConstantDouble"),
                    ],
                    keys.PROMOTE_ATTRIBUTES: [
                        ("Add.inputs:a", "one"),  # Promoted as inputs:one
                        ("Add.inputs:b", "outputs:two"),  # Promoted as inputs:outputs:two
                        ("Add.outputs:sum", "inputs:result"),  # Promoted as outputs:inputs:result
                        ("Constant1.inputs:value", "const"),  # Promoted as outputs:const
                    ],
                },
            ),
        ],
    },
)

Here, the appropriate prefix is appended to each of the promoted attribute names. In the case of Constants, which used input attributes that are marked as output-only, they become output attributes on the Compound Node.