Create a Standalone Application
This tutorial introduces how to create a standalone python script to set up an empty scene.
It introduces the main class used by Isaac Sim
simulator, SimulationApp
, as well as the timeline
concept that helps to launch and control the simulation timeline. In this tutorial we do NOT expose the
Pegasus API
yet.
1. Code
The tutorial corresponds to the 0_template_app.py
example in the examples
directory.
1#!/usr/bin/env python
2"""
3| File: 0_template_app.py
4| Author: Marcelo Jacinto (marcelo.jacinto@tecnico.ulisboa.pt)
5| License: BSD-3-Clause. Copyright (c) 2023, Marcelo Jacinto. All rights reserved.
6| Description: This files serves as a template on how to build a clean and simple Isaac Sim based standalone App.
7"""
8
9# Imports to start Isaac Sim from this script
10import carb
11from isaacsim import SimulationApp
12
13# Start Isaac Sim's simulation environment
14# Note: this simulation app must be instantiated right after the SimulationApp import, otherwise the simulator will crash
15# as this is the object that will load all the extensions and load the actual simulator.
16simulation_app = SimulationApp({"headless": False})
17
18# -----------------------------------
19# The actual script should start here
20# -----------------------------------
21import omni.timeline
22from omni.isaac.core import World
23
24class Template:
25 """
26 A Template class that serves as an example on how to build a simple Isaac Sim standalone App.
27 """
28
29 def __init__(self):
30 """
31 Method that initializes the template App and is used to setup the simulation environment.
32 """
33
34 # Acquire the timeline that will be used to start/stop the simulation
35 self.timeline = omni.timeline.get_timeline_interface()
36
37 # Acquire the World, .i.e, the singleton that controls that is a one stop shop for setting up physics,
38 # spawning asset primitives, etc.
39 self.world = World()
40
41 # Create a ground plane for the simulation
42 self.world.scene.add_default_ground_plane()
43
44 # Create an example physics callback
45 self.world.add_physics_callback('template_physics_callback', self.physics_callback)
46
47 # Create an example render callback
48 self.world.add_render_callback('template_render_callback', self.render_callback)
49
50 # Create an example timeline callback
51 self.world.add_timeline_callback('template_timeline_callback', self.timeline_callback)
52
53 # Reset the simulation environment so that all articulations (aka robots) are initialized
54 self.world.reset()
55
56 # Auxiliar variable for the timeline callback example
57 self.stop_sim = False
58
59 def physics_callback(self, dt: float):
60 """An example physics callback. It will get invoked every physics step.
61
62 Args:
63 dt (float): The time difference between the previous and current function call, in seconds.
64 """
65 carb.log_info("This is a physics callback. It is called every " + str(dt) + " seconds!")
66
67 def render_callback(self, data):
68 """An example render callback. It will get invoked for every rendered frame.
69
70 Args:
71 data: Rendering data.
72 """
73 carb.log_info("This is a render callback. It is called every frame!")
74
75 def timeline_callback(self, timeline_event):
76 """An example timeline callback. It will get invoked every time a timeline event occurs. In this example,
77 we will check if the event is for a 'simulation stop'. If so, we will attempt to close the app
78
79 Args:
80 timeline_event: A timeline event
81 """
82 if self.world.is_stopped():
83 self.stop_sim = True
84
85 def run(self):
86 """
87 Method that implements the application main loop, where the physics steps are executed.
88 """
89
90 # Start the simulation
91 self.timeline.play()
92
93 # The "infinite" loop
94 while simulation_app.is_running() and not self.stop_sim:
95
96 # Update the UI of the app and perform the physics step
97 self.world.step(render=True)
98
99 # Cleanup and stop
100 carb.log_warn("Template Simulation App is closing.")
101 self.timeline.stop()
102 simulation_app.close()
103
104def main():
105
106 # Instantiate the template app
107 template_app = Template()
108
109 # Run the application loop
110 template_app.run()
111
112if __name__ == "__main__":
113 main()
2. Explanation
The first step, when creating a standalone application
with Isaac Sim
is to instantiate the SimulationApp
object, and it takes a dictionary of parameters that can be used to configure the application. This
object will be responsible for opening a bare bones
version of the simulator. The headless
parameter
selects whether to launch the GUI window or not.
11from isaacsim import SimulationApp
12
13# Start Isaac Sim's simulation environment
14# Note: this simulation app must be instantiated right after the SimulationApp import, otherwise the simulator will crash
15# as this is the object that will load all the extensions and load the actual simulator.
16simulation_app = SimulationApp({"headless": False})
Note
You can NOT import other omniverse modules before instantiating the SimulationApp
, otherwise the simulation crashes
before the window it’s even open. For a deeper understanding on how the SimulationApp
works, check
NVIDIA’s offical documentation.
In order to create a Simulation Context, we resort to the World class environment. The World class inherits the Simulation Context, and provides already some default parameters for setting up a simulation for us. You can pass as arguments the physics time step and rendering time step (in seconds). The rendering and physics can be set to run at different rates. For now let’s use the defaults: 60Hz for both rendering and physics.
After creating the World class environment. The World class inherits the Simulation Context, we will
take advantage of its callback system
to declare that some functions defined by us should be called at every physics iteration,
render iteration or every time there is a timeline event, such as pressing the start/stop button. In this case, the
physics_callback
method will be invoked at every physics step, the render_callback
at every render step and
timeline_callback
every time there is a timeline event. You can add as many callbacks as you want. After having all your
callbacks defined, the world.reset()
method should be invoked to initialize the physics context
and set any existing
robot joints (in this case there is none) to their default state.
35 self.timeline = omni.timeline.get_timeline_interface()
36
37 # Acquire the World, .i.e, the singleton that controls that is a one stop shop for setting up physics,
38 # spawning asset primitives, etc.
39 self.world = World()
40
41 # Create a ground plane for the simulation
42 self.world.scene.add_default_ground_plane()
43
44 # Create an example physics callback
45 self.world.add_physics_callback('template_physics_callback', self.physics_callback)
46
47 # Create an example render callback
48 self.world.add_render_callback('template_render_callback', self.render_callback)
49
50 # Create an example timeline callback
51 self.world.add_timeline_callback('template_timeline_callback', self.timeline_callback)
52
53 # Reset the simulation environment so that all articulations (aka robots) are initialized
54 self.world.reset()
To start the actual simulation, we invoke the timeline’s play()
method. This is necessary in order to ensure that
every previously defined callback gets invoked. In order for the Isaac Sim
app to remain responsive, we need to create
a while loop
that invokes world.step(render=True)
to make sure that the UI get’s rendered.
85 def run(self):
86 """
87 Method that implements the application main loop, where the physics steps are executed.
88 """
89
90 # Start the simulation
91 self.timeline.play()
92
93 # The "infinite" loop
94 while simulation_app.is_running() and not self.stop_sim:
95
96 # Update the UI of the app and perform the physics step
97 self.world.step(render=True)
98
99 # Cleanup and stop
100 carb.log_warn("Template Simulation App is closing.")
101 self.timeline.stop()
102 simulation_app.close()
As you may have noticed, our infinite loop
is very clean. In this work, similarly to the ROS 2 standard,
we prefer to perform all the logic by resorting to function callbacks instead of cramming all the logic inside
the while loop. This structure allows our code to be more organized and more modular. As you will learn in the following
tutorials, the Pegasus Simulator API
is built on top of this idea of callbacks
.
3. Execution
Now let’s run the Python script:
ISAACSIM_PYTHON examples/0_template_app.py
This should open a stage with a blue ground-plane. The simulation should start playing automatically and the stage being rendered.
To stop the simulation, you can either close the window
, press the STOP button
in the UI, or press Ctrl+C
in the terminal.
Note
Notice that the window will only close when pressing the STOP button
, because we defined a method called
timeline_callback
that get’s invoked for every timeline event
, such as pressing the STOP button
.