生成多个资产#

典型的生成配置(见 生成基本物体到场景中 教程)会将同一个资产复制到表达式解析出的所有 prim 路径。多资产工作流满足两种相关的组合需求:

  1. 刚性物体集合将每个环境中的多个刚性物体批量组织起来,通过统一的数据和命令 API 访问。

  2. 多资产生成器为一个场景资产绑定声明多个变体,使不同环境可包含不同几何体或机器人变体。

本指南演示这两种机制,并解释它们在 PhysX 和 Newton 中的执行差异。

打包提供的 multi-asset 示例包含参考脚本 multi_asset.py 。

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"""Spawn several asset types across cloned environments.
  7
  8.. code-block:: bash
  9
 10    uvx isaaclab example multi-asset --num_envs 1024
 11"""
 12
 13from __future__ import annotations
 14
 15import argparse
 16from typing import TYPE_CHECKING
 17
 18from isaaclab.app import add_launcher_args, launch_simulation
 19
 20parser = argparse.ArgumentParser(
 21    description="Example of spawning different objects in multiple environments.",
 22    conflict_handler="resolve",
 23)
 24parser.add_argument("--num_envs", type=int, default=512, help="Number of environments to spawn.")
 25parser.add_argument(
 26    "--physics", default="newton_mjwarp", choices=["isaacsim_physx", "newton_mjwarp"], help="Physics backend."
 27)
 28parser.add_argument("--max_steps", type=int, default=-1, help="Stop after this many steps; negative runs forever.")
 29add_launcher_args(parser)
 30parser.set_defaults(visualizer=["newton_gl"])
 31args_cli = parser.parse_args()
 32if args_cli.max_steps == 0 or args_cli.max_steps < -1:
 33    parser.error("--max_steps must be positive or -1.")
 34
 35from isaaclab_newton.sim.schemas import NewtonArticulationCfg
 36from isaaclab_physx.sim.schemas import PhysxArticulationCfg, PhysxRigidBodyCfg
 37
 38import isaaclab.sim as sim_utils
 39from isaaclab.assets import ArticulationCfg, AssetBaseCfg, RigidObjectCfg, RigidObjectCollectionCfg
 40from isaaclab.scene import InteractiveSceneCfg
 41
 42from isaaclab_assets.robots.anymal import ANYDRIVE_3_LSTM_ACTUATOR_CFG  # isort: skip
 43
 44from isaaclab.utils import Timer, configclass, instantiate
 45from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR
 46
 47if TYPE_CHECKING:
 48    from isaaclab.assets import Articulation, RigidObject, RigidObjectCollection
 49    from isaaclab.scene import InteractiveScene
 50
 51
 52# Visual material presets for the multi-asset variants.
 53GREEN_MATERIAL = {"visual_material": sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0), metallic=0.2)}
 54RED_MATERIAL = {"visual_material": sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0), metallic=0.2)}
 55BLUE_MATERIAL = {"visual_material": sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0), metallic=0.2)}
 56GOLD_MATERIAL = {"visual_material": sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.75, 0.0), metallic=0.2)}
 57PURPLE_MATERIAL = {"visual_material": sim_utils.PreviewSurfaceCfg(diffuse_color=(0.5, 0.0, 1.0), metallic=0.2)}
 58OBJECT_PHYSICS = {
 59    "rigid_props": PhysxRigidBodyCfg(solver_position_iteration_count=4, solver_velocity_iteration_count=0),
 60    "mass_props": sim_utils.MassCfg(mass=1.0),
 61    "collision_props": sim_utils.UsdPhysicsCollisionCfg(),
 62}
 63
 64##
 65# Scene Configuration
 66##
 67
 68
 69@configclass
 70class MultiObjectSceneCfg(InteractiveSceneCfg):
 71    """Configuration for a multi-object scene."""
 72
 73    # ground plane
 74    ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
 75
 76    # lights
 77    dome_light = AssetBaseCfg(
 78        prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
 79    )
 80
 81    # rigid object
 82    object: RigidObjectCfg = RigidObjectCfg(
 83        prim_path="/World/envs/env_.*/Object",
 84        spawn=sim_utils.MultiAssetSpawnerCfg(
 85            assets_cfg=[
 86                sim_utils.CylinderCfg(radius=0.3, height=0.6, **GREEN_MATERIAL),
 87                sim_utils.CuboidCfg(size=(0.3, 0.3, 0.3), **RED_MATERIAL),
 88                sim_utils.SphereCfg(radius=0.3, **BLUE_MATERIAL),
 89                sim_utils.CylinderCfg(radius=0.3, height=0.6, **GOLD_MATERIAL),
 90                sim_utils.CuboidCfg(size=(0.3, 0.3, 0.3), **GOLD_MATERIAL),
 91                sim_utils.SphereCfg(radius=0.3, **GOLD_MATERIAL),
 92                sim_utils.CylinderCfg(radius=0.3, height=0.6, **PURPLE_MATERIAL),
 93                sim_utils.CuboidCfg(size=(0.3, 0.3, 0.3), **PURPLE_MATERIAL),
 94                sim_utils.SphereCfg(radius=0.3, **PURPLE_MATERIAL),
 95            ],
 96            random_choice=False,
 97            **OBJECT_PHYSICS,
 98        ),
 99        init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)),
