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