第三章、Isaac sim Python脚本(2):Core API概述及教程(Hello World)
0 前言
Debugging With Visual Studio Code官方文档:https://docs.isaacsim.omniverse.nvidia.com/latest/utilities/debugging/tutorial_advanced_python_debugging.html
API官方文档:https://docs.isaacsim.omniverse.nvidia.com/latest/reference_python_api.html
NVIDIA Isaac Sim 中的 Python 脚本可以通过两种方式完成:独立(standalone)和交互式(interactive)。独立 Python 脚本从命令行执行,用于自动执行任务或进行模拟。交互式 Python 脚本在 Python 控制台中执行,用于探索 NVIDIA Isaac Sim API 和调试代码片段。这两种类型的脚本都可用于创建自定义扩展,例如新的机器人控制器或传感器,以及与 Omniverse 应用程序交互。
该章内容主要由以下几个方面展开:
- 配置vscode
- Core API 概述
- Core API 教程
注:后续部分接之前的内容继续
1 Core API 概述
Isaac Sim Core API 是原始USD和物理引擎API包,该包为适应机器人应用而量身定制。官网给出了一部分代码案例(是基于原始API的了解即可)。该部分不做过多叙述,你可以在你的路径中创建一个test.py
文件并将该部分内容复制粘贴进去,利用上一章中的交互方式,逐段运行,并在isaacsim窗口中实时的看变化。
2 Core API 教程
本教程,首先,介绍Core API的概念及使用方法。我们首先在场景中添加一个立方体,并在此基础上创建一个多机器人同时执行多个任务的场景,如下图所示
本教程的学习目标:
- BaseSample、World、Scene类。
- 通过 Python 向场景添加物体。
- 添加回调函数。
- 访问对象的动态属性。
- 独立程序和应用扩展程序的主要区别。
3 Hello World
1 打开isaac sim
进入Isaac sim 文件夹中,打开终端,输入如下命令启动Isaac sim
./isaac-sim.sh
默认界面如下所示:
2 打开机器人示例中的源码
按照下图红色框位置依次点击,就可以在vscode中打开该示例的源代码,可以在此基础上进行编辑。
同样的点击8右侧的文件夹图标就可以打开源码所在的文件夹。该文件夹中包含下图文件:
脚本hello_world.py
是应用程序逻辑添加的地方,hello_world_extension.py
脚本用来添加应用程序的UI元素中并从而与脚本hello_world.py
相链接。
3 如何在以有场景的情况下加载空场景
按照下图红色框位置依次点击,并在弹出的弹框中选择Don't Save
4 代码概述
1 BaseSample
类
打开isaacsim/examples/interactive/hello_world/hello_world.py
该代码继承自 BaseSample
类,这是一个基础模板类,所有具体的机器人扩展应用都可以继承它,避免重复写底层代码,它提供了一些所有机器人应用都需要的通用功能包括:
- 加载世界和资产:通过一个按钮初始化模拟环境(例如机器人、场景、物体等)。
- 清除世界:当创建新场景(stage)时,清理旧场景的资产。
- 重置对象状态:将模拟环境中的物体恢复到初始位置或状态。
- 热重载(hot reloading):允许修改代码后实时更新模拟环境,无需重启程序(类似游戏开发中的热更新)。
2 World
类
作用:该类使用户可以通过简单且模块化的方式实现与机器人模拟器(simulator)的交互,负责管理时间、添加回调、物理模拟、场景重置和添加任务等操作。
3 Scene
类
一个世界包含一个场景实例。Scene 类管理 USD 场景(Stage)中感兴趣的模拟资产。它提供了一个简单的 API 来添加、操作、检查和重置场景中的不同 USD 资产。
# 从 Isaac Sim 的代码库中导入名为 BaseSample 的基类
from isaacsim.examples.interactive.base_sample import BaseSample #boiler plate of a robotics extension application
# 定义一个名为 HelloWorld 的类,继承自 BaseSample
class HelloWorld(BaseSample):
# 构造函数:初始化 HelloWorld 类实例。
# super().__init__():调用父类 BaseSample 的构造函数,确保基类的初始化逻辑(如资源加载、模拟器连接)正确执行。
# return:显式声明返回 None(Python 中可省略)
def __init__(self) -> None:
super().__init__()
return
# This function is called to setup the assets in the scene for the first time
# Class variables should not be assigned here, since this function is not called
# after a hot-reload, its only called to load the world starting from an EMPTY stage
# 重写父类 BaseSample 的 setup_scene 方法,用于自定义场景的初始化内容。
# 触发时机:当场景需要加载时(如点击“LOAD”按钮),Isaac Sim 会自动调用此方法。
# 在热重载时不会加载这个方法
def setup_scene(self):
# A world is defined in the BaseSample, can be accessed everywhere EXCEPT __init__
world = self.get_world()
# 获取 World 实例:通过 self.get_world() 方法从父类 BaseSample 中获取 World 类的实例。
# World 是控制模拟器的核心工具,后续通过 world 对象操作场景(如添加物体、控制物理模拟)。
world.scene.add_default_ground_plane() # adds a default ground plane to the scene
# 添加默认地面:调用 World 场景的 add_default_ground_plane() 方法,在模拟器中生成一个默认的地面平面。
return
4 Singleton World(只有一个世界)
在运行 NVIDIA Isaac Sim 时只能存在一个 World,并提供一个全局访问点获取该实例。若存在多个 World 实例,可能导致物理模拟混乱或资源冲突。
from isaacsim.examples.interactive.base_sample import BaseSample
from isaacsim.core.api import World
class HelloWorld(BaseSample):
def __init__(self) -> None:
super().__init__()
return
def setup_scene(self):
world = World.instance()
# 通过单例的全局访问方法 instance() 获取 World 的唯一实例。
# 与 self.get_world() 的区别:
# 1 self.get_world() 是 BaseSample 提供的方法,内部可能调用 World.instance()。
# 2 直接使用 World.instance() 更底层,适用于不依赖 BaseSample 的场景。
world.scene.add_default_ground_plane()
return
5 Adding to the Scene(将刚体加入场景)
使用 Python API 将立方体作为刚体添加到场景中。这里只按照官网给出一段代码,最后会给出一个完整的代码。
from isaacsim.examples.interactive.base_sample import BaseSample
import numpy as np
# Can be used to create a new cube or to point to an already existing cube in stage.
from isaacsim.core.api.objects import DynamicCuboid
class HelloWorld(BaseSample):
def __init__(self) -> None:
super().__init__()
return
def setup_scene(self):
world = self.get_world()
world.scene.add_default_ground_plane()
fancy_cube = world.scene.add(
# world.scene.add():将物体添加到场景中,并返回该物体的引用(此处为 fancy_cube)
DynamicCuboid(
prim_path="/World/random_cube", # The prim path of the cube in the USD stage
name="fancy_cube", # The unique name used to retrieve the object from the scene later on
position=np.array([0, 0, 1.0]), # Using the current stage units which is in meters by default.
scale=np.array([0.5015, 0.5015, 0.5015]), # most arguments accept mainly numpy arrays.
color=np.array([0, 0, 1.0]), # RGB channels, going from 0-1
))
# DynamicCuboid 参数详解:
# 1 prim_path:立方体在 USD(Universal Scene Description)场景中的唯一路径。
# 2 name:立方体的逻辑名称,用于后续从场景中检索对象(如 world.scene.get_object("fancy_cube"))。
# 3 position:立方体的初始位置(三维坐标)。默认米为单位。
# 4 scale:立方体的尺寸缩放(长、宽、高)。
# 5 color:立方体的 RGB 颜色,取值范围 [0.0, 1.0]。
return
- 按Ctrl+S保存代码并热重新加载 NVIDIA Isaac Sim。
- 再次打开菜单。
- 点击
File > New From Stage Template >Empty
,然后点击LOAD
按钮。如果你在setup_scene
中更改了任何内容,则需要执行此操作。否则,你只需按LOAD
按钮即可。 - 按下
PLAY
按钮开始模拟动态立方体并观察它掉落。(注:如果感兴趣可以调整isaacsim中的physicsScene
下的Gravity Direction
即重力方向,再点击PLAY
按钮会发现方块向你设定的方向自由落体。be like 盗梦空间~)
6 Inspecting Object Properties(查看对象性质)
打印立方体的世界姿势和速度。
from isaacsim.examples.interactive.base_sample import BaseSample
import numpy as np
from isaacsim.core.api.objects import DynamicCuboid
class HelloWorld(BaseSample):
def __init__(self) -> None:
super().__init__()
return
def setup_scene(self):
world = self.get_world()
world.scene.add_default_ground_plane()
fancy_cube = world.scene.add(
DynamicCuboid(
prim_path="/World/random_cube",
name="fancy_cube",
position=np.array([0, 0, 1.0]),
scale=np.array([0.5015, 0.5015, 0.5015]),
color=np.array([0, 0, 1.0]),
))
return
# Here we assign the class's variables
# this function is called after load button is pressed
# regardless starting from an empty stage or not
# this is called after setup_scene and after
# one physics time step to propagate appropriate
# physics handles which are needed to retrieve
# many physical properties of the different objects
async def setup_post_load(self):
self._world = self.get_world()
# self.get_world():从父类 BaseSample 继承的方法,返回全局唯一的 World 单例实例。
self._cube = self._world.scene.get_object("fancy_cube")
# self._world.scene.get_object():通过名称(name) "fancy_cube" 从场景中获取之前添加的立方体对象。
position, orientation = self._cube.get_world_pose()
# get_world_pose() 方法:返回立方体的全局坐标系下的位置和方向。
linear_velocity = self._cube.get_linear_velocity()
# get_linear_velocity() 方法:返回立方体在全局坐标系下的线速度(三维向量)。
# will be shown on terminal
print("Cube position is : " + str(position))
print("Cube's orientation is : " + str(orientation))
print("Cube's linear velocity is : " + str(linear_velocity))
return
7 Continuously Inspecting the Object Properties during Simulation(在仿真模拟过程中持续检查对象属性)
这段代码实现了 在 Isaac Sim 的物理模拟过程中,实时监控并打印立方体的位置、方向和速度信息。通过注册物理回调函数(physics callback),在每次物理步进(physics step)前执行自定义逻辑(打印物体状态)。
from isaacsim.examples.interactive.base_sample import BaseSample
import numpy as np
from isaacsim.core.api.objects import DynamicCuboid
class HelloWorld(BaseSample):
def __init__(self) -> None:
super().__init__()
return
def setup_scene(self):
world = self.get_world()
world.scene.add_default_ground_plane()
fancy_cube = world.scene.add(
DynamicCuboid(
prim_path="/World/random_cube",
name="fancy_cube",
position=np.array([0, 0, 1.0]),
scale=np.array([0.5015, 0.5015, 0.5015]),
color=np.array([0, 0, 1.0]),
))
return
async def setup_post_load(self):
self._world = self.get_world()
self._cube = self._world.scene.get_object("fancy_cube")
self._world.add_physics_callback("sim_step", callback_fn=self.print_cube_info) #callback names have to be unique
# add_physics_callback("sim_step", callback_fn=self.print_cube_info):
# 功能:向 World 实例添加一个物理回调函数
# 参数:
# 1 "sim_step":回调函数名称(需唯一,避免与其他回调冲突)。
# 2 callback_fn=self.print_cube_info:回调函数本身(每次物理步进前调用)。
# 触发时机:在 Isaac Sim 的物理引擎执行每个物理步进(step_physics)之前调用。
return
# here we define the physics callback to be called before each physics step, all physics callbacks must take
# step_size as an argument
def print_cube_info(self, step_size):
# 物理回调函数 print_cube_info
# 参数:step_size(物理步长时间,单位为秒)。
# Isaac Sim 要求所有物理回调函数必须接收此参数(即使未使用)。
position, orientation = self._cube.get_world_pose()
# 获取立方体状态:位置、方向、线速度。
linear_velocity = self._cube.get_linear_velocity()
# will be shown on terminal
print("Cube position is : " + str(position))
print("Cube's orientation is : " + str(orientation))
print("Cube's linear velocity is : " + str(linear_velocity))
# 打印信息:将状态输出到终端。
8 Converting the Example to a Standalone Application(将示例转换为独立程序)
什么是独立程序呢?
简单来说就是可以直接在命令行输入./python.sh ***.py
运行的程序。
#launch Isaac Sim before any other imports
#default first two lines in any standalone application
# 初始化并启动 Isaac Sim 应用程序。
from isaacsim import SimulationApp
simulation_app = SimulationApp({"headless": False}) # we can also run as headless.
from isaacsim.core.api import World
from isaacsim.core.api.objects import DynamicCuboid
import numpy as np
# 创建 World 单例实例(全局唯一)。注意没有.instance()
world = World()
# 在场景中添加无限延伸的默认地面,用于物理碰撞检测。
world.scene.add_default_ground_plane()
# 添加动态立方体
fancy_cube = world.scene.add(
DynamicCuboid(
prim_path="/World/random_cube",
name="fancy_cube",
position=np.array([0, 0, 1.0]),
scale=np.array([0.5015, 0.5015, 0.5015]),
color=np.array([0, 0, 1.0]),
))
# Resetting the world needs to be called before querying anything related to an articulation specifically.
# Its recommended to always do a reset after adding your assets, for physics handles to be propagated properly
# 重置世界:在查询物体属性(如速度、位姿)或启动模拟前,必须调用此方法。
# 1 初始化所有物理句柄(如刚体、关节的物理属性)。
# 2 确保新添加的物体(如立方体)正确注册到物理引擎。
world.reset()
# 推进物理模拟并监控状态
for i in range(500):
# 循环 500 次:执行 500 次物理步进
position, orientation = fancy_cube.get_world_pose()
linear_velocity = fancy_cube.get_linear_velocity()
# # 获取立方体状态
# will be shown on terminal
print("Cube position is : " + str(position))
print("Cube's orientation is : " + str(orientation))
print("Cube's linear velocity is : " + str(linear_velocity))
# we have control over stepping physics and rendering in this workflow
# things run in sync
world.step(render=True) # execute one physics step and one rendering step
# 推进模拟
# 1 物理步进:按默认步长(通常 1/60 秒)更新物理引擎。
# 2 渲染更新:更新 3D 窗口中的视觉效果(因 render=True)。
simulation_app.close() # close Isaac Sim
# 安全关闭 Isaac Sim 进程,释放资源。
补充:将循环部分代码替换为如下代码,方块会运行到下面归位重置再次继续仿真
for j in range(5):
world.reset()
for i in range(50):
position, orientation = fancy_cube.get_world_pose()
linear_velocity = fancy_cube.get_linear_velocity()
# will be shown on terminal
print("Cube position is : " + str(position))
print("Cube's orientation is : " + str(orientation))
print("Cube's linear velocity is : " + str(linear_velocity))
# we have control over stepping physics and rendering in this workflow
# things run in sync
world.step(render=True) # execute one physics step and one rendering step