Nvidia Isaac Sim 8 - Hello World

NVIDIA Omniverse™ Kit 是 NVIDIA Isaac Sim 用于构建其应用程序的工具包,它提供用于脚本编写的 Python 解释器。这意味着每个 GUI 命令以及许多额外功能都可以通过 Python API 使用。不过,使用 Pixar USD Python API 与 Omniverse Kit 交互的学习曲线很陡,而且相关步骤通常比较繁琐。因此,NVIDIA Isaac Sim 提供一组专为机器人应用而设计的 API。这些 API 封装 USD API 的复杂性,并且将常用任务中需要多步完成的操作整合为一步。

本教程将介绍 Core API 的概念及其使用方法。先从向空 Stage 中添加立方体开始,然后在此基础上逐步构建场景,其中包含多个同时执行多个任务的机器人。如图所示:core_api_tutorials_6_2.webp


1. 学习目标

本系列教程将介绍 Core API。完成本教程后,可以了解:

  • 按照 Core API 的定义创建 World 和 Scene
  • 如何向 Stage 中添加刚体,以及在 NVIDIA Isaac Sim 中使用 Python 对其进行仿真。
  • 在 Extension 工作流Standalone 工作流以及 Jupyter Notebook 中运行 Python 的区别。

2. 开始

先决条件

  • 本教程要求具备 Python 和异步编程的中级知识。

首先打开 Hello World 示例。激活 Windows > Examples > Robotics Examples,这将打开 Robotics Examples 选项卡。

  1. 点击 Robotics Examples > General > Hello World
  1. 确认 Hello World 示例扩展的窗口在工作区中可见。

这个文件夹包含三个文件:hello_world.pyhello_world_extension.py 和 __init__.py

hello_world.py 脚本是应用程序逻辑所在的地方,而应用程序的 UI 元素则在 hello_world_extension.py 脚本中添加,由此与逻辑进行关联。

  1. 点击 LOAD 按钮,加载 World。
  1. 点击 File > New From Stage Template > Empty 创建新 Stage;当系统提示是否保存当前 Stage 时,点击 Don’t Save
  1. 再次点击 LOAD 按钮,重新加载 World。
  1. 打开 hello_world.py,按 Ctrl+S 使用热重载功能。可以看到,菜单从工作区中消失(因为它已重新启动)。
  1. 再次打开示例菜单,点击 LOAD 按钮。

现在可以开始向这个示例添加内容。


3. 代码概览

该示例继承自 BaseSampleBaseSample 是 Extension 应用程序的模版,用于为机器人 Extension 应用程序搭建基础功能。下面是 BaseSample 执行的一些操作示例:

  1. 通过按钮加载 World 及其对应资源。
  1. 在创建新 Stage 时清空 World。
  1. 将 World 中的对象重置为默认状态。
  1. 处理热重载。

World 是能够以简单且模块化的方式与仿真器交互的核心类。它处理许多与时间相关的事件,比如添加回调、推进物理步、重置场景、添加任务等。

World 包含 Scene 实例。Scene 类用于管理 USD Stage 中需要关注的仿真资源。它提供简单易用的 API,用于在 Stage 中添加、操作、检查和重置不同的 USD 资源。

from isaacsim.examples.interactive.base_sample import BaseSample #boiler plate of a robotics extension application

class HelloWorld(BaseSample):
    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
    def setup_scene(self):
        # A world is defined in the BaseSample, can be accessed everywhere EXCEPT __init__
        world = self.get_world()
        world.scene.add_default_ground_plane() # adds a default ground plane to the scene
        return

3.1. 单例 World

World 是单例,这意味着在运行 NVIDIA Isaac Sim 时只能存在一个 World。下面的代码演示如何在不同文件和 Extension 中获取当前 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()
        world.scene.add_default_ground_plane()
        return

4. 向 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(
            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
            ))
        return
  1. 按 Ctrl+S 保存代码,热重载 NVIDIA Isaac Sim。
  1. 再次打开菜单。
  1. 点击 File > New From Stage Template > Empty,然后点击 LOAD 按钮。如已对 setup_scene 中的内容进行修改,则需要执行该操作。否则,只需点击 LOAD 按钮即可。
  1. 点击 PLAY 按钮开始仿真动态立方体,观察其下落。

5. 检查对象属性

打印立方体的世界位姿和速度。高亮行展示如何通过名称获取对象及查询其属性。

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._cube = self._world.scene.get_object("fancy_cube")
        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))
        return

5.1. 在仿真期间持续检查对象属性

仿真过程中,在每个物理步打印立方体的世界位姿和速度。在该工作流中,应用程序是异步运行的,无法控制何时推进物理步。不过,可以添加回调,确保某些操作在特定事件之前发生。

按如下方式添加物理回调:

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
        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):
        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))

6. 将示例转换为 Standalone 应用程序

  • 注意
    1. 在 Windows 上,使用 python.bat 代替 python.sh
    1. 下面关于 python.sh 工作方式的说明,同样适用于 python.bat

在此工作流中,从 Python 启动后,机器人应用立即开始运行,并且可以控制何时推进物理步和渲染步。

打开新 my_application.py 文件,并且添加以下内容:

#launch Isaac Sim before any other imports
#default first two lines in any standalone application
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 = 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
world.reset()
for i in range(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

simulation_app.close() # close Isaac Sim

使用 ./python.sh ./exts/isaacsim.examples.interactive/isaacsim/examples/interactive/user_examples/my_application.py 运行该脚本。