Simstim

Unreal

Scripting Control Rig with Python in Unreal Engine 5.8: bones, controls and graph nodes

Build Control Rig hierarchies and graphs from Python in UE 5.8 with ControlRigBlueprint, RigHierarchyController and RigVMController.

· 5 min read · unreal · control rig · python · animation

If you have written rig builders in Maya or Houdini, the idea behind Control Rig's Python API will be familiar. You create the skeleton and controls from code, add graph nodes from code, and rebuild the rig whenever the template changes. Unreal's version is editor-only and splits the work between a few objects. This post maps those objects and gives short snippets for the common jobs.

Every class and method below was checked against the Unreal Python API reference for 5.8, which Epic labels Experimental. We ran the snippets in Unreal Engine 5.8.4 on Oct 27, 2026, in order in one editor session, and they worked as written. Treat them as a starting point, and read the Python Log (covered below) to confirm exact calls in your build.

The objects you work with

Epic's Control Rig Python page describes the system as Model-View-Controller. In Python terms:

  • unreal.ControlRigBlueprint is the asset. It derives from RigVMBlueprint, which is where the graph methods live.
  • unreal.RigVMController edits a graph. Epic's docs say the controller is "the sole authority to perform changes on the Graph." You get one from the blueprint with get_controller(graph) or get_controller_by_name(graph_name).
  • unreal.RigHierarchy holds bones, nulls, controls and curves. It is available as rig.hierarchy or rig.get_hierarchy(), and you read from it.
  • unreal.RigHierarchyController changes the hierarchy. Get it from rig.get_hierarchy_controller() or rig.hierarchy.get_controller().
  • unreal.RigElementKey identifies an element by type (a RigElementType such as BONE, NULL or CONTROL) and name. Most hierarchy calls take and return these keys.

The editor-side code is in the ControlRigDeveloper module, and Epic's examples load it first with unreal.load_module('ControlRigDeveloper').

Getting a rig

There are three ways in: load an existing asset, pick up rigs already open in the editor, or create one. ControlRigBlueprintFactory has two class methods for creating a rig, and both take an optional modular_rig flag.

import unreal

unreal.load_module('ControlRigDeveloper')

# Rigs currently open in Control Rig editors
open_rigs = unreal.ControlRigBlueprint.get_currently_open_rig_blueprints()

# A new, empty rig asset
rig = unreal.ControlRigBlueprintFactory.create_new_control_rig_asset(
    desired_package_path='/Game/Rigs/CR_Prop')

# Or a rig built from a skeletal mesh or skeleton:
# mesh = unreal.load_object(name='/Game/Chars/SK_Hero.SK_Hero', outer=None)
# rig = unreal.ControlRigBlueprintFactory.create_control_rig_from_skeletal_mesh_or_skeleton(
#     selected_object=mesh)

If /Game/Rigs/CR_Prop already exists, create_new_control_rig_asset doesn't fail: in our run it made /Game/Rigs/CR_Prop1 instead. To attach a preview mesh, use rig.set_preview_mesh(preview_mesh=mesh).

Building the hierarchy

RigHierarchyController.add_bone takes a name, a parent key, a transform and transform_in_global (default True), and it returns the new key. To add a bone with no parent, pass an empty RigElementKey(), as Epic's own example does. add_null has the same shape. For an existing skeleton, import_bones, import_bones_from_asset and import_bones_from_skeletal_mesh bring in bones in bulk.

hc = rig.get_hierarchy_controller()

root = hc.add_bone(name='root', parent=unreal.RigElementKey(),
                   transform=unreal.Transform())
lid = hc.add_bone(name='lid', parent=root,
                  transform=unreal.Transform(location=[0, 0, 10]))

A control also needs a RigControlSettings and an initial value. The settings carry animation_type (for example RigControlAnimationType.ANIMATION_CONTROL, which the docs describe as "a visible, animatable control") and control_type, such as RigControlType.EULER_TRANSFORM, FLOAT or BOOL. You build the value with one of the RigHierarchy.make_control_value_from_* class methods.

