Creating a simulation scene#

This tutorial shows how to create a basic simulation scene using SimulationManager. It covers the setup of the simulation context, adding rigid objects, and running the simulation loop.

The Code#

The tutorial corresponds to the create_scene.py script in the scripts/tutorials/sim directory.

Code for create_scene.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 a simulation scene using SimulationManager.
 19It shows the basic setup of simulation context, adding objects, and sensors.
 20"""
 21
 22import argparse
 23import time
 24
 25from embodichain.lab.sim import SimulationManager, SimulationManagerCfg
 26from embodichain.lab.sim.cfg import RigidBodyAttributesCfg
 27from embodichain.lab.sim.shapes import CubeCfg, MeshCfg
 28from embodichain.lab.sim.objects import RigidObject, RigidObjectCfg
 29from embodichain.data import get_data_path
 30
 31
 32def main():
 33    """Main function to create and run the simulation scene."""
 34
 35    # Parse command line arguments
 36    parser = argparse.ArgumentParser(
 37        description="Create a simulation scene with SimulationManager"
 38    )
 39    parser.add_argument(
 40        "--headless",
 41        action="store_true",
 42        default=False,
 43        help="Run simulation in headless mode",
 44    )
 45    parser.add_argument(
 46        "--num_envs", type=int, default=1, help="Number of parallel environments"
 47    )
 48    parser.add_argument(
 49        "--device", type=str, default="cpu", help="Simulation device (cuda or cpu)"
 50    )
 51    parser.add_argument(
 52        "--enable_rt",
 53        action="store_true",
 54        default=False,
 55        help="Enable ray tracing for better visuals",
 56    )
 57    args = parser.parse_args()
 58
 59    # Configure the simulation
 60    sim_cfg = SimulationManagerCfg(
 61        width=1920,
 62        height=1080,
 63        headless=True,
 64        physics_dt=1.0 / 100.0,  # Physics timestep (100 Hz)
 65        sim_device=args.device,
 66        enable_rt=args.enable_rt,  # Enable ray tracing for better visuals
 67        num_envs=args.num_envs,
 68        arena_space=3.0,
 69    )
 70
 71    # Create the simulation instance
 72    sim = SimulationManager(sim_cfg)
 73
 74    # Add cube object to the scene
 75    cube: RigidObject = sim.add_rigid_object(
 76        cfg=RigidObjectCfg(
 77            uid="cube",
 78            shape=CubeCfg(size=[0.1, 0.1, 0.1]),
 79            body_type="dynamic",
 80            attrs=RigidBodyAttributesCfg(
 81                mass=1.0,
 82                dynamic_friction=0.5,
 83                static_friction=0.5,
 84                restitution=0.1,
 85            ),
 86            init_pos=[0.5, 0.0, 1.0],
 87        )
 88    )
 89
 90    # Add toy_duck object to the scene
 91    toy_duck_path = get_data_path("ToyDuck/toy_duck.glb")
 92    toy_duck: RigidObject = sim.add_rigid_object(
 93        cfg=RigidObjectCfg(
 94            uid="toy_duck",
 95            shape=MeshCfg(fpath=toy_duck_path),
 96            body_type="dynamic",
 97            attrs=RigidBodyAttributesCfg(
 98                mass=1.0,
 99                dynamic_friction=0.5,
100                static_friction=0.5,
101                restitution=0.1,
102            ),
103            init_pos=[0.0, 0.0, 0.2],
104            init_rot=[0.0, 0.0, 0.0],
105        )
106    )
107
108    print("[INFO]: Scene setup complete!")
109    print(f"[INFO]: Running simulation with {args.num_envs} environment(s)")
110    print("[INFO]: Press Ctrl+C to stop the simulation")
111
112    # Open window when the scene has been set up
113    if not args.headless:
114        sim.open_window()
115
116    # Run the simulation
117    run_simulation(sim)
118
119
120def run_simulation(sim: SimulationManager):
121    """Run the simulation loop.
122
123    Args:
124        sim: The SimulationManager instance to run
125    """
126
127    # Initialize GPU physics if using CUDA
128    if sim.is_use_gpu_physics:
129        sim.init_gpu_physics()
130
131    step_count = 0
132
133    try:
134        last_time = time.time()
135        last_step = 0
136        while True:
137            # Update physics simulation
138            sim.update(step=1)
139            step_count += 1
140
141            # Print FPS every second
142            if step_count % 100 == 0:
143                current_time = time.time()
144                elapsed = current_time - last_time
145                fps = (
146                    sim.num_envs * (step_count - last_step) / elapsed
147                    if elapsed > 0
148                    else 0
149                )
150                print(f"[INFO]: Simulation step: {step_count}, FPS: {fps:.2f}")
151                last_time = current_time
152                last_step = step_count
153
154    except KeyboardInterrupt:
155        print("\n[INFO]: Stopping simulation...")
156    finally:
157        # Clean up resources
158        sim.destroy()
159        print("[INFO]: Simulation terminated successfully")
160
161
162if __name__ == "__main__":
163    main()

The Code Explained#

Configuring the simulation#

The first step is to configure the simulation environment. This is done using the SimulationManagerCfg data class, which allows you to specify various parameters like window dimensions, headless mode, physics timestep, simulation device (CPU/GPU), and rendering options like ray tracing.

Command-line arguments are parsed using argparse to allow for easy customization of the simulation from the terminal.

    # Parse command line arguments
    parser = argparse.ArgumentParser(
        description="Create a simulation scene with SimulationManager"
    )
    parser.add_argument(
        "--headless",
        action="store_true",
        default=False,
        help="Run simulation in headless mode",
    )
    parser.add_argument(
        "--num_envs", type=int, default=1, help="Number of parallel environments"
    )
    parser.add_argument(
        "--device", type=str, default="cpu", help="Simulation device (cuda or cpu)"
    )
    parser.add_argument(
        "--enable_rt",
        action="store_true",
        default=False,
        help="Enable ray tracing for better visuals",
    )
    args = parser.parse_args()

    # Configure the simulation
    sim_cfg = SimulationManagerCfg(
        width=1920,
        height=1080,
        headless=True,
        physics_dt=1.0 / 100.0,  # Physics timestep (100 Hz)
        sim_device=args.device,
        enable_rt=args.enable_rt,  # Enable ray tracing for better visuals
        num_envs=args.num_envs,
        arena_space=3.0,
    )

    # Create the simulation instance
    sim = SimulationManager(sim_cfg)

There are two kinds of physics mode in SimulationManager:

  • manual: The physics updates only when the user calls the SimulationManager.update() function. This mode is used for robot learning tasks where precise control over simulation steps is required. Enabled by setting SimulationManager.set_manual_update() to True.

  • auto: The physics updates in a standalone thread, which enable asynchronous rendering and physics stepping. This mode is suitable for visualizations and demos for digital twins applications. This is the default mode.

Adding objects to the scene#

With the simulation context created, we can add objects. This tutorial demonstrates adding a dynamic rigid cube to the scene using the SimulationManager.add_rigid_object() method. The object’s properties, such as its shape, initial position, and physics attributes (mass, friction, restitution), are defined through a configuration object, cfg.RigidObjectCfg.

Running the simulation#

The simulation is advanced through a loop in the run_simulation function. Before starting the loop, GPU physics is initialized if a CUDA device is used.

Inside the loop, SimulationManager.update() is called to step the physics simulation forward. The script also includes logic to calculate and print the Frames Per Second (FPS) to monitor performance. The simulation runs until it’s manually stopped with Ctrl+C.

def run_simulation(sim: SimulationManager):
    """Run the simulation loop.

    Args:
        sim: The SimulationManager instance to run
    """

    # Initialize GPU physics if using CUDA
    if sim.is_use_gpu_physics:
        sim.init_gpu_physics()

    step_count = 0

    try:
        last_time = time.time()
        last_step = 0
        while True:
            # Update physics simulation
            sim.update(step=1)
            step_count += 1

            # Print FPS every second
            if step_count % 100 == 0:
                current_time = time.time()
                elapsed = current_time - last_time
                fps = (
                    sim.num_envs * (step_count - last_step) / elapsed
                    if elapsed > 0
                    else 0
                )
                print(f"[INFO]: Simulation step: {step_count}, FPS: {fps:.2f}")
                last_time = current_time
                last_step = step_count

Exiting the simulation#

Upon exiting the simulation loop (e.g., by a KeyboardInterrupt), it’s important to clean up resources. The SimulationManager.destroy() method is called in a finally block to ensure that the simulation is properly terminated and all allocated resources are released.

    except KeyboardInterrupt:
        print("\n[INFO]: Stopping simulation...")
    finally:
        # Clean up resources
        sim.destroy()

The Code Execution#

To run the script and see the result, execute the following command:

python scripts/tutorials/sim/create_scene.py

A window should appear showing a cube dropping onto a flat plane. To stop the simulation, you can either close the window or press Ctrl+C in the terminal.

You can also pass arguments to customize the simulation. For example, to run in headless mode with n parallel environments using specified device:

python scripts/tutorials/sim/create_scene.py --headless --num_envs <n> --device <cuda/cpu>

Now that we have a basic understanding of how to create a scene, let’s move on to more advanced topics.