Simulating a Robot#
This tutorial shows you how to create and simulate a robot using SimulationManager. You’ll learn how to load a robot from URDF files, configure control systems, and run basic robot simulation with joint control.
The Code#
The tutorial corresponds to the create_robot.py script in the scripts/tutorials/sim directory.
Code for create_robot.py
1# ----------------------------------------------------------------------------
2# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15# ----------------------------------------------------------------------------
16
17"""
18This script demonstrates how to create and simulate a robot using SimulationManager.
19It shows how to load a robot from URDF, set up control parts, and run basic simulation.
20"""
21
22from __future__ import annotations
23
24import argparse
25import numpy as np
26import time
27import torch
28
29torch.set_printoptions(precision=4, sci_mode=False)
30
31from scipy.spatial.transform import Rotation as R
32
33from embodichain.lab.sim import SimulationManager, SimulationManagerCfg
34from embodichain.lab.visualization import visualization_cfg_from_args
35from embodichain.lab.sim.objects import Robot
36from embodichain.lab.sim.cfg import (
37 RenderCfg,
38 JointDrivePropertiesCfg,
39 RobotCfg,
40 URDFCfg,
41)
42from embodichain.data import get_data_path
43from embodichain.lab.gym.utils.gym_utils import add_env_launcher_args_to_parser
44
45ACTION_SWITCH_INTERVAL = 100
46ACTION_CYCLE_STEPS = 4 * ACTION_SWITCH_INTERVAL
47
48
49def main():
50 """Main function to demonstrate robot simulation."""
51
52 # Parse command line arguments
53 parser = argparse.ArgumentParser(
54 description="Create and simulate a robot in SimulationManager"
55 )
56 add_env_launcher_args_to_parser(parser)
57 args = parser.parse_args()
58
59 # Initialize simulation
60 print("Creating simulation...")
61 config = SimulationManagerCfg(
62 headless=True,
63 sim_device=args.device,
64 arena_space=3.0,
65 render_cfg=RenderCfg(renderer=args.renderer),
66 physics_dt=1.0 / 100.0,
67 num_envs=args.num_envs,
68 visualization=visualization_cfg_from_args(args),
69 )
70 sim = SimulationManager(config)
71
72 # Create robot configuration
73 robot = create_robot(sim)
74
75 # Initialize GPU physics if using CUDA
76 if sim.is_use_gpu_physics:
77 sim.init_gpu_physics()
78
79 # Open visualization window if not headless
80 if not args.headless:
81 sim.open_window()
82
83 # Run simulation loop
84 run_simulation(sim, robot)
85
86
87def create_robot(sim):
88 """Create and configure a robot in the simulation."""
89
90 print("Loading robot...")
91
92 # Get SR5 arm URDF path
93 sr5_urdf_path = get_data_path("Rokae/SR5/SR5.urdf")
94
95 # Get hand URDF path
96 hand_urdf_path = get_data_path(
97 "BrainCoHandRevo1/BrainCoLeftHand/BrainCoLeftHand.urdf"
98 )
99
100 # Define control parts for the robot
101 # Joint names in control_parts can be regex patterns
102 CONTROL_PARTS = {
103 "arm": [
104 "joint[1-6]", # Matches JOINT1, JOINT2, ..., JOINT6
105 ],
106 "hand": ["LEFT_.*"], # Matches all joints starting with L_
107 }
108
109 # Define transformation for hand attachment
110 hand_attach_xpos = np.eye(4)
111 hand_attach_xpos[:3, :3] = R.from_rotvec([90, 0, 0], degrees=True).as_matrix()
112 hand_attach_xpos[2, 3] = 0.02
113
114 cfg = RobotCfg(
115 uid="sr5_with_brainco",
116 urdf_cfg=URDFCfg(
117 components=[
118 {
119 "component_type": "arm",
120 "urdf_path": sr5_urdf_path,
121 },
122 {
123 "component_type": "hand",
124 "urdf_path": hand_urdf_path,
125 "transform": hand_attach_xpos,
126 },
127 ]
128 ),
129 control_parts=CONTROL_PARTS,
130 drive_pros=JointDrivePropertiesCfg(
131 stiffness={"joint[1-6]": 1e4, "LEFT_.*": 1e3},
132 damping={"joint[1-6]": 1e3, "LEFT_.*": 1e2},
133 ),
134 )
135
136 # Add robot to simulation
137 robot: Robot = sim.add_robot(cfg=cfg)
138
139 print(f"Robot created successfully with {robot.dof} joints")
140
141 return robot
142
143
144def run_simulation(sim: SimulationManager, robot: Robot):
145 """Run the simulation loop with robot control."""
146
147 print("Starting simulation...")
148 print("Robot will move through different poses")
149 print("Press Ctrl+C to stop")
150
151 step_count = 0
152
153 arm_joint_ids = robot.get_joint_ids("arm")
154 # Define some target joint positions for demonstration
155 arm_position1 = (
156 torch.tensor(
157 [0.0, -0.5, 0.5, -1.0, 0.5, 0.0], dtype=torch.float32, device=sim.device
158 )
159 .unsqueeze_(0)
160 .repeat(sim.num_envs, 1)
161 )
162
163 arm_position2 = (
164 torch.tensor(
165 [0.5, 0.0, -0.5, 0.5, -0.5, 0.5], dtype=torch.float32, device=sim.device
166 )
167 .unsqueeze_(0)
168 .repeat(sim.num_envs, 1)
169 )
170
171 # Get joint IDs for the hand.
172 hand_joint_ids = robot.get_joint_ids("hand")
173 # Define hand open and close positions based on joint limits.
174 hand_position_open = robot.body_data.qpos_limits[:, hand_joint_ids, 1]
175 hand_position_close = robot.body_data.qpos_limits[:, hand_joint_ids, 0]
176
177 try:
178 while True:
179 # Update physics
180 sim.update(step=1)
181 cycle_step = step_count % ACTION_CYCLE_STEPS
182
183 if cycle_step == 0:
184 robot.set_qpos(qpos=arm_position1, joint_ids=arm_joint_ids)
185 print(f"Moving to arm position 1")
186
187 if cycle_step == ACTION_SWITCH_INTERVAL:
188 robot.set_qpos(qpos=arm_position2, joint_ids=arm_joint_ids)
189 print(f"Moving to arm position 2")
190
191 if cycle_step == 2 * ACTION_SWITCH_INTERVAL:
192 robot.set_qpos(qpos=hand_position_close, joint_ids=hand_joint_ids)
193 print(f"Closing hand")
194
195 if cycle_step == 3 * ACTION_SWITCH_INTERVAL:
196 robot.set_qpos(qpos=hand_position_open, joint_ids=hand_joint_ids)
197 print(f"Opening hand")
198
199 step_count += 1
200
201 except KeyboardInterrupt:
202 print("Stopping simulation...")
203 finally:
204 print("Cleaning up...")
205 sim.destroy()
206
207
208if __name__ == "__main__":
209 main()
The Code Explained#
Similar to the previous tutorial on creating a simulation scene, we use the SimulationManager class to set up the simulation environment. If you haven’t read that tutorial yet, please refer to Creating a simulation scene first.
Loading Robot URDF#
SimulationManager supports loading robots from URDF (Unified Robot Description Format) files. You can load either a single URDF file or compose multiple URDF components into a complete robot system.
For a simple two-component robot (arm + hand):
sr5_urdf_path = get_data_path("Rokae/SR5/SR5.urdf")
# Get hand URDF path
hand_urdf_path = get_data_path(
"BrainCoHandRevo1/BrainCoLeftHand/BrainCoLeftHand.urdf"
)
# Define control parts for the robot
# Joint names in control_parts can be regex patterns
CONTROL_PARTS = {
"arm": [
"joint[1-6]", # Matches JOINT1, JOINT2, ..., JOINT6
],
"hand": ["LEFT_.*"], # Matches all joints starting with L_
}
# Define transformation for hand attachment
hand_attach_xpos = np.eye(4)
hand_attach_xpos[:3, :3] = R.from_rotvec([90, 0, 0], degrees=True).as_matrix()
hand_attach_xpos[2, 3] = 0.02
cfg = RobotCfg(
uid="sr5_with_brainco",
urdf_cfg=URDFCfg(
components=[
{
"component_type": "arm",
"urdf_path": sr5_urdf_path,
},
{
"component_type": "hand",
"urdf_path": hand_urdf_path,
"transform": hand_attach_xpos,
},
]
),
control_parts=CONTROL_PARTS,
drive_pros=JointDrivePropertiesCfg(
stiffness={"joint[1-6]": 1e4, "LEFT_.*": 1e3},
damping={"joint[1-6]": 1e3, "LEFT_.*": 1e2},
),
)
# Add robot to simulation
robot: Robot = sim.add_robot(cfg=cfg)
The cfg.URDFCfg allows you to compose multiple URDF files with specific transformations, enabling complex robot assemblies.
Configuring Control Parts#
Control parts define how the robot’s joints are grouped for control purposes. This is useful for organizing complex robots with multiple subsystems.
# Define control parts for the robot
# Joint names in control_parts can be regex patterns
CONTROL_PARTS = {
"arm": [
"joint[1-6]", # Matches JOINT1, JOINT2, ..., JOINT6
],
"hand": ["LEFT_.*"], # Matches all joints starting with L_
}
Joint names in control parts can use regex patterns for flexible matching. For example:
"JOINT[1-6]"matches JOINT1, JOINT2, …, JOINT6"L_.*"matches all joints starting with “L_”
Setting Drive Properties#
Drive properties control how the robot’s joints behave during simulation, including stiffness, damping, and force limits.
drive_pros=JointDrivePropertiesCfg(
stiffness={"joint[1-6]": 1e4, "LEFT_.*": 1e3},
damping={"joint[1-6]": 1e3, "LEFT_.*": 1e2},
),
You can set different stiffness values for different joint groups using regex patterns. More details on drive properties can be found in cfg.JointDrivePropertiesCfg.
For more robot configuration options, refer to cfg.RobotCfg.
Robot Control#
For the basic control of robot joints, you can set position targets using objects.Robot.set_qpos(). The control action should be created as a torch.Tensor with shape (num_envs, num_joints), where num_joints is the total number of joints in the robot or the number of joints in a specific control part.
If you can control all joints, use:
robot.set_qpos(qpos=target_positions)
If you want to control a subset of joints, specify the joint IDs:
robot.set_qpos(qpos=target_positions, joint_ids=subset_joint_ids)
Getting Robot State#
You can query the robot’s current joint positions and velocities via objects.Robot.get_qpos() and objects.Robot.get_qvel(). For more robot API details, see objects.Robot.
The Code Execution#
To run the robot simulation script:
cd /root/sources/embodichain
python scripts/tutorials/sim/create_robot.py
You can customize the simulation with various command-line options:
# Run with GPU physics
python scripts/tutorials/sim/create_robot.py --device cuda
# Run multiple environments
python scripts/tutorials/sim/create_robot.py --num_envs 4
# Run in headless mode
python scripts/tutorials/sim/create_robot.py --headless
# Enable ray tracing rendering
python scripts/tutorials/sim/create_robot.py --renderer
The simulation will show the robot moving through different poses, demonstrating basic joint control capabilities.
Key Features Demonstrated#
This tutorial demonstrates several key features of robot simulation in SimulationManager:
URDF Loading: Both single-file and multi-component robot loading
Control Parts: Organizing joints into logical control groups
Drive Properties: Configuring joint stiffness and control behavior
Joint Control: Setting position targets and reading joint states
Multi-Environment: Running multiple robot instances in parallel
Next Steps#
After mastering basic robot simulation, you can explore:
End-effector control and inverse kinematics
Sensor integration (cameras, force sensors)
Robot-object interaction scenarios
This tutorial provides the foundation for creating sophisticated robotic simulation scenarios with SimulationManager.