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/source/standalone/demos directory.

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

    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(),
        ),
        init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.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:

    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
            },
        ),
        actuators={"legs": ANYDRIVE_3_LSTM_ACTUATOR_CFG},
    )
    

    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.

# Set main camera
sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0])

# Design scene

The Code Execution#

To execute the script with multiple environments and randomized assets, use the following command:

./isaaclab.sh -p source/standalone/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.