使用交互式场景#

到目前为止,在教程中,我们手动将资产生成到仿真中,并创建对象实例与之交互。然而,随着场景复杂度的增加,手动执行这些任务变得很繁琐。在本教程中,我们将介绍 scene.InteractiveScene 类,该类提供了一个方便的接口,用于生成 prims 并在仿真中管理它们。

在高层次上,交互式场景是场景实体的集合。每个实体可以是不可交互的 prim(例如地面平面、光源)、可交互的 prim(例如关节、刚体物体)或传感器(例如摄像机、激光雷达)。交互式场景提供了一个便捷的接口,用于生成这些实体并在仿真中管理它们。

相比手动方法,它提供以下优点:

  • 减轻了用户需要单独生成每个资产,因为这是隐含处理的。

  • 可便于用户友好地克隆场景 prims 以用于多个环境。

  • 将所有场景实体收集到一个单一对象中,这样更易于管理。

在这个教程中,我们从 与关节交互 教程中的 cartpole 示例开始,并用 scene.InteractiveScene 对象替换 design_scene 函数。虽然在这个简单的例子中使用交互式场景可能看起来有点过分,但随着场景中增加更多的资产和传感器,它将在将来变得更有用。

代码#

该教程对应于 scripts/tutorials/02_scene 中的 create_scene.py 脚本。

