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. See APEX Script for graph building for an introduction to APEX Script.
Specifying graph inputs and outputs ¶
BindInput() and BindOutput() are special functions that define the inputs and outputs of a graph. See this 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 will be using the variable:
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)
Specifying dictionary inputs ¶
In this example, we use values from two dictionaries as inputs to an APEX Script SOP:
Attribute Adjust Dictionary SOP
The Attribute Adjust Dictionary SOP defines a dictionary of input values. We set the following values for the two SOPs:
input_dictionary1
-
Attribute Name =
parms1
. By default, Attribute Class is set to Detail, so we have a detail attribute calledparms1
. -
In the Set Values section, we set two dictionary entries:
-
Key =
int1
, Type = Integer, Value =2
-
Key =
abc
, Type = String, Value =hi
-
input_dictionary2
-
Attribute Name =
parms2
-
In the Set Values section:
-
Key =
int2
, Type = Integer, Value =4
-
Key =
abc
, Type = String, Value =hi again
-
To view the new parms1
and parms2
dictionaries in the geometry spreadsheet:
-
Select one of the Attribute Adjust Dictionary SOPs.
-
At the top of a pane, click the New Tab icon and select New Pane Tab Type ▸ Inspectors ▸ Geometry Spreadsheet.
-
Select Detail on the top toolbar:
parms1: {"abc": "hi", "int1": 2 } parms2: {"abc": "hi again", "int2": 4 }
-
We have the following code in the Snippet parameter:
int1: Int = BindInput() int2: Int = BindInput() abc: String = BindInput() BindOutput(int1,int2,abc)
Note
If you don’t bind the output or do anything with the input values, the graph will not displayed any logic because nothing is being done with the inputs.
-
Specify the input dictionaries. In the Invocation section:
-
Under Input1 Bindings, turn on Dictionary To Bind, and specify the dictionary connected to the 1st input of the APEX Script SOP, in our case,
parms1
(from the Attribute Adjust Dictionary SOP’s Attribute Name parameter). -
Under Input2 Bindings, turn on Dictionary To Bind, and specify the dictionary connected to the 2nd input of the APEX Script SOP, in our case,
parms2
.
The APEX Script SOP looks in the input dictionaries,
parms1
andparms2
, and tries to find entries with keysint1
,int2
, andabc
. For keys that exist in multiple dictionaries, the latter dictionary entry is used. In our case, both dictionaries have an entry with keyabc
, so theabc
entry from the 2nd dictionary is used. In addition, a warning is raised on the APEX Script SOP. -
-
By default, the first entry of Output Dictionary Bindings is populated with the following:
-
Apex Outputs Group is the set to
output
. This is the name of the output node in the graph. -
Output Attrib is set to
parms
. We can set this to any name for the output dictionary.
To view the graph outputs in the geometry spreadsheet:
-
In the geometry spreadsheet, beside the node name, select Invoked from the drop-down menu.
-
Click Detail on the top toolbar. The graph outputs are stored in the dictionary specified in Output Attrib, in our case,
parms
:parms: {"abc": "hi again", "int1": 2, "int2": 4 }
-
Binding multiple outputs ¶
We can set the graph to have multiple input and output nodes by using BindInput
and BindOutput
functions with the special function argument __name
to name nodes:
-
In an APEX Script SOP, we have the following code in the Snippet parameter:
# Default 'parms' name used f = BindInput(3.1) # 2nd input node is named 'input2' s: String = BindInput('abc', __name='input2') # Default 'output' name used BindOutput(f) # 2nd output node is named 'output2' BindOutput(s, __name='output2')
-
By default, the first entry of Output Dictionary Bindings is filled out with the following:
-
Apex Outputs Group is the set to
output
. This is the name of the output node in the graph. -
Output Attrib is set to
parms
. We can set this to any name for the output dictionary.
To view the graph outputs in the geometry spreadsheet:
-
In the geometry spreadsheet, beside the node name, select Invoked from the drop-down menu.
-
Click Detail on the top toolbar. Only the
parms
dictionary is displayed because we have only told the APEX Script SOP to output the values of theoutput
node.
-
-
To also output the
output2
node values:-
Click beside Output Dictionary Bindings.
-
For the new entry, set Apex Outputs Group to
output2
, and Output Attrib to, say,parms2
.
On the geometry spreadsheet, there are now two output dictionaries displayed,
parms
andparms2
:parms: {"f": 3.1 } parms2: {"s": "abc" }
-
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:
<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 lookat 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 an APEX Script SOP:
-
In the APEX Script SOP, use the predefined headers to automatically add the APEX Script commands that 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 APEX Script code to create the same logic as the previous example is much more convoluted. In this case, the only way to add functionality to an incoming rig graph would be 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.
Below is the same logic as the previous example without using a function as a template:
# 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:
|