Hello World#

NVIDIA Omniverse™ Kit, the toolkit that NVIDIA Isaac Sim uses to build its applications, provides a Python interpreter for scripting. This means every single GUI command, as well as many additional functions are available as Python APIs. However, the learning curve for interfacing with Omniverse Kit using Pixar’s USD Python API is steep and steps are frequently tedious. Therefore we’ve provided a set of APIs that are designed to be used in robotics applications, APIs that abstract away the complexity of USD APIs and merge multiple steps into one for frequently performed tasks.

In this tutorial, we will present the concepts of Core APIs and how to use them. We will start with adding a cube to an empty stage, and we’ll build upon it to create a scene with multiple robots executing multiple tasks simultaneously, as seen below.

../_images/core_api_tutorials_6_2.webp

Learning Objectives#

This tutorial series introduces the Core API. After this tutorial, you learn:

  • How to use the Core APIs to manipulate the USD stage.

  • How to add a rigid body to the Stage and simulate it using Python in NVIDIA Isaac Sim.

  • The difference between running Python in an Extension Workflow vs a Standalone Workflow.

10-15 Minute Tutorial

Getting Started#

Prerequisites

  • Intermediate knowledge in Python and asynchronous programming is required for this tutorial.

  • Please download and install Visual Studio Code prior to beginning this tutorial.

  • Please review Quick Tutorials and Workflows prior to beginning this tutorial.

Begin by opening the Hello World example. First activate Windows > Examples > Robotics Examples which will open the Robotics Examples tab.

  1. Click Robotics Examples > General > Hello World.

  2. Verify that the window for the Hello World example extension is visible in the workspace.

  3. Click the Open Source Code button to launch the source code for editing in Visual Studio Code.

  4. Click the Open Containing Folder button to open the directory containing the example files.

This folder contains three files: hello_world.py, hello_world_extension.py, and __init__.py.

The hello_world.py script is where the logic of the application will be added, while the UI elements of the application will be added in hello_world_extension.py script and thus linked to the logic.

  1. Click the LOAD button to load the World.

  2. click File > New From Stage Template > Empty to create a new stage, click Don’t Save when prompted to save the current stage.

  3. Click the LOAD button to load the World again.

  4. Open hello_world.py and press “Ctrl+S” to use the hot-reload feature. You will notice that the menu disappears from the workspace (because it was restarted).

  5. Open the example menu again and click the LOAD button.

Now you can begin adding to this example.

Code Overview#

This example inherits from BaseSample, which is a boilerplate extension application that sets up the basics for every robotics extension application. The following are a few examples of the actions BaseSample performs:

  1. Loading assets into the stage using a button.

  2. Clearing the stage when a new stage is created.

  3. Resetting objects to their default states.

  4. Handling hot reloading.

 1import isaacsim.core.experimental.utils.stage as stage_utils
 2from isaacsim.examples.base.base_sample_experimental import BaseSample
 3from isaacsim.storage.native import get_assets_root_path
 4
 5class HelloWorld(BaseSample):
 6    def __init__(self) -> None:
 7        super().__init__()
 8
 9    # This function is called to setup the assets in the scene for the first time
10    def setup_scene(self):
11        # Add ground plane directly to the stage
12        ground_plane = stage_utils.add_reference_to_stage(
13            usd_path=get_assets_root_path() + "/Isaac/Environments/Grid/default_environment.usd",
14            path="/World/ground",
15        )

Key Concepts#

Stage Utilities: The stage_utils module provides functions for directly manipulating the USD stage, such as adding references, creating prims, and managing stage hierarchy.

Prim Classes: The API provides prim wrapper classes like RigidPrim, GeomPrim, and Articulation that give you direct control over USD prims with physics capabilities.

SimulationManager: For callbacks and simulation events, the SimulationManager class provides methods to register and deregister callbacks for various simulation events.

Adding to the Scene#

