Interacting with an articulation#

This runtime example complements Assets. It uses an existing robot configuration; see Robot and articulation configuration to create one and Choosing shared and backend-specific settings to adapt its spawn properties to PhysX or Newton.

This tutorial shows how to interact with an articulated robot in the simulation. It is a continuation of the Interacting with a rigid object tutorial, where we learned how to interact with a rigid object. On top of setting the root state, we will see how to set the joint state and apply commands to the articulated robot.

The Code#

The tutorial corresponds to the run_articulation.py script in the scripts/tutorials/01_assets directory.

Code for run_articulation.py
  1# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
  2# All rights reserved.
  3#
  4# SPDX-License-Identifier: BSD-3-Clause
  5
  6"""This script demonstrates how to spawn a cart-pole and interact with it.
  7
  8.. code-block:: bash
  9
 10    # Usage
 11    uv run python scripts/tutorials/01_assets/run_articulation.py
 12
 13"""
 14
 15"""Parse the command-line arguments first."""
 16
 17
 18import argparse
 19
 20from isaaclab.app import add_launcher_args, launch_simulation
 21from isaaclab.utils import clone
 22
 23# add argparse arguments
 24parser = argparse.ArgumentParser(description="Tutorial on spawning and interacting with an articulation.")
 25# append simulation launcher cli args
 26add_launcher_args(parser)
 27# parse the arguments
 28args_cli = parser.parse_args()
 29
 30"""Rest everything follows."""
 31
 32import torch
 33
 34import isaaclab.sim as sim_utils
 35from isaaclab.assets import Articulation
 36from isaaclab.sim import SimulationContext
 37
 38##
 39# Pre-defined configs
 40##
 41from isaaclab_assets import CARTPOLE_CFG  # isort:skip
 42
 43
 44def design_scene() -> tuple[dict, list[list[float]]]:
 45    """Designs the scene."""
 46    # Ground-plane
 47    cfg = sim_utils.GroundPlaneCfg()
 48    cfg.func("/World/defaultGroundPlane", cfg)
 49    # Lights
 50    cfg = sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
 51    cfg.func("/World/Light", cfg)
 52
 53    # Create separate groups called "Origin1", "Origin2"
 54    # Each group will have a robot in it
 55    origins = [[0.0, 0.0, 0.0], [-1.0, 0.0, 0.0]]
 56    # Origin 1
 57    sim_utils.create_prim("/World/Origin1", "Xform", translation=origins[0])
 58    # Origin 2
 59    sim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1])
 60
 61    # Articulation
 62    cartpole_cfg = clone(CARTPOLE_CFG)
 63    cartpole_cfg.prim_path = "/World/Origin.*/Robot"
 64    cartpole = Articulation(cfg=cartpole_cfg)
 65
 66    # return the scene information
 67    scene_entities = {"cartpole": cartpole}
 68    return scene_entities, origins
 69
 70
 71def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articulation], origins: torch.Tensor):
 72    """Runs the simulation loop."""
 73    # Extract scene entities
 74    # note: we only do this here for readability. In general, it is better to access the entities directly from
 75    #   the dictionary. This dictionary is replaced by the InteractiveScene class in the next tutorial.
 76    robot = entities["cartpole"]
 77    # Define simulation stepping
 78    sim_dt = sim.get_physics_dt()
 79    count = 0
 80    # Simulation loop
 81    while sim.is_running():
 82        # Reset
 83        if count % 500 == 0:
 84            # reset counter
 85            count = 0
 86            # reset the scene entities
 87            # root state
 88            # we offset the root state by the origin since the states are written in simulation world frame
 89            # if this is not done, then the robots will be spawned at the (0, 0, 0) of the simulation world
 90            root_pose = robot.data.default_root_pose.torch.clone()
 91            root_pose[:, :3] += origins
 92            robot.write_root_pose_to_sim_index(root_pose=root_pose)
 93            root_vel = robot.data.default_root_vel.torch.clone()
 94            robot.write_root_velocity_to_sim_index(root_velocity=root_vel)
 95            # set joint positions with some noise
 96            joint_pos, joint_vel = (
 97                robot.data.default_joint_pos.torch.clone(),
 98                robot.data.default_joint_vel.torch.clone(),
 99            )
100            joint_pos += torch.rand_like(joint_pos) * 0.1
101            robot.write_joint_position_to_sim_index(position=joint_pos)
102            robot.write_joint_velocity_to_sim_index(velocity=joint_vel)
103            # clear internal buffers
104            robot.reset()
105            print("[INFO]: Resetting robot state...")
106        # Apply random action
107        # -- generate random joint efforts
108        efforts = torch.randn_like(robot.data.joint_pos.torch) * 5.0
109        # -- apply action to the robot
110        robot.actuators.target_command.set_effort_index(value=efforts)
111        # -- write data to sim
112        robot.write_data_to_sim()
113        # Perform step
114        sim.step()
115        # Increment counter
116        count += 1
117        # Update buffers
118        robot.update(sim_dt)
119
120
121def main():
122    """Main function."""
123    # Configure the simulation
124    sim_cfg = sim_utils.SimulationCfg(device=args_cli.device)
125    # Launch the simulator runtime that the configuration needs
126    with launch_simulation(sim_cfg, args_cli):
127        # Initialize the simulation context
128        sim = SimulationContext(sim_cfg)
129        # Set main camera
130        sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0])
131        # Design scene
132        scene_entities, scene_origins = design_scene()
133        scene_origins = torch.tensor(scene_origins, device=sim.device)
134        # Play the simulator
135        sim.reset()
136        # Now we are ready!
137        print("[INFO]: Setup complete...")
138        # Run the simulator
139        run_simulator(sim, scene_entities, scene_origins)
140
141
142if __name__ == "__main__":
143    # run the main function
144    main()

