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

)

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

    
    # 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_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.

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