Use the Python API to add a cube as a rigid body to the scene. With the Core APIs, create the geometry first, then apply collision and rigid body properties.

 1import isaacsim.core.experimental.utils.stage as stage_utils
 2from isaacsim.core.experimental.materials import PreviewSurfaceMaterial
 3from isaacsim.core.experimental.objects import Cube
 4from isaacsim.core.experimental.prims import GeomPrim, RigidPrim
 5from isaacsim.examples.base.base_sample_experimental import BaseSample
 6from isaacsim.storage.native import get_assets_root_path
 7import numpy as np
 8
 9class HelloWorld(BaseSample):
10    def __init__(self) -> None:
11        super().__init__()
12
13    def setup_scene(self):
14        # Add ground plane
15        ground_plane = stage_utils.add_reference_to_stage(
16            usd_path=get_assets_root_path() + "/Isaac/Environments/Grid/default_environment.usd",
17            path="/World/ground",
18        )
19
20        # Create a blue visual material for the cube
21        visual_material = PreviewSurfaceMaterial("/World/Materials/blue")
22        visual_material.set_input_values("diffuseColor", [0.0, 0.0, 1.0])
23
24        # Create the cube geometry
25        self._cube_shape = Cube(
26            paths="/World/fancy_cube",
27            positions=np.array([[0.0, 0.0, 1.0]]),  # Starting position 1m above ground
28            sizes=[1.0],
29            scales=np.array([[0.5015, 0.5015, 0.5015]]),  # Scale the cube
30            reset_xform_op_properties=True,
31        )
32
33        # Apply collision APIs to enable physics collision
34        GeomPrim(paths=self._cube_shape.paths, apply_collision_apis=True)
35
36        # Make it a rigid body (dynamic object that responds to physics)
37        self._cube = RigidPrim(paths=self._cube_shape.paths)
38
39        # Apply the blue material
40        self._cube_shape.apply_visual_materials(visual_material)
  1. Press Ctrl+S to save the code and hot-reload NVIDIA Isaac Sim.

  2. Open the menu again.

  3. click File > New From Stage Template > Empty, then the LOAD button. You need to perform this action if you change anything in the setup_scene. Otherwise, you only need to press the LOAD button.

  4. See the dynamic cube falling as the simulation starts automatically.

../_images/core_api_tutorials_1_1.webp

Note

Every time the code is edited or changed, press Ctrl+S to save the code and hot-reload NVIDIA Isaac Sim.

Understanding the Prim Classes#

The experimental API uses a layered approach to create physics-enabled objects:

  1. Cube (or other shape classes): Creates the visual geometry on the USD stage.

  2. GeomPrim: Wraps the geometry and can apply collision APIs for physics interactions.

  3. RigidPrim: Adds rigid body dynamics, making the object respond to gravity and forces.

This modular approach gives you fine-grained control - you can create static colliders (GeomPrim without RigidPrim) or fully dynamic objects (with both).

Inspecting Object Properties#

Print the world pose and velocity of the cube. The highlighted lines show how you can query object properties.

 1import isaacsim.core.experimental.utils.stage as stage_utils
 2from isaacsim.core.experimental.materials import PreviewSurfaceMaterial
 3from isaacsim.core.experimental.objects import Cube
 4from isaacsim.core.experimental.prims import GeomPrim, RigidPrim
 5from isaacsim.examples.base.base_sample_experimental import BaseSample
 6from isaacsim.storage.native import get_assets_root_path
 7import numpy as np
 8
 9class HelloWorld(BaseSample):
