3. Hello World

3.1. Learning Objectives

Tutorials 3–8 in the Required Tutorials series build on each other to create a complex simulation with multiple robots executing multiple tasks as shown below.

../_images/isaac_sim_multiple_tasks.gif

This tutorial sets the foundation of the simulation. It introduces the programming API using the main workflows to create your robotics application using Omniverse Isaac Sim. After this tutorial, you will understand how to add a rigid body to the Stage and simulate it using python in Omniverse Isaac Sim.

10-15 Minute Tutorial

3.2. 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 Isaac Sim Interface prior to beginning this tutorial.

  • Please review Workflows prior to beginning this tutorial.

Let’s begin by opening the Hello World example. Go to the top Menu Bar and Click Isaac Examples > Hello World.

The window for the Hello World example extension should now be visible in the workspace. Click the Open Source Code button to launch the source code for editing in Visual Studio Code.

../_images/isaac_tutorial_required_hello_world_1.gif

Click the Open Containing Folder button to open the directory containing the example’s files. This folder contains three files hello_world.py, hello_world_extension.py and __init__.py.

hello_world.py 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 and thus linked to the logic.

  • Click on the LOAD button to load the World.

  • Click on the CLEAR button to clear the World.

  • Click on the LOAD button to load the World again.

  • Open hello_world.py and press Ctrl+S to checkout the hot-reload feature. You will notice that the menu disappeared from the workspace (because it was restarted).

  • Open now the example menu again and click on the LOAD button.

Now we can begin adding to this example.

3.3. Code Overview

This example inherits from BaseSample which is a boilerplate extension application which sets up the basics for every robotics extension application, such as:

  • Loading the world with its corresponding assets using a button

  • Clearing the world using a button

  • Resetting the world’s objects to their default states.

  • Handling hot reloading and much more…

World is the core class that enables you to interact with the simulator in an easy and modular way. It takes care of many time-related events such as adding callbacks, stepping physics, resetting the scene, adding tasks (this will be covered later in Adding a Manipulator Robot), etc.

A world contains an instance of a Scene. The Scene class manages simulation assets of interest in the USD Stage. It provides an easy API to add, manipulate, inspect, and reset different USD assets in the stage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
from omni.isaac.examples.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.3.1. Singleton World

World is a Singleton which means only one World can exist while running Omniverse Isaac Sim. The code below demonstrates how to retrieve the current instance of the World across different files and extensions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from omni.isaac.examples.base_sample import BaseSample
from omni.isaac.core 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

3.4. Adding to the Scene

Let’s use the python API to add a cube as a rigid body to the scene.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from omni.isaac.examples.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 omni.isaac.core.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, 100.0]), # Using the current stage units which is cms by default.
                size=np.array([50.15, 50.15, 50.15]), # most arguments accept mainly numpy arrays.
                color=np.array([0, 0, 1.0]), # RGB channels, going from 0-1
            ))
        return
  • Press Ctrl+S to save the code and hot-reload Omniverse Isaac Sim.

  • Open the menu again

  • If we change anything in setup_scene, CLEAR then LOAD buttons need to be pressed. Otherwise, only the LOAD button need to be pressed. In this case, we need to press CLEAR then LOAD.

  • Press the PLAY button to start simulating the dynamic cube and see it falling.

../_images/isaac_sim_hello_world.gif

Note

Everytime the code is edited/ changed, Press Ctrl+S to save the code and hot-reload Omniverse Isaac Sim.

3.5. Inspecting Objects Properties

Let’s print the cube’s world pose and velocity.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from omni.isaac.examples.base_sample import BaseSample
import numpy as np
from omni.isaac.core.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, 100.0]),
                size=np.array([50.15, 50.15, 50.15]),
                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

3.5.1. Continuously Inspecting the Object Properties during Simulation

Let’s print the cube’s world pose and velocity 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, callbacks can be added to ensure certain things happen before certain events.

Let’s learn how to add a physics callback here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from omni.isaac.examples.base_sample import BaseSample
import numpy as np
from omni.isaac.core.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, 100.0]),
                size=np.array([50.15, 50.15, 50.15]),
                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))

3.6. Adding a New Example in the Menu

