On this page |
A function is a block of code that performs a specific operation. In APEX Script, users can create their own functions or use the built-in APEX functions.
User-defined functions ¶
User-defined functions in APEX Script are represented as subnet nodes in the APEX network view. The contents of the subnet node contains the function logic.
APEX Script code:
# Function definition def test(a: Int, b: Int): c: Int = a + b return c # Function call x = test(1, 2)
![](../../images/char/kinefx_apexscript_function.png)
Users can define their own functions in APEX Script similar to Python:
-
Functions are defined using the
def
keyword followed by the function name. -
Information, called arguments, can be passed into a function through a comma-separated list of values within a set of parentheses
()
. These arguments follow the function name. -
The block of code that is executed when the function is called must be indented.
-
Information can be returned by a function, and these return values are specified using the
return
statement. A function can return multiple values separated by commas. -
Functions must be defined before they are called.
Function with no arguments:
# Function definition def test(): a = 1.5 return a # Function call; result is x = 1.5 x = test()
The number of arguments in the function call must match the number of arguments in the function definition. It is good practice to annotation the function arguments:
# Function definition def test(a: Int, b: Int): c: Int = a + b return c # Function call x = test(1, 2)
You can call a function with keyword arguments, which are arguments with the syntax <key>=<value>
, where <key>
is the name of the argument. When calling a function using all keyword arguments, the order of the arguments does not matter.
Note
If you use both keyword and positional (non-keyword) arguments in a function call, keyword arguments must be placed after positional arguments.
# Function definition def test(a: Int, b: Int): c: Int = a + b return c # Function call with keyword arguments x = test(b=1, a=2)
Keyword arguments that contain __dot__
are replaced with '.
'. This allows APEX Script to handle rigs with geometry names like Base.skel
and Base.shp
:
# Function takes in Base.skel and returns Base.shp def test(Base__dot__skel: Geometry): Base__dot__shp = Geometry() return Base__dot__shp # Graph input is Base.skel geo = BindInput(Base__dot__skel=Geometry()) x: Geometry = test(geo)
![](../../images/char/kinefx_apexscript_functionriggeo.png)
A function can return multiple values, and you can specify the return types following the ->
after the argument list:
def test(a: Vector3, b: Float) -> tuple(Vector3, Float): a = a * b b = b + 1.0 return a, b x, y = test(a=(3.0, 3.0, 3.0), b=5.5)
If you want to ignore certain return values, assign those values to an underscore (_
):
def test(a: Int): a1: Int = a + 1 a2: Int = a + 2 a3: Int = a + 3 a4: Int = a + 4 return a1, a2, a3, a4 _, _, x, _ = test(1)
You can specify default values for the function arguments. If the function is called without arguments, the default values are used:
def foo(a: String = 'test', b: Int = Int(Float(1.5))): return a, b # Result: x = 'test', y = 1 x, y = foo()
Expressions in function calls:
def foo(a: String, b: Int): return a, b m = 3 n = 4 p = 'test' # Result: x = 'test3', y = 7 x, y = foo(p+String(m), m+n)
Functions can be nested inside other functions:
def foo(a: Int): return a+2 # Call foo within this function def bar(a: Int) -> Int: return foo(a)+4 # Result: x1 = 3, x2 = 7 x1 = foo(1) x2 = bar(1)
Functions can be daisy-chained:
geo.computeTransform().transform(xform=Matrix4())
Saving functions as subgraphs ¶
Functions can be saved to disk as subgraphs, which can then be used in APEX Script code as global functions. Any subgraphs that you have, including ones built by other users, could be used as functions in APEX Script.
To save a function as a subgraph:
-
In the APEX Script SOP, turn on the Subgraphs parameter.
-
In the Subgraphs section, define the function in the Subgraph snippet parameter, and put the
@subgraph
decorator above the function definition.For example:
@subgraph def test(a: Int, b: Int): c: Int = a + b return c
-
The subgraph must be saved as a
.bgeo
file in@/apexgraph
, where@
expands to the directories in the HOUDINI_PATH. If you don’t specify the directory, the subgraph will be saved to$HOME/apexgraph
. You can set the subgraph location and filename in the Geometry File parameter, or in the subgraph decorator argument,@subgraph(<file_path>)
. -
Click the Save Subgraphs button.
In the same APEX Script SOP, or in a new APEX Script SOP, you can now use the saved subgraph as a function when writing APEX Script code.
Built-in functions ¶
A library of built-in functions is available within APEX Script. These functions can be called using either the APEX namespace, or an object-oriented syntax where the function operates on a variable. The list of available APEX functions can be found in the APEX nodes index, the APEX network view TAB menu, or in the autocomplete feature when typing in the Snippet parameter of the APEX Script SOP.
Different syntax for calling functions:
geo = BindInput(Geometry()) # Call function using the APEX and SOP namespaces a = apex.sop.copytopoints(geo) # Object-oriented syntax for calling a function b = geo.copytopoints()
In the example below, the array that is passed into the append() function is an in-place port. When using the APEX namespace, the input array is updated:
x = ['aaa'] x = apex.array.append_String(x, 'bbb') # Result: x = ['aaa', 'bbb']
If the result of append() is assigned to a different variable, the original array is not updated:
x = ['aaa'] y = apex.array.append_String(x, 'bbb') # Result: x = ['aaa'], y = ['aaa', 'bbb']
When using the object-oriented syntax, the array is passed into the function as if it were the first input, and is always updated in-place:
x = ['aaa'] x.append_String('bbb') # Result: x = ['aaa', 'bbb']
If the object-oriented append() is assigned to a variable, the result is the 2nd returned value of append() (the index of the appended value):
x = ['aaa'] idx = x.append_String('bbb') # Result: x = ['aaa', 'bbb'], idx = 1
Call a function with arguments:
box = apex.sop.box(t=Vector3(1,1,1), scale=Float(2))
Functions without a namespace, for example Sine(), do not need to be prefixed with the APEX namespace:
# With APEX namespace a = apex.sine(1.0) # Without APEX namespace b = sine(1.0)
By default, APEX Script uses the exact version of the APEX function that is called, and not the latest version. For example, IntBitMaskToBool()
calls the first version of the function. If you want to use the second version of IntBitMaskToBool, call IntBitMaskToBool.v2_0()
. See the special function HoudiniVersion() for information on how to use a specific version of APEX functions.
Templated functions ¶
Templated functions are functions that operate on different types of data. For example, geo::SetPrimAttribValue<T> is a templated function, where <T> could be one of several different types. Templated functions can be specified in two ways:
Use the specific function name that contains the type:
geo.setPrimAttribValue_String(prim, 'name', 'test')
Use the valuetype
argument to specify the type, valuetype=<type>
:
geo.setPrimAttribValue(prim, 'name', 'test', valuetype=String)
Special functions ¶
APEX Script provides special functions that allow you to perform various graph operations, including the ability to add information to graph nodes, use a specific version of APEX nodes, and invoke graphs from within a graph.
Function |
Description |
||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
BeginMetadata, EndMetadata |
Sets metadata on the block begin and end nodes for loops and if blocks (for example, ForBegin, IfEnd). The available metadata are listed in the special function arguments. y = 0 for x in range(3): # Set metadata on ForBegin node BeginMetadata(__name='test_begin', __color=(1,0,1), __tags=['tag1', 'tag2']) y = y + 1 # Set metadata on ForEnd node EndMetadata(__name='test_end', __pos=(1,1,1), __tags=['out_tag']) |
||||||||||||||
BindInput, BindOutput |
Defines the inputs and outputs of a graph. See this example for how to use BindInput() and BindOutput(), and specifying graph inputs for example uses of BindInput(). |
||||||||||||||
HoudiniVersion |
Sets the version of the APEX functions to use. For example, You could also specify the Houdini version in the APEX Script SOP header template:
Two other arguments are available for use with the HoudiniVersion() function -
If HoudiniVersion() is not present, this is the same as calling |
||||||||||||||
InputMetadata, OutputMetadata |
Sets metadata on the graph input (parms) and output nodes inside a subnet. The available metadata are listed in the special function arguments. def test(a,b): # Set metadata on subnet input node InputMetadata(__name='abc', __pos=(1,0,0), __properties={'test': 1.5}) c: Float = a + b # Set metadata on subnet output node OutputMetadata(__color=(1,0,1), __tags=['out_tag1','out_tag2']) return c x = test(1,2) |
||||||||||||||
InvokeOutputs |
See invoke graph. |
||||||||||||||
SetGraph |
|||||||||||||||
StickyNote |
Creates sticky notes in the graph. The arguments listed below, in addition to the special function arguments, can be used to set certain properties on the sticky note:
Example:
|
||||||||||||||
|
Creates another identifier for a variable without making a copy. This could introduce graph sorting issues, so use with caution. Create an alias of a variable: x = 1.5 y = _portalias(x) # y = 1.5 BindOutput(y) Create an alias of a variable that is assigned to a function: def test(): x = 1 return x m = test() m_alias = _portalias(m) # m_alias = 1, n = 3 n = m_alias + 2 Use an alias in a function: def test(a: Int, b: Int): c: Int = a + b d = _portalias(c) d += 2 return d # m = 5 m = test(1, 2) |
Decorators ¶
A decorator performs an operation on another function, and is specified with the @
symbol above the function it operates on.
For example:
@subgraph @namespace('my_namespace') @safeguard_inputs(True) def test(a: Int, b: Int): c: Int = a + b return c
Decorator |
Description |
---|---|
subgraph |
The @subgraph decorator allows the function that follows it to be saved as a subgraph. The <file_path> argument is optional:
Example: @subgraph('$HIP/subgraph_test.bgeo') |
namespace |
You can specify a namespace for the function with the namespace decorator. Then after saving the function as a subgraph, you can call the function using
For example, you can define the following subgraph in the APEX Script SOP, Subgraphs section: @subgraph @namespace('my_namespace') def test(a: Int, b: Int): c: Int = a + b return c After saving the function as a subgraph, you can call the subgraph function with: x = my_namespace.test(1,2) |
safeguard_inputs |
The safeguard_inputs decorator helps to prevent graph sorting issues by making copies of function input arguments where necessary. The syntax for the safeguard_inputs decorator is:
When @safeguard_inputs is set to True, APEX Script makes a copy of a function input argument (adds a Value node to the graph) if:
If @safeguard_inputs is set to False, APEX Script does not create copies of any function arguments. This could introduce graph sorting issues, so use caution when setting @safeguard_inputs to False. If @safeguard_inputs is not included before a function, it is set to True by default. |
Special function arguments ¶
The special function arguments listed below can be used to specify certain information on a node.
Argument |
Type |
Description |
---|---|---|
|
String |
The name of the node. |
|
Vector3 |
The color of the node. |
|
Vector3 |
The position of the node in the graph. |
|
Dict |
The properties stored on the node. |
|
StringArray |
The tags stored on the node. |
Example:
def abc(a: Vector3, b: Float): a = a * b b = b + 1.0 return a, b # Subnet node name is set to the function name subnet_a = abc(a=(3.0, 3.0, 3.0), b=5.5) # Special function arguments used to set the name and color of the subnet node subnet_b = abc(a=(3.0, 3.0, 3.0), b=5.5, __name='test_b', __color=(1,0,0))
![](../../images/char/kinefx_apexscript_specialargs.png)
Function mappings ¶
APEX Script provides convenient name mappings for some of the built-in APEX functions.
Graph functions ¶
The examples in this section use the following definitions:
graph = ApexGraphHandle() # Add two TransformObject nodes to the graph a = graph.addNode('test_a', 'TransformObject') b = graph.addNode('test_b', 'TransformObject') # Dictionary d = {'xord': 3}
Function |
APEX Module Function |
Example |
---|---|---|
|
graph::AddNode |
|
|
graph::ConnectInput |
|
|
graph::FindAndConnectInput |
|
|
graph::GetConnectedPorts |
|
|
graph::ConnectInput |
|
|
graph::FindAndConnectInput |
|
|
graph::Compile |
If
|
|
graph::FindOrAddPort |
|
|
Value<ApexGraphHandle> |
“Saves” the graph in a Value node.
|
|
graph::NodeInputs |
|
|
graph::NodeOutputs |
|
|
graph::FindPort |
|
|
graph::FindNodes |
|
|
graph::FindPorts |
|
|
graph::FindPorts |
|
|
graph::PromoteInput |
|
|
graph::PromoteOutput |
|
|
graph::UpdateNodeParms |
|
Node functions ¶
The examples in this section use the following definitions:
graph = ApexGraphHandle() # TransformObject node 'test_a' a = graph.addNode('test_a', 'TransformObject') # Dictionaries d = {'abc': 3}
Function |
APEX Module Function |
Example |
---|---|---|
|
graphutils::NodeAncestors |
# Pack node into a subnet node_arr: ApexNodeIDArray = [a] graph.packSubnet('my_test', node_arr) # Returns 'my_test' anc = a.ancestors() |
|
graphutils::NodeCallbackName |
|
|
graph::NodeData |
|
|
graph::NodeInputs |
|
|
graph::NodeName |
|
|
graph::NodeOutputs |
|
|
graphutils::NodeParent |
|
|
graphutils::NodeParms |
|
|
graphutils::NodePath |
|
|
graph::FindPort |
|
|
graphutils::NodeProperties |
|
|
graph::UpdateNode |
|
|
graph::UpdateNode |
|
|
graph::UpdateNodeParms |
|
|
graph::UpdateNode |
|
|
graph::UpdateNodeProperties |
|
|
graph::UpdateNodeTags |
|
|
graphutils::NodeTags |
|
Port functions ¶
The examples in this section use the following definitions:
graph = ApexGraphHandle() # Add two TransformObject nodes to graph a = graph.addNode('test_a', 'TransformObject') b = graph.addNode('test_b', 'TransformObject') # Ports on node 'test_a' a_in = a.t_in a_out = a.xform_out # Ports on node 'test_b' b_in = b.parent_in b_out = b.t_out
Function |
APEX Module Function |
Example |
---|---|---|
|
graph::ConnectInput |
Connect ports on the same nesting level in a graph.
|
|
graph::GetConnectedPorts |
|
|
graph::AddWirePath |
Connects ports on different nesting levels in a graph, for example, connecting to a node in a subnet. The name of the connection port that is created on the subnet node is set to # Add a node to the graph c = graph.addNode('test_x', 'TransformObject') # Pack the node into a subnet node_arr: ApexNodeIDArray = [c] graph.packSubnet('test_subnet', node_arr) # Connect to node in subnet a_out.connectPath(c.xform_in, 'abc') |
|
graph::PortData |
|
|
graph::ConnectInput |
|
|
graph::PortNode |
|
|
graph::PromotePort |
|
|
graph::GetPromotedPort |
|
|
graph::PromoteInput |
|
|
graph::PromoteOutput |
|
|
graph::GetSubPort |
If you use subport() to create a subport on a node, you won’t see the new subport in the graph until you connect something to it. # Add nodes to graph c = graph.addNode('test_c', 'Add<Float>') d = graph.addNode('test_d', 'Add<Float>') # Create subport 'dd' d_in = d.b_in.subport('dd') # Connect to subport 'dd' c.result.connect(d_in) |
Data functions ¶
The examples in this section use the following definitions:
# Dictionaries d = {'abc': 3} # Vectors v1 = (1,2,3) v2 = (2,4,5) # Matrix m = Matrix4(2)
Function |
APEX Module Function |
Example |
---|---|---|
|
sop::kinefx::computetransform |
geo: Geometry = BindInput() geo.addJoint('joint_1', Matrix4(2)) geo.addJoint('joint_2', Matrix4(3)) x = geo.computetransform(mode=2) |
|
CrossProduct |
|
|
Distance |
|
|
Distance |
|
|
DotProduct |
|
|
Value<Dict>, Value<Geometry> |
“Saves” the dictionary or geometry in a Value node.
|
|
Invert<Matrix4> |
This function is performed in-place - the input matrix is updated, so there is no need to assign to an output variable.
|
|
transform::Dihedral<Matrix4> |
|
|
Normalize |
|
|
Normalize |
This function is performed in-place - the input vector is updated:
If normalized() is assigned to a variable, the variable is set to the length of the vector:
|
|
transform::SmoothRotation |
|
Content library example ¶
This example in the content library shows how to set up a sine wave control on an elephant’s trunk: