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

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(),
            ),
            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.

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.