10    def __init__(self) -> None:
11        super().__init__()
12
13    def setup_scene(self):
14        # Add ground plane
15        ground_plane = stage_utils.add_reference_to_stage(
16            usd_path=get_assets_root_path() + "/Isaac/Environments/Grid/default_environment.usd",
17            path="/World/ground",
18        )
19
20        # Create a blue visual material for the cube
21        visual_material = PreviewSurfaceMaterial("/World/Materials/blue")
22        visual_material.set_input_values("diffuseColor", [0.0, 0.0, 1.0])
23
24        # Create the cube geometry
25        self._cube_shape = Cube(
26            paths="/World/fancy_cube",
27            positions=np.array([[0.0, 0.0, 1.0]]),
28            sizes=[1.0],
29            scales=np.array([[0.5015, 0.5015, 0.5015]]),
30            reset_xform_op_properties=True,
31        )
32
33        # Apply collision and rigid body
34        GeomPrim(paths=self._cube_shape.paths, apply_collision_apis=True)
35        self._cube = RigidPrim(paths=self._cube_shape.paths)
36        self._cube_shape.apply_visual_materials(visual_material)
37
38    # This function is called after load button is pressed
39    # It's called after setup_scene and after one physics time step
40    # to propagate physics handles needed to retrieve physical properties
41    async def setup_post_load(self):
42        # Query cube properties using RigidPrim methods
43        positions, orientations = self._cube.get_world_poses()
44        # get_velocities() returns a tuple: (linear_velocities, angular_velocities)
45        linear_velocities, angular_velocities = self._cube.get_velocities()
46
47        # Convert from warp arrays to numpy for printing
48        # Note: experimental APIs return batched results (even for single objects)
49        print("Cube position is : " + str(positions.numpy()[0]))
50        print("Cube's orientation is : " + str(orientations.numpy()[0]))
51        print("Cube's linear velocity is : " + str(linear_velocities.numpy()[0]))

Note

The experimental APIs return batched results as warp arrays. Use .numpy() to convert them to numpy arrays, and index with [0] to get the first (and only) element when working with a single object.

Continuously Inspecting the Object Properties during Simulation#

Print the world pose and velocity of the cube during simulation at every physics step executed. As mentioned in Workflows, in this workflow the application is running asynchronously and can’t control when to step physics. However, you can add callbacks to ensure certain things happen before certain events.

Add a physics callback using the SimulationManager:

 1import isaacsim.core.experimental.utils.stage as stage_utils
 2from isaacsim.core.experimental.materials import PreviewSurfaceMaterial
 3from isaacsim.core.experimental.objects import Cube
 4from isaacsim.core.experimental.prims import GeomPrim, RigidPrim
 5from isaacsim.examples.base.base_sample_experimental import BaseSample
 6from isaacsim.storage.native import get_assets_root_path
 7from isaacsim.core.simulation_manager import SimulationManager
 8import numpy as np
 9
10class HelloWorld(BaseSample):
11    def __init__(self) -> None:
12        super().__init__()
13        self._physics_callback_id = None
14
15    def setup_scene(self):
16        # Add ground plane
17        ground_plane = stage_utils.add_reference_to_stage(
18            usd_path=get_assets_root_path() + "/Isaac/Environments/Grid/default_environment.usd",
19            path="/World/ground",
20        )
21
22        # Create a blue visual material for the cube
23        visual_material = PreviewSurfaceMaterial("/World/Materials/blue")
24        visual_material.set_input_values("diffuseColor", [0.0, 0.0, 1.0])
25
26        # Create the cube geometry
27        self._cube_shape = Cube(
28            paths="/World/fancy_cube",
29            positions=np.array([[0.0, 0.0, 1.0]]),
30            sizes=[1.0],
31            scales=np.array([[0.5015, 0.5015, 0.5015]]),
32            reset_xform_op_properties=True,
33        )
34
35        # Apply collision and rigid body
36        GeomPrim(paths=self._cube_shape.paths, apply_collision_apis=True)
37        self._cube = RigidPrim(paths=self._cube_shape.paths)
38        self._cube_shape.apply_visual_materials(visual_material)
39
40    async def setup_post_load(self):
41        # Register a physics callback using SimulationManager
42        from isaacsim.core.simulation_manager.impl.isaac_events import IsaacEvents
43
44        self._physics_callback_id = SimulationManager.register_callback(
45            self.print_cube_info, IsaacEvents.POST_PHYSICS_STEP
46        )
47
48    # Physics callback function - called after each physics step
49    # Takes dt (delta time) and context as arguments
50    def print_cube_info(self, dt, context):
51        positions, orientations = self._cube.get_world_poses()
52        linear_velocities, angular_velocities = self._cube.get_velocities()
53
54        print("Cube position is : " + str(positions.numpy()[0]))
55        print("Cube's orientation is : " + str(orientations.numpy()[0]))
56        print("Cube's linear velocity is : " + str(linear_velocities.numpy()[0]))
57
58    def physics_cleanup(self):
59        # Clean up callback when the extension is unloaded
60        if self._physics_callback_id is not None:
61            SimulationManager.deregister_callback(self._physics_callback_id)
62            self._physics_callback_id = None

