Creating a Modular Environment#

This tutorial demonstrates how to create sophisticated robotic environments using EmbodiChain’s modular architecture. You’ll learn how to use the advanced envs.EmbodiedEnv class with configuration-driven setup, event managers, observation managers, and randomization systems.

The Code#

The tutorial corresponds to the modular_env.py script in the scripts/tutorials/gym directory.

Code for modular_env.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
 17import torch
 18
 19from typing import List, Dict, Any
 20
 21import embodichain.lab.gym.envs.managers.randomization as rand
 22import embodichain.lab.gym.envs.managers.events as events
 23import embodichain.lab.gym.envs.managers.observations as obs
 24
 25from embodichain.lab.gym.envs.managers import (
 26    EventCfg,
 27    SceneEntityCfg,
 28    ObservationCfg,
 29)
 30from embodichain.lab.gym.envs import EmbodiedEnv, EmbodiedEnvCfg
 31from embodichain.lab.gym.utils.registration import register_env
 32from embodichain.lab.sim.robots import DexforceW1Cfg
 33from embodichain.lab.sim.sensors import StereoCameraCfg, SensorCfg
 34from embodichain.lab.sim.shapes import MeshCfg
 35from embodichain.lab.sim.cfg import (
 36    RenderCfg,
 37    LightCfg,
 38    ArticulationCfg,
 39    RobotCfg,
 40    RigidObjectCfg,
 41    RigidBodyAttributesCfg,
 42)
 43from embodichain.data import get_data_path
 44from embodichain.utils import configclass
 45
 46
 47@configclass
 48class ExampleEventCfg:
 49
 50    replace_obj: EventCfg = EventCfg(
 51        func=events.replace_assets_from_group,
 52        mode="reset",
 53        params={
 54            "entity_cfg": SceneEntityCfg(
 55                uid="fork",
 56            ),
 57            "folder_path": get_data_path("TableWare/tableware/fork/"),
 58        },
 59    )
 60
 61    randomize_fork_mass: EventCfg = EventCfg(
 62        func=rand.randomize_rigid_object_mass,
 63        mode="reset",
 64        params={
 65            "entity_cfg": SceneEntityCfg(
 66                uid="fork",
 67            ),
 68            "mass_range": (0.1, 2.0),
 69        },
 70    )
 71
 72    randomize_light: EventCfg = EventCfg(
 73        func=rand.randomize_light,
 74        mode="interval",
 75        interval_step=5,
 76        params={
 77            "entity_cfg": SceneEntityCfg(
 78                uid="point",
 79            ),
 80            "position_range": [[-0.5, -0.5, 2], [0.5, 0.5, 2]],
 81            "color_range": [[0.6, 0.6, 0.6], [1, 1, 1]],
 82            "intensity_range": [10.0, 30.0],
 83        },
 84    )
 85
 86    randomize_table_mat: EventCfg = EventCfg(
 87        func=rand.randomize_visual_material,
 88        mode="interval",
 89        interval_step=10,
 90        params={
 91            "entity_cfg": SceneEntityCfg(
 92                uid="table",
 93            ),
 94            "random_texture_prob": 0.5,
 95            "texture_path": get_data_path("CocoBackground/coco"),
 96            "base_color_range": [[0.2, 0.2, 0.2], [1.0, 1.0, 1.0]],
 97        },
 98    )
 99
