On this page |
|
APEX Script allows users to access and manipulate graph elements, add graph logic directly into another graph, and invoke other graphs from within a graph.
APEX Script for building and modifying graphs ¶
APEX Script code can be directly represented as an APEX graph, as shown in this example. APEX Script can also be used as a tool to build new graphs and modify existing graphs.
Build a new graph ¶
In this example, we use APEX Script to create a graph that contains a single node.
-
In the APEX Script SOP, add the following code to the Snippet parameter:
# Initialize a graph graph = ApexGraphHandle() # Add a TransformObject node to the new graph graph.addNode('test_a', 'TransformObject')
-
The APEX Script snippet itself is a graph. You can view the graph representation of the APEX Script code in the APEX network view:
-
To output the new graph that the APEX Script code created:
-
Add the following commands to the end of the APEX Script snippet to write out the graph as geometry:
geo = graph.writeToGeo() BindOutput(geo)
The APEX Script graph is updated to include the functionality that outputs the new graph:
-
Set Bind Output Geometry to
output:geo
.
-
-
To see the new graph in the APEX network view:
-
On the APEX Script SOP, in the Visualizer section, set Show to Output 1.
or
-
Connect the 1st output of the APEX Script SOP to a Null SOP, and select the Null SOP.
-
Modify an existing graph ¶
In this example, we use APEX Script to add a node to an existing graph.
-
Create a graph in the APEX Edit Graph SOP that contains only an input and output node:
-
Connect the graph as an input to the APEX Script SOP:
-
In the APEX Script SOP, use the predefined headers to automatically add the APEX Script commands that allow you to take in and modify an input graph:
-
Turn on the Header parameter.
-
In the Header section, set Template to Graph.
-
-
Specify the input to the APEX Script SOP. In the Invocation section, under Input1 Bindings, turn on Bind To Geometry Parameter, and set the parameter to
geo
. -
To output the updated graph, set Bind Output Geometry to
output:geo
. -
The APEX Script SOP now takes in the input graph and outputs it unchanged. To see the graph that is output by the APEX Script SOP:
-
On the APEX Script SOP, in the Visualizer section, set Show to Output 1.
or
-
Connect the 1st output of the APEX Script SOP to a Null SOP, and select the Null SOP.
-
-
To update the input graph, add the following code to the Snippet parameter:
# Add a TransformObject node to the input graph graph.addNode('test_a', 'TransformObject')
Specifying graph inputs and outputs ¶
BindInput() and BindOutput() are special functions that allow you to define the inputs and outputs of a graph. See this simple example for how to use BindInput() and BindOutput().
The examples below show how to specify different types of graph inputs:
# Initialize float value f = BindInput(3.1) # Uninitialized string type s: String = BindInput() # Initialize to empty array a = BindInput(DictArray()) # Default dictionary key-value pair: {'<key>': <value>} d = BindInput({'abc': 1.5}) # Create multiple graph inputs x, y, z = BindInput(2.6, 'test', Dict()) # Type conversion when initializing i = BindInput(Int(Float(1.5)))
Note
If you don’t specify a type for the graph input, a warning will be raised. Only do the following if you know exactly how you are using the variable afterwards.
a = BindInput()
The input port a that is defined in this way is not displayed in the APEX network view because its type is ambiguous.
You can rename the input and output ports on a graph using keyword arguments (arguments with the syntax <key>=<value>
):
i: Int = BindInput(int_test=3) x,y = BindInput(2, my_str='hello') BindOutput(y, int_test=i)
Note
Keyword arguments must be placed after positional (non-keyword) arguments.
Keyword arguments that contain __dot__
are replaced with '.
'. This allows APEX Script to handle rigs with geometry names like Base.skel
and Base.shp
:
x = BindInput(Base__dot__shp=Geometry()) Base__dot__skel: Geometry = BindInput() BindOutput(Base__dot__skel, Base__dot__shp=x)
Graph nodes and ports ¶
APEX Script provides functions that allow you to perform various graph operations, and access graph elements like nodes and ports. These functions are primarily used in rigging for building up the rig logic of a character. See the graph::* nodes for the graph operations that are available in APEX Script.
The syntax to find a graph node with a specific node name:
graph.<node_name>
The syntax to find a specific input or output port on a node:
graph.<node_name>.<port_name>_[in|out]
Connect two ports:
# Create graph graph = ApexGraphHandle() # Add two TransformObject nodes to graph a = graph.addNode('test_a', 'TransformObject') b = graph.addNode('test_b', 'TransformObject') # Set port variables p_a = graph.test_a.xform_out p_b = graph.test_b.xform_in # Connect ports graph.addWire(p_a, p_b)
Find all nodes based on a pattern:
# Create graph graph = ApexGraphHandle() # Add two TransformObject nodes to graph a = graph.addNode('test_a', 'TransformObject') b = graph.addNode('test_b', 'TransformObject') # Find all nodes that begin with 'test' n = graph.findNodes('test*')
Find a port on a node:
# Create graph graph = ApexGraphHandle() # Add TransformObject node to graph a = graph.addNode('test_a', 'TransformObject') # Find input port 'xform' on node p = graph.findPort(a, 'xform[in]')
Get data on nodes and ports (see function mappings for how to use the data() function):
# Create graph graph = ApexGraphHandle() # Add TransformObject node to graph a = graph.addNode('test_a', 'TransformObject') # Get node data (a_name, a_callback, a_pos, a_color, a_parms) = a.data() # Output node data BindOutput(a_name, a_callback, a_pos, a_color, a_parms) # 'xform' output port of TransformObject node p = a.xform_out # Get port data (p_name, p_alias, p_datatype, p_porttype, p_isconnected, p_ispromoted) = p.data() # Output port data BindOutput(p_name, p_alias, p_datatype, p_porttype, p_isconnected, p_ispromoted)
See the list of ports returned from a function:
# x is an array of APEX port IDs x = graph.matchPorts('*xform*') # Initialize an array of strings arr = StringArray() # Add the port names to the array for p in x: p_name = p.data() arr.append(p_name) # Output the array of port names BindOutput(arr)
Variadic ports ¶
Variadic ports are ports that can take in multiple connections. As you make connections to a variadic port, subports are created on the node.
Variadic inputs ¶
In APEX Script, variadic inputs are stored in a dictionary with the following key-value pair:
-
key - subport name
-
value - subport value
The example below adds three values. The first argument is a regular input, and the second argument is a variadic input stored as a dictionary with two entries. The subport names of the variadic input are explicitly set:
x = 3 sum = Add_Int(1, {'bbb': 2, 'ccc': x})
If the variadic input is the last port on the node, there is no need to specify the subport names:
x = 3 sum = Add_Int(1, 2, x)
If no values are specified for the non-variadic ports, the variadic port name must be specified with the following syntax:
<function>(<variadic_port>={<subport1>:<value1>, <subport2>:<value2>, ...})
sum = Add_Int(b={'x': 4, 'y': 5})
Variadic outputs ¶
To name the subports of a variadic output, use the keyword argument <variadic_arg_name>_out=[<subport_names>]
, where <subport_names>
is an array of strings.
The example below gets the variadic outputs from IntBitMaskToBool:
input: Int = BindInput(8) # Variadic output subports named 'aa', 'bb', 'cc', 'dd' a,b,c,d = input.IntBitMaskToBool.v2_0(mask__out=['aa', 'bb', 'cc', 'dd']) # Output subport values BindOutput(input,a,b,c,d)
If the keyword argument is not provided, the subport names are set to the variable names:
input: Int = BindInput(8) # Variadic output subports named 'a', 'b', 'c', 'd' a,b,c,d = input.IntBitMaskToBool.v2_0() # Output subport values BindOutput(input,a,b,c,d)
Dictionary entries can be of any type, so to extract the entries of a dictionary using dict::Extract, you must first specify the types of the variables you want to extract the dictionary entries to:
# Specify dictionary entry types a: Int b: Float c: Vector3 dict_test = { 'aa': 5, 'bb': 0.2, 'cc': (5,10,15) } a,b,c = dict_test.extract(args__out=['aa', 'bb', 'cc']) BindOutput(a,b,c)
Graph templates ¶
In APEX Script, a function can be treated like a graph template to easily add logic to an existing graph. You can use APEX Script to write expressions and logic that is bundled up in a function, and then insert this function directly into an existing graph to modify its functionality.
In APEX rigging, a character rig is represented as a graph. If you have a function that has a reverse foot or look at functionality, you could insert this function into an incoming character rig graph to procedurally build up the character’s final rig.
Note
Calling a function simply executes the function logic in the current APEX Script. To use the function logic in another graph (like an incoming rig), the function must be inserted into the incoming graph.
In this example, we start with an input rig graph that contains only an input and output node:
We then add a simple mathematical operation to the input rig graph using the APEX Script SOP:
-
In the APEX Script SOP, use the predefined headers to automatically add the APEX Script commands that allow you to take in and modify an input graph:
-
Turn on the Header parameter.
-
In the Header section, set Template to Graph.
-
-
Specify the input to the APEX Script SOP. In the Invocation section, under Input1 Bindings, turn on Bind To Geometry Parameter, and set the parameter to
geo
. -
To output the updated rig graph, set Bind Output Geometry to
output:geo
. -
Add the following code to the Snippet parameter. The code adds function
test
to the input graph as a subnet using graph::AddSubnet:# Functionality to add to the input graph def test(a: vector3, b: float) -> tuple(vector3, float): a = a * b b = b + 1.0 return a, b # Add function 'test' to the input graph as a subnet named 'my_test' my_subnet = graph.addSubnet(test, 'my_test') # Connect subnet to the input node of the input graph my_subnet.a_in.promote('aa') my_subnet.b_in.promote('bb') # Connect subnet to the output node of the input graph my_subnet.a_out.promote('aa') my_subnet.b_out.promote('bb')
-
To see the updated rig graph in the APEX network view:
-
On the APEX Script SOP, in the Visualizer section, set Show to Output 1.
or
-
Connect the 1st output of the APEX Script SOP to a Null SOP, and select the Null SOP.
Dive into the
my_test
subnet to view the graph logic of functiontest
from the APEX Script code: -
Note
In the example above, graph::AddSubnet is used to add the function to the input graph as a subnet. You could also use graph::Merge to merge the function directly into the input graph without packing it into a subnet. However, the resulting graph could contain multiple graph input and output nodes, which you would then have to connect to the appropriate nodes. The cleaner approach is to use graph::AddSubnet.
Adding rig logic without graph templates ¶
Without using a function as a template, the only way to add functionality to an incoming rig graph is to:
-
Add the individual mathematical nodes.
-
Script each node connection.
-
Pack up the above logic into a subnet.
-
Connect the subnet to the main graph.
Without graph templates, the APEX Script code to create the same logic as the previous example is much more convoluted:
# Add nodes to graph mult = graph.addNode('MultiplyExpr', 'Multiply<Vector3,Float>') add = graph.addNode('AddExpr', 'Add<Float>') val = graph.addNode('Value_right', 'Value<Float>', parms={'value':1.0}) # Connect Multiply node to graph input and output mult.a_in.promote() mult.b_in.promote('b') mult.result_out.promote('a') # Connect Add node to graph input and output add.a_in.promote('b') add.result_out.promote('b') # Connect Value node to Add node val.value_out.connect(add.b_in) # Pack nodes (preserving the connections) into a subnet # Output names of subnet is set to default values node_arr: ApexNodeIDArray = [mult, add, val] graph.packSubnet('my_test', node_arr)
Invoking another graph ¶
InvokeOutputs() is a special function that is used to execute another graph. When you have an input that is a graph, InvokeOutputs() is used to execute that graph input.
In this example, InvokeOutputs() executes the function foo:
def foo (a: Int, b: Int) -> tuple[Int, Int]: return b, a x: Int y: Int x, y = InvokeOutputs(foo, parms={"a": 1, "b": 2}, outputs=["output:a", "output:b"])
The syntax of InvokeOutputs() is:
InvokeOutputs(<function>, parms=<dict>, outputs=[array of strings])
where:
-
parms - Dictionary of parameter values to pass into the function.
-
outputs - An array of strings that specify the values returned by the function. The strings are in the format
<output_node>:<output_port>
, where<output_node>
is the output node in the function graph (function foo, in our case).
The outputs
argument is matched up with the function return values. In the example above:
-
output:a
is set to function return valuea
. Variablex
is then set tooutput:a
. -
output:b
is set to function return valueb
. Variabley
is then set tooutput:b
.
If the function return statement contains an expression, set the outputs
argument to result<x>
, where x
is the position of the function return value starting at 0:
def foo(a: Int): return a+1 x: Int = InvokeOutputs(foo, parms={"a": 1}, outputs=["output:result0"]) # Different way of specifying the return value type y = Int(InvokeOutputs(foo, parms={"a": 2}, outputs=["output:result0"]))
If the function return statement contains multiple expressions, the order of the return values is important. For the return values that are not expressions, the corresponding outputs
argument must be set to output:<variable>
:
# Function return values using expressions (a+b and a*b) def foo(a: Int, b: Int): return a+b, a, a*b x: Int y: Int z: Int # Function returns a+b in output:result0, and # a*b in output:result2 x, y, z = InvokeOutputs(foo, parms={"a": 1, "b": 2}, outputs=["output:result0", "output:a", "output:result2"])
Tip
It is more readable and less error-prone for a function to return only variables and not expressions.
Retrieve only certain values from the function:
def foo(a: Int, b: Int): return a+b, a, a*b x: Int # Only retrieves the a*b return value from function foo x = InvokeOutputs(foo, parms={"a": 1, "b": 2}, outputs=["output:result2"])
Associating APEX node IDs to a graph ¶
If you have multiple graphs, you can use the special function setGraph() to associate node IDs to a particular graph:
graph1: ApexGraphHandle = BindInput() graph2 = ApexGraphHandle() my_node: ApexNodeID = BindInput() # Associate my_node with graph2 my_node.setGraph(graph2)
Associate multiple node IDs to different graphs:
graph1: ApexGraphHandle = BindInput() graph2 = ApexGraphHandle() node_arr = ApexNodeIDArray() # Append APEX node IDs to array for i in range(4): node_arr.append(Value_ApexNodeID_()) # Associate node ID in node_arr[1] to graph2 x = node_arr[1] x.setGraph(graph2) # Associate node ID in node_arr[2] to graph1 # Different way of calling setGraph() y = node_arr[2] setGraph(y, graph1)
Converting graphs to APEX Script code ¶
The APEX Script, APEX Autorig Component, and APEX Rigscript Component SOPs provide the option to convert graphs to APEX Script code.
For the APEX Script SOP:
-
Connect an existing graph into the 2nd input of the APEX Script SOP.
-
In the Decompile section, set the drop-down menu to Second Input.
-
Click the Convert to Snippet button.
The APEX Script code will appear in the Snippet parameter.
See the special functions that may be added to the decompiled code.
How-to ¶
To... | Do this |
---|---|
See the nodes or ports returned from a function |
View a single port ID: # x is an APEX port ID name = x.data() BindOutput(name) View a list of port IDs: # x is an array of APEX port IDs x = graph.matchPorts('*xform*') # Initialize an array of strings arr = StringArray() # Add the port names to the array for p in x: p_name = p.data() arr.append(p_name) # Output the array of port names BindOutput(arr) To view the port names:
|