The Code Explained#

Designing the scene#

Similar to the previous tutorial, we populate the scene with a ground plane and a distant light. Instead of spawning rigid objects, we now spawn a cart-pole articulation from its USD file. The cart-pole is a simple robot consisting of a cart and a pole attached to it. The cart is free to move along the x-axis, and the pole is free to rotate about the cart. The USD file for the cart-pole contains the robot’s geometry, joints, and other physical properties.

For the cart-pole, we use its pre-defined configuration object, which is an instance of the assets.ArticulationCfg class. This class contains information about the articulation’s spawning strategy, default initial state, actuator models for different joints, and other meta-information. A deeper-dive into how to create this configuration object is provided in the Robot and articulation configuration tutorial.

As seen in the previous tutorial, we can spawn the articulation into the scene in a similar fashion by creating an instance of the assets.Articulation class by passing the configuration object to its constructor.

    # Create separate groups called "Origin1", "Origin2"
    # Each group will have a robot in it
    origins = [[0.0, 0.0, 0.0], [-1.0, 0.0, 0.0]]
    # Origin 1
    sim_utils.create_prim("/World/Origin1", "Xform", translation=origins[0])
    # Origin 2
    sim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1])

    # Articulation
    cartpole_cfg = clone(CARTPOLE_CFG)
    cartpole_cfg.prim_path = "/World/Origin.*/Robot"
    cartpole = Articulation(cfg=cartpole_cfg)

Running the simulation loop#

Continuing from the previous tutorial, we reset the simulation at regular intervals, set commands to the articulation, step the simulation, and update the articulation’s internal buffers.

Resetting the simulation#

Similar to a rigid object, an articulation also has a root state. This state corresponds to the root body in the articulation tree. On top of the root state, an articulation also has joint states. These states correspond to the joint positions and velocities.

To reset the articulation, we first set the root state by calling the Articulation.write_root_pose_to_sim() and Articulation.write_root_velocity_to_sim() methods. Similarly, we set the joint states by calling the Articulation.write_joint_state_to_sim() method. Finally, we call the Articulation.reset() method to reset any internal buffers and caches.

            # reset the scene entities
            # root state
            # we offset the root state by the origin since the states are written in simulation world frame
            # if this is not done, then the robots will be spawned at the (0, 0, 0) of the simulation world
            root_pose = robot.data.default_root_pose.torch.clone()
            root_pose[:, :3] += origins
            robot.write_root_pose_to_sim_index(root_pose=root_pose)
            root_vel = robot.data.default_root_vel.torch.clone()
            robot.write_root_velocity_to_sim_index(root_velocity=root_vel)
            # set joint positions with some noise
            joint_pos, joint_vel = (
                robot.data.default_joint_pos.torch.clone(),
                robot.data.default_joint_vel.torch.clone(),
            )
            joint_pos += torch.rand_like(joint_pos) * 0.1
            robot.write_joint_position_to_sim_index(position=joint_pos)
            robot.write_joint_velocity_to_sim_index(velocity=joint_vel)
            # clear internal buffers
            robot.reset()

Stepping the simulation#

Applying commands to the articulation involves two steps:

  1. Setting actuator commands: This provides desired position, velocity, or effort values to the actuator models in joint-side coordinates.

  2. Writing the data to the simulation: Based on the articulation’s configuration, this step handles any actuation conversions and writes the converted values to the simulation buffers.

In this tutorial, we control the articulation using joint effort commands. For this to work, we need to set the articulation’s stiffness and damping parameters to zero. This is done a-priori inside the cart-pole’s pre-defined configuration object.

At every step, we randomly sample joint efforts and set them on the articulation’s actuator collection by calling robot.actuators.target_command.set_effort_index. After setting the commands, we call the Articulation.write_data_to_sim() method to write the data to the simulation buffers. Finally, we step the simulation.

        # Apply random action
        # -- generate random joint efforts
        efforts = torch.randn_like(robot.data.joint_pos.torch) * 5.0
        # -- apply action to the robot
        robot.actuators.target_command.set_effort_index(value=efforts)
        # -- write data to sim
        robot.write_data_to_sim()

Updating the state#

Every articulation class contains a assets.ArticulationData object. This stores the state of the articulation. To update the state inside the buffer, we call the assets.Articulation.update() method.

        # Update buffers
        robot.update(sim_dt)

The Code Execution#

This script uses Isaac Sim PhysX and requires Isaac Sim. The commands below display it with the Isaac Sim viewport shown below:

uv run isaaclab -p scripts/tutorials/01_assets/run_articulation.py --viz kit
./isaaclab.sh -p scripts/tutorials/01_assets/run_articulation.py --viz kit

This command should open a stage with a ground plane, lights, and two cart-poles that are moving around randomly. Press Ctrl+C in the terminal to stop the simulation.

result of run_articulation.py

In this tutorial, we learned how to create and interact with a simple articulation. We saw how to set the state of an articulation (its root and joint state) and how to apply commands to it. We also saw how to update its buffers to read the latest state from the simulation.

The packaged Zoo demo also animates several robot families in one scene:

uv run --extra isaacsim isaaclab demo zoo --viz kit
./isaaclab.sh demo zoo --viz kit