settings = unreal.RigControlSettings()
settings.set_editor_property('animation_type',
                             unreal.RigControlAnimationType.ANIMATION_CONTROL)
settings.set_editor_property('control_type', unreal.RigControlType.EULER_TRANSFORM)
settings.set_editor_property('shape_visible', True)

value = unreal.RigHierarchy.make_control_value_from_euler_transform(
    unreal.EulerTransform())

lid_ctrl = hc.add_control(name='lid_ctrl', parent=unreal.RigElementKey(),
                          settings=settings, value=value)

Reading is done on the hierarchy. get_bones(), get_controls() and get_all_keys() return key arrays, get_global_transform(key, initial=False) returns a transform, and get_selected_keys() returns what the rigger has selected. The controller covers the rest of the editing: set_parent, add_parent (for multi-parent elements), rename_element, remove_element and set_control_settings.

Watch the undo defaults. add_bone and add_null default to setup_undo=False, while add_control and add_curve default to True. If you want a build to undo cleanly, set the flag explicitly.

Adding nodes to the graph

Graph nodes are added through a RigVMController. add_unit_node takes a script struct, a method name (default 'Execute'), a position and an optional node name. You get the struct with static_struct(), which every Python struct wrapper inherits.

graph = rig.get_default_model()
ctrl = rig.get_controller(graph)

ctrl.open_undo_bracket('Add float add')
node = ctrl.add_unit_node(
    script_struct=unreal.RigVMFunction_MathFloatAdd.static_struct(),
    method_name='Execute',
    position=unreal.Vector2D(0, 0),
    node_name='AddOffset')
ctrl.close_undo_bracket()

rig.recompile_vm()

The Control Rig scripting page uses unreal.RigUnit_MathFloatAdd for this example. The 5.8 API reference marks that name as deprecated and notes that it "was renamed to 'RigVMFunction_MathFloatAdd'." Expect the same rename for other math nodes in older snippets.

To wire nodes, use add_link(output_pin_path, input_pin_path) and break_link with the same arguments. Pin paths are strings. The easiest way to get the exact form is to make a link by hand and read it from the Python Log. Related calls include add_template_node, add_variable_node, add_comment_node and remove_node. On the blueprint, add_member_variable(name, cpp_type, ...) creates rig variables, and recompile_vm_if_required() and request_auto_vm_recompilation() are alternatives to a forced recompile.

Let the editor write the first draft

Two features save more time than any snippet.

The Control Rig Python Log. Epic's docs describe it as "a text log of all the actions that you performed within the Control Rig Editor," including the viewport, the hierarchy and the graph. To open it, choose Window > Message Log in the Control Rig editor and select Control Rig Python Log. You can copy lines with Ctrl+C and paste them into the Output Log in Python mode.

Python Context. Commands need to know which rig they target. Clicking Run Python Context in the Class Settings Details panel sets the current rig as that context.

For an existing rig there is also generate_python_commands(), which returns an array of strings. It exists on RigVMBlueprint, RigVMController and RigHierarchyController. The 5.8 reference does not say exactly what the output covers, so test it on a copy of a real rig before you build a pipeline on it.

Caveats for 5.8

  • The API is marked Experimental. Pin your scripts to an engine version and re-check signatures when you upgrade.
  • Some examples on the scripting page are out of date. Besides the renamed math unit, the page calls ControlRigBlueprint.get_available_rig_units() and request_control_rig_init(), and neither appears on the 5.8 class pages. The 5.8 reference has RigVMController.get_registered_unit_structs() and RigVMBlueprint.request_rig_vm_init(). Trust the class reference over the prose page.
  • Modular rigs have their own controller. get_modular_rig_controller() returns a ModularRigController, and the asset can switch between the standalone and module forms with turn_into_control_rig_module() and turn_into_standalone_rig().
  • Physics components go through the component API. Control Rig Physics stores its bodies and joints as hierarchy components, and RigHierarchyController.add_component exists. This post has not verified a physics setup from Python. For now, build physics in the graph with the RigPhysics nodes.

Further reading