100    )
101
102    # object collection
103    object_collection: RigidObjectCollectionCfg = RigidObjectCollectionCfg(
104        rigid_objects={
105            "object_A": RigidObjectCfg(
106                prim_path="/World/envs/env_.*/Object_A",
107                spawn=sim_utils.SphereCfg(radius=0.1, **RED_MATERIAL, **OBJECT_PHYSICS),
108                init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.5, 2.0)),
109            ),
110            "object_B": RigidObjectCfg(
111                prim_path="/World/envs/env_.*/Object_B",
112                spawn=sim_utils.CuboidCfg(size=(0.1, 0.1, 0.1), **RED_MATERIAL, **OBJECT_PHYSICS),
113                init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.5, 2.0)),
114            ),
115            "object_C": RigidObjectCfg(
116                prim_path="/World/envs/env_.*/Object_C",
117                spawn=sim_utils.CylinderCfg(radius=0.1, height=0.3, **RED_MATERIAL, **OBJECT_PHYSICS),
118                init_state=RigidObjectCfg.InitialStateCfg(pos=(0.5, 0.0, 2.0)),
119            ),
120        }
121    )
122
123    # articulation
124    robot: ArticulationCfg = ArticulationCfg(
125        prim_path="/World/envs/env_.*/Robot",
126        spawn=sim_utils.MultiUsdFileCfg(
127            usd_path=[
128                f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-C/anymal_c.usd",
129                f"{ISAACLAB_NUCLEUS_DIR}/Robots/ANYbotics/ANYmal-D/anymal_d.usd",
130            ],
131            random_choice=False,
132            rigid_props=PhysxRigidBodyCfg(
133                disable_gravity=False,
134                retain_accelerations=False,
135                linear_damping=0.0,
136                angular_damping=0.0,
137                max_linear_velocity=1000.0,
138                max_angular_velocity=1000.0,
139                max_depenetration_velocity=1.0,
140            ),
141            articulation_props=[
142                PhysxArticulationCfg(
143                    enabled_self_collisions=True, solver_position_iteration_count=4, solver_velocity_iteration_count=0
144                ),
145                NewtonArticulationCfg(self_collision_enabled=True),
146            ],
147            activate_contact_sensors=True,
148        ),
149        init_state=ArticulationCfg.InitialStateCfg(
150            pos=(0.0, 0.0, 0.6),
151            joint_pos={
152                ".*HAA": 0.0,  # all HAA
153                ".*F_HFE": 0.4,  # both front HFE
154                ".*H_HFE": -0.4,  # both hind HFE
155                ".*F_KFE": -0.8,  # both front KFE
156                ".*H_KFE": 0.8,  # both hind KFE
157            },
158        ),
159        actuators={"legs": ANYDRIVE_3_LSTM_ACTUATOR_CFG},
160    )
161
162
163##
164# Simulation Loop
165##
166
167
168def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
169    """Runs the simulation loop."""
170    # Extract scene entities
171    # note: we only do this here for readability.
172    rigid_object: RigidObject = scene["object"]
173    rigid_object_collection: RigidObjectCollection = scene["object_collection"]
174    robot: Articulation = scene["robot"]
175    # Define simulation stepping
176    sim_dt = sim.get_physics_dt()
177    count = 0
178    step_count = 0
179    # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton.
180    while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps):
181        # Reset
182        if count % 250 == 0:
183            # reset counter
184            count = 0
185            # reset the scene entities
186            # object
187            root_pose = rigid_object.data.default_root_pose.torch.clone()
188            root_pose[:, :3] += scene.env_origins
189            rigid_object.write_root_pose_to_sim_index(root_pose=root_pose)
190            root_vel = rigid_object.data.default_root_vel.torch.clone()
191            rigid_object.write_root_velocity_to_sim_index(root_velocity=root_vel)
192            # object collection
193            default_pose_w = rigid_object_collection.data.default_body_pose.torch.clone()
194            default_pose_w[..., :3] += scene.env_origins.unsqueeze(1)
195            rigid_object_collection.write_body_pose_to_sim_index(body_poses=default_pose_w)
196            default_vel_w = rigid_object_collection.data.default_body_vel.torch.clone()
197            rigid_object_collection.write_body_com_velocity_to_sim_index(body_velocities=default_vel_w)
198            # robot
199            # -- root state
200            root_pose = robot.data.default_root_pose.torch.clone()
201            root_pose[:, :3] += scene.env_origins
202            robot.write_root_pose_to_sim_index(root_pose=root_pose)
203            root_vel = robot.data.default_root_vel.torch
204            robot.write_root_velocity_to_sim_index(root_velocity=root_vel)
205            # -- joint state
206            joint_pos = robot.data.default_joint_pos.torch
207            joint_vel = robot.data.default_joint_vel.torch
208            robot.write_joint_position_to_sim_index(position=joint_pos)
209            robot.write_joint_velocity_to_sim_index(velocity=joint_vel)
210            # clear internal buffers
211            scene.reset()
212            print("[INFO]: Resetting scene state...")
213
214        # Apply action to robot
215        robot.set_joint_position_target_index(target=robot.data.default_joint_pos.torch)
216        # Write data to sim
217        scene.write_data_to_sim()
218        # Perform step
219        sim.step()
220        step_count += 1
221        # Increment counter
222        count += 1
223        # Update buffers
224        scene.update(sim_dt)
225
226
227def main():
228    """Main function."""
229    sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device)
230    with launch_simulation(sim_cfg, args_cli):
231        sim = sim_utils.SimulationContext(sim_cfg)
232        # Set main camera
233        sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0])
234
235        # Design scene
236        scene_cfg = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
237        if args_cli.physics == "newton_mjwarp":
238            # Newton views currently require a uniform body layout across worlds.
239            scene_cfg.object.spawn.assets_cfg = scene_cfg.object.spawn.assets_cfg[1:2]
240            scene_cfg.robot.spawn.usd_path = scene_cfg.robot.spawn.usd_path[0]
241        with Timer("[INFO] Time to create scene: "):
242            scene = instantiate(scene_cfg)
243
244        sim.reset()
245        print("[INFO]: Setup complete...")
246        run_simulator(sim, scene)
247
248
249if __name__ == "__main__":
250    main()

