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/scripts/demos
directory.
Code for multi_asset.py
1# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2# All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5
6# Copyright (c) 2022-2025, The Isaac Lab Project Developers.
7# All rights reserved.
8#
9# SPDX-License-Identifier: BSD-3-Clause
10
11"""This script demonstrates how to spawn multiple objects in multiple environments.
12
13.. code-block:: bash
14
15 # Usage
16 ./isaaclab.sh -p scripts/demos/multi_asset.py --num_envs 2048
17
18"""
19
20from __future__ import annotations
21
22"""Launch Isaac Sim Simulator first."""
23
24
25import argparse
26
27from isaaclab.app import AppLauncher
28
29# add argparse arguments
30parser = argparse.ArgumentParser(description="Demo on spawning different objects in multiple environments.")
31parser.add_argument("--num_envs", type=int, default=512, help="Number of environments to spawn.")
32# append AppLauncher cli args
33AppLauncher.add_app_launcher_args(parser)
34# parse the arguments
35args_cli = parser.parse_args()
36
37# launch omniverse app
38app_launcher = AppLauncher(args_cli)
39simulation_app = app_launcher.app
40
41"""Rest everything follows."""
42
43import random
44
45import omni.usd
46from pxr import Gf, Sdf
47
48import isaaclab.sim as sim_utils
49from isaaclab.assets import (
50 Articulation,
51 ArticulationCfg,
52 AssetBaseCfg,
53 RigidObject,
54 RigidObjectCfg,
55 RigidObjectCollection,
56 RigidObjectCollectionCfg,
57)
58from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
59from isaaclab.sim import SimulationContext
60from isaaclab.utils import Timer, configclass
61from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR
62
63##
64# Pre-defined Configuration
65##
66
67from isaaclab_assets.robots.anymal import ANYDRIVE_3_LSTM_ACTUATOR_CFG # isort: skip
68
69
70##
71# Randomization events.
72##
73
74
75def randomize_shape_color(prim_path_expr: str):
76 """Randomize the color of the geometry."""
77 # acquire stage
78 stage = omni.usd.get_context().get_stage()
79 # resolve prim paths for spawning and cloning
80 prim_paths = sim_utils.find_matching_prim_paths(prim_path_expr)
81 # manually clone prims if the source prim path is a regex expression
82 with Sdf.ChangeBlock():
83 for prim_path in prim_paths:
84 # spawn single instance
85 prim_spec = Sdf.CreatePrimInLayer(stage.GetRootLayer(), prim_path)
86
87 # DO YOUR OWN OTHER KIND OF RANDOMIZATION HERE!
88 # Note: Just need to acquire the right attribute about the property you want to set
89 # Here is an example on setting color randomly
90 color_spec = prim_spec.GetAttributeAtPath(prim_path + "/geometry/material/Shader.inputs:diffuseColor")
91 color_spec.default = Gf.Vec3f(random.random(), random.random(), random.random())
92
93
94##
95# Scene Configuration
96##
97
98
99@configclass
100class MultiObjectSceneCfg(InteractiveSceneCfg):
101 """Configuration for a multi-object scene."""
102
103 # ground plane
104 ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
105
106 # lights
107 dome_light = AssetBaseCfg(
108 prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
109 )
110
111 # rigid object
112 object: RigidObjectCfg = RigidObjectCfg(
113 prim_path="/World/envs/env_.*/Object",
114 spawn=sim_utils.MultiAssetSpawnerCfg(
115 assets_cfg=[
116 sim_utils.ConeCfg(
117 radius=0.3,
118 height=0.6,
119 visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0), metallic=0.2),
120 ),
121 sim_utils.CuboidCfg(
122 size=(0.3, 0.3, 0.3),
123 visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
124 ),
125 sim_utils.SphereCfg(
126 radius=0.3,
127 visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0), metallic=0.2),
128 ),
129 ],
130 random_choice=True,
131 rigid_props=sim_utils.RigidBodyPropertiesCfg(
132 solver_position_iteration_count=4, solver_velocity_iteration_count=0
133 ),
134 mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
135 collision_props=sim_utils.CollisionPropertiesCfg(),
136 ),
137 init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)),
138 )
139
140 # object collection
141 object_collection: RigidObjectCollectionCfg = RigidObjectCollectionCfg(
142 rigid_objects={
143 "object_A": RigidObjectCfg(
144 prim_path="/World/envs/env_.*/Object_A",
145 spawn=sim_utils.SphereCfg(
146 radius=0.1,
147 visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
148 rigid_props=sim_utils.RigidBodyPropertiesCfg(
149 solver_position_iteration_count=4, solver_velocity_iteration_count=0
150 ),
151 mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
152 collision_props=sim_utils.CollisionPropertiesCfg(),
153 ),
154 init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.5, 2.0)),
155 ),
156 "object_B": RigidObjectCfg(
157 prim_path="/World/envs/env_.*/Object_B",
158 spawn=sim_utils.CuboidCfg(
159 size=(0.1, 0.1, 0.1),
160 visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
161 rigid_props=sim_utils.RigidBodyPropertiesCfg(
162 solver_position_iteration_count=4, solver_velocity_iteration_count=0
163 ),
164 mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
165 collision_props=sim_utils.CollisionPropertiesCfg(),
166 ),
167 init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.5, 2.0)),
168 ),
169 "object_C": RigidObjectCfg(
170 prim_path="/World/envs/env_.*/Object_C",
171 spawn=sim_utils.ConeCfg(
172 radius=0.1,
173 height=0.3,
174 visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2),
175 rigid_props=sim_utils.RigidBodyPropertiesCfg(
176 solver_position_iteration_count=4, solver_velocity_iteration_count=0
177 ),
178 mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
179 collision_props=sim_utils.CollisionPropertiesCfg(),
180 ),
181 init_state=RigidObjectCfg.InitialStateCfg(pos=(0.5, 0.0, 2.0)),
182 ),
183 }
184 )
185
186 # articulation
187 robot: ArticulationCfg = ArticulationCfg(
188 prim_path="/World/envs/env_.*/Robot",
189 spawn=sim_utils.MultiUsdFileCfg(
190 usd_path=[
191 f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-C/anymal_c.usd",
192 f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-D/anymal_d.usd",
193 ],
194 random_choice=True,
195 rigid_props=sim_utils.RigidBodyPropertiesCfg(
196 disable_gravity=False,
197 retain_accelerations=False,
198 linear_damping=0.0,
199 angular_damping=0.0,
200 max_linear_velocity=1000.0,
201 max_angular_velocity=1000.0,
202 max_depenetration_velocity=1.0,
203 ),
204 articulation_props=sim_utils.ArticulationRootPropertiesCfg(
205 enabled_self_collisions=True, solver_position_iteration_count=4, solver_velocity_iteration_count=0
206 ),
207 activate_contact_sensors=True,
208 ),
209 init_state=ArticulationCfg.InitialStateCfg(
210 pos=(0.0, 0.0, 0.6),
211 joint_pos={
212 ".*HAA": 0.0, # all HAA
213 ".*F_HFE": 0.4, # both front HFE
214 ".*H_HFE": -0.4, # both hind HFE
215 ".*F_KFE": -0.8, # both front KFE
216 ".*H_KFE": 0.8, # both hind KFE
217 },
218 ),
219 actuators={"legs": ANYDRIVE_3_LSTM_ACTUATOR_CFG},
220 )
221
222
223##
224# Simulation Loop
225##
226
227
228def run_simulator(sim: SimulationContext, scene: InteractiveScene):
229 """Runs the simulation loop."""
230 # Extract scene entities
231 # note: we only do this here for readability.
232 rigid_object: RigidObject = scene["object"]
233 rigid_object_collection: RigidObjectCollection = scene["object_collection"]
234 robot: Articulation = scene["robot"]
235 # Define simulation stepping
236 sim_dt = sim.get_physics_dt()
237 count = 0
238 # Simulation loop
239 while simulation_app.is_running():
240 # Reset
241 if count % 250 == 0:
242 # reset counter
243 count = 0
244 # reset the scene entities
245 # object
246 root_state = rigid_object.data.default_root_state.clone()
247 root_state[:, :3] += scene.env_origins
248 rigid_object.write_root_pose_to_sim(root_state[:, :7])
249 rigid_object.write_root_velocity_to_sim(root_state[:, 7:])
250 # object collection
251 object_state = rigid_object_collection.data.default_object_state.clone()
252 object_state[..., :3] += scene.env_origins.unsqueeze(1)
253 rigid_object_collection.write_object_link_pose_to_sim(object_state[..., :7])
254 rigid_object_collection.write_object_com_velocity_to_sim(object_state[..., 7:])
255 # robot
256 # -- root state
257 root_state = robot.data.default_root_state.clone()
258 root_state[:, :3] += scene.env_origins
259 robot.write_root_pose_to_sim(root_state[:, :7])
260 robot.write_root_velocity_to_sim(root_state[:, 7:])
261 # -- joint state
262 joint_pos, joint_vel = robot.data.default_joint_pos.clone(), robot.data.default_joint_vel.clone()
263 robot.write_joint_state_to_sim(joint_pos, joint_vel)
264 # clear internal buffers
265 scene.reset()
266 print("[INFO]: Resetting scene state...")
267
268 # Apply action to robot
269 robot.set_joint_position_target(robot.data.default_joint_pos)
270 # Write data to sim
271 scene.write_data_to_sim()
272 # Perform step
273 sim.step()
274 # Increment counter
275 count += 1
276 # Update buffers
277 scene.update(sim_dt)
278
279
280def main():
281 """Main function."""
282 # Load kit helper
283 sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device)
284 sim = SimulationContext(sim_cfg)
285 # Set main camera
286 sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0])
287
288 # Design scene
289 scene_cfg = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0, replicate_physics=False)
290 with Timer("[INFO] Time to create scene: "):
291 scene = InteractiveScene(scene_cfg)
292
293 with Timer("[INFO] Time to randomize scene: "):
294 # DO YOUR OWN OTHER KIND OF RANDOMIZATION HERE!
295 # Note: Just need to acquire the right attribute about the property you want to set
296 # Here is an example on setting color randomly
297 randomize_shape_color(scene_cfg.object.prim_path)
298
299 # Play the simulator
300 sim.reset()
301 # Now we are ready!
302 print("[INFO]: Setup complete...")
303 # Run the simulator
304 run_simulator(sim, scene)
305
306
307if __name__ == "__main__":
308 # run the main execution
309 main()
310 # close sim app
311 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.
collision_props=sim_utils.CollisionPropertiesCfg(),
),
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)),
)
# 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(),
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
:dome_light = AssetBaseCfg( prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75)) ) # rigid object 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 ),
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
:), } ) # articulation 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
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.
def main():
"""Main function."""
# Load kit helper
sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device)
The Code Execution#
To execute the script with multiple environments and randomized assets, use the following command:
./isaaclab.sh -p scripts/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.