Spawning Multiple Assets#
Typical spawning configurations (introduced in the Spawning prims into the scene tutorial) copy the same asset (or USD primitive) across the different resolved prim paths from the expressions. For instance, if the user specifies to spawn the asset at “/World/Table_.*/Object”, the same asset is created at the paths “/World/Table_0/Object”, “/World/Table_1/Object” and so on.
However, we also support multi-asset spawning with two mechanisms:
Rigid object collections. This allows the user to spawn multiple rigid objects in each environment and access/modify them with a unified API, improving performance.
Spawning different assets under the same prim path. This allows the user to create diverse simulations, where each environment has a different asset.
This guide describes how to use these two mechanisms.
The sample script multi_asset.py
is used as a reference, located in the
IsaacLab/source/standalone/demos
directory.
Code for multi_asset.py
1# Copyright (c) 2022-2024, The Isaac Lab Project Developers.
2# All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5
6"""This script demonstrates how to spawn multiple objects in multiple environments.
7
8.. code-block:: bash
9
10 # Usage
11 ./isaaclab.sh -p source/standalone/demos/multi_asset.py --num_envs 2048
12
13"""
14
15from __future__ import annotations
16
17"""Launch Isaac Sim Simulator first."""
18
19
20import argparse
21
22from omni.isaac.lab.app import AppLauncher
23
24# add argparse arguments
25parser = argparse.ArgumentParser(description="Demo on spawning different objects in multiple environments.")
26parser.add_argument("--num_envs", type=int, default=512, help="Number of environments to spawn.")
27# append AppLauncher cli args
28AppLauncher.add_app_launcher_args(parser)
29# parse the arguments
30args_cli = parser.parse_args()
31
32# launch omniverse app
33app_launcher = AppLauncher(args_cli)
34simulation_app = app_launcher.app
35
36"""Rest everything follows."""
37
38import random
39
40import omni.usd
41from pxr import Gf, Sdf
42
43import omni.isaac.lab.sim as sim_utils
44from omni.isaac.lab.assets import (
45 Articulation,
46 ArticulationCfg,
47 AssetBaseCfg,
48 RigidObject,
49 RigidObjectCfg,
50 RigidObjectCollection,
51 RigidObjectCollectionCfg,
52)
53from omni.isaac.lab.scene import InteractiveScene, InteractiveSceneCfg
54from omni.isaac.lab.sim import SimulationContext
55from omni.isaac.lab.utils import Timer, configclass
56from omni.isaac.lab.utils.assets import ISAACLAB_NUCLEUS_DIR
57
58##
59# Pre-defined Configuration
60##
61
62from omni.isaac.lab_assets.anymal import ANYDRIVE_3_LSTM_ACTUATOR_CFG # isort: skip
63
64
65##
66# Randomization events.
67##
68
69
70def randomize_shape_color(prim_path_expr: str):
71 """Randomize the color of the geometry."""
72 # acquire stage
73 stage = omni.usd.get_context().get_stage()
74 # resolve prim paths for spawning and cloning
75 prim_paths = sim_utils.find_matching_prim_paths(prim_path_expr)
76 # manually clone prims if the source prim path is a regex expression
77 with Sdf.ChangeBlock():
78 for prim_path in prim_paths:
79 # spawn single instance
80 prim_spec = Sdf.CreatePrimInLayer(stage.GetRootLayer(), prim_path)
81
82 # DO YOUR OWN OTHER KIND OF RANDOMIZATION HERE!
83 # Note: Just need to acquire the right attribute about the property you want to set
84 # Here is an example on setting color randomly
85 color_spec = prim_spec.GetAttributeAtPath(prim_path + "/geometry/material/Shader.inputs:diffuseColor")
86 color_spec.default = Gf.Vec3f(random.random(), random.random(), random.random())
87
88
89##
90# Scene Configuration
91##
92
93
94@configclass
95class MultiObjectSceneCfg(InteractiveSceneCfg):
96 """Configuration for a multi-object scene."""
97
98 # ground plane
99 ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
100
101 # lights
102 dome_light = AssetBaseCfg(
103 prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
104 )
105
106 # rigid object
107 object: RigidObjectCfg = RigidObjectCfg(
108 prim_path="/World/envs/env_.*/Object",
109 spawn=sim_utils.MultiAssetSpawnerCfg(
110 assets_cfg=[
111 sim_utils.ConeCfg(
112 radius=0.3,
113 height=0.6,
114 visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0), metallic=0.2),
115 ),
116 sim_utils.CuboidCfg(
117 size=(0.3, 0.3, 0.3),
118 visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
119 ),
120 sim_utils.SphereCfg(
121 radius=0.3,
122 visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0), metallic=0.2),
123 ),
124 ],
125 random_choice=True,
126 rigid_props=sim_utils.RigidBodyPropertiesCfg(
127 solver_position_iteration_count=4, solver_velocity_iteration_count=0
128 ),
129 mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
130 collision_props=sim_utils.CollisionPropertiesCfg(),
131 ),
132 init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)),
133 )
134
135 # object collection
136 object_collection: RigidObjectCollectionCfg = RigidObjectCollectionCfg(
137 rigid_objects={
138 "object_A": RigidObjectCfg(
139 prim_path="/World/envs/env_.*/Object_A",
140 spawn=sim_utils.SphereCfg(
141 radius=0.1,
142 visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
143 rigid_props=sim_utils.RigidBodyPropertiesCfg(
144 solver_position_iteration_count=4, solver_velocity_iteration_count=0
145 ),
146 mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
147 collision_props=sim_utils.CollisionPropertiesCfg(),
148 ),
149 init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.5, 2.0)),
150 ),
151 "object_B": RigidObjectCfg(
152 prim_path="/World/envs/env_.*/Object_B",
153 spawn=sim_utils.CuboidCfg(
154 size=(0.1, 0.1, 0.1),
155 visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
156 rigid_props=sim_utils.RigidBodyPropertiesCfg(
157 solver_position_iteration_count=4, solver_velocity_iteration_count=0
158 ),
159 mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
160 collision_props=sim_utils.CollisionPropertiesCfg(),
161 ),
162 init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.5, 2.0)),
163 ),
164 "object_C": RigidObjectCfg(
165 prim_path="/World/envs/env_.*/Object_C",
166 spawn=sim_utils.ConeCfg(
167 radius=0.1,
168 height=0.3,
169 visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
170 rigid_props=sim_utils.RigidBodyPropertiesCfg(
171 solver_position_iteration_count=4, solver_velocity_iteration_count=0
172 ),
173 mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
174 collision_props=sim_utils.CollisionPropertiesCfg(),
175 ),
176 init_state=RigidObjectCfg.InitialStateCfg(pos=(0.5, 0.0, 2.0)),
177 ),
178 }
179 )
180
181 # articulation
182 robot: ArticulationCfg = ArticulationCfg(
183 prim_path="/World/envs/env_.*/Robot",
184 spawn=sim_utils.MultiUsdFileCfg(
185 usd_path=[
186 f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-C/anymal_c.usd",
187 f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-D/anymal_d.usd",
188 ],
189 random_choice=True,
190 rigid_props=sim_utils.RigidBodyPropertiesCfg(
191 disable_gravity=False,
192 retain_accelerations=False,
193 linear_damping=0.0,
194 angular_damping=0.0,
195 max_linear_velocity=1000.0,
196 max_angular_velocity=1000.0,
197 max_depenetration_velocity=1.0,
198 ),
199 articulation_props=sim_utils.ArticulationRootPropertiesCfg(
200 enabled_self_collisions=True, solver_position_iteration_count=4, solver_velocity_iteration_count=0
201 ),
202 activate_contact_sensors=True,
203 ),
204 init_state=ArticulationCfg.InitialStateCfg(
205 pos=(0.0, 0.0, 0.6),
206 joint_pos={
207 ".*HAA": 0.0, # all HAA
208 ".*F_HFE": 0.4, # both front HFE
209 ".*H_HFE": -0.4, # both hind HFE
210 ".*F_KFE": -0.8, # both front KFE
211 ".*H_KFE": 0.8, # both hind KFE
212 },
213 ),
214 actuators={"legs": ANYDRIVE_3_LSTM_ACTUATOR_CFG},
215 )
216
217
218##
219# Simulation Loop
220##
221
222
223def run_simulator(sim: SimulationContext, scene: InteractiveScene):
224 """Runs the simulation loop."""
225 # Extract scene entities
226 # note: we only do this here for readability.
227 rigid_object: RigidObject = scene["object"]
228 rigid_object_collection: RigidObjectCollection = scene["object_collection"]
229 robot: Articulation = scene["robot"]
230 # Define simulation stepping
231 sim_dt = sim.get_physics_dt()
232 count = 0
233 # Simulation loop
234 while simulation_app.is_running():
235 # Reset
236 if count % 250 == 0:
237 # reset counter
238 count = 0
239 # reset the scene entities
240 # object
241 root_state = rigid_object.data.default_root_state.clone()
242 root_state[:, :3] += scene.env_origins
243 rigid_object.write_root_state_to_sim(root_state)
244 # object collection
245 object_state = rigid_object_collection.data.default_object_state.clone()
246 object_state[..., :3] += scene.env_origins.unsqueeze(1)
247 rigid_object_collection.write_object_state_to_sim(object_state)
248 # robot
249 # -- root state
250 root_state = robot.data.default_root_state.clone()
251 root_state[:, :3] += scene.env_origins
252 robot.write_root_state_to_sim(root_state)
253 # -- joint state
254 joint_pos, joint_vel = robot.data.default_joint_pos.clone(), robot.data.default_joint_vel.clone()
255 robot.write_joint_state_to_sim(joint_pos, joint_vel)
256 # clear internal buffers
257 scene.reset()
258 print("[INFO]: Resetting scene state...")
259
260 # Apply action to robot
261 robot.set_joint_position_target(robot.data.default_joint_pos)
262 # Write data to sim
263 scene.write_data_to_sim()
264 # Perform step
265 sim.step()
266 # Increment counter
267 count += 1
268 # Update buffers
269 scene.update(sim_dt)
270
271
272def main():
273 """Main function."""
274 # Load kit helper
275 sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device)
276 sim = SimulationContext(sim_cfg)
277 # Set main camera
278 sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0])
279
280 # Design scene
281 scene_cfg = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0, replicate_physics=False)
282 with Timer("[INFO] Time to create scene: "):
283 scene = InteractiveScene(scene_cfg)
284
285 with Timer("[INFO] Time to randomize scene: "):
286 # DO YOUR OWN OTHER KIND OF RANDOMIZATION HERE!
287 # Note: Just need to acquire the right attribute about the property you want to set
288 # Here is an example on setting color randomly
289 randomize_shape_color(scene_cfg.object.prim_path)
290
291 # Play the simulator
292 sim.reset()
293 # Now we are ready!
294 print("[INFO]: Setup complete...")
295 # Run the simulator
296 run_simulator(sim, scene)
297
298
299if __name__ == "__main__":
300 # run the main execution
301 main()
302 # close sim app
303 simulation_app.close()
This script creates multiple environments, where each environment has:
a rigid object collection containing a cone, a cube, and a sphere
a rigid object that is either a cone, a cube, or a sphere, chosen at random
an articulation that is either the ANYmal-C or ANYmal-D robot, chosen at random
Rigid Object Collections#
Multiple rigid objects can be spawned in each environment and accessed/modified with a unified (env_ids, obj_ids)
API.
While the user could also create multiple rigid objects by spawning them individually, the API is more user-friendly and
more efficient since it uses a single physics view under the hood to handle all the objects.
# object collection
object_collection: RigidObjectCollectionCfg = RigidObjectCollectionCfg(
rigid_objects={
"object_A": RigidObjectCfg(
prim_path="/World/envs/env_.*/Object_A",
spawn=sim_utils.SphereCfg(
radius=0.1,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
rigid_props=sim_utils.RigidBodyPropertiesCfg(
solver_position_iteration_count=4, solver_velocity_iteration_count=0
),
mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
collision_props=sim_utils.CollisionPropertiesCfg(),
),
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.5, 2.0)),
),
"object_B": RigidObjectCfg(
prim_path="/World/envs/env_.*/Object_B",
spawn=sim_utils.CuboidCfg(
size=(0.1, 0.1, 0.1),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
rigid_props=sim_utils.RigidBodyPropertiesCfg(
solver_position_iteration_count=4, solver_velocity_iteration_count=0
),
mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
collision_props=sim_utils.CollisionPropertiesCfg(),
),
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.5, 2.0)),
),
"object_C": RigidObjectCfg(
prim_path="/World/envs/env_.*/Object_C",
spawn=sim_utils.ConeCfg(
radius=0.1,
height=0.3,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
rigid_props=sim_utils.RigidBodyPropertiesCfg(
solver_position_iteration_count=4, solver_velocity_iteration_count=0
),
mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
collision_props=sim_utils.CollisionPropertiesCfg(),
),
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.5, 0.0, 2.0)),
),
}
)
The configuration RigidObjectCollectionCfg
is used to create the collection. It’s attribute rigid_objects
is a dictionary containing RigidObjectCfg
objects. The keys serve as unique identifiers for each
rigid object in the collection.
Spawning different assets under the same prim path#
It is possible to spawn different assets and USDs under the same prim path in each environment using the spawners
MultiAssetSpawnerCfg
and MultiUsdFileCfg
:
We set the spawn configuration in
RigidObjectCfg
to beMultiAssetSpawnerCfg
:object: RigidObjectCfg = RigidObjectCfg( prim_path="/World/envs/env_.*/Object", spawn=sim_utils.MultiAssetSpawnerCfg( assets_cfg=[ sim_utils.ConeCfg( radius=0.3, height=0.6, visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0), metallic=0.2), ), sim_utils.CuboidCfg( size=(0.3, 0.3, 0.3), visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2), ), sim_utils.SphereCfg( radius=0.3, visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0), metallic=0.2), ), ], random_choice=True, rigid_props=sim_utils.RigidBodyPropertiesCfg( solver_position_iteration_count=4, solver_velocity_iteration_count=0 ), mass_props=sim_utils.MassPropertiesCfg(mass=1.0), collision_props=sim_utils.CollisionPropertiesCfg(), ), init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)), )
This function allows you to define a list of different assets that can be spawned as rigid objects. When
random_choice
is set to True, one asset from the list is randomly selected and spawned at the specified prim path.Similarly, we set the spawn configuration in
ArticulationCfg
to beMultiUsdFileCfg
:robot: ArticulationCfg = ArticulationCfg( prim_path="/World/envs/env_.*/Robot", spawn=sim_utils.MultiUsdFileCfg( usd_path=[ f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-C/anymal_c.usd", f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-D/anymal_d.usd", ], random_choice=True, rigid_props=sim_utils.RigidBodyPropertiesCfg( disable_gravity=False, retain_accelerations=False, linear_damping=0.0, angular_damping=0.0, max_linear_velocity=1000.0, max_angular_velocity=1000.0, max_depenetration_velocity=1.0, ), articulation_props=sim_utils.ArticulationRootPropertiesCfg( enabled_self_collisions=True, solver_position_iteration_count=4, solver_velocity_iteration_count=0 ), activate_contact_sensors=True, ), init_state=ArticulationCfg.InitialStateCfg( pos=(0.0, 0.0, 0.6), joint_pos={ ".*HAA": 0.0, # all HAA ".*F_HFE": 0.4, # both front HFE ".*H_HFE": -0.4, # both hind HFE ".*F_KFE": -0.8, # both front KFE ".*H_KFE": 0.8, # both hind KFE }, ), actuators={"legs": ANYDRIVE_3_LSTM_ACTUATOR_CFG}, )
Similar to before, this configuration allows the selection of different USD files representing articulated assets.
Things to Note#
Similar asset structuring#
While spawning and handling multiple assets using the same physics interface (the rigid object or articulation classes), it is essential to have the assets at all the prim locations follow a similar structure. In case of an articulation, this means that they all must have the same number of links and joints, the same number of collision bodies and the same names for them. If that is not the case, the physics parsing of the prims can get affected and fail.
The main purpose of this functionality is to enable the user to create randomized versions of the same asset, for example robots with different link lengths, or rigid objects with different collider shapes.
Disabling physics replication in interactive scene#
By default, the flag scene.InteractiveScene.replicate_physics
is set to True. This flag informs the physics
engine that the simulation environments are copies of one another so it just needs to parse the first environment
to understand the entire simulation scene. This helps speed up the simulation scene parsing.
However, in the case of spawning different assets in different environments, this assumption does not hold
anymore. Hence the flag scene.InteractiveScene.replicate_physics
must be disabled.
# Design scene
scene_cfg = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0, replicate_physics=False)
with Timer("[INFO] Time to create scene: "):
scene = InteractiveScene(scene_cfg)
The Code Execution#
To execute the script with multiple environments and randomized assets, use the following command:
./isaaclab.sh -p source/standalone/demos/multi_asset.py --num_envs 2048
This command runs the simulation with 2048 environments, each with randomly selected assets.
To stop the simulation, you can close the window, or press Ctrl+C
in the terminal.