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 with default PhysX physics and default kit visualizer.
11 ./isaaclab.sh -p scripts/demos/multi_asset.py --num_envs 1024
12
13 # Usage with Newton visualizer and default PhysX physics.
14 ./isaaclab.sh -p scripts/demos/multi_asset.py --visualizer newton --num_envs 1024
15
16 # Usage with Newton (MJWarp) physics and default kit visualizer.
17 ./isaaclab.sh -p scripts/demos/multi_asset.py --physics newton_mjwarp --num_envs 1024
18
19 # Usage with Newton visualizer and Newton (MJWarp) physics.
20 ./isaaclab.sh -p scripts/demos/multi_asset.py --visualizer newton --physics newton_mjwarp --num_envs 1024
21
22"""
23
24from __future__ import annotations
25
26"""Parse CLI first so we can decide whether to launch Isaac Sim Kit."""
27
28import argparse
29from typing import TYPE_CHECKING
30
31from isaaclab.app import add_launcher_args, launch_simulation
32
33# add argparse arguments
34parser = argparse.ArgumentParser(
35 description="Demo on spawning different objects in multiple environments.",
36 conflict_handler="resolve",
37)
38parser.add_argument("--num_envs", type=int, default=512, help="Number of environments to spawn.")
39parser.add_argument("--physics", default="physx", choices=["physx"], help="Physics backend.")
40add_launcher_args(parser)
41# demos should open Kit visualizer by default
42parser.set_defaults(visualizer=["kit"])
43# parse the arguments
44args_cli = parser.parse_args()
45
46import isaaclab.sim as sim_utils
47
48##
49# Pre-defined configs
50##
51from isaaclab.assets import ArticulationCfg, AssetBaseCfg, RigidObjectCfg, RigidObjectCollectionCfg
52from isaaclab.physics import PhysicsCfg
53from isaaclab.scene import InteractiveSceneCfg
54
55from isaaclab_assets.robots.anymal import ANYDRIVE_3_LSTM_ACTUATOR_CFG # isort: skip
56
57from isaaclab.utils import Timer
58from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR
59from isaaclab.utils.configclass import configclass
60
61if TYPE_CHECKING:
62 from isaaclab.assets import Articulation, RigidObject, RigidObjectCollection
63 from isaaclab.scene import InteractiveScene
64
65
66# Visual material presets for the multi-asset variants.
67GREEN_MATERIAL = {"visual_material": sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0), metallic=0.2)}
68RED_MATERIAL = {"visual_material": sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2)}
69BLUE_MATERIAL = {"visual_material": sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0), metallic=0.2)}
70GOLD_MATERIAL = {"visual_material": sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.75, 0.0), metallic=0.2)}
71PURPLE_MATERIAL = {"visual_material": sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.0, 1.0), metallic=0.2)}
72OBJECT_PHYSICS = {
73 "rigid_props": sim_utils.RigidBodyPropertiesCfg(
74 solver_position_iteration_count=4, solver_velocity_iteration_count=0
75 ),
76 "mass_props": sim_utils.MassPropertiesCfg(mass=1.0),
77 "collision_props": sim_utils.CollisionPropertiesCfg(),
78}
79
80##
81# Scene Configuration
82##
83
84
85@configclass
86class MultiObjectSceneCfg(InteractiveSceneCfg):
87 """Configuration for a multi-object scene."""
88
89 # ground plane
90 ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
91
92 # lights
93 dome_light = AssetBaseCfg(
94 prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
95 )
96
97 # rigid object
98 object: RigidObjectCfg = RigidObjectCfg(
99 prim_path="/World/envs/env_.*/Object",
100 spawn=sim_utils.MultiAssetSpawnerCfg(
101 assets_cfg=[
102 sim_utils.CylinderCfg(radius=0.3, height=0.6, **GREEN_MATERIAL),
103 sim_utils.CuboidCfg(size=(0.3, 0.3, 0.3), **RED_MATERIAL),
104 sim_utils.SphereCfg(radius=0.3, **BLUE_MATERIAL),
105 sim_utils.CylinderCfg(radius=0.3, height=0.6, **GOLD_MATERIAL),
106 sim_utils.CuboidCfg(size=(0.3, 0.3, 0.3), **GOLD_MATERIAL),
107 sim_utils.SphereCfg(radius=0.3, **GOLD_MATERIAL),
108 sim_utils.CylinderCfg(radius=0.3, height=0.6, **PURPLE_MATERIAL),
109 sim_utils.CuboidCfg(size=(0.3, 0.3, 0.3), **PURPLE_MATERIAL),
110 sim_utils.SphereCfg(radius=0.3, **PURPLE_MATERIAL),
111 ],
112 random_choice=False,
113 **OBJECT_PHYSICS,
114 ),
115 init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)),
116 )
117
118 # object collection
119 object_collection: RigidObjectCollectionCfg = RigidObjectCollectionCfg(
120 rigid_objects={
121 "object_A": RigidObjectCfg(
122 prim_path="/World/envs/env_.*/Object_A",
123 spawn=sim_utils.SphereCfg(radius=0.1, **RED_MATERIAL, **OBJECT_PHYSICS),
124 init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.5, 2.0)),
125 ),
126 "object_B": RigidObjectCfg(
127 prim_path="/World/envs/env_.*/Object_B",
128 spawn=sim_utils.CuboidCfg(size=(0.1, 0.1, 0.1), **RED_MATERIAL, **OBJECT_PHYSICS),
129 init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.5, 2.0)),
130 ),
131 "object_C": RigidObjectCfg(
132 prim_path="/World/envs/env_.*/Object_C",
133 spawn=sim_utils.CylinderCfg(radius=0.1, height=0.3, **RED_MATERIAL, **OBJECT_PHYSICS),
134 init_state=RigidObjectCfg.InitialStateCfg(pos=(0.5, 0.0, 2.0)),
135 ),
136 }
137 )
138
139 # articulation
140 robot: ArticulationCfg = ArticulationCfg(
141 prim_path="/World/envs/env_.*/Robot",
142 spawn=sim_utils.MultiUsdFileCfg(
143 usd_path=[
144 f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-C/anymal_c.usd",
145 f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-D/anymal_d.usd",
146 ],
147 random_choice=False,
148 rigid_props=sim_utils.RigidBodyPropertiesCfg(
149 disable_gravity=False,
150 retain_accelerations=False,
151 linear_damping=0.0,
152 angular_damping=0.0,
153 max_linear_velocity=1000.0,
154 max_angular_velocity=1000.0,
155 max_depenetration_velocity=1.0,
156 ),
157 articulation_props=sim_utils.ArticulationRootPropertiesCfg(
158 enabled_self_collisions=True, solver_position_iteration_count=4, solver_velocity_iteration_count=0
159 ),
160 activate_contact_sensors=True,
161 ),
162 init_state=ArticulationCfg.InitialStateCfg(
163 pos=(0.0, 0.0, 0.6),
164 joint_pos={
165 ".*HAA": 0.0, # all HAA
166 ".*F_HFE": 0.4, # both front HFE
167 ".*H_HFE": -0.4, # both hind HFE
168 ".*F_KFE": -0.8, # both front KFE
169 ".*H_KFE": 0.8, # both hind KFE
170 },
171 ),
172 actuators={"legs": ANYDRIVE_3_LSTM_ACTUATOR_CFG},
173 )
174
175
176##
177# Simulation Loop
178##
179
180
181def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
182 """Runs the simulation loop."""
183 # Extract scene entities
184 # note: we only do this here for readability.
185 rigid_object: RigidObject = scene["object"]
186 rigid_object_collection: RigidObjectCollection = scene["object_collection"]
187 robot: Articulation = scene["robot"]
188 # Define simulation stepping
189 sim_dt = sim.get_physics_dt()
190 count = 0
191 # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton.
192 while sim.is_headless_or_exist_active_visualizer():
193 # Reset
194 if count % 250 == 0:
195 # reset counter
196 count = 0
197 # reset the scene entities
198 # object
199 root_pose = rigid_object.data.default_root_pose.torch.clone()
200 root_pose[:, :3] += scene.env_origins
201 rigid_object.write_root_pose_to_sim_index(root_pose=root_pose)
202 root_vel = rigid_object.data.default_root_vel.torch.clone()
203 rigid_object.write_root_velocity_to_sim_index(root_velocity=root_vel)
204 # object collection
205 default_pose_w = rigid_object_collection.data.default_body_pose.torch.clone()
206 default_pose_w[..., :3] += scene.env_origins.unsqueeze(1)
207 rigid_object_collection.write_body_pose_to_sim_index(body_poses=default_pose_w)
208 default_vel_w = rigid_object_collection.data.default_body_vel.torch.clone()
209 rigid_object_collection.write_body_com_velocity_to_sim_index(body_velocities=default_vel_w)
210 # robot
211 # -- root state
212 root_pose = robot.data.default_root_pose.torch.clone()
213 root_pose[:, :3] += scene.env_origins
214 robot.write_root_pose_to_sim_index(root_pose=root_pose)
215 root_vel = robot.data.default_root_vel.torch
216 robot.write_root_velocity_to_sim_index(root_velocity=root_vel)
217 # -- joint state
218 joint_pos = robot.data.default_joint_pos.torch
219 joint_vel = robot.data.default_joint_vel.torch
220 robot.write_joint_position_to_sim_index(position=joint_pos)
221 robot.write_joint_velocity_to_sim_index(velocity=joint_vel)
222 # clear internal buffers
223 scene.reset()
224 print("[INFO]: Resetting scene state...")
225
226 # Apply action to robot
227 robot.set_joint_position_target_index(target=robot.data.default_joint_pos.torch)
228 # Write data to sim
229 scene.write_data_to_sim()
230 # Perform step
231 sim.step()
232 # Increment counter
233 count += 1
234 # Update buffers
235 scene.update(sim_dt)
236
237
238def main():
239 """Main function."""
240 with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg:
241 sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device, physics=physics_cfg)
242 sim = sim_utils.SimulationContext(sim_cfg)
243 # Set main camera
244 sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0])
245
246 # Design scene
247 scene_cfg = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0, replicate_physics=True)
248 if args_cli.physics == "newton_mjwarp":
249 # Newton views currently require a uniform body layout across worlds.
250 scene_cfg.object.spawn.assets_cfg = scene_cfg.object.spawn.assets_cfg[1:2]
251 scene_cfg.robot.spawn.usd_path = scene_cfg.robot.spawn.usd_path[0]
252 with Timer("[INFO] Time to create scene: "):
253 scene = scene_cfg.class_type(scene_cfg)
254
255 # Play the simulator
256 sim.reset()
257 # Now we are ready!
258 print("[INFO]: Setup complete...")
259 # Run the simulator
260 run_simulator(sim, scene)
261
262
263if __name__ == "__main__":
264 # run the main execution
265 main()
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.
),
}
)
# 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=False,
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},
)
##
# Simulation Loop
##
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:sim_utils.SphereCfg(radius=0.3, **GOLD_MATERIAL), sim_utils.CylinderCfg(radius=0.3, height=0.6, **PURPLE_MATERIAL), sim_utils.CuboidCfg(size=(0.3, 0.3, 0.3), **PURPLE_MATERIAL), sim_utils.SphereCfg(radius=0.3, **PURPLE_MATERIAL), ], random_choice=False, **OBJECT_PHYSICS, ), 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, **RED_MATERIAL, **OBJECT_PHYSICS), 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), **RED_MATERIAL, **OBJECT_PHYSICS), init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.5, 2.0)), ), "object_C": RigidObjectCfg( prim_path="/World/envs/env_.*/Object_C", spawn=sim_utils.CylinderCfg(radius=0.1, height=0.3, **RED_MATERIAL, **OBJECT_PHYSICS),
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:"""Runs the simulation loop.""" # Extract scene entities # note: we only do this here for readability. rigid_object: RigidObject = scene["object"] rigid_object_collection: RigidObjectCollection = scene["object_collection"] robot: Articulation = scene["robot"] # Define simulation stepping sim_dt = sim.get_physics_dt() count = 0 # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. while sim.is_headless_or_exist_active_visualizer(): # Reset if count % 250 == 0: # reset counter count = 0 # reset the scene entities # object root_pose = rigid_object.data.default_root_pose.torch.clone() root_pose[:, :3] += scene.env_origins rigid_object.write_root_pose_to_sim_index(root_pose=root_pose) root_vel = rigid_object.data.default_root_vel.torch.clone() rigid_object.write_root_velocity_to_sim_index(root_velocity=root_vel) # object collection default_pose_w = rigid_object_collection.data.default_body_pose.torch.clone() default_pose_w[..., :3] += scene.env_origins.unsqueeze(1) rigid_object_collection.write_body_pose_to_sim_index(body_poses=default_pose_w) default_vel_w = rigid_object_collection.data.default_body_vel.torch.clone() rigid_object_collection.write_body_com_velocity_to_sim_index(body_velocities=default_vel_w) # robot # -- root state root_pose = robot.data.default_root_pose.torch.clone() root_pose[:, :3] += scene.env_origins robot.write_root_pose_to_sim_index(root_pose=root_pose) root_vel = robot.data.default_root_vel.torch
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.
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 when the spawned assets
do not share the same structure.
For a full guide on the template-based cloning system including strategies and collision filtering,
see Cloning Environments.
scene_cfg = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0, replicate_physics=True)
if args_cli.physics == "newton_mjwarp":
# Newton views currently require a uniform body layout across worlds.
scene_cfg.object.spawn.assets_cfg = scene_cfg.object.spawn.assets_cfg[1:2]
scene_cfg.robot.spawn.usd_path = scene_cfg.robot.spawn.usd_path[0]
The Code Execution#
To execute the script with multiple environments and randomized assets, use the following command:
python 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.