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:
Mode:
"reset"- triggers at environment resetPurpose: Randomly selects different fork models from a folder
Light Randomization Event
The randomize_light event creates dynamic lighting conditions:
Function:
envs.managers.randomization.rendering.randomize_light()Mode:
"interval"- triggers every 5 stepsParameters: 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 stepsFeatures: 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:
Function:
envs.managers.observations.get_rigid_object_pose()Mode:
"add"- appends data to observation dictionaryName: Custom key for the observation data
Target: Tracks the fork object’s pose in the scene
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:
Configuration: Creates an instance of
ExampleCfgRegistration: Uses the registered environment ID
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:
Configuration-Driven Design: Declarative environment specification
Manager Systems: Modular event and observation handling
Asset Management: Dynamic loading and randomization
Sensor Integration: Realistic camera systems with stereo vision
Physics Simulation: Complex articulated and rigid body dynamics
Visual Randomization: Automated domain randomization
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_envdecorator, base class methods,__init__.pyupdate, and test stub./add-functor — Add observation, reward, event, or randomization functors with the correct signature and module placement.