100
101@configclass
102class ObsCfg:
103
104    obj_pose: ObservationCfg = ObservationCfg(
105        func=obs.get_rigid_object_pose,
106        mode="add",
107        name="fork_pose",
108        params={"entity_cfg": SceneEntityCfg(uid="fork")},
109    )
110
111
112@configclass
113class ExampleCfg(EmbodiedEnvCfg):
114
115    # Define the robot configuration using DexforceW1Cfg
116    robot: RobotCfg = DexforceW1Cfg.from_dict(
117        {
118            "uid": "dexforce_w1",
119            "version": "v021",
120            "arm_kind": "anthropomorphic",
121            "init_pos": [0.0, 0, 0.0],
122        }
123    )
124
125    # Define the sensor configuration using StereoCameraCfg
126    sensor: List[SensorCfg] = [
127        StereoCameraCfg(
128            uid="eye_in_head",
129            width=960,
130            height=540,
131            enable_mask=True,
132            enable_depth=True,
133            left_to_right_pos=(0.06, 0, 0),
134            intrinsics=(450, 450, 480, 270),
135            intrinsics_right=(450, 450, 480, 270),
136            extrinsics=StereoCameraCfg.ExtrinsicsCfg(
137                parent="eyes",
138            ),
139        )
140    ]
141
142    light: EmbodiedEnvCfg.EnvLightCfg = EmbodiedEnvCfg.EnvLightCfg(
143        direct=[
144            LightCfg(
145                uid="point",
146                light_type="point",
147                color=(1.0, 1.0, 1.0),
148                intensity=20.0,
149                init_pos=(0, 0, 2),
150            )
151        ]
152    )
153
154    background: List[RigidObjectCfg] = [
155        RigidObjectCfg(
156            uid="table",
157            shape=MeshCfg(
158                fpath=get_data_path("CircleTableSimple/circle_table_simple.ply"),
159                compute_uv=True,
160            ),
161            attrs=RigidBodyAttributesCfg(
162                mass=10.0,
163                static_friction=0.95,
164                dynamic_friction=0.85,
165                restitution=0.01,
166            ),
167            body_type="kinematic",
168            init_pos=(0.80, 0, 0.8),
169            init_rot=(0, 90, 0),
170        ),
171    ]
172
173    rigid_object: List[RigidObjectCfg] = [
174        RigidObjectCfg(
175            uid="fork",
176            shape=MeshCfg(
177                fpath=get_data_path("TableWare/tableware/fork/standard_fork_scale.ply"),
178            ),
179            body_scale=(0.75, 0.75, 1.0),
180            init_pos=(0.8, 0, 1.0),
181        ),
182    ]
183
184    articulation_cfg: List[ArticulationCfg] = [
185        ArticulationCfg(
186            uid="drawer",
187            fpath="SlidingBoxDrawer/SlidingBoxDrawer.urdf",
188            init_pos=(0.5, 0.0, 0.85),
189        )
190    ]
191
192    events = ExampleEventCfg()
193
194    observations = ObsCfg()
195
196
197@register_env("ModularEnv-v1", max_episode_steps=100, override=True)
198class ModularEnv(EmbodiedEnv):
199    """
200    An example of a modular environment that inherits from EmbodiedEnv
201    and uses custom event and observation managers.
202    """
203
204    def __init__(self, cfg: EmbodiedEnvCfg, **kwargs):
205        super().__init__(cfg, **kwargs)
206
207
208if __name__ == "__main__":
209    import gymnasium as gym
210    import argparse
211
212    from embodichain.lab.sim import SimulationManagerCfg
213    from embodichain.lab.gym.utils.gym_utils import add_env_launcher_args_to_parser
214
215    parser = argparse.ArgumentParser()
216    add_env_launcher_args_to_parser(parser)
217    args = parser.parse_args()
218
219    env_cfg = ExampleCfg(
220        sim_cfg=SimulationManagerCfg(
221            render_cfg=RenderCfg(renderer=args.renderer),
222            headless=args.headless,
223            sim_device=args.device,
224            num_envs=args.num_envs,
225        )
226    )
227
228    # Create the Gym environment
229    env = gym.make("ModularEnv-v1", cfg=env_cfg)
230
231    while True:
232        obs, info = env.reset()
233
234        for i in range(100):
235            action = torch.zeros(env.action_space.shape, dtype=torch.float32)
236            obs, reward, done, truncated, info = env.step(action)

The Code Explained#

This tutorial showcases EmbodiChain’s most powerful environment creation approach using the envs.EmbodiedEnv class. Unlike the basic environment tutorial, this approach uses declarative configuration classes and manager systems for maximum flexibility and reusability.

Event Configuration#

Events define automated behaviors that occur during simulation. There are three types of supported modes:

  • startup: triggers once when the environment is initialized

  • reset: triggers every time the environment is reset

  • interval: triggers at fixed step intervals during simulation