使用默认 Newton 配置时,此脚本会创建多个环境,其中包含:

  • 由球体、立方体和圆柱体组成的刚性物体集合

  • 由克隆计划从九种几何和材料变体中选择的刚性物体

  • 由克隆计划从 ANYmal-C 和 ANYmal-D 变体中选择的关节体

multi_asset.py 的结果

刚性物体集合#

如果每个环境都包含同一组独立运动的刚体,且希望以一个批次访问它们,可使用刚性物体集合。集合以 (env, object, ...) 布局提供数据,并接受 (env_ids, obj_ids) 选择来执行命令。与分别管理各物体相比,集合只使用一个批量物理视图。

object_collection: RigidObjectCollectionCfg = RigidObjectCollectionCfg(
    rigid_objects={
        "object_A": RigidObjectCfg(
            prim_path="/World/envs/env_.*/Object_A",
            spawn=sim_utils.SphereCfg(radius=0.1, **RED_MATERIAL, **OBJECT_PHYSICS),
            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), **RED_MATERIAL, **OBJECT_PHYSICS),
            init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.5, 2.0)),
        ),
        "object_C": RigidObjectCfg(
            prim_path="/World/envs/env_.*/Object_C",
            spawn=sim_utils.CylinderCfg(radius=0.1, height=0.3, **RED_MATERIAL, **OBJECT_PHYSICS),
            init_state=RigidObjectCfg.InitialStateCfg(pos=(0.5, 0.0, 2.0)),
        ),
    }
)

RigidObjectCollectionCfg 配置持有一个由 RigidObjectCfg 实例组成的字典。每个字典键都是对应物体在集合中的稳定标识。

本例通过两个物理后端共用的 API 重置集合中的所有成员:

default_pose_w = rigid_object_collection.data.default_body_pose.torch.clone()
default_pose_w[..., :3] += scene.env_origins.unsqueeze(1)
rigid_object_collection.write_body_pose_to_sim_index(body_poses=default_pose_w)
default_vel_w = rigid_object_collection.data.default_body_vel.torch.clone()
rigid_object_collection.write_body_com_velocity_to_sim_index(body_velocities=default_vel_w)

