Houdini 20.5 Character KineFX

APEX Script language reference

On this page
This feature is still under development. The current functionality is unfinished and subject to change, and may have thin or no documentation. Please bear this in mind when using it.

This page describes the syntax and language elements of APEX Script, including the provided types, operations, and statements.

Syntax

The APEX Script syntax borrows heavily from Python with a main difference being that variable types in APEX Script are statically typed, that is, once a variable is defined to be of a particular type, the variable type cannot be changed.

Indentation indicates a block of code. If you don’t indent the line after a for or if statement, or if you indent a line outside a for or if block, APEX Script will raise an error.

Commented lines begin with a #. Any text that follows a # is commented out. To comment a block of code, select the block of code in the Snippet parameter and press ⌃ Ctrl + /.

Variables

Variables are containers for storing data values, and are created when you assign a value to it. Variable names can consist of uppercase and lowercase letters, digits, and underscores (_), but cannot start with a digit. Variable names are case-sensitive.

# Create a Value<Float> node and initialize it to 1.5
f = 1.5
Value node created from initializing a variable

Data types

Variables can store different types of data. The data types that are available in APEX Script are listed in the Null node doc, and can be initialized using <type>(), for example, Float(), String(), and Dict().

# Create a float with a default value of 0
f1 = Float()

# Create a float and initialize it to 1.5
f2 = Float(1.5)

# Create a graph handle object
g = ApexGraphHandle()

If the type of a variable is not explicitly specified, it is inferred by the value that is first assigned to it.

# x is a float
x = 1.5

Once a variable is initialized with a particular type, its type cannot be changed.

x = 3

# Trying to change type raises an error
x = 'string'

Certain data types can be converted to other types. See Convert for information on which types can be converted other types:

# Float
a = 1.5

# Convert float to int
b = Int(a)

# Convert int to bool
c = Bool(b)

Annotation

Python-style annotation can be used to indicate that a variable or function argument is of a specific type. Annotation is important for defining dictionaries, function arguments, and specifying graph input types. Annotations have the following syntax:

<argument>: <annotation>

An annotated variable is uninitialized until it has been explicitly initialized, therefore an annotated variable cannot be used until it has been initialized:

# Uninitialized variable
a: Float

# Initialize another variable
b = Float(1)

# Error because 'a' is not initialized
b = a + b

Annotating and initializing a variable:

# Annotate and initialize variable
a: Float = 1.5

# Initialize another variable
b = Float(1)

# This works
b = a + b

Operators

Operators are used to perform operations on values and variables.

Arithmetic operators

Arithmetic operators are used to perform mathematical operations.

Operation

Operator

Example

Addition

+

x = 1 + 2

Subtraction

-

x = 2 - 1

Multiplication

*

x = 1 * 2

Division

/

x = 2 / 1

Exponentiation

**

x = 2 ** 3

Assignment operators

Assignment operators are used to assign values to variables.

Operation

Operator

Example

Addition

+=

# x = x + 2
x += 2

Subtraction

-=

# x = x - 2
x -= 2

Multiplication

*=

# x = x * 2
x *= 2

Division

/=

# x = x / 2
x /= 2

Exponentiation

**=

# x = x ** 2
x **= 2

Comparison operators

Comparison operators are used to compare two values. The result of a comparison operation is a boolean (True or False)

Operation

Operator

Example

Equal

==

x == 3

Equal using is

is

x is 3

Not equal

!=

x != 3

Not equal using is not

is not

x is not 3

Greater than

>

x > 3

Less than

<

x < 3

Greater than or equal to

>

x >= 3

Less than or equal to

<

x <= 3

Logical operators

Logical operators are used to check if certain conditions are true.

Operation

Operator

Description

Example

And

and

Returns True if both conditions are true.

x == 3 and y == 2

Or

or

Returns True if either condition is true.

x == 3 or y == 2

Not

not

Returns True if the condition is not true.

not(x == 3)

Membership operators

Membership operators are used to check whether a value is in a string or array.

Operator

Description

Example

in

Returns True if the value is in the string or array.

x in a

not in

Returns True if the value is not in the string or array.

x not in a

Operator precedence

The precedence of the different operators is listed below, starting with the highest precedence operator:

  • Parentheses

  • Exponentiation

  • Multiplication, division

  • Addition, subtraction

  • Comparison, membership

  • Logical NOT

  • Logical AND

  • Logical OR

Strings

Strings can be specified with single or double quotes.

# Empty string
s1 = ''

# Create and initialize strings
s2 = 'hello_world'
s3 = String("hello_world")