So far, we have been editing the Hello World example. Let’s create a new example under the Isaac Examples menu.

  • Copy the current files to the user_examples folder under exts/omni.isaac.examples/omni/isaac/examples/

    cd ~/.local/share/ov/pkg/isaac_sim_*/
    cd exts/omni.isaac.examples/omni/isaac/examples/
    cp hello_world/hello_world* user_examples/
    
  • Edit the exts/omni.isaac.examples/omni/isaac/examples/user_examples/__init__.py file by adding the following lines.

    1
    2
    from omni.isaac.examples.user_examples.hello_world import HelloWorld
    from omni.isaac.examples.user_examples.hello_world_extension import HelloWorldExtension
    
  • Edit the exts/omni.isaac.examples/omni/isaac/examples/user_examples/hello_world_extension.py file

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import os
    from omni.isaac.examples.base_sample import BaseSampleExtension
    from omni.isaac.examples.user_examples import HelloWorld
    
    
    class HelloWorldExtension(BaseSampleExtension):
        def on_startup(self, ext_id: str):
            super().on_startup(ext_id)
            super().start_extension(
                menu_name="",
                submenu_name="",
                name="Awesome Example",
                title="My Awesome Example",
                doc_link="https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/tutorial_required_hello_world.html",
                overview="This Example introduces the user on how to do cool stuff with Isaac Sim through scripting in asynchronous mode.",
                file_path=os.path.abspath(__file__),
                sample=HelloWorld(),
            )
            return
    
../_images/isaac_sim_new_example.gif

Note

Everytime the code is edited/ changed, Press Ctrl+S to save the code and hot-reload Omniverse Isaac Sim.

3.7. Converting it to a Standalone Application

As mentioned in Workflows, in this workflow, the robotics application is started when launched from python right away and we have control over when to step physics and rendering.

  • Open a new file my_application.py and add the following.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    #launch Isaac Sim before any other imports
    #default first two lines in any standalone application
    from omni.isaac.kit import SimulationApp
    simulation_app = SimulationApp({"headless": False}) # we can also run as headless.
    
    from omni.isaac.core import World
    from omni.isaac.core.objects import DynamicCuboid
    import numpy as np
    
    world = World(stage_units_in_meters=0.01)
    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, 100.0]),
            size=np.array([50.15, 50.15, 50.15]),
            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
    
  • Run it using python my_application.py. Note: if you didn’t setup your python environment, you can use python.sh located at ~/.local/share/ov/pkg/isaac_sim_*/

3.8. Converting it to a Standalone Application using Jupyter Notebook

  1. Open a jupyter notebook

cd ~/.local/share/ov/pkg/isaac_sim_*/
./jupyter_notebook.sh standalone_examples/notebooks/hello_world.ipynb
  1. Access the notebook through the browser as shown on the terminal

  2. Execute the first cell in the notebook

    1
    2
    3
    4
    5
    6
    import getpass
    user = getpass.getuser()
    from omni.isaac.kit import SimulationApp
    # Set the path below to your desired nucleus server
    # Make sure you installed a local nucleus server before this
    simulation_app = SimulationApp({"livesync_usd": f'omniverse://localhost/Users/{user}/temp_jupyter_stage.usd'})
    
  3. Open the same usd file after launching Isaac Sim through the launcher or the terminal and turn the live syncing option on. Note: DON’T press play, pause or stop in the GUI in this workflow.

    ../_images/isaac_sim_live_sync.gif
  4. Run through the rest of the cells in the notebook, while viewing the stage directly in Omniverse Isaac Sim GUI.

Note

DON’T press play, pause or stop in the GUI in this workflow.

Note

A .render call is needed at the end of each cell in order for the changes to propagate to the synced USD (i.e World.render or SimulationContext.render)

Note

Use world.clear() or clear_stage at the beginning of a cell for iterative development.

3.9. Summary

This tutorial covered the following topics:

  1. Overview of what is World and Scene classes.

  2. Adding Content to the Scene via Python

  3. Adding Callbacks

  4. Accessing Dynamic Properties for Objects

  5. Main differences in a standalone application

  6. Main differences when developing your application using jupyter

3.9.1. Next Steps

Continue on to the next tutorial in our Essential Tutorials series, Hello Robot, to learn how to add a robot to the simulation.

Note: next tutorials will be developed mainly using the extensions application workflow. However, conversion to other workflows should be straightforward given what was covered in this tutorial.