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:

  1. 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.

  2. 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

result of multi_asset.py

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 RigidObjectCfg to be MultiAssetSpawnerCfg:

        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_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 be MultiUsdFileCfg:

        }
    )
    
    # 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.