Surround a string that spans multiple lines with triple quotes (''' or """).

multi_s = """This is the longest string
in the world."""

Concatenate strings with a +:

a = 'Hello'
b = 'world'
c = a + ' ' + b

The function len() returns the number of characters in a string:

s = 'hello world'

# s_len = 11
s_len = len(s)

Python-style string formatting is supported in APEX Script. To perform string formatting, put an f in front of the string, and use curly brackets {} to specify the formatting to perform on the string. See string::Format for information on the formatting specification.

s = 'world'
a = 1.2
b = 2.3

# s2 = "hello world +3.50"
s2 = f"hello {s} {a+b:+.2f}"

See the string::* nodes for the string operations that are available in APEX Script.

Arrays

Arrays are used to store multiple items of the same type in a single variable. Arrays are defined using square brackets []. Elements in an array are indexed (the first element has index 0, the second element has index 1, etc.), and can be accessed by referencing its index number. Array elements can be any of the types listed as <type>Array in the Null node doc.

See the array::* nodes for the array operations that are available in APEX Script.

Create and initialize arrays:

# Empty arrays
a1 = IntArray()
a2: IntArray = []
a3: IntArray = IntArray()

# Initialize array with default values
a4 = IntArray(1,2,3)

f = 1.5
a5 = [f,f]

# Array of strings
s = 'hi'
arr_of_str = [s,'bbb','ccc']

# Array of vectors
v = (1,2,3)
arr_of_vec = [v,v]

The function len() returns the number of items in an array:

a = [2,4,6]

# a_len = 3
a_len = len(a)

Set and get array elements:

# Create array
a = ['x','y','z']

# Set array elements
a[0] = 'aaa'
a[1] = apex.string.fromInteger(3)

# Get array element
elem = a[2]

A negative index gets the element starting from the end of the array:

# Create array
a = ['x','y','z']

# neg = 'z'
neg = a[-1]

Use expressions to specify array indices:

a = IntArray(1,2,3)
x = 5

# a = [3,2,3]
a[Int(0.5)] = a[Int(x-3)]

# a = [0,2,3]
a[Int(x-5)] = 0

Nested indices:

arr1 = [0,1,2]
arr2 = [5,10,15]

# arr_elem = 15
arr_elem = arr2[arr1[2]]

Working with dictionary arrays:

# Create array of dictionaries
dict1 = {'test': 1.0, 'c': 5}
dict2 = {'alpha': 'aaa', 'beta': 'bbb'}
dictarray = [dict1, dict2]

# Replace dictionary at first array element
dictarray[0] = {'x': 'hello_world'}

Vectors

Vectors store multiple items of the same type in a single variable, where the items in a vector are not indexed. In APEX Script, vectors can have a length of 2, 3, or 4. Vectors are defined using parentheses (), and the number of elements within the parentheses determine whether the vector is of type Vector2, Vector3, or Vector4.

Initialize vectors using Vector3 as an example:

# Create a zero vector
v1 = Vector3()

# Create a vector with all elements initialized to 5
v2 = Vector3(5)

# Create and initialize vectors
v3 = (1,2,3)
v4 = Vector3(1,2,3)

# Initialize a vector with variables
a = Float(1)
v5 = Vector3(a*1,a+1,3)

Matrices

A matrix is essentially a 2-dimensional vector, where all the components in a matrix are of the same type. Matrices are defined using parentheses (), where the number of elements within the parentheses determine whether the matrix is of type Matrix3 (3×3 matrix with 9 elements) or Matrix4 (4×4 matrix with 16 elements).

Taking Matrix3 as an example, a matrix could also be seen as a collection of 3 Vector3's, or a “vector” with 9 components.

Initialize matrices using Matrix3 as an example:

# Create a zero matrix
m1 = Matrix3()

# Create a matrix with diagonal values set to 2
m2 = Matrix3(2)

# Initialize matrices
m3 = (1,2,3,4,5,6,7,8,9)
m4 = Matrix3(1,2,3,4,5,6,7,8,9) 
m5 = Matrix3((1,2,3),(4,5,6),(7,8,9))

# Initialize matrices with variables
v = (1,2,3)
m6 = (v,(4,5,6),(7,8,9))

a = Float(1)
m7 = (a,2,3,4,5,6,7,8,9)
m8 = Matrix3(a+1,a+2,a+3,4,5,6,7,8,9)

Dictionaries

Dictionaries are used to store data in key-value pairs. They are defined with curly brackets {} that contain a list of <key>:<value> entries. The key name must be a string type, and the value can be any type. Dictionaries cannot have different entries with the same key.

See the dict::* nodes for the dictionary operations that are available in APEX Script.

Create dictionaries (these dictionaries are used in subsequent examples):

# Empty dictionaries
d1 = Dict()
d2: Dict = {}
d3: Dict = Dict()

dict = {
    'key1': 1.0, 
    'key2': 'hello_world'
}

# Nested dictionary
dict_nested = {
    'test1': 2, 
    'test2': [1,2,3],
    'test3': {
        '3a': 1.5,
        'say': 'hello_world'
    },
    'd_array': DictArray(
        {'a': 1}
    )
}

The function len() returns the number of entries in a dictionary:

d = {
    'a': 1,
    'b': 3.5,
    'c': 'test'
}

# d_len = 3
d_len = len(d)

Dictionary entries are referenced by their key name within square brackets []. To add items into a dictionary:

# Add constant to a dictionary
dict['key3'] = 5.0

# Add to dictionary from a variable
dict['key4'] = dict_nested

# Add array element to a dictionary
test_tags = ['tag1', 'tag2', 'tag3']

dict['tag'] = test_tags[0]

# Add another dictionary to a dictionary
dict['d_test'] = {
    'x': 10,
    'y': 20
}

# Add entry into a nested dictionary
dict_nested['test3']['x'] = 'testing'
dict_nested['d_array'][0]['b'] = 2

Set a new value in an existing dictionary entry:

dict_nested['test3']['say']: String = 'hello_again'

Dictionary values can be any type, so type annotation must be used when initializing a variable from a dictionary entry:

s: String = dict['key2']
f: Float = dict_nested['test3']['3a']

Examples with annotation:

# Create a dictionary with 2 key-value pairs
d1 = {'key1': Geometry(), 'key2': 1.2}

# Create another dictionary
d2 = {'a': 2.2}

# Add entry to the 2nd dictionary
d2['b']: Float = d1['key2']

Conditionals

APEX Script supports conditionals with the use of the if statement combined with the comparison, logical, or membership operators. The elif (else if) and else parts of the if statement are optional. The indented lines after the if statement is the block of code that is executed within the conditional.

The syntax for the if statement is:

if <if_condition>:
    <statement1>
elif <else_if_condition>:
    <statement2>
else:
    <statement3>
  • <statement1> is executed if <if_condition> is true.

  • <statement2> is executed if <if_condition> is false and <else_if_condition> is true.

  • <statement3> is executed if <if_condition> and <else_if_condition> are both false.

If statement with the comparison operators:

x = 0

if x < 0:
    test = 'aaa'
elif x > 0:
    test = 'eee'
else:
    test = 'ccc'

If statement with the membership operator:

test_tags = ['aaa', 'bbb', 'ccc']
test = 'ccc'

# Check if array contains an item
if test in test_tags:
    test_tags.append('repeated_tag')

If statement checks if a dictionary entry exists:

d = {'a': 3}

if d['a']:
    x = 1

If statement with the length function, len():

s = 'hello'

if len(s):
    x = 1

Nested if statement:

x = 10

if x >= 0:
    a = 'non-negative'

    if x > 0:
        b = 'positive'

Loops

APEX Script supports the use of for loops to iterate over the elements of an array or a range of ints. The indented lines after the for statement is the block of code that is executed.

The syntax for the for loop is:

for <element> in [<array>]:

h = 'hello'

for x in ['world', 'again']:
    h = h + ' ' + x

# Result after for loop: h = 'hello world again'

You can specify the for loop to iterate over the block of code a specific number of times using the range keyword and the number of iterations inside parentheses ():

for <element> in range(<int>):

y = 0

for x in range(3):
    y = y + 1

To get access to the index while iterating over the elements of an array, use the enumerate keyword:

for <index>, <element> in enumerate(<array>)

test_tags = ['aaa', 'bbb', 'ccc']

for t, test in enumerate(test_tags):
    test = (test + String(t))

# Result for the different iterations: aaa0, bbb1, ccc2

Nested for loop:

h = 'hello'
i: Int = 0

for x in range(3):
    for x in ['world', 'again']:
        h = h + ' ' + x
        i = i + 1

    i = i + 10

Loop over geometry points and primitives:

  • for <point_number> in geo.points()

  • for <primitive_number> in geo.prims()

geo = BindInput(Geometry())
transform = Matrix4()

for pt in geo.points():
    geo.transform(transform)

    for prim in geo.prims():
        geo.setPrimAttribValue(prim, 'name', 'test', valuetype=String)

Note

geo::SetPrimAttribValue<T> is an example of a templated function. See templated functions for information on how to specify these special types of functions.

KineFX

Overview

Preparing character elements

Rigging with APEX graphs

Building rig graphs with APEX Script

Rigging with rig components

Animating in the viewport

SOP-based animation

Deformation

Animation retargeting

Pre-H20

Panes

Appendix