为一个场景资产生成不同变体#

使用 MultiAssetSpawnerCfg 和 MultiUsdFileCfg ,为一个场景资产绑定声明可用变体。 InteractiveScene 将这些变体纳入克隆计划,并为每个环境分配一个有效的原型组合。

对于通过配置构建的资产,将 MultiAssetSpawnerCfg 赋给 RigidObjectCfg 的生成配置:

object: RigidObjectCfg = RigidObjectCfg(
    prim_path="/World/envs/env_.*/Object",
    spawn=sim_utils.MultiAssetSpawnerCfg(
        assets_cfg=[
            sim_utils.CylinderCfg(radius=0.3, height=0.6, **GREEN_MATERIAL),
            sim_utils.CuboidCfg(size=(0.3, 0.3, 0.3), **RED_MATERIAL),
            sim_utils.SphereCfg(radius=0.3, **BLUE_MATERIAL),
            sim_utils.CylinderCfg(radius=0.3, height=0.6, **GOLD_MATERIAL),
            sim_utils.CuboidCfg(size=(0.3, 0.3, 0.3), **GOLD_MATERIAL),
            sim_utils.SphereCfg(radius=0.3, **GOLD_MATERIAL),
            sim_utils.CylinderCfg(radius=0.3, height=0.6, **PURPLE_MATERIAL),
            sim_utils.CuboidCfg(size=(0.3, 0.3, 0.3), **PURPLE_MATERIAL),
            sim_utils.SphereCfg(radius=0.3, **PURPLE_MATERIAL),
        ],
        random_choice=False,
        **OBJECT_PHYSICS,
    ),
    init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 2.0)),
)

assets_cfg 列表定义克隆计划可用的原型。变体分配由 clone_strategy 控制;默认的 sequential() 策略按轮转顺序分配组合。如果希望随机采样组合,应在构建场景前设置策略:

from isaaclab import cloner

scene_cfg.clone_cfg.clone_strategy = cloner.random

对于 USD 资产,将 MultiUsdFileCfg 赋给 ArticulationCfg 的生成配置:

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=False,
        rigid_props=PhysxRigidBodyCfg(
            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=[
            PhysxArticulationCfg(
                enabled_self_collisions=True, solver_position_iteration_count=4, solver_velocity_iteration_count=0
            ),
            NewtonArticulationCfg(self_collision_enabled=True),
        ],
        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},
)


变体兼容性#

同一批量资产接口下的所有变体必须结构兼容。关节体变体必须具有相同的连杆、关节、碰撞刚体数量及名称。刚性物体变体可以有不同的几何和材料,但必须保留兼容的刚体布局。结构不同的资产应建模为独立的场景绑定。

克隆规划与物理复制#

InteractiveScene 将多资产变体表示为克隆计划中的原型,并仅向各原型所分配的环境分发。物理资产使用其声明的克隆上下文,默认采用原生物理复制。不同环境的舞台差异应声明为 clone_combinations 原型。

本例依赖物理复制。对于 Newton,独立物体与关节系统还各限制为单个变体,因为其批量 Newton 视图目前要求各世界具有统一的刚体布局:

scene_cfg = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
if args_cli.physics == "newton_mjwarp":
    # Newton views currently require a uniform body layout across worlds.
    scene_cfg.object.spawn.assets_cfg = scene_cfg.object.spawn.assets_cfg[1:2]
    scene_cfg.robot.spawn.usd_path = scene_cfg.robot.spawn.usd_path[0]

原型分配与复制的更多细节见 克隆环境 。

运行示例#

物理后端与可视化器分别选择。从仓库根目录运行以下任一命令:

uv run --extra isaacsim isaaclab example multi-asset \
    --physics isaacsim_physx --visualizer kit --num_envs 2048
uv run --extra isaacsim isaaclab example multi-asset \
    --physics newton_mjwarp --visualizer kit --num_envs 2048
uv run isaaclab example multi-asset \
    --physics newton_mjwarp --visualizer newton_gl --num_envs 2048

Newton 命令与 PhysX 命令使用相同的 RigidObjectCollectionCfg 和 (env_ids, obj_ids) API。由于上述统一布局限制,它们不展示逐环境的物体或关节系统变体;这部分请使用 PhysX 命令查看。运行不依赖 Kit 的命令前,请阅读 安装指南。

关闭窗口或在终端按 Ctrl+C ,即可停止仿真。