使用交互式场景#
到目前为止,在教程中,我们手动将资产生成到模拟中,并创建对象实例与之交互。然而,随着场景复杂度的增加,手动执行这些任务变得很繁琐。在本教程中,我们将介绍 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
接口的脚本类似,只是调用的方法有一些小的不同:
assets.Articulation.reset()
⟶scene.InteractiveScene.reset()
assets.Articulation.write_data_to_sim()
⟶scene.InteractiveScene.write_data_to_sim()
assets.Articulation.update()
⟶scene.InteractiveScene.update()
在幕后, scene.InteractiveScene
的方法调用了场景中实体的相应方法。
代码执行#
让我们运行脚本以模拟场景中的32个cartpoles。我们可以通过向脚本传递 --num_envs
参数来实现这一点。
./isaaclab.sh -p source/standalone/tutorials/02_scene/create_scene.py --num_envs 32
这应该会打开一个场景,上面有32个随机摆动的cartpole。您可以使用鼠标旋转摄像机,使用箭头键在场景中移动。
在本教程中,我们学习了如何使用 scene.InteractiveScene
创建一个带有多个资产的场景。我们还学习了如何使用 num_envs
参数来为多个环境克隆场景。
在 omni.isaac.lab_tasks
扩展中, scene.InteractiveSceneCfg
还有许多其他示例用法。请查看源代码,了解它们在更复杂的场景中的使用方式。