create_scene.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 use the interactive scene interface to setup a scene with multiple prims.
  7
  8.. code-block:: bash
  9
 10    # Usage
 11    uv run python scripts/tutorials/02_scene/create_scene.py --num_envs 32
 12
 13"""
 14
 15"""Parse the command-line arguments first."""
 16
 17
 18import argparse
 19from typing import TYPE_CHECKING
 20
 21from isaaclab.app import add_launcher_args, launch_simulation
 22
 23# add argparse arguments
 24parser = argparse.ArgumentParser(description="Tutorial on using the interactive scene interface.")
 25parser.add_argument("--num_envs", type=int, default=2, help="Number of environments to spawn.")
 26# append simulation launcher cli args
 27add_launcher_args(parser)
 28# parse the arguments
 29args_cli = parser.parse_args()
 30
 31"""Rest everything follows."""
 32
 33import torch
 34
 35import isaaclab.sim as sim_utils
 36from isaaclab.assets import ArticulationCfg, AssetBaseCfg
 37from isaaclab.scene import InteractiveSceneCfg
 38from isaaclab.sim import SimulationContext
 39from isaaclab.utils import configclass, instantiate, replace
 40
 41if TYPE_CHECKING:
 42    from isaaclab.scene import InteractiveScene
 43
 44##
 45# Pre-defined configs
 46##
 47from isaaclab_assets import CARTPOLE_CFG  # isort:skip
 48
 49
 50@configclass
 51class CartpoleSceneCfg(InteractiveSceneCfg):
 52    """Configuration for a cart-pole scene."""
 53
 54    # ground plane
 55    ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
 56
 57    # lights
 58    dome_light = AssetBaseCfg(
 59        prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
 60    )
 61
 62    # articulation
 63    cartpole: ArticulationCfg = replace(CARTPOLE_CFG, prim_path="{ENV_REGEX_NS}/Robot")
 64
 65
 66def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"):
 67    """Runs the simulation loop."""
 68    # Extract scene entities
 69    # note: we only do this here for readability.
 70    robot = scene["cartpole"]
 71    # Define simulation stepping
 72    sim_dt = sim.get_physics_dt()
 73    count = 0
 74    # Simulation loop
 75    while sim.is_running():
 76        # Reset
 77        if count % 500 == 0:
 78            # reset counter
 79            count = 0
 80            # reset the scene entities
 81            # root state
 82            # we offset the root state by the origin since the states are written in simulation world frame
 83            # if this is not done, then the robots will be spawned at the (0, 0, 0) of the simulation world
 84            root_pose = robot.data.default_root_pose.torch.clone()
 85            root_pose[:, :3] += scene.env_origins
 86            robot.write_root_pose_to_sim_index(root_pose=root_pose)
 87            root_vel = robot.data.default_root_vel.torch.clone()
 88            robot.write_root_velocity_to_sim_index(root_velocity=root_vel)
 89            # set joint positions with some noise
 90            joint_pos, joint_vel = (
 91                robot.data.default_joint_pos.torch.clone(),
 92                robot.data.default_joint_vel.torch.clone(),
 93            )
 94            joint_pos += torch.rand_like(joint_pos) * 0.1
 95            robot.write_joint_position_to_sim_index(position=joint_pos)
 96            robot.write_joint_velocity_to_sim_index(velocity=joint_vel)
 97            # clear internal buffers
 98            scene.reset()
 99            print("[INFO]: Resetting robot state...")
100        # Apply random action
101        # -- generate random joint efforts
102        efforts = torch.randn_like(robot.data.joint_pos.torch) * 5.0
103        # -- apply action to the robot
104        robot.set_joint_effort_target_index(target=efforts)
105        # -- write data to sim
106        scene.write_data_to_sim()
107        # Perform step
108        sim.step()
109        # Increment counter
110        count += 1
111        # Update buffers
112        scene.update(sim_dt)
113
114
115def main():
116    """Main function."""
117    # Configure the simulation
118    sim_cfg = sim_utils.SimulationCfg(device=args_cli.device)
119    # Launch the simulator runtime that the configuration needs
120    with launch_simulation(sim_cfg, args_cli):
121        # Initialize the simulation context
122        sim = SimulationContext(sim_cfg)
123        # Set main camera
124        sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0])
125        # Design scene
126        scene_cfg = CartpoleSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
127        scene = instantiate(scene_cfg)
128        # Play the simulator
129        sim.reset()
130        # Now we are ready!
131        print("[INFO]: Setup complete...")
132        # Run the simulator
133        run_simulator(sim, scene)
134
135
136if __name__ == "__main__":
137    # run the main function
138    main()

代码解释#

虽然代码与上一个教程类似,但有一些关键区别我们将详细介绍。

场景配置#

场景由一系列带有自己配置的实体组成。这些都是在继承自 scene.InteractiveSceneCfg 的配置类中指定的。然后将配置类传递给 scene.InteractiveScene 构造函数以创建场景。

对于 cartpole 示例,我们指定与以前教程中相同的场景,但现在将它们列在配置类 CartpoleSceneCfg 中,而不是手动生成它们。

@configclass
class CartpoleSceneCfg(InteractiveSceneCfg):
    """Configuration for a cart-pole scene."""

    # ground plane
    ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())

    # lights
    dome_light = AssetBaseCfg(
        prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
    )

    # articulation
    cartpole: ArticulationCfg = replace(CARTPOLE_CFG, prim_path="{ENV_REGEX_NS}/Robot")

配置类中的变量名称用作用于从 scene.InteractiveScene 对象访问对应实体的键。例如,可以通过 scene["cartpole"] 来访问 cartpole。然而,我们会稍后再解释这个。首先,让我们看看如何配置单个场景实体。

与先前教程中配置刚体物体和关节类似,配置使用配置类来指定。然而,地面平面和光源的配置与 cartpole 的配置之间存在一个关键区别。地面平面和光源是不可交互的,而 cartpole 是可交互的。这种区别在用于指定它们的配置类中得到了体现。地面平面和光源的配置使用 assets.AssetBaseCfg 的实例来指定,而 cartpole 使用 assets.ArticulationCfg 的实例来进行配置。在仿真步骤期间,不处理任何不是交互式 prim(即既不是资产也不是传感器)。

另一个需要注意的关键区别是不同 prim 的 prim 路径的指定方法:

  • 地面平面:/World/defaultGroundPlane

  • 光源:/World/Light

  • Cartpole:{ENV_REGEX_NS}/Robot

如前所述,Omniverse 在 USD 场景创建了 prims 的图形。prim 路径用于指定图中 prim 的位置。地面平面和光源使用绝对路径来指定,而 cartpole 使用相对路径来指定。相对路径使用 ENV_REGEX_NS 变量来进行指定,这是一个特殊的变量,在场景创建期间会被环境名称替换。任何带有 ENV_REGEX_NS 变量的实体的 prim 路径在每个环境中都会被克隆。这个路径会被场景对象替换为 /World/envs/env_{i} ,其中 i 是环境索引。

场景实例化#

之前调用 design_scene 函数创建场景,现在则通过配置对象创建 scene.InteractiveScene 实例。配置的 class_type 属性指定要构建的类,由 instantiate() 据此构建场景。创建 CartpoleSceneCfg 配置实例时,使用 num_envs 参数指定环境副本数,随后据此为各环境克隆场景。

        # Design scene
        scene_cfg = CartpoleSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
        scene = instantiate(scene_cfg)

CartpoleSceneCfg 配置类可以在脚本开头定义,但 scene.InteractiveScene 类操作的是运行中仿真器的 USD 舞台。 class_type 属性延迟解析该类,因此只有运行时启动后,在 with launch_simulation(...) 块内构建场景时,才会导入场景类。

访问场景元素#

与以前教程中的方法类似,可以使用 InteractiveScene 对象从字典中获取场景元素,通过使用 [] 操作符。操作符接受一个字符串键,并返回相应的实体。键是通过每个实体的配置类指定的。例如,cartpole 在配置类中使用键 "cartpole" 来指定。

    # Extract scene entities
    # note: we only do this here for readability.
    robot = scene["cartpole"]

运行仿真循环#

脚本的其余部分看起来与先前与 assets.Articulation 接口的脚本类似,只是调用的方法有一些小的不同:

在幕后, scene.InteractiveScene 的方法调用了场景中实体的相应方法。

代码执行#

让我们运行脚本以仿真场景中的 32 个 cartpoles。我们可以通过向脚本传递 --num_envs 参数来实现这一点。

uv run python scripts/tutorials/02_scene/create_scene.py --num_envs 32
./isaaclab.sh -p scripts/tutorials/02_scene/create_scene.py --num_envs 32

这应该会打开一个场景,上面有 32 个随机摆动的 cartpole。您可以使用鼠标旋转摄像机,使用箭头键在场景中移动。

create_scene.py 的结果

在本教程中,我们学习了如何使用 scene.InteractiveScene 创建一个带有多个资产的场景。我们还学习了如何使用 num_envs 参数来为多个环境克隆场景。

在 isaaclab_tasks 扩展中, scene.InteractiveSceneCfg 还有许多其他示例用法。请查看源代码,了解它们在更复杂的场景中的使用方式。