如是我闻: “在之前的指南中,我们讨论了独立脚本( standalone script)的基本工作原理以及如何在模拟器中生成不同的对象(prims)。在指南03中,我们将展示如何创建并与刚体进行交互。为此,我们使用Orbit中提供的assets.RigidObject类。”
指南03 对应于orbit/source/standalone/tutorials/01_assets
目录下的run_rigid_object.py
脚本,让我们先搂一眼完整的代码
# Copyright (c) 2022-2024, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This script demonstrates how to create a rigid object and interact with it.
.. code-block:: bash
# Usage
./orbit.sh -p source/standalone/tutorials/01_assets/run_rigid_object.py
"""
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 spawning and interacting with a rigid object.")
# 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.core.utils.prims as prim_utils
import omni.isaac.orbit.sim as sim_utils
import omni.isaac.orbit.utils.math as math_utils
from omni.isaac.orbit.assets import RigidObject, RigidObjectCfg
from omni.isaac.orbit.sim import SimulationContext
def design_scene():
"""Designs the scene."""
# Ground-plane
cfg = sim_utils.GroundPlaneCfg()
cfg.func("/World/defaultGroundPlane", cfg)
# Lights
cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.8, 0.8, 0.8))
cfg.func("/World/Light", cfg)
# Create separate groups called "Origin1", "Origin2", "Origin3"
# Each group will have a robot in it
origins = [[0.25, 0.25, 0.0], [-0.25, 0.25, 0.0], [0.25, -0.25, 0.0], [-0.25, -0.25, 0.0]]
for i, origin in enumerate(origins):
prim_utils.create_prim(f"/World/Origin{i}", "Xform", translation=origin)
# Rigid Object
cone_cfg = RigidObjectCfg(
prim_path="/World/Origin.*/Cone",
spawn=sim_utils.ConeCfg(
radius=0.1,
height=0.2,
rigid_props=sim_utils.RigidBodyPropertiesCfg(),
mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
collision_props=sim_utils.CollisionPropertiesCfg(),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0), metallic=0.2),
),
init_state=RigidObjectCfg.InitialStateCfg(),
)
cone_object = RigidObject(cfg=cone_cfg)
# return the scene information
scene_entities = {"cone": cone_object}
return scene_entities, origins
def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, RigidObject], origins: torch.Tensor):
"""Runs the simulation loop."""
# Extract scene entities
# note: we only do this here for readability. In general, it is better to access the entities directly from
# the dictionary. This dictionary is replaced by the InteractiveScene class in the next tutorial.
cone_object = entities["cone"]
# Define simulation stepping
sim_dt = sim.get_physics_dt()
sim_time = 0.0
count = 0
# Simulate physics
while simulation_app.is_running():
# reset
if count % 250 == 0:
# reset counters
sim_time = 0.0
count = 0
# reset root state
root_state = cone_object.data.default_root_state.clone()
# sample a random position on a cylinder around the origins
root_state[:, :3] += origins
root_state[:, :3] += math_utils.sample_cylinder(
radius=0.1, h_range=(0.25, 0.5), size=cone_object.num_instances, device=cone_object.device
)
# write root state to simulation
cone_object.write_root_state_to_sim(root_state)
# reset buffers
cone_object.reset()
print("----------------------------------------")
print("[INFO]: Resetting object state...")
# apply sim data
cone_object.write_data_to_sim()
# perform step
sim.step()
# update sim-time
sim_time += sim_dt
count += 1
# update buffers
cone_object.update(sim_dt)
# print the root position
if count % 50 == 0:
print(f"Root position (in world): {cone_object.data.root_state_w[:, :3]}")
def main():
"""Main function."""
# Load kit helper
sim_cfg = sim_utils.SimulationCfg()
sim = SimulationContext(sim_cfg)
# Set main camera
sim.set_camera_view(eye=[1.5, 0.0, 1.0], target=[0.0, 0.0, 0.0])
# Design scene
scene_entities, scene_origins = design_scene()
scene_origins = torch.tensor(scene_origins, device=sim.device)
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# Run the simulator
run_simulator(sim, scene_entities, scene_origins)
if __name__ == "__main__":
# run the main function
main()
# close sim app
simulation_app.close()
代码解析
在这个脚本中,我们将主函数main
分割成两个独立的函数,这两个函数是在模拟器中设置任何模拟的两个主要步骤:
-
场景设计:顾名思义,这部分负责将所有图元( prims)添加到场景中。
-
模拟运行:这部分负责步进模拟器,与场景中的原始物体进行互动,例如,改变它们的姿态,并对它们应用指令。
区别这两个步骤是必要的,因为第二步只有在第一步完成并且模拟器被重置后才会发生。一旦模拟器被重置(自动播放模拟),新的(启用物理的)原始物体(prims)不应该被添加到场景中,因为这可能会导致意外的行为。然而,可以通过它们各自的句柄(respective handles)与原始物体(prims)进行互动。
场景设计
与前一个指南类似,我们使用地面平面和光源填充场景。此外,我们使用assets.RigidObject
类将一个刚体添加到场景中。这个类负责在输入路径处生成原始物体,并初始化它们对应的刚体物理句柄。
在本指南中,我们使用与“生成对象指南”中的刚体圆锥相似的生成配置,创建一个圆锥形的刚体对象。唯一的区别是,现在我们将生成配置包装进assets.RigidObjectCfg
类中。这个类包含有关资产生成策略、默认初始状态和其他元信息的信息。当这个类传递给assets.RigidObject
类时,它会在播放模拟时生成对象并初始化相应的物理句柄。
作为例子,关于多次生成刚体原始物体,我们创建了它们的父Xform原始物体,/World/Origin{i}
,这对应于不同的生成位置。当正则表达式/World/Origin*/Cone
传递给assets.RigidObject
类时,它会在每个/World/Origin{i}
位置生成刚体原始物体。例如,如果场景中存在/World/Origin1
和/World/Origin2
,则刚体原始物体分别在位置/World/Origin1/Cone
和/World/Origin2/Cone
处生成。
# Create separate groups called "Origin1", "Origin2", "Origin3"
# Each group will have a robot in it
origins = [[0.25, 0.25, 0.0], [-0.25, 0.25, 0.0], [0.25, -0.25, 0.0], [-0.25, -0.25, 0.0]]
for i, origin in enumerate(origins):
prim_utils.create_prim(f"/World/Origin{i}", "Xform", translation=origin)
# Rigid Object
cone_cfg = RigidObjectCfg(
prim_path="/World/Origin.*/Cone",
spawn=sim_utils.ConeCfg(
radius=0.1,
height=0.2,
rigid_props=sim_utils.RigidBodyPropertiesCfg(),
mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
collision_props=sim_utils.CollisionPropertiesCfg(),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0), metallic=0.2),
),
init_state=RigidObjectCfg.InitialStateCfg(),
)
cone_object = RigidObject(cfg=cone_cfg)
由于我们是想与刚体进行互动,我们将这个实体返回给主函数。然后在模拟循环中使用这个实体与刚体进行互动。在后续的指南中,我们将看到使用scene.InteractiveScene
类处理多个场景实体的更便捷方式。
# return the scene information
scene_entities = {"cone": cone_object}
return scene_entities, origins
运行模拟循环
我们修改模拟循环已进行与刚体的互动,包括三个步骤
- 在固定时间间隔重置模拟状态、
- 步进模拟
- 以及更新刚体的内部缓冲区。
为了简便一些,我们从场景的字典中提取出刚体的实体,并将其存储在一个变量中。
在固定时间间隔重置模拟状态
为了重置生成的刚体原始物体的模拟状态,我们需要设置它们的姿态和速度。这两者共同定义了生成的刚体对象的根状态。重要的是要注意,这个状态是在模拟世界框架中定义的,而不是它们的父Xform原始物体的框架。这是因为物理引擎只理解世界框架,而不理解父Xform原始物体的框架。因此,在设置之前,我们需要将刚体原始物体的期望状态转换到世界框架中。
我们使用assets.RigidObject.data.default_root_state
属性来获取生成的刚体原始物体的默认根状态。这个默认状态可以从assets.RigidObjectCfg.init_state
属性配置,我们在这个指南中将其保留为身份。然后,我们随机化根状态的平移,并使用assets.RigidObject.write_root_state_to_sim()
方法设置刚体原始物体的期望状态。正如名称所示,这个方法将刚体原始物体的根状态写入到模拟缓冲区中。
# reset root state
root_state = cone_object.data.default_root_state.clone()
# sample a random position on a cylinder around the origins
root_state[:, :3] += origins
root_state[:, :3] += math_utils.sample_cylinder(
radius=0.1, h_range=(0.25, 0.5), size=cone_object.num_instances, device=cone_object.device
)
# write root state to simulation
cone_object.write_root_state_to_sim(root_state)
# reset buffers
cone_object.reset()
PS:我这段真看了半天,就只能直译了,我现在的理解是,我们要是想刷新这个圆锥的状态,就必须找出他的根状态,在根状态上做改变,当然如果是单纯的图形的话,在上一级Xform上改动也就可以了,也就是这一段代码,对orbit同样绝望还必须得用的可以私信我,我们一起研究一下
root_state = cone_object.data.default_root_state.clone()
步进模拟
在步进模拟之前,我们执行assets.RigidObject.write_data_to_sim()
方法。这个方法将其他数据,比如外部力,写入模拟缓冲区。在本指南中,我们没有对刚体施加任何外部力,所以这个方法不是必需的。然而,为了完整性,我们将他写在这里。
# apply sim data
cone_object.write_data_to_sim()
更新状态
在步进模拟之后,我们更新刚体原始物体的内部缓冲区,以便在assets.RigidObject.data
属性内反映它们的新状态。这是通过使用assets.RigidObject.update()
方法完成的。
# update buffers
cone_object.update(sim_dt)
代码执行
现在我们已经讲解了代码,让我们运行脚本并查看结果:
./orbit.sh -p source/standalone/tutorials/01_assets/run_rigid_object.py
这应该会打开一个带有地面平面、灯光和几个绿色圆锥的舞台。圆锥必须从随机高度落下并落在地面上。
要停止模拟,可以关闭窗口,或在UI中按停止按钮,或在终端中按Ctrl+C。
本指南展示了如何生成刚体对象,并将它们包装在RigidObject类中以初始化它们的物理句柄,这允许设置和获取它们的状态。在下一个指南中,我们将看到如何与由关节连接的刚体对象集合即关节物体进行互动。
愿本文渡一切机器人模拟器苦
以上