Rigid object group tutorial#

This tutorial shows how to create and use a RigidObjectGroup in SimulationManager. It follows the style used in the create_scene tutorial and references the example script located in scripts/tutorials/sim/create_rigid_object_group.py.

The Code#

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

Code for create_rigid_object_group.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 rigid object group using SimulationManager.
 19"""
 20
 21from __future__ import annotations
 22
 23import argparse
 24import time
 25from embodichain.cli.sim import add_sim_args_to_parser
 26
 27
 28def build_parser() -> argparse.ArgumentParser:
 29    """Build CLI options without initializing simulation resources."""
 30    parser = argparse.ArgumentParser(
 31        description="Create a simulation scene with SimulationManager"
 32    )
 33    add_sim_args_to_parser(parser)
 34    return parser
 35
 36
 37if __name__ == "__main__":
 38    # Parse before importing optional simulation/planning dependencies.
 39    _cli_args = build_parser().parse_args()
 40
 41
 42from embodichain.lab.sim import SimulationManager, SimulationManagerCfg
 43from embodichain.lab.sim.cfg import (
 44    RigidBodyPhysicsCfg,
 45    RenderCfg,
 46    physics_cfg_for_backend,
 47)
 48from embodichain.lab.visualization import visualization_cfg_from_args
 49from embodichain.lab.sim.shapes import CubeCfg
 50from embodichain.lab.sim.objects import (
 51    RigidObjectGroup,
 52    RigidObjectGroupCfg,
 53    RigidObjectCfg,
 54)
 55
 56
 57def main(args: argparse.Namespace | None = None) -> None:
 58    """Main function to create and run the simulation scene."""
 59
 60    # Parse command line arguments
 61    parser = build_parser()
 62    if args is None:
 63        args = parser.parse_args()
 64
 65    # Configure the simulation
 66    sim_cfg = SimulationManagerCfg(
 67        width=1920,
 68        height=1080,
 69        headless=True,
 70        physics_dt=1.0 / 100.0,  # Physics timestep (100 Hz)
 71        device=args.device,
 72        render_cfg=RenderCfg(
 73            renderer=args.renderer
 74        ),  # Enable ray tracing for better visuals
 75        physics_cfg=physics_cfg_for_backend(args.physics),
 76        num_envs=args.num_envs,
 77        arena_space=3.0,
 78        visualization=visualization_cfg_from_args(args),
 79    )
 80
 81    # Create the simulation instance
 82    sim = SimulationManager(sim_cfg)
 83
 84    physics_attrs = RigidBodyPhysicsCfg.from_dict(
 85        {
 86            "mass_props": {"mass": 1.0},
 87            "material_props": {
 88                "dynamic_friction": 0.5,
 89                "static_friction": 0.5,
 90                "restitution": 0.1,
 91            },
 92        }
 93    )
 94
 95    # Add objects to the scene
 96    obj_group: RigidObjectGroup = sim.add_rigid_object_group(
 97        cfg=RigidObjectGroupCfg(
 98            uid="obj_group",
 99            rigid_objects={
100                "cube_1": RigidObjectCfg(
101                    uid="cube_1",
102                    shape=CubeCfg(size=[0.1, 0.1, 0.1]),
103                    attrs=physics_attrs,
104                    init_pos=[0.0, 0.0, 1.0],
105                ),
106                "cube_2": RigidObjectCfg(
107                    uid="cube_2",
108                    shape=CubeCfg(size=[0.2, 0.2, 0.2]),
109                    attrs=physics_attrs,
110                    init_pos=[0.5, 0.0, 1.0],
111                ),
112                "cube_3": RigidObjectCfg(
113                    uid="cube_3",
114                    shape=CubeCfg(size=[0.3, 0.3, 0.3]),
115                    attrs=physics_attrs,
116                    init_pos=[-0.5, 0.0, 1.0],
117                ),
118            },
119        )
120    )
121
122    print("[INFO]: Scene setup complete!")
123    print(f"[INFO]: Running simulation with {args.num_envs} environment(s)")
124    print("[INFO]: Press Ctrl+C to stop the simulation")
125
126    # Open window when the scene has been set up
127    sim.prepare()
128    if not args.headless:
129        sim.open_window()
130
131    # Run the simulation
132    run_simulation(sim)
133
134
135def run_simulation(sim: SimulationManager):
136    """Run the simulation loop.
137
138    Args:
139        sim: The SimulationManager instance to run
140    """
141
142    step_count = 0
143
144    try:
145        last_time = time.time()
146        last_step = 0
147        while True:
148            # Update physics simulation
149            sim.update(step=1)
150            step_count += 1
151
152            # Print FPS every second
153            if step_count % 100 == 0:
154                current_time = time.time()
155                elapsed = current_time - last_time
156                fps = (
157                    sim.num_envs * (step_count - last_step) / elapsed
158                    if elapsed > 0
159                    else 0
160                )
161                print(f"[INFO]: Simulation step: {step_count}, FPS: {fps:.2f}")
162                last_time = current_time
163                last_step = step_count
164
165    except KeyboardInterrupt:
166        print("\n[INFO]: Stopping simulation...")
167    finally:
168        # Clean up resources
169        sim.destroy()
170        print("[INFO]: Simulation terminated successfully")
171
172
173if __name__ == "__main__":
174    main(_cli_args)

The Code Explained#

Adding a RigidObjectGroup#

The key part of the tutorial demonstrates creating a RigidObjectGroup via sim.add_rigid_object_group. The group is configured with a mapping of object UIDs to RigidObjectCfg entries. Each entry defines a shape (here CubeCfg), physics attributes, and initial pose.

    obj_group: RigidObjectGroup = sim.add_rigid_object_group(
        cfg=RigidObjectGroupCfg(
            uid="obj_group",
            rigid_objects={
                "cube_1": RigidObjectCfg(
                    uid="cube_1",
                    shape=CubeCfg(size=[0.1, 0.1, 0.1]),
                    attrs=physics_attrs,
                    init_pos=[0.0, 0.0, 1.0],
                ),
                "cube_2": RigidObjectCfg(
                    uid="cube_2",
                    shape=CubeCfg(size=[0.2, 0.2, 0.2]),
                    attrs=physics_attrs,
                    init_pos=[0.5, 0.0, 1.0],
                ),
                "cube_3": RigidObjectCfg(
                    uid="cube_3",
                    shape=CubeCfg(size=[0.3, 0.3, 0.3]),
                    attrs=physics_attrs,
                    init_pos=[-0.5, 0.0, 1.0],
                ),
            },
        )
    )

    print("[INFO]: Scene setup complete!")

Running the tutorial#

To run the script from the repository root:

python scripts/tutorials/sim/create_rigid_object_group.py

You can pass flags such as --headless, --num_envs <n>, and --device <cpu|cuda> to customize the run.

Use --viser to publish every constituent object in the group to the browser:

python scripts/tutorials/sim/create_rigid_object_group.py --viser

Each group member has its own pose and browser node. Shared geometry remains batched by the Viser backend. See Browser visualization with Viser for server and environment-selection options.