Set Up Actuators from Python#
Note
The isaacsim.core.experimental.actuators extension is experimental.
APIs may change between releases.
This tutorial walks through building Newton actuators in Python and attaching them to a Franka robot, without authoring any USD.
By the end of this tutorial you’ll know how to:
Build an
ActuatorConfigfrom the stocknewton.actuatorscontrollers, clamping stages, and delays.Construct an
ArticulationActuatorsfrom a list of Python configs.Drive an articulation to a position target through the actuator pipeline.
All code examples come from the complete, runnable file newton_actuators_python_example.py:
# Newton Actuators Python example
./python.sh standalone_examples/api/isaacsim.core.experimental.actuators/newton_actuators_python_example.py
Prerequisites
Read Newton Actuators for the high-level structure of a Newton actuator pipeline.
Building a stock PD actuator config#
ActuatorConfig is a thin bundle
of three components: a required Newton Controller plus an optional list of
Clamping stages and an optional Delay. The simplest case is a PD
controller with no clamping and no delay:
import warp as wp
from isaacsim.core.experimental.actuators import ActuatorConfig
from newton.actuators import ControllerPD
# Per-robot Warp arrays. Even a single-robot scene must size them with
# n_robots = len(Articulation(paths)).
pd_config = ActuatorConfig(
controller=ControllerPD(
kp=wp.array([kp] * n_robots, dtype=wp.float32),
kd=wp.array([kd] * n_robots, dtype=wp.float32),
)
)
Note
The arrays are sized for n_robots, not for the number of DOFs. When the
articulation path matches N robot instances, the same actuator is fanned
out across all N; the array entries are the per-instance gains for that
one actuator.
Adding clamping and delay#
Most real actuators need at least an effort limit. Append clamping stages
to ActuatorConfig.clamping and optionally add a Delay for
command-input latency:
import warp as wp
from isaacsim.core.experimental.actuators import ActuatorConfig
from newton.actuators import ClampingMaxEffort, ControllerPD, Delay
config = ActuatorConfig(
controller=ControllerPD(
kp=wp.array([kp] * n_robots, dtype=wp.float32),
kd=wp.array([kd] * n_robots, dtype=wp.float32),
),
clamping=[
ClampingMaxEffort(max_effort=wp.array([max_effort] * n_robots, dtype=wp.float32)),
],
delay=Delay(
delay_steps=wp.array([2] * n_robots, dtype=wp.int32),
max_delay=2,
),
)
Three clamping types ship with Newton:
ClampingMaxEffort— symmetric saturation to[-max_effort, +max_effort].ClampingDCMotor— linear torque-speed envelope for DC motors.ClampingPositionBased— joint-position-dependent effort lookup table.
Constructing ArticulationActuators#
Use from_actuators()
to skip USD discovery and supply the configs directly. Each config is paired
with the DOF name (the leaf of the joint’s USD path) it should drive:
from isaacsim.core.experimental.actuators import ArticulationActuators
from isaacsim.core.experimental.prims import Articulation
# FRANKA_PRIM_PATH = "/World/Franka"
# ARM_JOINTS = ["panda_joint1", "panda_joint2", ..., "panda_joint7"]
# Single Franka instance, so n_robots = 1.
n_robots = len(Articulation(FRANKA_PRIM_PATH))
actuators = [
(build_pd_actuator_config(n_robots, kp, kd), name) for name, kp, kd in zip(ARM_JOINTS, KP_GAINS, KD_GAINS)
]
actuated = ArticulationActuators.from_actuators(
FRANKA_PRIM_PATH,
actuators=actuators,
)
# A small armature on every DOF improves numerical stability when joints
# are driven externally (the Newton-actuator effort can excite high-frequency
# modes that the implicit USD drive would normally damp). Real motors and
# gearboxes also carry rotor inertia, so a non-zero armature is closer to
# physical reality regardless of stability concerns.
actuated.articulation.set_dof_armatures(0.1)
If the same DOF name appears twice, or the name does not exist on the
articulation, from_actuators raises ValueError with a list of the
DOFs available on the articulation.
Note
The construction snippet ends with a call to
set_dof_armatures().
An armature is virtual rotor inertia that many physics engines add to joints
to improve numerical stability. For externally-driven joints
the Newton-actuator effort can excite high-frequency dynamics that the
implicit USD drive normally damps for you. Real motors and gearboxes
also carry significant rotor inertia, so a non-zero armature is closer
to physical reality regardless of stability concerns. If you observe high-frequency instability,
increasing the armature value may help.
Trying the non-ideal actuators#
The companion script supports a --non-ideal flag that swaps the simple
PD configs for the PD + per-joint effort clamp + 2-step input delay variant
from the previous section:
./python.sh standalone_examples/api/isaacsim.core.experimental.actuators/newton_actuators_python_example.py --non-ideal
Running with this flag is a useful sanity check: the same robot still tracks the position target, but with saturated effort and delayed commands — closer to real hardware behavior.
Driving the robot#
By default, ArticulationActuators registers a callback that runs
immediately before every physics step. On each call the callback reads
the current joint state and target, evaluates each actuator’s
step pipeline, and writes the resulting joint
effort to the articulation. You don’t drive this loop yourself; once the
ArticulationActuators wrapper is constructed, the actuators are live.
To send commands, write to the underlying
Articulation exactly as you would
without actuators; the callback picks the targets up on the next tick:
articulation = actuated.articulation
timeline = omni.timeline.get_timeline_interface()
timeline.play()
simulation_app.update()
# Send a per-DOF position target. The PD controller reads it each tick.
# Values match the Franka robot descriptor's default_q (home pose).
target_positions = [0.8, -1.3, 0.0, -2.87, 0.0, 2.0, 0.75]
arm_dof_indices = articulation.get_dof_indices(ARM_JOINTS)
articulation.set_dof_position_targets(
positions=target_positions,
dof_indices=arm_dof_indices,
)
for _ in range(num_steps): # ~4 seconds at 60 Hz
simulation_app.update()
The Franka converges to the home pose under the Newton PD actuators, then has a feedforward applied at one joint.#
You can also send a feedforward effort that is added on top of the controller
output via
set_dof_feedforward_effort_targets().
With kp and kd set to zero this becomes a pure open-loop torque drive:
# Feedforward effort is added on top of the controller output every tick.
# With kp = kd = 0 it becomes the entire output — a pure open-loop torque drive.
feedforward_efforts = [50.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
actuated.set_dof_feedforward_effort_targets(
feedforward_efforts,
dof_indices=arm_dof_indices,
)
Note
You don’t have to attach an explicit actuator to every joint. In this
tutorial the seven arm joints get explicit Newton actuators while the two
finger joints do not; those finger joints keep their authored
UsdPhysics.DriveAPI stiffness and damping and behave exactly as they
would on a stock Franka.
Note
The method
set_dof_feedforward_effort_targets()
only affects joints that have an explicit actuator. A feedforward value
written to a DOF without an explicit actuator has no effect.
When you’re done, tear the wrapper down. ArticulationActuators is a
context manager; __exit__ calls actuated.close(), which deregisters
all SimulationManager callbacks owned by the instance. The recommended
idiom is to construct the wrapper directly inside the with statement so
its lifetime is bounded by the block (construct below is whichever
factory you wrote — for example
from_actuators()
or the construct_articulation_actuators helper from earlier in this
tutorial):
# Recommended teardown pattern: construct ``ArticulationActuators`` inside
# the ``with`` statement so the wrapper's lifetime is bounded by the block.
# ``__exit__`` calls ``actuated.close()``, which deregisters every
# ``SimulationManager`` lifecycle callback owned by the instance and is
# guaranteed to run even if the body raises.
with construct() as actuated:
drive_to_target(actuated)
If a with block isn’t convenient — for example, when the construction
and use happen in different scopes — call actuated.close() explicitly
when finished. Either path unhooks the pre-physics callback so the wrapper
stops updating actuator efforts each tick; the articulation itself is
unaffected and continues to exist on the stage.
Note
The built-in controllers, clampings, and delays cover almost every case.
If you do need something custom, ActuatorConfig accepts any user-written
subclass of newton.actuators.Controller, Clamping, or Delay —
refer to the Newton documentation for the base-class contracts. Custom
subclasses cannot be authored in USD; attach them via
from_actuators().
What’s next#
Author and Parse Actuators from USD — bake an actuator configuration into the robot USD so it is rebuilt automatically on load and travels with the asset between Isaac Sim and Isaac Lab.
Drive an Actuated Robot from OmniGraph — drive an actuated robot from OmniGraph.