如是我闻: 到目前为止,在之前的指南中,我们都是手动将资产生成到模拟中,并创建对象实例与它们进行互动。然而,随着场景的复杂性增加,手动执行这些任务会变得很墨迹。在本指南中,我们将介绍scene.InteractiveScene
类,它为生成原始物体并在模拟中管理它们提供了一个便捷的接口。
总体上来说,交互式场景是场景实体的集合。每个实体可以是非交互式原始物体(例如,地面平面、光源)、交互式原始物体(例如,关节物体、刚体)或传感器(例如,相机、激光雷达)。交互式场景为这些实体的生成和管理提供了一个便捷的接口。
与纯人工手动方法相比,它有以下的好处:
-
缓解用户需要分别生成每个资产的需求,因为这是隐式处理的。
-
使得场景原始物体(scene prims)的克隆变得有益身心健康,适用于多环境。
-
将所有场景实体收集到一个对象中,这使得它们更容易管理。
在这个指南中,我们取自与指南04关节物体互动指南中的车杆示例,并将design_scene
函数替换为scene.InteractiveScene
对象。虽然对于这个简单的示例使用交互式场景看起来可能有些过分,但随着更多资产和传感器被添加到场景中,它将变得更加有用。
指南05对应于orbit/source/standalone/tutorials/02_scene
目录下的create_scene.py
脚本,让我们先搂一眼完整的代码
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""This script demonstrates how to use the interactive scene interface to setup a scene with multiple prims.
.. code-block:: bash
# Usage
./orbit.sh -p source/standalone/tutorials/03_scene/create_scene.py --num_envs 32
"""
from __future__ import annotations
"""Launch Isaac Sim Simulator first."""
import argparse
from omni.isaac.orbit.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(description="Tutorial on using the interactive scene interface.")
parser.add_argument("--num_envs", type=int, default=2, help="Number of environments to spawn.")
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
args_cli = parser.parse_args()
# launch omniverse app
app_launcher = AppLauncher(args_cli)
simulation_app = app_launcher.app
"""Rest everything follows."""
import torch
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.assets import ArticulationCfg, AssetBaseCfg
from omni.isaac.orbit.scene import InteractiveScene, InteractiveSceneCfg
from omni.isaac.orbit.sim import SimulationContext
from omni.isaac.orbit.utils import configclass
##
# Pre-defined configs
##
from omni.isaac.orbit_assets import CARTPOLE_CFG # isort:skip
@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")
def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene):
"""Runs the simulation loop."""
# Extract scene entities
# note: we only do this here for readability.
robot = scene["cartpole"]
# Define simulation stepping
sim_dt = sim.get_physics_dt()
count = 0
# Simulation loop
while simulation_app.is_running():
# Reset
if count % 500 == 0:
# reset counter
count = 0
# reset the scene entities
# root state
# we offset the root state by the origin since the states are written in simulation world frame
# if this is not done, then the robots will be spawned at the (0, 0, 0) of the simulation world
root_state = robot.data.default_root_state.clone()
root_state[:, :3] += scene.env_origins
robot.write_root_state_to_sim(root_state)
# set joint positions with some noise
joint_pos, joint_vel = robot.data.default_joint_pos.clone(), robot.data.default_joint_vel.clone()
joint_pos += torch.rand_like(joint_pos) * 0.1
robot.write_joint_state_to_sim(joint_pos, joint_vel)
# clear internal buffers
scene.reset()
print("[INFO]: Resetting robot state...")
# Apply random action
# -- generate random joint efforts
efforts = torch.randn_like(robot.data.joint_pos) * 5.0
# -- apply action to the robot
robot.set_joint_effort_target(efforts)
# -- write data to sim
scene.write_data_to_sim()
# Perform step
sim.step()
# Increment counter
count += 1
# Update buffers
scene.update(sim_dt)
def main():
"""Main function."""
# Load kit helper
sim_cfg = sim_utils.SimulationCfg(device="cpu", use_gpu_pipeline=False)
sim = SimulationContext(sim_cfg)
# Set main camera
sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0])
# Design scene
scene_cfg = CartpoleSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
scene = InteractiveScene(scene_cfg)
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# Run the simulator
run_simulator(sim, scene)
if __name__ == "__main__":
# run the main function
main()
# close sim app
simulation_app.close()
代码解析
虽然代码与前一个指南相似,但有几个关键区别我们将详细讨论一下。
场景配置
场景由一系列各自拥有配置的实体组成。这些配置在一个继承自scene.InteractiveSceneCfg
的配置类中指定。然后,配置类传递给scene.InteractiveScene
构造函数以创建场景。
对于车杆示例,我们指定了与前一个指南中相同的场景,但现在在配置类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"]
访问。不过,我们稍后再详细说明。首先,让我们看看单个场景实体是如何配置的。
与之前指南中配置刚体和关节物体的方式类似,配置是使用配置类指定的。然而,地面平面和光源的配置与车杆配置之间存在一个关键区别。地面平面和光源是非交互式原始物体,而车杆是一个交互式原始物体。这一区别反映在用于指定它们的配置类中。地面平面和光源的配置使用assets.AssetBaseCfg类的实例指定,而车杆则使用assets.ArticulationCfg的实例配置。任何不是交互式原始物体的东西(即既不是资产也不是传感器)在模拟步骤中被处理。
另一个需要注意的关键区别是在不同原始物体的原始路径规格中:
- 地面平面:
/World/defaultGroundPlane
- 光源:
/World/Light
- 车杆:
{ENV_REGEX_NS}/Robot
正如我们早前学到的,Omniverse 在USD舞台上创建了一个原始物体图。原始路径用于指定原始物体在图中的位置。地面平面和光源使用绝对路径指定,而车杆使用相对路径指定。相对路径使用ENV_REGEX_NS
变量指定,这是一个特殊变量,在场景创建期间被替换为环境名称。任何在其原始路径中包含ENV_REGEX_NS
变量的实体都会为每个环境被克隆。这个路径由场景对象替换为/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”
指定。
# 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个车杆。我们可以通过向脚本传递–num_envs参数来实现这一点。
./orbit.sh -p source/standalone/tutorials/02_scene/create_scene.py --num_envs 32
这应该会打开一个展台,有32个车杆随机摆动。
在这个指南中,我们看到了如何使用scene.InteractiveScene
来创建包含多个资产的场景。我们还看到了如何使用num_envs
参数为多个环境克隆场景。
在omni.isaac.orbit_tasks
扩展下找到的任务中,有许多更多的scene.InteractiveSceneCfg
的示例用法。那些例子我还得细琢磨一下子
愿本文渡一切机器人模拟器苦
以上