Converting the Example to a Standalone Application#

Note

  • On windows use python.bat instead of python.sh

  • The details of how python.sh works below are similar to how python.bat works

As mentioned in Workflows, in this workflow, the robotics application is started when launched from Python right away.

  1. Open a new my_application.py file and add the following:

 1# Launch Isaac Sim before any other imports
 2# Default first two lines in any standalone application
 3from isaacsim import SimulationApp
 4simulation_app = SimulationApp({"headless": False})  # we can also run as headless
 5
 6# Now import Isaac Sim modules
 7import isaacsim.core.experimental.utils.stage as stage_utils
 8import omni.timeline
 9from isaacsim.core.experimental.materials import PreviewSurfaceMaterial
10from isaacsim.core.experimental.objects import Cube
11from isaacsim.core.experimental.prims import GeomPrim, RigidPrim
12from isaacsim.core.simulation_manager import SimulationManager
13from isaacsim.storage.native import get_assets_root_path
14import numpy as np
15
16# Add ground plane
17ground_plane = stage_utils.add_reference_to_stage(
18    usd_path=get_assets_root_path() + "/Isaac/Environments/Grid/default_environment.usd",
19    path="/World/ground",
20)
21
22# Create a blue visual material for the cube
23visual_material = PreviewSurfaceMaterial("/World/Materials/blue")
24visual_material.set_input_values("diffuseColor", [0.0, 0.0, 1.0])
25
26# Create the cube geometry
27cube_shape = Cube(
28    paths="/World/fancy_cube",
29    positions=np.array([[0.0, 0.0, 1.0]]),
30    sizes=[1.0],
31    scales=np.array([[0.5, 0.5, 0.5]]),
32    reset_xform_op_properties=True,
33)
34
35# Apply collision and rigid body
36GeomPrim(paths=cube_shape.paths, apply_collision_apis=True)
37cube = RigidPrim(paths=cube_shape.paths)
38cube_shape.apply_visual_materials(visual_material)
39
40# Start the timeline (physics simulation)
41omni.timeline.get_timeline_interface().play()
42simulation_app.update()
43
44# Run the simulation loop
45for i in range(500):
46    # Only query when physics is actively simulating
47    if SimulationManager.is_simulating():
48        positions, orientations = cube.get_world_poses()
49        linear_velocities, angular_velocities = cube.get_velocities()
50
51        # Will be shown on terminal
52        print("Cube position is : " + str(positions.numpy()[0]))
53        print("Cube's orientation is : " + str(orientations.numpy()[0]))
54        print("Cube's linear velocity is : " + str(linear_velocities.numpy()[0]))
55
56    # Step the app (physics + rendering)
57    simulation_app.update()
58
59simulation_app.close()  # close Isaac Sim
  1. Run it using ./python.sh ./exts/isaacsim.examples.interactive/isaacsim/examples/interactive/user_examples/my_application.py.

Summary#

This tutorial covered the following topics:

  1. Overview of the Core APIs for direct stage manipulation.

  2. Using stage_utils to add assets to the stage.

  3. Creating dynamic objects with Cube, GeomPrim, and RigidPrim.

  4. Registering physics callbacks with SimulationManager.

  5. Accessing dynamic properties for objects using prim wrapper methods.

  6. The main differences in a standalone application.

Next Steps#

Continue to Hello Robot to learn how to add a robot to the simulation.

Note

The next tutorials will be developed mainly using the extensions application workflow. However, conversion to other workflows is similar given what was covered in this tutorial.