使用交互式场景#

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

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

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

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

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

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

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

代码#

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

create_scene.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 use the interactive scene interface to setup a scene with multiple prims.
  7
  8.. code-block:: bash
  9
 10    # Usage
 11    ./isaaclab.sh -p source/standalone/tutorials/02_scene/create_scene.py --num_envs 32
 12
 13"""
 14
 15"""Launch Isaac Sim Simulator first."""
 16
 17
 18import argparse
 19
 20from omni.isaac.lab.app import AppLauncher
 21
 22# add argparse arguments
 23parser = argparse.ArgumentParser(description="Tutorial on using the interactive scene interface.")
 24parser.add_argument("--num_envs", type=int, default=2, help="Number of environments to spawn.")
 25# append AppLauncher cli args
 26AppLauncher.add_app_launcher_args(parser)
 27# parse the arguments
 28args_cli = parser.parse_args()
 29
 30# launch omniverse app
 31app_launcher = AppLauncher(args_cli)
 32simulation_app = app_launcher.app
 33
 34"""Rest everything follows."""
 35
 36import torch
 37
 38import omni.isaac.lab.sim as sim_utils
 39from omni.isaac.lab.assets import ArticulationCfg, AssetBaseCfg
 40from omni.isaac.lab.scene import InteractiveScene, InteractiveSceneCfg
 41from omni.isaac.lab.sim import SimulationContext
 42from omni.isaac.lab.utils import configclass
 43
 44##
 45# Pre-defined configs
 46##
 47from omni.isaac.lab_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 = CARTPOLE_CFG.replace(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 simulation_app.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_state = robot.data.default_root_state.clone()
 85            root_state[:, :3] += scene.env_origins
 86            robot.write_root_state_to_sim(root_state)
 87            # set joint positions with some noise
 88            joint_pos, joint_vel = robot.data.default_joint_pos.clone(), robot.data.default_joint_vel.clone()
 89            joint_pos += torch.rand_like(joint_pos) * 0.1
 90            robot.write_joint_state_to_sim(joint_pos, joint_vel)
 91            # clear internal buffers
 92            scene.reset()
 93            print("[INFO]: Resetting robot state...")
 94        # Apply random action
 95        # -- generate random joint efforts
 96        efforts = torch.randn_like(robot.data.joint_pos) * 5.0
 97        # -- apply action to the robot
 98        robot.set_joint_effort_target(efforts)
 99        # -- write data to sim
100        scene.write_data_to_sim()
101        # Perform step
102        sim.step()
103        # Increment counter
104        count += 1
105        # Update buffers
106        scene.update(sim_dt)
107
108
109def main():
110    """Main function."""
111    # Load kit helper
112    sim_cfg = sim_utils.SimulationCfg(device=args_cli.device)
113    sim = SimulationContext(sim_cfg)
114    # Set main camera
115    sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0])
116    # Design scene
117    scene_cfg = CartpoleSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
118    scene = InteractiveScene(scene_cfg)
119    # Play the simulator
120    sim.reset()
121    # Now we are ready!
122    print("[INFO]: Setup complete...")
123    # Run the simulator
124    run_simulator(sim, scene)
125
126
127if __name__ == "__main__":
128    # run the main function
129    main()
130    # close sim app
131    simulation_app.close()

代码解释#

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

场景配置#

场景由一系列带有自己配置的实体组成。这些都是在继承自 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 = CARTPOLE_CFG.replace(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 类的实例,并将配置对象传递给它的构造函数。在创建 CartpoleSceneCfg 配置实例时,我们使用 num_envs 参数指定要创建多少个环境的副本。这将用于为每个环境克隆场景。

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

访问场景元素#

与以前教程中的方法类似,可以使用 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 参数来实现这一点。

./isaaclab.sh -p source/standalone/tutorials/02_scene/create_scene.py --num_envs 32

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

create_scene.py 的结果

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

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