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