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

        collision_props=sim_utils.CollisionPropertiesCfg(),
    ),
    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:

    dome_light = AssetBaseCfg(
        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
            ),
    

    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
    

    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.

def main():
    """Main function."""
    # Load kit helper
    sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device)

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.