The ExampleEventCfg demonstrates three types of events:

    RenderCfg,
    LightCfg,
    ArticulationCfg,
    RobotCfg,
    RigidObjectCfg,
    RigidBodyAttributesCfg,
)
from embodichain.data import get_data_path
from embodichain.utils import configclass


@configclass
class ExampleEventCfg:

    replace_obj: EventCfg = EventCfg(
        func=events.replace_assets_from_group,
        mode="reset",
        params={
            "entity_cfg": SceneEntityCfg(
                uid="fork",
            ),
            "folder_path": get_data_path("TableWare/tableware/fork/"),
        },
    )

    randomize_fork_mass: EventCfg = EventCfg(
        func=rand.randomize_rigid_object_mass,
        mode="reset",
        params={
            "entity_cfg": SceneEntityCfg(
                uid="fork",
            ),
            "mass_range": (0.1, 2.0),
        },
    )

    randomize_light: EventCfg = EventCfg(
        func=rand.randomize_light,
        mode="interval",
        interval_step=5,
        params={

Asset Replacement Event

The replace_obj event demonstrates dynamic asset swapping:

Light Randomization Event

The randomize_light event creates dynamic lighting conditions:

  • Function: envs.managers.randomization.rendering.randomize_light()

  • Mode: "interval" - triggers every 5 steps

  • Parameters: Randomizes position, color, and intensity within specified ranges

Material Randomization Event

The randomize_table_mat event varies visual appearance:

  • Function: envs.managers.randomization.rendering.randomize_visual_material()

  • Mode: "interval" - triggers every 10 steps

  • Features: Random textures from COCO dataset and base color variations

For more randomization events, please refer to Event Functors.

Observation Configuration#

The default observation from envs.EmbodiedEnv includes: - robot: robot proprioceptive data (joint positions, velocities, efforts) - sensor: all available sensor data (images, depth, segmentation, etc.)

However, users always need to define some custom observation for specified learning tasks. To handle this, the observation manager system allows users to declaratively specify additional observations.

            ),
            "position_range": [[-0.5, -0.5, 2], [0.5, 0.5, 2]],
            "color_range": [[0.6, 0.6, 0.6], [1, 1, 1]],
            "intensity_range": [10.0, 30.0],
        },
    )

    randomize_table_mat: EventCfg = EventCfg(
        func=rand.randomize_visual_material,

This configuration:

For details documentation, see envs.managers.cfg.ObservationCfg.

Environment Configuration#

The main environment configuration inherits from envs.EmbodiedEnvCfg and defines all scene components:

Robot Configuration

    robot: RobotCfg = DexforceW1Cfg.from_dict(
        {
            "uid": "dexforce_w1",
            "version": "v021",
            "arm_kind": "anthropomorphic",
            "init_pos": [0.0, 0, 0.0],
        }
    )

Uses the pre-configured DexforceW1Cfg with customizations:

  • Version: Specific robot variant (v021)

  • Arm Type: Anthropomorphic configuration

  • Position: Initial placement in the scene

Sensor Configuration

    obj_pose: ObservationCfg = ObservationCfg(
        func=obs.get_rigid_object_pose,
        mode="add",
        name="fork_pose",
        params={"entity_cfg": SceneEntityCfg(uid="fork")},
    )


@configclass
class ExampleCfg(EmbodiedEnvCfg):

    # Define the robot configuration using DexforceW1Cfg
    robot: RobotCfg = DexforceW1Cfg.from_dict(
        {
            "uid": "dexforce_w1",

Configures a stereo camera system using StereoCameraCfg:

  • Resolution: 960x540 pixels for realistic visual input

  • Features: Depth sensing and segmentation masks enabled

  • Stereo Setup: 6cm baseline between left and right cameras

  • Mounting: Attached to robot’s “eyes” frame

Lighting Configuration

            "arm_kind": "anthropomorphic",
            "init_pos": [0.0, 0, 0.0],
        }
    )

    # Define the sensor configuration using StereoCameraCfg
    sensor: List[SensorCfg] = [
        StereoCameraCfg(
            uid="eye_in_head",
            width=960,
            height=540,

Defines scene illumination with controllable point lights:

  • Type: Point light for realistic shadows

  • Properties: Configurable color, intensity, and position

  • UID: Named reference for event system manipulation

Rigid Objects

            enable_depth=True,
            left_to_right_pos=(0.06, 0, 0),
            intrinsics=(450, 450, 480, 270),
            intrinsics_right=(450, 450, 480, 270),
            extrinsics=StereoCameraCfg.ExtrinsicsCfg(
                parent="eyes",
            ),
        )
    ]

    light: EmbodiedEnvCfg.EnvLightCfg = EmbodiedEnvCfg.EnvLightCfg(
        direct=[
            LightCfg(
                uid="point",
                light_type="point",
                color=(1.0, 1.0, 1.0),
                intensity=20.0,
                init_pos=(0, 0, 2),
            )
        ]
    )

    background: List[RigidObjectCfg] = [
        RigidObjectCfg(
            uid="table",
            shape=MeshCfg(

Multiple objects demonstrate different physics properties:

Table Configuration:

  • Shape: Custom PLY mesh with UV mapping

  • Physics: Kinematic body (movable but not affected by forces)

  • Material: Friction and restitution properties for realistic contact

Fork Configuration:

  • Shape: Detailed mesh from asset library

  • Scale: Proportionally scaled for scene consistency

  • Physics: Dynamic body affected by gravity and collisions

Articulated Objects

                compute_uv=True,
            ),
            attrs=RigidBodyAttributesCfg(
                mass=10.0,
                static_friction=0.95,
                dynamic_friction=0.85,
                restitution=0.01,
            ),
            body_type="kinematic",
            init_pos=(0.80, 0, 0.8),
            init_rot=(0, 90, 0),

Demonstrates complex mechanisms with moving parts:

  • URDF: Sliding drawer with joints and constraints

  • Positioning: Placed on table surface for interaction

Environment Implementation#

The actual environment class is remarkably simple due to the configuration-driven approach:

The envs.EmbodiedEnv base class automatically:

  • Loads all configured scene components

  • Sets up observation and action spaces

  • Initializes event and observation managers

  • Handles environment lifecycle (reset, step, etc.)

The Code Execution#

To run the modular environment:

cd /path/to/embodichain
python scripts/tutorials/gym/modular_env.py

The script demonstrates the complete workflow:

  1. Configuration: Creates an instance of ExampleCfg

  2. Registration: Uses the registered environment ID

  3. Execution: Runs episodes with zero actions to observe automatic behaviors

Manager System Benefits#

The manager-based architecture provides several key advantages:

Event Managers

  • Modularity: Reusable event functions across environments

  • Timing Control: Flexible scheduling (reset, interval, condition-based)

  • Parameter Binding: Type-safe configuration with validation

  • Extensibility: Easy to add custom event behaviors

Observation Managers

  • Flexible Data: Any simulation data can become an observation

  • Processing Pipeline: Built-in normalization and transformation

  • Dynamic Composition: Runtime observation space modification

  • Performance: Efficient data collection and GPU acceleration

Key Features Demonstrated#

This tutorial showcases the most advanced features of EmbodiChain environments:

  1. Configuration-Driven Design: Declarative environment specification

  2. Manager Systems: Modular event and observation handling

  3. Asset Management: Dynamic loading and randomization

  4. Sensor Integration: Realistic camera systems with stereo vision

  5. Physics Simulation: Complex articulated and rigid body dynamics

  6. Visual Randomization: Automated domain randomization

  7. Extensible Architecture: Easy customization and extension points

This tutorial demonstrates the full power of EmbodiChain’s modular environment system, providing the foundation for creating sophisticated robotic learning scenarios.

Tip

Using an AI coding agent? These skills can help you build on this tutorial:

  • /add-task-env — Scaffold a new task environment with the correct file structure, @register_env decorator, base class methods, __init__.py update, and test stub.

  • /add-functor — Add observation, reward, event, or randomization functors with the correct signature and module placement.