Unverified Commit 623b2d99 authored by Pascal Roth's avatar Pascal Roth Committed by GitHub

Updates the tutorials documentation to latest framework (#246)

# Description

This MR updates the tutorials to the latest version of Orbit. It
introduced a new structure for them. They cover basic sim elements,
assets, sensors, scenes, and envs.

Fixes #190 , #149 , #148 , #147 , #151, #152, #155, #156, #157

## Type of change

- New feature (non-breaking change which adds functionality)
- This change requires a documentation update

## Checklist

- [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./orbit.sh --format`
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [x] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

---------
Signed-off-by: 's avatarPascal Roth <57946385+pascal-roth@users.noreply.github.com>
Co-authored-by: 's avatarJames Smith <jsmith@theaiinstitute.com>
parent a61e262d
......@@ -203,7 +203,7 @@ html_theme_options = {
"show_toc_level": 1,
"use_sidenotes": True,
"logo": {
"text": f"orbit documentation",
"text": "orbit documentation",
"image_light": "source/_static/NVIDIA-logo-white.png",
"image_dark": "source/_static/NVIDIA-logo-black.png",
},
......
......@@ -37,23 +37,33 @@ For more information about the framework, please refer to the `paper <https://ar
source/features/actuators
source/features/motion_generators
.. toctree::
:maxdepth: 1
:caption: How-to Guides
source/how_to_guides/index
.. toctree::
:maxdepth: 1
:caption: Tutorials (Core)
source/tutorials/00_empty
source/tutorials/01_arms
source/tutorials/02_cloner
source/tutorials/03_ik_controller
source/tutorials/00_sim/empty
source/tutorials/00_sim/prims
source/tutorials/01_assets/articulation
source/tutorials/01_assets/rigid_object
source/tutorials/02_scene/scene
.. toctree::
:hidden:
:maxdepth: 1
:caption: Tutorials (Environments)
source/tutorials_envs/00_gym_env
source/tutorials_envs/01_create_env
source/tutorials_envs/02_wrappers
source/tutorials/03_envs/00_gym_env
source/tutorials/03_envs/01_create_base_env
source/tutorials/03_envs/02_create_rl_env
source/tutorials/03_envs/03_wrappers
.. toctree::
:maxdepth: 2
......
.. _actuators-guide:
Actuators
=========
......
Creating an Articulation
========================
In this tutorial, we move beyond the use of pre-built Articulations such as
Anymal and Franka, focusing instead on the steps required to integrate
custom robots into Orbit. The tutorial provides a
step-by-step guide on importing a robot design in either USD format and
spawning it in Orbit as an :class:`Articulation`.
.. TODO: Talk about how to import via URDF
What is a Cartpole?
~~~~~~~~~~~~~~~~~~~
Cartpole, a variation of the inverted pendulum problem
(https://en.wikipedia.org/wiki/Inverted_pendulum), serves as a practical
example for learning traditional control and RL. The cartpole has a single
controllable degree of freedom (DOF) at the joint between the cart and
the rail. The attached pole has 1 DOF that allows it to rotate freely.
.. TODO: Add isaac sim screenshot and replace GIF with a webdb
In :ref:`creating-base-env` participants will learn to control the
pole to stabilize the cart, but this tutorial focuses on merely constructing
the :class:`ArticulationCfg` that defines the cartpole.
The Code
~~~~~~~~
The tutorial corresponds to the ``cartpole.py`` script in the
``orbit/source/standalone/tutorials/01_assets`` directory.
.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/articulation.py
:language: python
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
Creating the Cartpole Articulation
-----------------------------------
In Orbit, we define an :class:`Articulation` by constructing its
configuration :class:`ArticulationCfg`. In the following sections we will break
down each part of the configuration.
.. dropdown:: :fa:`eye,mr-1` Code for USD import configuration
.. literalinclude:: ../../../../source/extensions/omni.isaac.orbit/omni/isaac/orbit/assets/config/cartpole.py
:language: python
:linenos:
Importing Cartpole's USD
^^^^^^^^^^^^^^^^^^^^^^^^
The next chunk of code handles the USD import of the Cartpole:
* Defining the USD file path from which to spawn the Cartpole
* Defining the rigid body properties of the Cartpole
* Defining properties of the root of the Cartpole
.. dropdown:: :fa:`eye,mr-1` Code for USD import configuration
.. literalinclude:: ../../../../source/extensions/omni.isaac.orbit/omni/isaac/orbit/assets/config/cartpole.py
:language: python
:start-after: # USD file configuration
:end-before: # Initial state definition
.. note::
To import articulation from a URDF file instead of a USD file, use ``UrdfFileCfg`` found in
``source/extensions/omni.isaac.orbit/omni/isaac/orbit/sim/spawners/from_files/from_file_cfg``
and replace ``usd_path`` argument with ``urdf_path``. For more details, see the API documentation.
.. TODO: Either add an example of this here or make a separate tutorial
Defining Cartpole's USD File Path
"""""""""""""""""""""""""""""""""
First we define the path the Cartpole USD file will be loaded from. In this
case ``cartpole.usd`` is included in the Nucleus server.
.. TODO: Document Nucleus server somewhere or link it if docs exist
.. literalinclude:: ../../../../source/extensions/omni.isaac.orbit/omni/isaac/orbit/assets/config/cartpole.py
:language: python
:start-after: # Location of USD file
:end-before: # Rigid body properties
Defining Cartpole's Rigid Body Properties
"""""""""""""""""""""""""""""""""""""""""
The rigid body properties define how the cartpole will interact with its
environment. The settings we want to modify in this example are:
* The rigid body to be enabled
* | the maximum values for linear and angular velocity and depenetration
| velocity which defines the speed at which objects in collision with one
| another move away from one another
* The Gyroscopic forces on our cartpole to be enabled
.. TODO: Either go into more detail here, or add tutorial on rigid body properties
.. literalinclude:: ../../../../source/extensions/omni.isaac.orbit/omni/isaac/orbit/assets/config/cartpole.py
:language: python
:start-after: # Rigid body properties
:end-before: # Articulation root properties
Defining the Initial State of Cartpole
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :class:`InitialStateCfg` object defines the initial state of the root of
an articulation in addition to the initial state of any joints. In this
example, we will spawn the Cartpole at the origin of the XY plane at a Z height
of 2.0 meters. The cart's joints will default to 0.0 as defined by the ``joint_pos``
parameter.
.. literalinclude:: ../../../../source/extensions/omni.isaac.orbit/omni/isaac/orbit/assets/config/cartpole.py
:language: python
:start-after: # Initial state definition
:end-before: # Actuators definition
Defining the Cartpole's Actuators
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The cartpole articulation has two actuators, one corresponding to each joint
``cart_to_pole`` and ``slider_to_cart``. for more details on actuators, see
:ref:`actuators-guide`.
.. literalinclude:: ../../../../source/extensions/omni.isaac.orbit/omni/isaac/orbit/assets/config/cartpole.py
:language: python
:start-after: # Actuators definition
:end-before: # End cartpole articulation configuration
Putting it all together
-----------------------
Finally, let's handle the main portion of the the script where the scene is
created, the robot is spawned and the simulation loop lives.
:meth:`design_scene` adds a ground plane and a light to the scene.
Scene Setup
-----------
In this section, the :class:``SimulationContext`` is initialized,
the camera view is set, the ground plane and lights are spawned:
.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/articulation.py
:language: python
:start-after: def main():
:end-before: # spawn cartpole articulation
Spawning the Cartpole
^^^^^^^^^^^^^^^^^^^^^
The last step to finalize the scene is to spawn the :class:`Articulation`.
We configure the prim path that the Cartpole will be spawned to within the USD
stage. Because we only have 1 robot in this toy example ``/World/Robot`` is
suitable:
.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/articulation.py
:language: python
:start-after: # spawn cartpole articulation
:end-before: # Play the simulator
When using other pre-defined :class:`ArticulationCfg` (e.g. Anymal, Franka,
etc.), you can use :class:`replace` as in this example to update parameters
without having to update the source code.
Finally, the simulator is reset (as this is necessary to initialize PhysX handles)
and we are ready to run the simulation loop.
Running the simulation loop
---------------------------
This part should be familiar from the previous tutorials, where we run the
simulation loop and update the joint data for the cartpole to it's defaults.
We set the ``cart_to_pole`` joint to ``pi / 8`` here to ensure that the pole
is not perfectly vertical at the start of the simulation which would result
in a static simulation.
.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/articulation.py
:language: python
:start-after: # Simulation loop
:end-before: # End simulation loop
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code, let's run the script and see the
result:
.. code-block:: bash
./orbit.sh -p source/standalone/tutorials/01_assets/articulation.py
This should open a stage with a single cartpole. The simulation should be
playing with the pole swinging freely.
.. _how_to_camera_label:
Using the Camera Sensor in Orbit
=================================
This tutorial demonstrates using the :class:`Camera` from the Orbit framework. The camera sensor is created and interfaced through the Omniverse Replicator API.
The Code
~~~~~~~~
The tutorial corresponds to the ``usd_camera.py`` script in the ``orbit/source/standalone/tutorials/02_sensors`` directory.
.. dropdown:: :fa:`eye,mr-1` Code for `usd_camera.py`
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/usd_camera.py
:language: python
:emphasize-lines: 102-112,113-114,121-123,125-133,135-139,140-141,155-179,181-215
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
As usual, we design a minimal scene for this example. In addition to the GroundPlane and a Distant Light, we add some simple shapes to the scene for this how-to guide.
In the following, we will detail the ``add_sensor`` function, responsible for adding the camera to the scene, and the ``run_simulator`` function, which steps the simulator and saves the rendered images.
Adding the camera sensor
------------------------
As Orbit is a config-driven framework, the camera is defined over its config class, :class:`CameraCfg`. Whereas parameters that do not depend on the used camera type are direct arguments of this class, all camera type-related arguments are defined in the spawn config. With the camera type, we refer to either PinholeCamera or FisheyeCamera. The type-independent parameters are, e.g., the data types to capture (e.g., "rgb," "distance_to_image_plane," "normals," "motion_vectors," "semantic_segmentation"), the width and height of the image and its offset. The spawn configurations defined parameters such as the aperture or the focus distance. These are given together with all other spawn-related configs under :class:`PinholeCameraCfg` and :class:`FisheyeCameraCfg`.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/usd_camera.py
:language: python
:lines: 102-112
:linenos:
:lineno-start: 102
While in previous how-to guides, we had to manually call the function to spawn the object into the scene, sensors already include this functionality when initialized. Consequently, we only have to pass the ``camera_cfg`` to the :class:`Camera` class.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/usd_camera.py
:language: python
:lines: 113-114
:linenos:
:lineno-start: 113
Please note that the Replicator Render Products, camera buffers and other aspects are initialized when the simulation is played. Thus, we must call ``sim.play()`` before rendering camera images.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/usd_camera.py
:language: python
:lines: 140-141
:linenos:
:lineno-start: 140
Running the simulation loop
---------------------------
In this tutorial, we step the simulation and efficiently render and save the camera images. To save the images, we use the Replicator BasicWriter (more information `here <www.docs.omniverse.nvidia.com/isaacsim/latest/replicator_tutorials/tutorial_replicator_recorder.html?highlight=basic%20writer#writer-parameters>`_).
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/usd_camera.py
:language: python
:lines: 121-123
:linenos:
:lineno-start: 121
The camera's position and orientation can be set within the config by defining the offset relative to the parent frame. Alternatively, we can set the position and orientation of the camera directly in the scene. In this example, we provide two options for the latter: either by providing the camera center and a target point (:meth:`Camera:set_world_poses_from_view`) or by providing the camera center and a rotation quaternion (:meth:`Camera:set_world_poses_from_view`). To allow for maximum flexibility, the provided quaternions can be provided in three conventions:
* `"opengl"` - forward axis: -Z - up axis +Y - OpenGL (Usd.Camera) convention
* `"ros"` - forward axis: +Z - up axis -Y - ROS convention
* `"world"` - forward axis: +X - up axis +Z - World Frame convention
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/usd_camera.py
:language: python
:lines: 125-133
:linenos:
:lineno-start: 125
While stepping the simulator, we write the images to the defined folder. Therefore, we first convert them to numpy arrays before packing them in a dictionary, which the BasicWriter can handle.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/usd_camera.py
:language: python
:lines: 155-179
:linenos:
:lineno-start: 155
In addition, we provide the functionality to project the depth image into 3D space. This reprojection is done by using the camera intrinsics and the depth image. The resulting point cloud is visualized using the ``_debug_draw`` extension of Isaac Sim.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/usd_camera.py
:language: python
:lines: 181-215
:linenos:
:lineno-start: 99
.. attention::
For all replicator buffers to be filled with the latest data, we may need to render the simulation multiple times.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/usd_camera.py
:language: python
:lines: 135-139
:linenos:
:lineno-start: 135
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code, let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/tutorials/02_sensors/usd_camera.py
This should open a stage with a ground plane, lights, and some slowly falling down objects.
To stop the simulation, you can either close the window, press the ``STOP`` button in the UI, or press ``Ctrl+C`` in the terminal.
This guide showed how to spawn a camera into the scene and save data. The following guides present other sensors and the possibility of putting them on a robot.
.. _how_to_frame_transformer_label:
Using the Frame Transformer Sensor in Orbit
===========================================
This tutorial demonstrates using the :class:`FrameTransformer` sensor in the ORBIT framework.
The :class:`FrameTransformer` sensor is used to report the transformation of one or more frames (target frames) with respect to another frame (source frame)
The Code
~~~~~~~~
The tutorial corresponds to the ``frame_transformer.py`` script in the ``orbit/source/standalone/tutorials/02_sensors`` directory.
.. dropdown:: :fa:`eye,mr-1` Code for `frame_transformer.py`
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/frame_transformer.py
:language: python
:emphasize-lines: 72-90, 116-123, 125-139, 150-151
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
As usual, we design a minimal scene for this example. In addition to the GroundPlane and a Distant Light, we spawn a quadrupedal robot.
Spawning such an articulated robot in the scene is explained in the :ref:`Articulation How-to-Guide <_how_to_articulation_label>`.
In the following, we will detail the ``add_sensor`` function, responsible for adding the ray caster sensor to the scene.
The ``run_simulator`` function visualizes the different frames given the computed transforms from the sensor and passes the default actions to the robot.
Adding the frame transformer sensor
-----------------------------------
As usual, the frame transformer is defined over its config class, :class:`FrameTransformerCfg`.
We need to specify the source frame (`prim_path`) and the target frames for the Frame Transformer sensor.
The source frame is the frame with respect to which the translations are reported.
We can specify a list of frames for the target frames and include regex patterns to match multiple frames.
In some cases, the target frame is not an individual prim; its relation to a "parent" prim can be defined over an offset.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/frame_transformer.py
:language: python
:lines: 72-90
:linenos:
:lineno-start: 72
Please note that the buffers, physics handles for the robot, and other aspects are initialized when the simulation is played. Thus, we must call ``sim.reset()``.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/frame_transformer.py
:language: python
:lines: 150-151
:linenos:
:lineno-start: 150
Running the simulation loop
---------------------------
After each step call, we must update the frame transformer sensor to get the latest transforms.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/frame_transformer.py
:language: python
:lines: 116-123
:linenos:
:lineno-start: 116
To visualize the transforms, we visualize a different frame in a regular interval based on the transform reported by the sensor.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/frame_transformer.py
:language: python
:lines: 125-139
:linenos:
:lineno-start: 125
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/tutorials/02_sensors/frame_transformer.py
This should open a stage with a ground plane, lights, and a quadrupedal robot. Consistently, one frame should be visualized.
To stop the simulation, you can either close the window, press the ``STOP`` button in the UI, or press ``Ctrl+C`` in the terminal.
In this guide, we saw how to use a frame transformer sensor. In the following how-to guides, other sensors will be introduced, and how to place sensors on the robot will be explained.
.. _how_to_ray_caster_label:
Using the Ray-Caster Sensor in Orbit
====================================
This tutorial demonstrates using the :class:`RayCaster` sensor from the Orbit framework.
Here, we present its usability as a height-scanner, but it can also be used as LiDAR or for any other purpose achieved by ray-casting.
The Code
~~~~~~~~
The tutorial corresponds to the ``ray_caster.py`` script in the ``orbit/source/standalone/tutorials/02_sensors`` directory.
.. dropdown:: :fa:`eye,mr-1` Code for `ray_caster.py`
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/ray_caster.py
:language: python
:emphasize-lines: 84-94, 100-117, 137-138, 118-123
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
As usual, we design a minimal scene for this example. In addition to the GroundPlane and a Distant Light, for this how-to guide, we add some spheres to the scene that are wrapped as a RigidObject to access their position, orientation, and velocity.
In the following, we will detail the ``add_sensor`` function, responsible for adding the raycaster sensor to the scene.
Adding the ray-caster sensor
----------------------------
As usual, the ray-caster is defined over its config class, :class:`RayCasterCfg`.
Unlike the Camera sensor, the ray-caster operates as a virtual sensor and does not require instantiation within the scene. Instead, it is solely attached to a prim, employed to specify its location, along with a potential offset. This attachment allows the ray-caster to cast a predetermined pattern of rays against a mesh.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/ray_caster.py
:language: python
:lines: 84-94
:linenos:
:lineno-start: 84
Please note that the buffers, physics handles for the RigidObject, and other aspects are initialized when the simulation is played. Thus, we must call ``sim.reset()``.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/ray_caster.py
:language: python
:lines: 137-138
:linenos:
:lineno-start: 137
.. attention::
Currently, ray-casting is only supported against a single mesh. We are working on extending this functionality to multiple meshes.
Running the simulation loop
---------------------------
In this tutorial, we step the simulation and reset the position of the spheres to initial random positions. For a detailed explanation of the simulation loop, please refer to the :ref:`RigidObject How-to-Guide <_how_to_rigid_objects_label>`.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/ray_caster.py
:language: python
:lines: 100-117
:linenos:
:lineno-start: 100
For the ray-caster sensor, we execute the ray-casting operation. Here, we also time this operation to give an impression of how much time such operations require.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/ray_caster.py
:language: python
:lines: 118-123
:linenos:
:lineno-start: 118
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/tutorials/02_sensors/ray_caster.py
This command should open a stage with a ground plane, lights, and some spheres first falling and then rolling on rough terrain with a raycaster pattern next to them.
To stop the simulation, you can either close the window, press the ``STOP`` button in the UI, or press ``Ctrl+C`` in the terminal.
In this guide, we saw how to use a ray-caster sensor. In the following how-to guide, we will see how to use the ray-caster sensor as a faster camera when only geometric information is required.
Using the Ray-Caster Camera Sensor in Orbit
===========================================
This tutorial demonstrates using the :class:`RayCasterCamera` sensor as a depth camera from the Orbit framework.
We already saw how to use the USD camera sensor (see :ref:`Camera How-to-Guide <_how_to_camera_label>`) and the ray-caster sensor when used as a height scanner (see :ref:`Height Scanner How-to-Guide <_how_to_ray_caster_label>`).
As the current implementation of the ray-caster is faster and more memory efficient than the USD camera sensor, it is a good alternative when only geometric information is required.
The interfaces for both cameras are identical, including the data buffers they use. However, the initialization configuration differs slightly as no spawn configuration is required for the ray-caster camera.
The Code
~~~~~~~~
The tutorial corresponds to the ``ray_caster_camera.py`` script in the ``orbit/source/standalone/tutorials/02_sensors`` directory.
.. dropdown:: :fa:`eye,mr-1` Code for `ray_caster_camera.py`
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/ray_caster_camera.py
:language: python
:emphasize-lines: 68-88,94-106,111-145,156-157
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
We designed a minimal scene for this example, composed of a rough terrain and a distant light.
In the following, we will detail the ``add_sensor`` function, which is responsible for adding the raycaster sensor to the scene, and the ``run_simulator`` function, which steps the simulator and saves the rendered images.
Adding the ray-caster camera sensor
-----------------------------------
As usual, the ray-caster camera is defined over its config class, :class:`RayCasterCameraCfg`.
Like the ray-casting-based height scanner and unlike the USD camera, the ray-caster operates as a virtual sensor and does not require a spawn within the scene. Instead, it is solely attached to a prim, employed to specify its location, along with a potential offset.
For the ray-caster camera, we specify the pinhole camera pattern. The pattern config includes the camera intrinsics and the image dimension, which will define the direction of the rays.
Other parameters are defined in the general config, as they are independent of the pattern. These include data types, offset, or the mesh to be ray-casted against.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/ray_caster_camera.py
:language: python
:lines: 68-88
:linenos:
:lineno-start: 68
Please note that the buffers and other aspects are initialized when the simulation is played. Thus, we must call ``sim.reset()``.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/ray_caster_camera.py
:language: python
:lines: 156-157
:linenos:
:lineno-start: 156
.. attention::
Currently, ray-casting is only supported against a single mesh. We are working on extending this functionality to multiple meshes.
Running the simulation loop
---------------------------
In this tutorial, we step the simulation and efficiently render and save the camera images. To save the images, we use the Replicator BasicWriter (more information `here <www.docs.omniverse.nvidia.com/isaacsim/latest/replicator_tutorials/tutorial_replicator_recorder.html?highlight=basic%20writer#writer-parameters>`_).
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/usd_camera.py
:language: python
:lines: 94-96
:linenos:
:lineno-start: 94
The camera's position and orientation can be set within the config by defining the offset relative to the parent frame. Alternatively, we can set the position and orientation of the camera directly in the scene. In this example, we provide two options for the latter: either by providing the camera center and a target point (:meth:`RayCasterCamera:set_world_poses_from_view`) or by providing the camera center and a rotation quaternion (:meth:`RayCasterCamera:set_world_poses_from_view`). To allow for maximum flexibility, the provided quaternions can be provided in three conventions:
* `"opengl"` - forward axis: -Z - up axis +Y - OpenGL (Usd.Camera) convention
* `"ros"` - forward axis: +Z - up axis -Y - ROS convention
* `"world"` - forward axis: +X - up axis +Z - World Frame convention
This behavior is the same as for the USD camera.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/usd_camera.py
:language: python
:lines: 98-106
:linenos:
:lineno-start: 98
While stepping the simulator, we update the camera and write the images to the defined folder. Therefore, we first convert them to numpy arrays before packing them in a dictionary, which the BasicWriter can handle.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/usd_camera.py
:language: python
:lines: 111-145
:linenos:
:lineno-start: 111
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/tutorials/02_sensors/ray_caster_camera.py
This call should open a stage with a ground plane, lights, and a visualization of the points where the rays hit the mesh.
To stop the simulation, you can either close the window, press the ``STOP`` button in the UI, or press ``Ctrl+C`` in the terminal.
In this guide, we saw how to use a ray-caster camera sensor. Moving forward, we will see how sensors can be used further and how to combine and place them on a robot.
Spawn Sensors on a Robot in Orbit
=================================
This tutorial demonstrates how to simulate various sensors onboard the quadruped robot ANYmal-C (ANYbotics) using the ORBIT framework. The included sensors are:
- USD-Camera
- Height Scanner
- Contact Sensor
Please review their how-to guides before proceeding with this guide.
The Code
~~~~~~~~
The tutorial corresponds to the ``sensors_on_robot.py`` script in the ``orbit/source/standalone/tutorials/02_sensors`` directory.
.. dropdown:: :fa:`eye,mr-1` Code for `sensors_on_robot.py`
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/sensors_on_robot.py
:language: python
:emphasize-lines: 72-90, 116-123, 125-139, 150-151
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
The script designs a scene with a ground plane, lights, and two instances of the ANYmal-C robot.
This guide will not explain the individual sensors; please refer to their corresponding how-to guides for more details (see :ref:`Height-Scanner How-to-Guide <_how_to_ray_caster_label>` and :ref:`Camera How-to-Guide <_how_to_camera_label>`).
Furthermore, how to spawn such an articualted robot in the scene is explained in the :ref:`Articulation How-to-Guide <_how_to_articulation_label>`.
In the following, we will detail the ``add_sensor`` function, which is responsible for adding the sensors on the robot.
The ``run_simulator`` function updates the sensors and provides some information on them.
Adding the sensors
------------------
For each sensor, the corresponding config class has to be created, and the corresponding parameters have to be set.
To add the sensors to the robot, the prim paths must be set as a child prim of the robot.
In this case, the sensor will move with the robot. The offsets have to be provided w.r.t. the parent frame on the robot.
The resulting configurations and initialization calls are shown below.
Camera sensor:
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/sensors_on_robot.py
:language: python
:lines: 88-102
:linenos:
:lineno-start: 88
Height scanner sensor:
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/sensors_on_robot.py
:language: python
:lines: 103-114
:linenos:
:lineno-start: 103
Contact sensor:
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/sensors_on_robot.py
:language: python
:lines: 115-120
:linenos:
:lineno-start: 115
Please note that the buffers, physics handles for the camera and robot, and other aspects are initialized when the simulation is played. Thus, we must call ``sim.reset()``.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/sensors_on_robot.py
:language: python
:lines: 181-182
:linenos:
:lineno-start: 181
Running the simulation loop
---------------------------
For every simulation step, the sensors are updated and we print some information.
.. literalinclude:: ../../../../source/standalone/tutorials/02_sensors/sensors_on_robot.py
:language: python
:lines: 150-168
:linenos:
:lineno-start: 150
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/tutorials/02_sensors/sensors_on_robot.py
This command should open a stage with a ground plane, lights, and two quadrupedal robots.
To stop the simulation, you can either close the window, press the ``STOP`` button in the UI, or press ``Ctrl+C`` in the terminal.
In this guide, we saw how to add sensors to a robot and how to update them in the simulation loop.
Creating Markers in Orbit
=========================
In this tutorial, we will explore how to create different types of markers in `Orbit` using a Python script.
The script demonstrates the creation of markers with various shapes and visual properties.
Please ensure you have gone through the previous tutorials, especially creating an empty scene for a foundational understanding.
The Code
~~~~~~~~
The tutorial corresponds to the ``markers.py`` script in the ``orbit/source/standalone/demos`` directory.
Let's take a look at the Python script:
.. literalinclude:: ../../../../source/standalone/demos/markers.py
:language: python
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
Creating and spawning markers
-----------------------------
The :function:`spawn_markers` function creates different types of markers with specified configurations.
For example, we include frames, arrows, cubes, spheres, cylinders, cones, and meshes.
The function returns a :obj:`VisualizationMarkers` object.
.. literalinclude:: ../../../../source/standalone/demos/markers.py
:language: python
:lines: 37-84
:linenos:
:lineno-start: 37
Main simulation logic
---------------------
The `main` function sets up the simulation context, camera view, and spawns lights into the stage.
It then creates instances of the markers and places them in a grid pattern.
The markers are rotated around the z-axis during the simulation for visualization purposes.
.. literalinclude:: ../../../../source/standalone/demos/markers.py
:language: python
:lines: 86-111
:linenos:
:lineno-start: 86
Executing the Script
~~~~~~~~~~~~~~~~~~~~
To run the script, execute the following command:
.. code-block:: bash
./orbit.sh -p source/standalone/demos/markers.py
The simulation should start, and you can observe the different types of markers arranged in a grid pattern.
To stop the simulation, close the window, press the ``STOP`` button in the UI, or use ``Ctrl+C`` in the terminal.
This tutorial provides a foundation for working with markers in Orbit.
You can further customize markers by adjusting their configurations and exploring additional options
available in the Orbit API.
......@@ -12,7 +12,7 @@ The Code
The tutorial corresponds to the ``play_ik_control.py`` script in the ``orbit/source/standalone/demo`` directory.
.. literalinclude:: ../../../source/standalone/demo/play_ik_control.py
.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/ik_control.py
:language: python
:emphasize-lines: 44-47,130-138,147,201-217
:linenos:
......@@ -51,7 +51,7 @@ is that of the parent body and not the end-effector. Thus, the IK controller tak
as input the end-effector offset from the parent frame. This offset is typically specified
in the robot's configuration instance and thus is obtained from there.
.. literalinclude:: ../../../source/standalone/demo/play_ik_control.py
.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/ik_control.py
:language: python
:lines: 130-138
:linenos:
......@@ -70,7 +70,7 @@ batched array. The first three columns correspond to the desired position and th
last four columns correspond to the desired quaternion orientation in ``(w, x, y, z)``
order.
.. literalinclude:: ../../../source/standalone/demo/play_ik_control.py
.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/ik_control.py
:language: python
:lines: 201-202
:linenos:
......@@ -82,7 +82,7 @@ current joint positions. We read the Jacobian matrix from the robot's data, whic
its value computed from the physics engine.
.. literalinclude:: ../../../source/standalone/demo/play_ik_control.py
.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/ik_control.py
:language: python
:lines: 203-209
:linenos:
......@@ -94,7 +94,7 @@ offsets from the desired joint positions. The joint offsets are obtained from th
robot's data which is a constant value obtained from the robot's configuration.
For more details, we suggest reading the :doc:`/source/api/orbit.actuators.group` tutorial.
.. literalinclude:: ../../../source/standalone/demo/play_ik_control.py
.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/ik_control.py
:language: python
:lines: 210-215
:linenos:
......@@ -102,7 +102,7 @@ For more details, we suggest reading the :doc:`/source/api/orbit.actuators.group
These actions can then be applied on the robot, as done in the previous tutorials.
.. literalinclude:: ../../../source/standalone/demo/play_ik_control.py
.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/ik_control.py
:language: python
:lines: 216-219
:linenos:
......@@ -116,7 +116,7 @@ The marker class takes as input the associated prim name, the number of markers
USD file corresponding to the marker, and the scale of the marker. By default, it uses a frame marker
to display the pose.
.. literalinclude:: ../../../source/standalone/demo/play_ik_control.py
.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/ik_control.py
:language: python
:lines: 81-83
:linenos:
......@@ -126,7 +126,7 @@ We can then set the pose of the marker using the :attr:`set_world_poses` method.
It is important to ensure that the set poses are in the simulation world frame and not the
local environment frame.
.. literalinclude:: ../../../source/standalone/demo/play_ik_control.py
.. literalinclude:: ../../../../source/standalone/tutorials/05_controllers/ik_control.py
:language: python
:lines: 223-229
:linenos:
......@@ -140,7 +140,7 @@ Now that we have gone through the code, let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/demo/play_ik_control.py --robot franka_panda --num_envs 128
./orbit.sh -p source/standalone/tutorials/05_controllers/ik_control.py --robot franka_panda --num_envs 128
The script will start a simulation with 128 robots. The robots will be controlled using a task-space controller.
The current and desired end-effector poses should be displayed using frame markers. When the robot reaches the
......
How to Guides
=============
This document provides an overview of the How-To-Guides available in the Orbit Software documentation. Each guide focuses on a specific aspect of the software and provides step-by-step instructions to help you get started.
.. _sim-control-objects:
Custom Articulations
--------------------
The `assets` section includes guides that help you develop your own Articulations as opposed to using those included in Orbit.
.. toctree::
:maxdepth: 1
Creating your own Articulation: Cartpole <01_assets/cartpole>
Sensors
-------
The `sensors` section focuses on guides related to perception and sensing capabilities in the Orbit Software.
These guides will help you understand how to use sensors, such as cameras and depth sensors, to perceive the environment and extract useful information for your applications.
.. toctree::
:maxdepth: 1
Cameras <02_sensors/camera>
Ray-Casters <02_sensors/ray_caster>
Ray-Caster Camera <02_sensors/ray_caster_camera>
Frame Transformer <02_sensors/frame_transformer>
Sensors on Articulated Robots <02_sensors/sensors_on_robot>
Markers
-------
The `markers` section focuses on how to add different visualization markers in the Orbit Software.
.. toctree::
:maxdepth: 1
Markers <03_markers/markers>
Control
-------
The `control` section focuses on how to implement controllers within Orbit.
.. toctree::
:maxdepth: 1
Markers <04_controllers/ik_controller>
Please refer to the individual guides in each section for detailed instructions and examples.
......@@ -274,9 +274,9 @@ top of the repository:
# Option 1: Using the orbit.sh executable
# note: this works for both the bundled python and the virtual environment
./orbit.sh -p source/standalone/demo/play_empty.py
./orbit.sh -p source/standalone/demo/00_sim/empty.py
# Option 2: Using python in your virtual environment
python source/standalone/demo/play_empty.py
python source/standalone/demo/00_sim/empty.py
The above command should launch the simulator and display a window with a black
ground plane. You can exit the script by pressing ``Ctrl+C`` on your terminal or
......
Creating an empty scene
=======================
This tutorial introduces how to create a standalone python script to set up a simple empty scene in Orbit.
It introduces the two main classes used in the simulator, :class:`AppLauncher` and :class:`SimulationContext`,
that help launch and control the simulation timeline respectively.
Please review `Isaac Sim Interface <https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/tutorial_intro_interface.html#isaac-sim-app-tutorial-intro-interface>`_
and `Isaac Sim Workflows <https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/tutorial_intro_workflows.html#standalone-application>`_
prior to beginning this tutorial.
The Code
~~~~~~~~
The tutorial corresponds to the ``play_empty.py`` script in the ``orbit/source/standalone/demo`` directory.
.. literalinclude:: ../../../source/standalone/demo/play_empty.py
:language: python
:emphasize-lines: 11-23,27-31,37-41,44,49-51,64
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
Launching the simulator
-----------------------
The first step when working with standalone python scripts is to import the :class:`AppLauncher` class. This
class is used to launch the simulation app from the python script. It takes in a :class:`argparse.Namespace` object of configuration parameters
that can be used to configure the launched application. There is a set of standard parameters that can be
added automatically to the parser with the :meth:`AppLauncher.add_app_launcher_args()` method. These parameters include
`headless` (launch app in no-gui mode), `livestream` (determine the streamining option of the app), `ros`
(enables the ROS1 or ROS2 bridge) and `offscreen_render`(enables offscreen-render mode).
For more information on the configuration parameters, please
check the `SimulationApp documentation <https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.kit/docs/index.html#simulation-application-omni-isaac-kit>`_.
Here, we only use the arguments defined by :meth:`AppLauncher.add_app_launcher_args()` which can be set from the
command line. If not explicitly set, the default values are used.
.. literalinclude:: ../../../source/standalone/demo/play_empty.py
:language: python
:lines: 11-23
:linenos:
:lineno-start: 11
Importing python modules
------------------------
It is important that the simulation app is launched at the start of the script since it loads various python modules
that are required for the rest of the script to run. Once that is done, we can import the various python modules that
we will be using in the script.
.. literalinclude:: ../../../source/standalone/demo/play_empty.py
:language: python
:lines: 27-31
:linenos:
:lineno-start: 31
Designing the simulation scene
------------------------------
The next step is to design the simulation scene. This includes creating the stage, setting up the physics scene,
and adding objects to the stage.
Isaac Sim core provides the `SimulationContext <https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.core/docs/index.html#module-omni.isaac.core.simulation_context>`_ that handles various timeline related events (pausing, playing,
stepping, or stopping the simulator), configures the stage (such as stage units or up-axis), and creates the
`physicsScene <https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_physics.html#physics-scene>`_
prim (which provides physics simulation parameters such as gravity direction and magnitude, the simulation
time-step size, and advanced solver algorithm settings).
.. attention::
The :class:`SimulationContext` class also takes in the ``backend`` parameter which specifies the tensor library
(such as ``"numpy"`` and ``"torch"``) in which the returned physics tensors are casted in. Currently, ``orbit``
only supports ``"torch"`` backend.
For this tutorial, we set the physics and rendering time step to 0.01 seconds. Rendering,
here, refers to updating a frame of the current simulation state, which includes the viewport, cameras, and other
existing UI elements.
.. literalinclude:: ../../../source/standalone/demo/play_empty.py
:language: python
:lines: 37-39
:linenos:
:lineno-start: 37
Next, we set the initial view shown the GUI. The view is defined as the position where the viewpoint is placed (here `[2.5, 2.5, 2.5]`)
and the target to look at which defines the orientation of the viewpoint (here `[0, 0, 0]`).
.. literalinclude:: ../../../source/standalone/demo/play_empty.py
:language: python
:lines: 40-41
:linenos:
:lineno-start: 40
Running the simulation loop
---------------------------
As mentioned earlier, the :class`SimulationContext` class provides methods to control timeline events such as resetting,
playing, pausing and stepping the simulator. An important thing to note is that the simulator is not running until
the :meth:`sim.reset()` method is called. Once the method is called, it plays the timeline that initializes all the
physics handles and tensors.
After playing the timeline, we can start the main loop. The main loop is where we step the physics simulation, rendering
and other timeline events. The :meth:`sim.step()` method takes in a ``render`` parameter that specifies whether the
current simulation state should be rendered or not. This parameter is set to ``False`` when the ``headless`` flag is
set to ``True``.
.. literalinclude:: ../../../source/standalone/demo/play_empty.py
:language: python
:lines: 43-51
:linenos:
:lineno-start: 43
To ensure a safe execution, we wrap the execution loop with checks to ensure that the simulation app is running and
that the simulator is playing. If the simulator is not playing, we simply step the simulator and continue to the next
iteration of the loop.
Lastly, we call the :meth:`simulation_app.close()` method to stop the simulation application and close the window.
.. literalinclude:: ../../../source/standalone/demo/play_empty.py
:language: python
:lines: 55-64
:linenos:
:lineno-start: 55
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code, let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/demo/play_empty.py
The simulation should be playing and the stage should be rendering. To stop the simulation,
you can either close the window, or press the ``STOP`` button in the UI, or press ``Ctrl+C``
in the terminal.
Now that we have a basic understanding of how to run a simulation, let's move on to the next tutorial
where we will learn how to add a assets to the stage.
Create an empty scene
=====================
.. currentmodule:: omni.isaac.orbit
This tutorial shows how to launch and control Isaac Sim simulator from a standalone Python script. It sets up an
empty scene in Orbit and introduces the two main classes used in the framework, :class:`app.AppLauncher` and
:class:`sim.SimulationContext`.
Please review `Isaac Sim Interface`_ and `Isaac Sim Workflows`_ prior to beginning this tutorial to get
an initial understanding of working with the simulator.
The Code
~~~~~~~~
The tutorial corresponds to the ``empty.py`` script in the ``orbit/source/standalone/tutorials/00_sim`` directory.
.. dropdown:: :fa:`eye,mr-1` Code for ``empty.py``
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/empty.py
:language: python
:emphasize-lines: 18-30,34-38,44-48,51,56-58,71
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
Launching the simulator
-----------------------
The first step when working with standalone Python scripts is to launch the simulation application.
This is necessary to do at the start since various dependency modules of Isaac Sim are only available
after the simulation app is running.
This can be done by importing the :class:`app.AppLauncher` class. This utility class wraps around
:class:`omni.isaac.kit.SimulationApp` class to launch the simulator. It provides mechanisms to
configure the simulator using command-line arguments and environment variables.
For this tutorial, we mainly look at adding the command-line options to a user-defined
:class:`argparse.ArgumentParser`. This is done by passing the parser instance to the
:meth:`app.AppLauncher.add_app_launcher_args` method, which appends different parameters
to it. These include launching the app headless, configuring different Livestream options,
and enabling off-screen rendering.
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/empty.py
:language: python
:lines: 18-30
:linenos:
:lineno-start: 18
Importing python modules
------------------------
Once the simulation app is running, it is possible to import different Python modules from
Isaac Sim and other libraries. Here we import two modules:
* `carb`_: A library that provides various microservices and diagnostics utilities in Omniverse.
* :mod:`omni.isaac.orbit.sim`: A sub-package in Orbit for all the core simulator-related operations.
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/empty.py
:language: python
:lines: 36-38
:linenos:
:lineno-start: 36
Configuring the simulation context
----------------------------------
When launching the simulator from a standalone script, the user has complete control over playing,
pausing and stepping the simulator. All these operations are handled through the **simulation
context**. It takes care of various timeline events and also configures the `physics scene`_ for
simulation.
In Orbit, the :class:`sim.SimulationContext` class inherits from Isaac Sim's
:class:`omni.isaac.core.simulation_context.SimulationContext` to allow configuring the simulation
through Python's `dataclass` object and handle certain intricacies of the simulation stepping.
For this tutorial, we set the physics and rendering time step to 0.01 seconds. This is done
by passing these quantities to the :class:`sim.SimulationCfg`, which is then used to create an
instance of the simulation context.
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/empty.py
:language: python
:lines: 44-48
:linenos:
:lineno-start: 44
Following the creation of the simulation context, we have only configured the physics acting on the
simulated scene. This includes the device to use for simulation, the gravity vector, and other advanced
solver parameters. There are now two main steps remaining to run the simulation:
1. Designing the simulation scene: Adding sensors, robots and other simulated objects
2. Running the simulation loop: Stepping the simulator, and setting and getting data from the simulator
In this tutorial, we look at Step 2 first for an empty scene to focus on the simulation control first.
In the following tutorials, we will look into Step 1 and working with simulation handles for interacting
with the simulator.
Running the simulation
----------------------
The first thing, after setting up the simulation scene, is to call the :meth:`sim.SimulationContext.reset`
method. This method plays the timeline and initializes the physics handles in the simulator. It must always
be called the first time before stepping the simulator. Otherwise, the simulation handles are not initialized
properly.
.. note::
:meth:`sim.SimulationContext.reset` is different from :meth:`sim.SimulationContext.play` method as the latter
only plays the timeline and does not initializes the physics handles.
After playing the simulation timeline, we set up a simple simulation loop where the simulator is stepped repeatedly
while the simulation app is running. The method :meth:`sim.SimulationContext.step` takes in as argument :attr:`render`,
which dictates whether the step includes updating the rendering-related events or not. By default, this flag is
set to True.
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/empty.py
:language: python
:lines: 56-58
:linenos:
:lineno-start: 56
Exiting the simulation
----------------------
Lastly, the simulation application is stopped and its window is closed by calling
:meth:`omni.isaac.kit.SimulationApp.close` method. We do this operation under a try-catch
statement to close the app gracefully in case an error happens.
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/empty.py
:language: python
:lines: 70-71
:linenos:
:lineno-start: 70
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code, let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/tutorials/00_sim/empty.py
The simulation should be playing, and the stage should be rendering. To stop the simulation,
you can either close the window, or press ``Ctrl+C`` in the terminal.
Passing ``--help`` to the above script will show the different command-line arguments added
earlier by the :class:`app.AppLauncher` class. To run the script headless, you can execute the
following:
.. code-block:: bash
./orbit.sh -p source/standalone/tutorials/00_sim/empty.py --headless
Now that we have a basic understanding of how to run a simulation, let's move on to the
following tutorial where we will learn how to add assets to the stage.
.. _`Isaac Sim Interface`: https://docs.omniverse.nvidia.com/isaacsim/latest/introductory_tutorials/tutorial_intro_interface.html#isaac-sim-app-tutorial-intro-interface
.. _`Isaac Sim Workflows`: https://docs.omniverse.nvidia.com/isaacsim/latest/introductory_tutorials/tutorial_intro_workflows.html
.. _carb: https://docs.omniverse.nvidia.com/kit/docs/carbonite/latest/index.html
.. _`physics scene`: https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_physics.html#physics-scene
.. _how_to_spawn_objects_label:
Spawn prims into the scene
==========================
.. currentmodule:: omni.isaac.orbit
This tutorial explores how to spawn various objects (or prims) into the scene in Orbit from Python.
It builds upon the previous tutorial on running the simulator from a standalone script and
demonstrates how to spawn a ground plane, lights, primitive shapes, and meshes from USD files.
The Code
~~~~~~~~
The tutorial corresponds to the ``prims.py`` script in the ``orbit/source/standalone/tutorials/00_sim`` directory.
Let's take a look at the Python script:
.. dropdown:: :fa:`eye,mr-1` Code for `prims.py`
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/prims.py
:language: python
:emphasize-lines: 43-79
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
Scene designing in Omniverse is built around a software system and file format called USD (Universal Scene Description).
It allows describing 3D scenes in a hierarchical manner, similar to a file system. Since USD is a comprehensive framework,
we recommend reading the `USD documentation`_ to learn more about it.
For completion, we introduce the must know concepts of USD in this tutorial.
* **Primitives (Prims)**: These are the basic building block of a USD scene. They can be thought of as nodes in a scene
graph. Each node can be a mesh, a light, a camera, or a transform. It can also be a group of other prims under it.
* **Attributes**: These are the properties of a prim. They can be thought of as key-value pairs. For example, a prim can
have an attribute called ``color`` with a value of ``red``.
* **Relationships**: These are the connections between prims. They can be thought of as pointers to other prims. For
example, a mesh prim can have a relationship to a material prim for shading.
A collection of these prims, with their attributes and relationships, is called a **USD stage**. It can be thought of
as a container for all prims in a scene. When we say we are designing a scene, we are actually designing a USD stage.
While working with direct USD APIs provides a lot of flexibility, it can be cumbersome to learn and use. To make it
easier to design scenes, Orbit builds on top of the USD APIs to provide a configuration-drive interface to spawn prims
into a scene. These are included in the :mod:`sim.spawners` module.
When spawning prims into the scene, each prim requires a configuration class instance that defines the prim's attributes
and relationships (through material and shading information). The configuration class is then passed to its respective
function where the prim name and transformation are specified. The function then spawns the prim into the scene.
At a high-level, this is how it works:
.. code-block:: python
# Create a configuration class instance
cfg = MyPrimCfg()
prim_path = "/path/to/prim"
# Spawn the prim into the scene using the corresponding spawner function
spawn_my_prim(prim_path, cfg, translation=[0, 0, 0], orientation=[1, 0, 0, 0], scale=[1, 1, 1])
# OR
# Use the spawner function directly from the configuration class
cfg.func(prim_path, cfg, translation=[0, 0, 0], orientation=[1, 0, 0, 0], scale=[1, 1, 1])
In this tutorial, we demonstrate the spawning of various different prims into the scene. For more
information on the available spawners, please refer to the :mod:`sim.spawners` module in Orbit.
.. attention::
All the scene designing must happen before the simulation starts. Once the simulation starts, we recommend keeping
the scene frozen and only altering the properties of the prim. This is particularly important for GPU simulation
as adding new prims during simulation may alter the physics simulation buffers on GPU and lead to unexpected
behaviors.
Spawning a ground plane
-----------------------
The :class:`sim.spawners.GroundPlaneCfg` configures a grid-like ground plane with modifiable properties such as its
appearance and size.
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/prims.py
:language: python
:lines: 45-47
:linenos:
:lineno-start: 45
Spawning lights
---------------
It is possible to spawn `different light prims`_ into the stage. These include distant lights, sphere lights, disk
lights, and cylinder lights. In this tutorial, we spawn a distant light which is a light that is infinitely far away
from the scene and shines in a single direction.
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/prims.py
:language: python
:lines: 49-54
:linenos:
:lineno-start: 49
Spawning primitive shapes
-------------------------
Before spawning primitive shapes, we introduce the concept of a transform prim or Xform. A transform prim is a prim that
contains only transformation properties. It is used to group other prims under it and to transform them as a group.
Here we make an Xform prim to group all the primitive shapes under it.
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/prims.py
:language: python
:lines: 56-57
:linenos:
:lineno-start: 56
Next, we spawn a cone using the :class:`sim.spawners.ConeCfg` class. It is possible to specify the radius, height,
physics properties, and material properties of the cone. By default, the physics and material properties are disabled.
The first two cones we spawn ``Cone1`` and ``Cone2`` are visual elements and do not have physics enabled.
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/prims.py
:language: python
:lines: 58-65
:linenos:
:lineno-start: 58
For the third cone ``ConeRigid``, we add rigid body physics to it by setting the attributes for that in the configuration
class. Through these attributes, we can specify the mass, friction, and restitution of the cone. If unspecified, they
default to the default values set by USD Physics.
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/prims.py
:language: python
:lines: 67-76
:linenos:
:lineno-start: 67
Spawning from another file
--------------------------
Lastly, it is possible to spawn prims from other file formats such as other USD, URDF, or OBJ files. In this tutorial,
we spawn a USD file of a table into the scene. The table is a mesh prim and has a material prim associated with it.
All of this information is stored in its USD file.
.. literalinclude:: ../../../../source/standalone/tutorials/00_sim/prims.py
:language: python
:lines: 78-80
:linenos:
:lineno-start: 78
The table above is added as a reference to the scene. In layman terms, this means that the table is not actually added
to the scene, but a `pointer` to the table asset is added. This allows us to modify the table asset and have the changes
reflected in the scene in a non-destructive manner. For example, we can change the material of the table without
actually modifying the underlying file for the table asset directly. Only the changes are stored in the USD stage.
Executing the Script
~~~~~~~~~~~~~~~~~~~~
Similar to the tutorial before, to run the script, execute the following command:
.. code-block:: bash
./orbit.sh -p source/standalone/tutorials/00_sim/prims.py
Once the simulation starts, you should see a window with a ground plane, a light, some cones, and a table.
The green cone, which has rigid body physics enabled, should fall and collide with the table and the ground
plane. The other cones are visual elements and should not move. To stop the simulation, you can close the window,
or press ``Ctrl+C`` in the terminal.
This tutorial provided a foundation for spawning various prims into the scene in Orbit. Although simple, it
demonstrates the basic concepts of scene designing in Orbit and how to use the spawners. In the coming tutorials,
we will now look at how to interact with the scene and the simulation.
.. _`USD documentation`: https://graphics.pixar.com/usd/docs/index.html
.. _`different light prims`: https://youtu.be/c7qyI8pZvF4?feature=shared
Adding a robotic arm to the scene
=================================
In the previous tutorial, we explained the basic working of the standalone script and how to
play the simulator. This tutorial shows how to add a robotic arm into the stage and control the
arm by providing random joint commands.
The tutorial will cover how to use the robot classes provided in Orbit. This includes spawning,
initializing, resetting, and controlling the robot.
The Code
~~~~~~~~
The tutorial corresponds to the ``play_arms.py`` script in the ``orbit/source/standalone/demo`` directory.
.. literalinclude:: ../../../source/standalone/demo/play_arms.py
:language: python
:emphasize-lines: 45-47,92-95,100-103,130-133,144-147,152-154
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
Most of the code is the same as the previous tutorial. The only difference is that we are adding a robot to the scene.
We explain the changes below.
Designing the simulation scene
------------------------------
The single arm manipulators refer to robotic arms with a fixed base. These robots are at the ground height, i.e. `z=0`.
Accordingly, the robots are placed on a table that define their workspace. In this tutorial, we spawn two robots on two
different tables. The first robot is placed on the left table ``/World/Table_1`` and the second robot is placed on the
right table ``/World/Table_2``.
The tables are loaded from their respective USD file which is hosted on Omniverse Nucleus server
.. literalinclude:: ../../../source/standalone/demo/play_arms.py
:language: python
:lines: 80-83
:linenos:
:lineno-start: 80
Next, we create an instance of the single arm robot class. The robot class is initialized with a configuration object
that contains information on the associated USD file, default initial state, actuator models for different joints, and
other meta-information about the robot kinematics. The robot class provides method to spawn the robot in the scene at
a given position and orientation, if provided.
In this tutorial, we disambiguate the robot configuration to load through the parsed command line argument. In Orbit,
we include pre-configured instances of the configuration class to simplify usage. After creating
the robot instance, we can spawn it at the origin defined by the table's location.
.. literalinclude:: ../../../source/standalone/demo/play_arms.py
:language: python
:lines: 84-95
:linenos:
:lineno-start: 84
Initializing the robot
----------------------
Prims in the scene with physics schemas enabled on them have an associated physics handle created by the
physics engine. These handles are created only when the simulator starts playing. After that, the allocated
tensor buffers used to store the associated prim's state are accessible. This data is exposed through *physics
views* that provide a convenient interface to access the state of the prims. The physics views can be used to
group or encapsulate multiple prims and obtain their data in a batched manner.
Using the robot class in Orbit, a user can initialize the physics views to obtain views over the articulation
and essential rigid bodies (such as the end-effector) as shown below. Multiple prims are grouped together by
specifying their paths as regex patterns.
.. literalinclude:: ../../../source/standalone/demo/play_arms.py
:language: python
:lines: 97-103
:linenos:
:lineno-start: 97
Running the simulation loop
---------------------------
The robot class provides a method to obtain the default state of the robot. This state is the initial state of the
robot when it is spawned in the scene. The default state is a tuple of two tensors, one for the joint positions and
the other for the joint velocities. It is used to reset the robot to its initial state at a pre-defined interval of
steps.
.. danger::
Since the underlying physics engine in Isaac Sim does not separate the kinematics forwarding and dynamics stepping,
the robot's state does not take into affect until after stepping the simulation.
.. literalinclude:: ../../../source/standalone/demo/play_arms.py
:language: python
:lines: 126-133
:linenos:
:lineno-start: 126
At the start of an episode, we randmly generate the joint commands for the arm. However, we toggle the gripper command
at a regular interval to simulate a grasp and release action. The robot class provides a method to apply the joint
commands. The type of command is configured in the robot configuration object.
.. literalinclude:: ../../../source/standalone/demo/play_arms.py
:language: python
:lines: 140-147
:linenos:
:lineno-start: 140
After stepping the simulator, we can obtain the current state of the robot. The robot class provides a method to
update the buffers by reading the data through the physics views. By default, the simulation engine provides all data
in the world frame. Thus, the update method also takes care of transforming quantities to other frames such as the
base frame of the robot.
.. literalinclude:: ../../../source/standalone/demo/play_arms.py
:language: python
:lines: 151-154
:linenos:
:lineno-start: 151
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code, let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/demo/play_arms.py --robot franka_panda
This should open a stage with a ground plane, lights, tables and robots.
The simulation should be playing with the robot arms going to random joint configurations. The
gripper, if present, should be opening or closing at regular intervals. To stop the simulation,
you can either close the window, or press the ``STOP`` button in the UI, or press ``Ctrl+C``
in the terminal
In addition to the demo script for playing single arm manipulators, we also provide scripts
for playing other robots such as quadrupeds or mobile manipulators. You can run these as follows:
.. code-block:: bash
# Quadruped -- Spawns ANYmal C, ANYmal B, Unitree A1 on one stage
./orbit.sh -p source/standalone/demo/play_quadrupeds.py
# Mobile manipulator -- Spawns Franka Panda on Clearpath Ridgeback
./orbit.sh -p source/standalone/demo/play_ridgeback_franka.py
In this tutorial, we saw how to spawn a robot multiple times and initialize the physics views to access
the simulation state of the robots. In the next tutorial, we will see how to simplify duplicating a simulation
scene multiple times by using the cloner APIs.
.. _how_to_articulation_label:
Interacting with an articulation
================================
In the previous tutorial, we explained the essential workings of the standalone script and how to
play the simulator. This tutorial shows how to add a robotic arm to the stage and control the
arm by providing random joint commands.
The tutorial will cover how to use the robot classes provided in Orbit. This includes spawning,
initializing, resetting, and controlling the robot.
The Code
~~~~~~~~
The tutorial corresponds to the ``arms.py`` script in the ``orbit/source/standalone/demos`` directory.
.. dropdown:: :fa:`eye,mr-1` Code for `arms.py`
.. literalinclude:: ../../../../source/standalone/demos/arms.py
:language: python
:emphasize-lines: 74-85,92-95,105-120,121-125,126-135,146-147
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
The scene is designed similarly to the previous tutorial.
Here, we add two articulated robots to the scene and apply some actions to them. You can choose from franka_panda or ur10 robots.
In the following, we will detail the ``add_robots`` function, which is responsible for adding the robots to the scene, and the ``run_simulator`` function, which steps the simulator, applies some actions to the robot, and handles their reset.
Adding the robots
-----------------
We create an instance of the single-arm robot class using a pre-defined configuration object in `Orbit`. This object contains information on the associated USD file, default initial state, actuator models for different joints, and other meta information about the robot's kinematics.
All pre-defined config files can be found in `orbit/source/extensions/omni.isaac.orbit/omni/isaac/orbit/assets/config`.
Single arm manipulators refer to robotic arms with a fixed base. These robots are at the ground height, i.e., `z=0`. Similar to previous objects, a spawn function is defined in each configuration, which is used to place the robot in the scene. Again, we provide the prim path, the spawn configuration, and the translation to the spawn function.
.. literalinclude:: ../../../../source/standalone/demos/arms.py
:language: python
:lines: 74-83
:linenos:
:lineno-start: 74
As we want to articulate the robot and enable it to move, we need to model it as a combination of fixed and articulated joints. While the articulated joints are defined in the configuration, we want to initialize their physics handles. In `Orbit`, we provide this behavior in the form of an :class:`Articulation` class that allows us to set actions to the articulated joints and retrieve the robot's current state.
Here, multiple prims can be grouped by specifying their paths as regex patterns.
.. literalinclude:: ../../../../source/standalone/demos/arms.py
:language: python
:lines: 84-85
:linenos:
:lineno-start: 84
Please note that the physics handles are initialized when the simulation is played. Thus, we must call ``sim.reset()`` before accessing the physics handles.
.. literalinclude:: ../../../../source/standalone/demos/arms.py
:language: python
:lines: 146-147
:linenos:
:lineno-start: 146
Running the simulation loop
---------------------------
In this tutorial, we step the simulation, apply some actions to the robot, and reset the robot at regular intervals.
At first, we generate a joint position target that should be achieved. Every articulation class contains a :class:`ArticulationData` object that contains the current state of the robot. This object can be used to retrieve the current state of the robot as well as some default values. Here, we use it to get the default joint positions and add a slight random random offset to get a target position.
.. literalinclude:: ../../../../source/standalone/demos/arms.py
:language: python
:lines: 92-95
:linenos:
:lineno-start: 92
As long as the simulation runs, we reset the robot regularly.
We first acquire the default joint position and velocity from the data buffer to perform the reset. By calling the :meth:`Articulation.write_joint_state_to_sim` method, we directly write these values into the PhysX buffer. Then, we call :meth:`Articulation.reset` to reset the robot to its default state.
Following the reset, a new target position of the robot and, if present, the gripper is generated.
.. literalinclude:: ../../../../source/standalone/demos/arms.py
:language: python
:lines: 105-120
:linenos:
:lineno-start: 105
If a gripper is present, we toggle the command regularly to simulate a grasp and release action.
.. literalinclude:: ../../../../source/standalone/demos/arms.py
:language: python
:lines: 121-125
:linenos:
:lineno-start: 121
The joint position target is set to the Articulation object by calling the :meth:`Articulation.set_joint_target` method. Similar methods exist to set velocity and effort targets depending on the use case. Afterward, the values are again written into the PhysX buffer before the simulation is stepped. Finally, we update the articulation object's internal buffers to reflect the robot's new state.
.. literalinclude:: ../../../../source/standalone/demos/arms.py
:language: python
:lines: 126-135
:linenos:
:lineno-start: 126
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/demos/arms.py --robot franka_panda
This command should open a stage with a ground plane, lights, and robots.
The simulation should play with the robot arms going to random joint configurations. The
gripper, if present, should be opening or closing at regular intervals. To stop the simulation,
you can either close the window, press the ``STOP`` button in the UI, or press ``Ctrl+C``
in the terminal
In addition to the demo script for playing single-arm manipulators, we also provide a script
for spawning a few quadrupeds:
.. code-block:: bash
# Quadruped -- Spawns ANYmal C, ANYmal B, Unitree A1 on one stage
./orbit.sh -p source/standalone/demos/quadrupeds.py
In this tutorial, we saw how to spawn a robot multiple times and wrap it in an Articulation class to initialize all physics handles, and that lets us control the robot. We also saw how to reset the robot and set joint targets.
.. _how_to_rigid_objects_label:
Interacting with a rigid object
===============================
In the previous tutorial, we explained the essential workings of the standalone script, how to
play the simulator and add the first robot to the scene.
This tutorial demonstrates how to wrap objects in the :class:`RigidObject` class and how to control their position and velocity by directly interacting with the physx buffers.
The Code
~~~~~~~~
The tutorial corresponds to the ``rigid_object.py`` script in the ``orbit/source/standalone/tutorials/01_assets`` directory.
.. dropdown:: :fa:`eye,mr-1` Code for `rigid_object.py`
.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/rigid_object.py
:language: python
:emphasize-lines: 64-74, 76-79, 130-131, 92-110, 111-119
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
The scene is designed similarly to the previous tutorial. In the following, we will discuss in detail the ``add_rigid_objects`` function, which is responsible for adding the objects to the scene, and the ``run_simulator`` function, which steps the simulator and changes their position and orientation.
Adding the rigid_objects
------------------------
The objects are included in the Nucleus Server of Omniverse. At first, we define their path and then spawn them into the scene. This procedure is discussed in the :ref:`Spawn Objects <_how_to_spawn_objects_label>` how-to guide.
.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/rigid_object.py
:language: python
:lines: 64-74
:linenos:
:lineno-start: 64
In addition, we now wrap the spawned objects as objects of the :class:`RigidObject` class. This class is a wrapper around the PhysX rigid-body view and allows us to access the physics handles of the object directly. The class also provides methods to set the object's pose and velocity and retrieve the current state.
.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/rigid_object.py
:language: python
:lines: 76-79
:linenos:
:lineno-start: 76
Please note that the physics handles are initialized when the simulation is played. Thus, we must call ``sim.reset()`` before accessing the physics handles.
.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/rigid_object.py
:language: python
:lines: 130-131
:linenos:
:lineno-start: 130
Running the simulation loop
---------------------------
In this tutorial, we step the simulation and change the translation and orientation of the objects at regular intervals. The translation is sampled uniformly from a cylinder surface while the orientation is random. By calling the :meth:`RigidObject.write_root_state_to_sim` method, we directly write these values into the PhysX buffer. Then, we call :meth:`RigidObject.reset` the internal buffers of the objects are reset.
.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/rigid_object.py
:language: python
:lines: 92-110
:linenos:
:lineno-start: 92
The method :meth:`RigidObject.write_data_to_sim` is included for completeness. It is only necessary when external torques or wrenches are applied to the objects and have to be written into the PhysX buffer. Afterward, we step the simulator and update the articulation object's internal buffers to reflect the robot's new state.
.. literalinclude:: ../../../../source/standalone/tutorials/01_assets/rigid_object.py
:language: python
:lines: 111-119
:linenos:
:lineno-start: 111
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/tutorials/01_assets/rigid_object.py
This should open a stage with a ground plane, lights, and objects. To stop the simulation,
you can either close the window, or press the ``STOP`` button in the UI, or press ``Ctrl+C``
in the terminal
In this how-to guide, we saw how to spawn rigid objects and wrap them in a :class:`RigidObject` class to initialize all physics handles and that lets us control their position and velocity.
Duplicating a scene efficiently
===============================
In the previous tutorial, we needed to spawn each individual prim manually to create multiple robots in the scene.
This operation can be cumbersome and slow when defining a large scene which needs to duplicated a large number of
times, such for reinforement learning. In this tutorial we will look at duplicating the scene with the cloner APIs
provided by Isaac Sim.
.. note::
A more descriptive tutorial on the cloner APIs can be found in the `cloner API tutorial
<https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/tutorial_gym_cloner.html>`_. We introduce its main
concepts in this tutorial for convenience.
The Code
~~~~~~~~
The tutorial corresponds to the ``play_cloner.py`` script in the ``orbit/source/standalone/demo`` directory.
.. literalinclude:: ../../../source/standalone/demo/play_cloner.py
:language: python
:emphasize-lines: 37,61-67,69-73,94,105,107-120,126
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
Configuring the simulation stage
--------------------------------
Cloning a scene to a large number of instances can lead to slow accessibility of the physics buffers from Python. This
is because in Omniverse, all read and write happens through the intermediate USD layer. Thus, whenever a physics
step happens. the data is first written into the USD buffers from the physics tensor buffers, and then available
to the users. Thus, to avoid this overhead, we can configure the simulation stage to write the data directly into
the tensors accessed by the users. This is performed by enabling the
`PhysX flatcache <https://docs.omniverse.nvidia.com/prod_extensions/prod_extensions/ext_physics.html#physx-short-flatcache-also-known-as-fabric-rename-in-next-release>`_.
However, once flatcache is enabled, the rendering is not updated since it still reads from the USD buffers, which are
no longer being updated. Thus, we also need to enable scene graph instancing to render the scene.
These are done by setting the following flags:
.. literalinclude:: ../../../source/standalone/demo/play_cloner.py
:language: python
:lines: 61-67
:linenos:
:lineno-start: 61
Cloning the scene
-----------------
The basic :class:`Cloner` class clones all the prims under a source prim path and puts them under the specified paths.
It provides a method to generate the paths for the cloned prims based on the number of clones. The generated paths
follow the pattern of the source prim path with a number appended to the end. For example, if the source prim path is
`/World/envs/env_0`, the generated paths will be `/World/envs/env_0`, `/World/envs/env_1`, `/World/envs/env_2`, etc.
These are then passed to the :meth:`clone()` method to clone the scene.
In this tutorial, we use the :class:`GridCloner` class which clones the scene in a grid pattern. The grid spacing
defines the distance between the cloned scenes. We define the base environment path to be `/World/envs`, which is
the parent prim of all the cloned scenes. The source prim path is then defined to be `/World/envs/env_0`. All the
prims under this prim path are cloned.
.. literalinclude:: ../../../source/standalone/demo/play_cloner.py
:language: python
:lines: 69-73
:linenos:
:lineno-start: 69
Unlike the previous tutorial, in this tutorial, we only spawn one environment and clone it to multiple instances.
We spawn the table and the robot under the source prim path.
.. literalinclude:: ../../../source/standalone/demo/play_cloner.py
:language: python
:lines: 92-105
:linenos:
:lineno-start: 92
The :meth:`generate_paths()` method generates the paths for the cloned prims. The generated paths are then passed to
the :meth:`clone()` method to clone the source scene. It returns the positions of the cloned scenes relative to
the simulation world origin.
.. literalinclude:: ../../../source/standalone/demo/play_cloner.py
:language: python
:lines: 107-120
:linenos:
:lineno-start: 107
Applying collision filtering
----------------------------
Collisions between the cloned environments is filtered by using the :meth:`filter_collisions()` method. This
is done by specifying the physics scene path, the collision prim path, the cloned prim paths, and the global prim paths.
The global prim paths are the prims that are not cloned and are shared between the cloned scenes. For instance,
the ground plane belongs to the global prim paths.
.. literalinclude:: ../../../source/standalone/demo/play_cloner.py
:language: python
:lines: 116-120
:linenos:
:lineno-start: 116
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code, let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/demo/play_cloner.py --robot franka_panda --num_robots 128
This should behave the same as the previous tutorial, except that it is much faster to spawn the robots.
To stop the simulation, you can either close the window, or press the ``STOP`` button in the UI, or
press ``Ctrl+C`` in the terminal.
Now that we have learned how to design a scene with a robot and clone it multiple times, we can move on to
the next tutorial to learn how to control the robot with a task-space controller.
.. _interactive-scene:
Using InteractiveScene
======================
In previous tutorials, we've used :meth:`spawn` to manually spawn assets,
but in this tutorial we introduce :class:`InteractiveScene` and its
associated configuration class :class:`InteractiveSceneCfg`.
:class:`InteractiveScene` provides a few benefits over using assets'
:meth:`spawn` methods directly:
* collects all of the assets in a single configuration object which makes them easier
to manage
* enables user-friendly cloning of scene elements for multiple environments
* user doesn't need to call :meth:`spawn` for each asset - this is handled implicitly
We will implement an :class:`InteractiveSceneCfg` to design a simple scene
for the Cartpole, which consists of a ground
plane, light, and the cartpole :class:`Articulation`.
The Code
~~~~~~~~
This tutorial corresponds to the ``scene_creation.py`` script within
``orbit/source/standalone/tutorials``.
.. dropdown:: :fa:`eye,mr-1` Code for ``scene_creation.py``
.. literalinclude:: ../../../../source/standalone/tutorials/03_scene/scene_creation.py
:language: python
:linenos:
The Code Explained
~~~~~~~~~~~~~~~~~~
We compose our scene configuration by sub-classing :class:`InteractiveSceneCfg`. We
then add the elements we want as attributes of the class, which is a common pattern
for configuration classes in Orbit. In this case, we add a
ground plane, light, and cartpole. The names of the attributes (``ground``,
``robot``, ``dome_light``, ``distant_light``) are used as keys to access the
corresponding assets in the :class:`InteractiveScene` object.
.. literalinclude:: ../../../../source/extensions/omni.isaac.orbit_tasks/omni/isaac/orbit_tasks/classic/cartpole/cartpole_scene.py
:language: python
:linenos:
Within the config elements of :class:`CartpoleSceneCfg`, notice that we pass
in a few arguments. We will describe these briefly as they are fundamental to
how the scene interface works - refer to in-code documentation for more depth.
* **prim_path**: The USD layer to associate the asset with
* **spawn**: The configuration object for spawning
* **init_state**: The desired initial pose of the asset. Defaults to identity if unspecified
You can find more about additional arguments by looking directly at the docstrings for
:class:`AssetBaseCfg`, :class:`RigidObjectCfg` and :class:`ArticulationCfg`.
:class:`InteractiveSceneCfg` is simply a collection of
asset configurations, so add new elements as you see fit
(including sensors, markers, terrain, etc.) you've learned to utilize in previous tutorials.
Setup Simulator and Spawn Scene
-------------------------------
.. literalinclude:: ../../../../source/standalone/tutorials/03_scene/scene_creation.py
:language: python
:start-after: # Main
:end-before: # Extract cartpole from InteractiveScene
Accessing Scene Elements
------------------------
Individual scene elements can then be accessed from the :class:`InteractiveScene` via
the different asset groups: ``articulations``, ``rigid_objects``, ``sensors``,
``extras`` (where lights are found for instance). Each of these is a dictionary with the keys being assigned based
on the object instance name.
In the example script we access our cartpole :class:`Articulation` here using the ``"robot"`` key:
.. literalinclude:: ../../../../source/standalone/tutorials/03_scene/scene_creation.py
:language: python
:start-after: # Extract cartpole from InteractiveScene
:end-before: # Simulation loop
Simulation Loop
---------------
.. literalinclude:: ../../../../source/standalone/tutorials/03_scene/scene_creation.py
:language: python
:emphasize-lines: 15-16, 18, 28
:start-after: # Simulation loop
:end-before: # End simulation loop
The rest of the script should look familiar to previous scripts that interfaced with :class:`Articulation`,
with a few small differences:
* :meth:`Articulation.set_joint_position_target` and :meth:`Articulation.set_joint_velocity_target`
in combination with :meth:`InteractiveScene.write_data_to_sim`
are used instead of :meth:`Articulation.write_joint_data_to_sim` to set the desired position and velocity targets
without writing them to the simulation.
* :meth:`InteractiveScene.update` is used in place of :meth:`Articulation.update`
Under the hood, ``InteractiveScene`` calls the ``update`` and ``write_data_to_sim`` for each asset in the scene,
so you only need to call these once per simulation step.
Cloning
-------
As mentioned previously, one of the key benefits of using :class:`InteractiveScene`
is its ability to handle cloning of assets seamlessly with the only user input
being the ``num_envs`` (The number of desired environments to spawn). The spacing between
environments is also configurable via ``env_spacing``.
We will exercise this below by passing in ``--num_envs 32`` to the tutorial script to spawn 32 cartpoles.
The Code Execution
~~~~~~~~~~~~~~~~~~
Now that we have gone through the code, let's run the script and see the result:
.. code-block:: bash
./orbit.sh -p source/standalone/tutorials/03_scene/scene_creation.py --num_envs 32
This should open a stage with 32 cartpoles. The simulation should be
playing with the poles of each cartpole balancing vertically.
In this tutorial we saw how to use :class:`InteractiveScene` to create a
scene with multiple assets. We also saw how to use the ``num_envs`` argument
to clone the scene for multiple environments.
.. note::
There are many more examples of other ``InteractiveSceneCfg`` in the tasks found in
``source/extensions/omni.isaac.orbit_tasks/omni/isaac/orbit_tasks`` for
reference.
......@@ -18,7 +18,7 @@ The Code
The tutorial corresponds to the ``zero_agent.py`` script in the ``orbit/source/standalone/environments`` directory.
.. literalinclude:: ../../../source/standalone/environments/zero_agent.py
.. literalinclude:: ../../../../source/standalone/environments/zero_agent.py
:language: python
:emphasize-lines: 34-35,41-44,49-55
:linenos:
......@@ -52,7 +52,7 @@ function as shown later in the tutorial.
To inform the ``gym`` registry with all the environments provided by the ``omni.isaac.orbit_tasks`` extension,
we must import the module at the start of the script.
.. literalinclude:: ../../../source/standalone/environments/zero_agent.py
.. literalinclude:: ../../../../source/standalone/environments/zero_agent.py
:language: python
:lines: 33-35
:linenos:
......@@ -70,7 +70,7 @@ In this tutorial, the task name is read from the command line. The task name is
as well as to create the environment instance. In addition, other parsed command line arguments such as the
number of environments, the simulation device, and whether to render, are used to override the default configuration.
.. literalinclude:: ../../../source/standalone/environments/zero_agent.py
.. literalinclude:: ../../../../source/standalone/environments/zero_agent.py
:language: python
:lines: 42-45
:linenos:
......@@ -82,7 +82,7 @@ Running the environment
Once creating the environment, the rest of the execution follows the standard resetting and stepping.
.. literalinclude:: ../../../source/standalone/environments/zero_agent.py
.. literalinclude:: ../../../../source/standalone/environments/zero_agent.py
:language: python
:lines: 45-55
:linenos:
......@@ -91,7 +91,7 @@ Once creating the environment, the rest of the execution follows the standard re
Similar to previous tutorials, to ensure a safe exit when running the script, we need to add checks
for whether the simulation is stopped or not.
.. literalinclude:: ../../../source/standalone/environments/zero_agent.py
.. literalinclude:: ../../../../source/standalone/environments/zero_agent.py
:language: python
:lines: 57-59
:linenos:
......
This diff is collapsed.
This diff is collapsed.
......@@ -71,7 +71,7 @@ The camera's pose and image resolution can be configured through the
.. dropdown:: :fa:`eye,mr-1` Default parameters of the :class:`ViewerCfg` class:
.. literalinclude:: ../../../source/extensions/omni.isaac.orbit/omni/isaac/orbit/envs/base_env_cfg.py
.. literalinclude:: ../../../../source/extensions/omni.isaac.orbit/omni/isaac/orbit/envs/base_env_cfg.py
:language: python
:pyobject: ViewerCfg
......
This diff is collapsed.
......@@ -57,8 +57,8 @@ class AssetBaseCfg:
Example: ``{ENV_REGEX_NS}/Robot`` will be replaced with ``/World/envs/env_.*/Robot``.
"""
spawn: SpawnerCfg | None = MISSING
"""Spawn configuration for the asset.
spawn: SpawnerCfg | None = None
"""Spawn configuration for the asset. Defaults to None.
If None, then no prims are spawned by the asset class. Instead, it is assumed that the
asset is already present in the scene.
......
......@@ -11,6 +11,7 @@ their corresponding asset classes during construction.
"""
from .anymal import *
from .cartpole import *
from .franka import *
from .ridgeback_franka import *
from .unitree import *
......
# Copyright (c) 2022-2023, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Configuration for a simple Cartpole robot
"""
from __future__ import annotations
from omni.isaac.orbit_assets import ORBIT_ASSETS_DATA_DIR
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.actuators import ImplicitActuatorCfg
from omni.isaac.orbit.assets import ArticulationCfg
# Cartpole articulation configuration
CARTPOLE_CFG = ArticulationCfg(
# USD file configuration
spawn=sim_utils.UsdFileCfg(
# Location of USD file
usd_path=f"{ORBIT_ASSETS_DATA_DIR}/Robots/Classic/Cartpole/cartpole.usd",
# Rigid body properties
rigid_props=sim_utils.RigidBodyPropertiesCfg(
rigid_body_enabled=True,
max_linear_velocity=1000.0,
max_angular_velocity=1000.0,
max_depenetration_velocity=100.0,
enable_gyroscopic_forces=True,
),
# Articulation root properties
articulation_props=sim_utils.ArticulationRootPropertiesCfg(
enabled_self_collisions=False,
solver_position_iteration_count=4,
solver_velocity_iteration_count=0,
sleep_threshold=0.005,
stabilization_threshold=0.001,
),
),
# Initial state definition
init_state=ArticulationCfg.InitialStateCfg(
pos=(0.0, 0.0, 2.0), joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0}
),
# Actuators definition
actuators={
"cart_actuator": ImplicitActuatorCfg(
joint_names_expr=["slider_to_cart"],
effort_limit=400.0,
velocity_limit=100.0,
stiffness=0.0,
damping=10.0,
),
"pole_actuator": ImplicitActuatorCfg(
joint_names_expr=["cart_to_pole"], effort_limit=400.0, velocity_limit=100.0, stiffness=0.0, damping=0.0
),
},
# End cartpole articulation configuration
)
......@@ -6,11 +6,11 @@
"""
This script demonstrates how to simulate a mobile manipulator.
We currently support the following robots:
.. code-block:: bash
* Franka Emika Panda on a Clearpath Ridgeback Omni-drive Base
# Usage
./orbit.sh -p source/extensions/omni.isaac.orbit/test/assets/check_ridgeback_franka.py
From the default configuration file for these robots, zero actions imply a default pose.
"""
from __future__ import annotations
......@@ -26,7 +26,6 @@ from omni.isaac.orbit.app import AppLauncher
parser = argparse.ArgumentParser(
description="This script demonstrates how to simulate a mobile manipulator with dummy joints."
)
parser.add_argument("--robot", type=str, default="franka_panda", help="Name of the robot.")
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
......@@ -47,33 +46,22 @@ import carb
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.assets import Articulation
from omni.isaac.orbit.assets.config.ridgeback_franka import RIDGEBACK_FRANKA_PANDA_CFG
from omni.isaac.orbit.sim import SimulationContext
"""
Main
"""
def main():
"""Main function."""
# Load kit helper
# note: there is a bug in Isaac Sim 2022.2.1 that prevents the use of GPU pipeline
sim = SimulationContext(
sim_utils.SimulationCfg(device="cpu", use_gpu_pipeline=False, physx=sim_utils.PhysxCfg(use_gpu=False))
)
# Set main camera
sim.set_camera_view([1.5, 1.5, 1.5], [0.0, 0.0, 0.0])
# Spawn things into stage
def design_scene():
"""Designs the scene."""
# Ground-plane
cfg = sim_utils.GroundPlaneCfg()
cfg.func("/World/defaultGroundPlane", cfg)
# Lights
cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
cfg.func("/World/Light", cfg)
# add robots and return them
return add_robots()
# Robots
def add_robots() -> Articulation:
"""Adds robots to the scene."""
robot_cfg = RIDGEBACK_FRANKA_PANDA_CFG
# -- Spawn robot
robot_cfg.spawn.func("/World/Robot_1", robot_cfg.spawn, translation=(0.0, -1.0, 0.0))
......@@ -81,12 +69,11 @@ def main():
# -- Create interface
robot = Articulation(cfg=robot_cfg.replace(prim_path="/World/Robot.*"))
# Play the simulator
sim.reset()
return robot
# Now we are ready!
print("[INFO]: Setup complete...")
def run_simulator(sim: sim_utils.SimulationContext, robot: Articulation):
"""Runs the simulator by applying actions to the robot at every time-step"""
# dummy action
actions = robot.data.default_joint_pos.clone()
......@@ -99,14 +86,12 @@ def main():
while simulation_app.is_running():
# reset
if ep_step_count % 1000 == 0:
# reset counters
sim_time = 0.0
ep_step_count = 0
# reset dof state
joint_pos, joint_vel = robot.data.default_joint_pos.clone(), robot.data.default_joint_vel.clone()
robot.write_joint_state_to_sim(joint_pos, joint_vel)
# reset default joint target
robot.set_joint_position_target(joint_pos)
robot.write_data_to_sim()
# reset internals
robot.reset()
# reset command
......@@ -162,6 +147,22 @@ def main():
robot.update(sim_dt)
def main():
"""Main function."""
# Initialize the simulation context
sim = sim_utils.SimulationContext(sim_utils.SimulationCfg())
# Set main camera
sim.set_camera_view([1.5, 1.5, 1.5], [0.0, 0.0, 0.0])
# design scene
robot = design_scene()
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# Run the simulator
run_simulator(sim, robot)
if __name__ == "__main__":
try:
# run the main execution
......
......@@ -11,6 +11,7 @@ import gymnasium as gym
from . import agents
from .cartpole_env_cfg import CartpoleEnvCfg
from .cartpole_scene import CartpoleSceneCfg
##
# Register Gym environments.
......
......@@ -5,11 +5,6 @@
import math
from omni.isaac.orbit_assets import ORBIT_ASSETS_DATA_DIR
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.actuators import ImplicitActuatorCfg
from omni.isaac.orbit.assets import ArticulationCfg, AssetBaseCfg
from omni.isaac.orbit.envs import RLTaskEnvCfg
from omni.isaac.orbit.managers import ObservationGroupCfg as ObsGroup
from omni.isaac.orbit.managers import ObservationTermCfg as ObsTerm
......@@ -17,78 +12,18 @@ from omni.isaac.orbit.managers import RandomizationTermCfg as RandTerm
from omni.isaac.orbit.managers import RewardTermCfg as RewTerm
from omni.isaac.orbit.managers import SceneEntityCfg
from omni.isaac.orbit.managers import TerminationTermCfg as DoneTerm
from omni.isaac.orbit.scene import InteractiveSceneCfg
from omni.isaac.orbit.utils import configclass
import omni.isaac.orbit_tasks.classic.cartpole.mdp as mdp
##
# Scene definition
##
@configclass
class CartpoleSceneCfg(InteractiveSceneCfg):
"""Configuration for the cartpole scene."""
# ground plane
ground = AssetBaseCfg(
prim_path="/World/ground",
spawn=sim_utils.GroundPlaneCfg(size=(100.0, 100.0)),
)
# robots
robot = ArticulationCfg(
prim_path="{ENV_REGEX_NS}/Robot",
spawn=sim_utils.UsdFileCfg(
usd_path=f"{ORBIT_ASSETS_DATA_DIR}/Robots/Classic/Cartpole/cartpole.usd",
rigid_props=sim_utils.RigidBodyPropertiesCfg(
max_linear_velocity=1000.0,
max_angular_velocity=1000.0,
max_depenetration_velocity=100.0,
enable_gyroscopic_forces=True,
),
articulation_props=sim_utils.ArticulationRootPropertiesCfg(
enabled_self_collisions=False,
solver_position_iteration_count=4,
solver_velocity_iteration_count=0,
sleep_threshold=0.005,
stabilization_threshold=0.001,
),
),
init_state=ArticulationCfg.InitialStateCfg(
pos=(0.0, 0.0, 2.0), joint_pos={"slider_to_cart": 0.0, "cart_to_pole": 0.0}
),
actuators={
"cart_actuator": ImplicitActuatorCfg(
joint_names_expr=["slider_to_cart"],
effort_limit=400.0,
velocity_limit=100.0,
stiffness=0.0,
damping=10.0,
),
"pole_actuator": ImplicitActuatorCfg(
joint_names_expr=["cart_to_pole"], effort_limit=400.0, velocity_limit=100.0, stiffness=0.0, damping=0.0
),
},
)
# lights
dome_light = AssetBaseCfg(
prim_path="/World/DomeLight",
spawn=sim_utils.DomeLightCfg(color=(0.9, 0.9, 0.9), intensity=500.0),
)
distant_light = AssetBaseCfg(
prim_path="/World/DistantLight",
spawn=sim_utils.DistantLightCfg(color=(0.9, 0.9, 0.9), intensity=2500.0),
init_state=AssetBaseCfg.InitialStateCfg(rot=(0.738, 0.477, 0.477, 0.0)),
)
from .cartpole_scene import CartpoleSceneCfg
##
# MDP settings
##
# Actions configuration
@configclass
class CommandsCfg:
"""Command terms for the MDP."""
......@@ -104,6 +39,7 @@ class ActionsCfg:
joint_effort = mdp.JointEffortActionCfg(asset_name="robot", joint_names=["slider_to_cart"], scale=100.0)
# Observations configuration
@configclass
class ObservationsCfg:
"""Observation specifications for the MDP."""
......@@ -124,6 +60,7 @@ class ObservationsCfg:
policy: PolicyCfg = PolicyCfg()
# Randomization configuration
@configclass
class RandomizationCfg:
"""Configuration for randomization."""
......@@ -150,6 +87,7 @@ class RandomizationCfg:
)
# Rewards configuration
@configclass
class RewardsCfg:
"""Reward terms for the MDP."""
......@@ -178,6 +116,7 @@ class RewardsCfg:
)
# Terminations configuration
@configclass
class TerminationsCfg:
"""Termination terms for the MDP."""
......@@ -191,6 +130,7 @@ class TerminationsCfg:
)
# Curriculum configuration
@configclass
class CurriculumCfg:
"""Configuration for the curriculum."""
......@@ -220,6 +160,7 @@ class CartpoleEnvCfg(RLTaskEnvCfg):
# No command generator
commands: CommandsCfg = CommandsCfg()
# Post initialization
def __post_init__(self) -> None:
"""Post initialization."""
# general settings
......
# Copyright (c) 2022-2023, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.assets import ArticulationCfg, AssetBaseCfg
from omni.isaac.orbit.assets.config.cartpole import CARTPOLE_CFG
from omni.isaac.orbit.scene import InteractiveSceneCfg
from omni.isaac.orbit.utils import configclass
@configclass
class CartpoleSceneCfg(InteractiveSceneCfg):
"""Configuration for a cartpole scene."""
# ground plane
ground = AssetBaseCfg(
prim_path="/World/ground",
spawn=sim_utils.GroundPlaneCfg(size=(100.0, 100.0)),
)
# cartpole
robot: ArticulationCfg = CARTPOLE_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot")
# lights
dome_light = AssetBaseCfg(
prim_path="/World/DomeLight",
spawn=sim_utils.DomeLightCfg(color=(0.9, 0.9, 0.9), intensity=500.0),
)
distant_light = AssetBaseCfg(
prim_path="/World/DistantLight",
spawn=sim_utils.DistantLightCfg(color=(0.9, 0.9, 0.9), intensity=2500.0),
init_state=AssetBaseCfg.InitialStateCfg(rot=(0.738, 0.477, 0.477, 0.0)),
)
# Copyright (c) 2022-2023, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This script demonstrates how to simulate a single-arm manipulator with Orbit interfaces.
We currently support the following robots:
* Franka Emika Panda
* Universal Robot UR10
From the default configuration file for these robots, zero actions imply a default pose.
"""
from __future__ import annotations
"""Launch Isaac Sim Simulator first."""
import argparse
from omni.isaac.orbit.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(
description="This script demonstrates how to use the physics engine to simulate a single-arm manipulator."
)
parser.add_argument(
"--robot", type=str, default="franka_panda", choices=["franka_panda", "ur10"], help="Name of the robot."
)
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
args_cli = parser.parse_args()
# launch omniverse app
app_launcher = AppLauncher(args_cli)
simulation_app = app_launcher.app
import torch
import traceback
import carb
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.assets import Articulation
from omni.isaac.orbit.assets.config.franka import FRANKA_PANDA_ARM_WITH_PANDA_HAND_CFG
from omni.isaac.orbit.assets.config.universal_robots import UR10_CFG
from omni.isaac.orbit.sim import SimulationContext
from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR
def main():
"""Main function."""
# Load kit helper
# note: there is a bug in Isaac Sim 2022.2.1 that prevents the use of GPU pipeline
sim = SimulationContext(sim_utils.SimulationCfg(device="cpu", use_gpu_pipeline=False))
# Set main camera
sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0])
# Spawn things into stage
# Ground-plane
cfg = sim_utils.GroundPlaneCfg()
cfg.func("/World/defaultGroundPlane", cfg, translation=(0.0, 0.0, -1.05))
# Lights
cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
cfg.func("/World/Light", cfg)
# Table
cfg = sim_utils.UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Mounts/SeattleLabTable/table_instanceable.usd")
cfg.func("/World/Table_1", cfg, translation=(0.55, -1.0, 0.0))
cfg.func("/World/Table_2", cfg, translation=(0.55, 1.0, 0.0))
# Robots
# -- Resolve robot config from command-line arguments
if args_cli.robot == "franka_panda":
robot_cfg = FRANKA_PANDA_ARM_WITH_PANDA_HAND_CFG
elif args_cli.robot == "ur10":
robot_cfg = UR10_CFG
else:
raise ValueError(f"Robot {args_cli.robot} is not supported!")
# -- Spawn robot
robot_cfg.spawn.func("/World/Robot_1", robot_cfg.spawn, translation=(0.0, -1.0, 0.0))
robot_cfg.spawn.func("/World/Robot_2", robot_cfg.spawn, translation=(0.0, 1.0, 0.0))
# -- Create interface
robot = Articulation(cfg=robot_cfg.replace(prim_path="/World/Robot.*"))
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# dummy actions
actions = torch.rand(robot.num_instances, robot.num_joints, device=robot.device) + robot.data.default_joint_pos
has_gripper = args_cli.robot == "franka_panda"
# Define simulation stepping
sim_dt = sim.get_physics_dt()
# episode counter
sim_time = 0.0
ep_step_count = 0
# Simulate physics
while simulation_app.is_running():
# reset
if ep_step_count % 1000 == 0:
sim_time = 0.0
ep_step_count = 0
# reset dof state
joint_pos, joint_vel = robot.data.default_joint_pos, robot.data.default_joint_vel
robot.write_joint_state_to_sim(joint_pos, joint_vel)
robot.reset()
# reset command
actions = torch.rand(robot.num_instances, robot.num_joints, device=robot.device)
actions += robot.data.default_joint_pos
# reset gripper
if has_gripper:
actions[:, -2:] = 0.04
print("[INFO]: Resetting robots state...")
# change the gripper action
if ep_step_count % 200 == 0 and has_gripper:
# flip command for the gripper
actions[:, -2:] = 0.0 if actions[0, -2] > 0.0 else 0.04
print(f"[INFO]: [Step {ep_step_count:03d}]: Flipping gripper command...")
# apply action to the robot
robot.set_joint_position_target(actions)
robot.write_data_to_sim()
# perform step
sim.step()
# update buffers
robot.update(sim_dt)
# update sim-time
sim_time += sim_dt
ep_step_count += 1
if __name__ == "__main__":
try:
# run the main execution
main()
except Exception as err:
carb.log_error(err)
carb.log_error(traceback.format_exc())
raise
finally:
# close sim app
simulation_app.close()
This diff is collapsed.
......@@ -4,9 +4,13 @@
# SPDX-License-Identifier: BSD-3-Clause
"""
This script demonstrates how to use the cloner API from Isaac Sim.
This script demonstrates different single-arm manipulators.
.. code-block:: bash
# Usage
./orbit.sh -p source/standalone/demos/arms.py
Reference: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/tutorial_gym_cloner.html
"""
from __future__ import annotations
......@@ -19,9 +23,7 @@ import argparse
from omni.isaac.orbit.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(description="This script demonstrates how to use the cloner API from Isaac Sim.")
parser.add_argument("--robot", type=str, default="franka_panda", help="Name of the robot.")
parser.add_argument("--num_robots", type=int, default=128, help="Number of robots to spawn.")
parser = argparse.ArgumentParser(description="This script demonstrates different single-arm manipulators.")
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
......@@ -31,119 +33,120 @@ args_cli = parser.parse_args()
app_launcher = AppLauncher(args_cli)
simulation_app = app_launcher.app
"""Rest everything follows."""
import torch
import traceback
import carb
import omni.isaac.core.utils.prims as prim_utils
from omni.isaac.cloner import GridCloner
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.assets import Articulation
from omni.isaac.orbit.assets.config import FRANKA_PANDA_ARM_WITH_PANDA_HAND_CFG, UR10_CFG
from omni.isaac.orbit.sim import SimulationContext
from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR
def main():
"""Main function."""
# Load kit helper
sim = SimulationContext(sim_utils.SimulationCfg(device="cuda:0", use_gpu_pipeline=True))
# Set main camera
sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0])
# Create interface to clone the scene
cloner = GridCloner(spacing=2.0)
cloner.define_base_env("/World/envs")
# Everything under the namespace "/World/envs/env_0" will be cloned
prim_utils.define_prim("/World/envs/env_0")
# Spawn things into stage
def design_scene() -> tuple[dict, list[list[float]]]:
"""Designs the scene."""
# Ground-plane
cfg = sim_utils.GroundPlaneCfg()
cfg.func("/World/defaultGroundPlane", cfg, translation=(0.0, 0.0, -1.05))
cfg.func("/World/defaultGroundPlane", cfg)
# Lights
cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75))
cfg.func("/World/Light", cfg)
# Table
# Create separate groups called "Origin1", "Origin2", "Origin3"
# Each group will have a mount and a robot on top of it
origins = [[0.0, -1.0, 0.0], [0.0, 1.0, 0.0]]
# Origin 1 with Franka Panda
prim_utils.create_prim("/World/Origin1", "Xform", translation=origins[0])
# -- Table
cfg = sim_utils.UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Mounts/SeattleLabTable/table_instanceable.usd")
cfg.func("/World/envs/env_0/Table", cfg)
# Robot
# -- resolve robot config from command-line arguments
if args_cli.robot == "franka_panda":
robot_cfg = FRANKA_PANDA_ARM_WITH_PANDA_HAND_CFG
elif args_cli.robot == "ur10":
robot_cfg = UR10_CFG
else:
raise ValueError(f"Robot {args_cli.robot} is not supported. Valid: franka_panda, ur10")
# -- spawn internally and create interface
robot = Articulation(cfg=robot_cfg.replace(prim_path="/World/envs/env_.*/Robot"))
# Clone the scene
num_envs = args_cli.num_robots
cloner.define_base_env("/World/envs")
envs_prim_paths = cloner.generate_paths("/World/envs/env", num_paths=num_envs)
envs_positions = cloner.clone(
source_prim_path="/World/envs/env_0", prim_paths=envs_prim_paths, replicate_physics=True
)
# convert environment positions to torch tensor
envs_positions = torch.tensor(envs_positions, dtype=torch.float, device=sim.device)
# filter collisions within each environment instance
physics_scene_path = sim.get_physics_context().prim_path
cloner.filter_collisions(
physics_scene_path, "/World/collisions", envs_prim_paths, global_paths=["/World/defaultGroundPlane"]
cfg.func("/World/Origin1/Table", cfg, translation=(0.55, 0.0, 1.05))
# -- Robot
franka_arm_cfg = FRANKA_PANDA_ARM_WITH_PANDA_HAND_CFG.replace(prim_path="/World/Origin1/Robot")
franka_arm_cfg.init_state.pos = (0.0, 0.0, 1.05)
robot_franka = Articulation(cfg=franka_arm_cfg)
# Origin 2 with UR10
prim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1])
# -- Table
cfg = sim_utils.UsdFileCfg(
usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Mounts/Stand/stand_instanceable.usd", scale=(2.0, 2.0, 2.0)
)
cfg.func("/World/Origin2/Table", cfg, translation=(0.0, 0.0, 1.03))
# -- Robot
ur10_cfg = UR10_CFG.replace(prim_path="/World/Origin2/Robot")
ur10_cfg.init_state.pos = (0.0, 0.0, 1.03)
robot_ur10 = Articulation(cfg=ur10_cfg)
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# return the scene information
scene_entities = {"robot_franka": robot_franka, "robot_ur10": robot_ur10}
return scene_entities, origins
# dummy actions
actions = torch.rand(robot.num_instances, robot.num_joints, device=robot.device) + robot.data.default_joint_pos
has_gripper = args_cli.robot == "franka_panda"
def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articulation], origins: torch.Tensor):
"""Runs the simulator by applying actions to the robot at every time-step"""
# Define simulation stepping
sim_dt = sim.get_physics_dt()
# episode counter
sim_time = 0.0
ep_step_count = 0
count = 0
# Simulate physics
while simulation_app.is_running():
# reset
if ep_step_count % 100 == 0:
if count % 200 == 0:
# reset counters
sim_time = 0.0
ep_step_count = 0
# reset dof state
joint_pos, joint_vel = robot.data.default_joint_pos, robot.data.default_joint_vel
robot.write_joint_state_to_sim(joint_pos, joint_vel)
robot.reset()
# reset command
actions = torch.rand(robot.num_instances, robot.num_joints, device=robot.device)
actions += robot.data.default_joint_pos
# reset gripper
if has_gripper:
actions[:, -2:] = 0.04
count = 0
# reset the scene entities
for index, robot in enumerate(entities.values()):
# root state
root_state = robot.data.default_root_state.clone()
root_state[:, :3] += origins[index]
robot.write_root_state_to_sim(root_state)
# set joint positions
joint_pos, joint_vel = robot.data.default_joint_pos.clone(), robot.data.default_joint_vel.clone()
robot.write_joint_state_to_sim(joint_pos, joint_vel)
# clear internal buffers
robot.reset()
print("[INFO]: Resetting robots state...")
# change the gripper action
if ep_step_count % 50 == 0 and has_gripper:
# flip command for the gripper
actions[:, -2:] = 0.0 if actions[0, -2] > 0.0 else 0.04
# apply action to the robot
robot.set_joint_position_target(actions)
robot.write_data_to_sim()
# apply random actions to the robots
for robot in entities.values():
# generate random joint positions
joint_pos_target = robot.data.default_joint_pos + torch.randn_like(robot.data.joint_pos) * 0.1
joint_pos_target = joint_pos_target.clamp_(
robot.data.soft_joint_pos_limits[..., 0], robot.data.soft_joint_pos_limits[..., 1]
)
# apply action to the robot
robot.set_joint_position_target(joint_pos_target)
# write data to sim
robot.write_data_to_sim()
# perform step
sim.step()
# update sim-time
sim_time += sim_dt
ep_step_count += 1
count += 1
# update buffers
robot.update(sim_dt)
for robot in entities.values():
robot.update(sim_dt)
def main():
"""Main function."""
# Initialize the simulation context
sim_cfg = sim_utils.SimulationCfg()
sim = sim_utils.SimulationContext(sim_cfg)
# Set main camera
sim.set_camera_view([3.5, 0.0, 3.2], [0.0, 0.0, 0.5])
# design scene
scene_entities, scene_origins = design_scene()
scene_origins = torch.tensor(scene_origins, device=sim.device)
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# Run the simulator
run_simulator(sim, scene_entities, scene_origins)
if __name__ == "__main__":
......
......@@ -3,7 +3,14 @@
#
# SPDX-License-Identifier: BSD-3-Clause
"""This script demonstrates how to create different types of markers in Orbit."""
"""This script demonstrates different types of markers.
.. code-block:: bash
# Usage
./orbit.sh -p source/standalone/demos/markers.py
"""
from __future__ import annotations
......@@ -15,9 +22,7 @@ import argparse
from omni.isaac.orbit.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(
description="This script demonstrates how to create different types of markers in Orbit."
)
parser = argparse.ArgumentParser(description="This script demonstrates different types of markers.")
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
......@@ -41,20 +46,8 @@ from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR
from omni.isaac.orbit.utils.math import quat_from_angle_axis
def main():
"""Spawns lights in the stage and sets the camera view."""
# Load kit helper
sim = SimulationContext(sim_utils.SimulationCfg(dt=0.01, substeps=1))
# Set main camera
sim.set_camera_view([0.0, 17.0, 12.0], [0.0, 2.0, 0.0])
# Spawn things into stage
# Lights
cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
cfg.func("/World/Light", cfg)
# Create markers with various different shapes
def spawn_markers():
"""Spawns markers with various different shapes."""
marker_cfg = VisualizationMarkersCfg(
prim_path="/Visuals/myMarkers",
markers={
......@@ -96,16 +89,31 @@ def main():
),
},
)
my_visualizer = VisualizationMarkers(marker_cfg)
return VisualizationMarkers(marker_cfg)
# marker locations
num_marker_types = len(marker_cfg.markers)
def main():
"""Spawns lights in the stage and sets the camera view."""
# Load kit helper
sim = SimulationContext(sim_utils.SimulationCfg(dt=0.01, substeps=1))
# Set main camera
sim.set_camera_view([0.0, 17.0, 12.0], [0.0, 2.0, 0.0])
# Spawn things into stage
# Lights
cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
cfg.func("/World/Light", cfg)
# create markers
my_visualizer = spawn_markers()
# define a grid of positions where the markers should be placed
num_markers_per_type = 5
grid_spacing = 2.0
# Calculate the half-width and half-height
half_width = (num_markers_per_type - 1) / 2.0
half_height = (num_marker_types - 1) / 2.0
half_height = (my_visualizer.num_prototypes - 1) / 2.0
# Create the x and y ranges centered around the origin
x_range = torch.arange(-half_width * grid_spacing, (half_width + 1) * grid_spacing, grid_spacing)
y_range = torch.arange(-half_height * grid_spacing, (half_height + 1) * grid_spacing, grid_spacing)
......@@ -116,7 +124,7 @@ def main():
z_grid = torch.zeros_like(x_grid)
# marker locations
marker_locations = torch.stack([x_grid, y_grid, z_grid], dim=1)
marker_indices = torch.arange(num_marker_types).repeat(num_markers_per_type)
marker_indices = torch.arange(my_visualizer.num_prototypes).repeat(num_markers_per_type)
# Play the simulator
sim.reset()
......
# Copyright (c) 2022-2023, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This script demonstrates different legged robots.
.. code-block:: bash
# Usage
./orbit.sh -p source/standalone/demos/quadrupeds.py
"""
from __future__ import annotations
"""Launch Isaac Sim Simulator first."""
import argparse
from omni.isaac.orbit.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(description="This script demonstrates different legged robots.")
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
args_cli = parser.parse_args()
# launch omniverse app
app_launcher = AppLauncher(args_cli)
simulation_app = app_launcher.app
"""Rest everything follows."""
import numpy as np
import torch
import traceback
import carb
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.assets import Articulation
from omni.isaac.orbit.assets.config.anymal import ANYMAL_B_CFG, ANYMAL_C_CFG
from omni.isaac.orbit.assets.config.unitree import UNITREE_A1_CFG
def define_origins(num_origins: int, spacing: float) -> list[list[float]]:
"""Defines the origins of the the scene."""
# create tensor based on number of environments
env_origins = torch.zeros(num_origins, 3)
# create a grid of origins
num_cols = np.floor(np.sqrt(num_origins))
num_rows = np.ceil(num_origins / num_cols)
xx, yy = torch.meshgrid(torch.arange(num_rows), torch.arange(num_cols), indexing="xy")
env_origins[:, 0] = spacing * xx.flatten()[:num_origins] - spacing * (num_rows - 1) / 2
env_origins[:, 1] = spacing * yy.flatten()[:num_origins] - spacing * (num_cols - 1) / 2
env_origins[:, 2] = 0.0
# return the origins
return env_origins.tolist()
def design_scene() -> tuple[dict, list[list[float]]]:
"""Designs the scene."""
# Ground-plane
cfg = sim_utils.GroundPlaneCfg()
cfg.func("/World/defaultGroundPlane", cfg)
# Lights
cfg = sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75))
cfg.func("/World/Light", cfg)
# Create separate groups called "Origin1", "Origin2", "Origin3"
# Each group will have a mount and a robot on top of it
origins = define_origins(num_origins=4, spacing=1.25)
# Origin 1 with Anymal B
prim_utils.create_prim("/World/Origin1", "Xform", translation=origins[0])
# -- Robot
anymal_b = Articulation(ANYMAL_B_CFG.replace(prim_path="/World/Origin1/Robot"))
# Origin 2 with Anymal C
prim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1])
# -- Robot
anymal_c = Articulation(ANYMAL_C_CFG.replace(prim_path="/World/Origin2/Robot"))
# Origin 3 with Unitree A1
prim_utils.create_prim("/World/Origin3", "Xform", translation=origins[2])
# -- Robot
unitree_a = Articulation(UNITREE_A1_CFG.replace(prim_path="/World/Origin3/Robot"))
# Origin 4 with Unitree Go1
prim_utils.create_prim("/World/Origin4", "Xform", translation=origins[2])
# -- Robot
unitree_go1 = Articulation(UNITREE_A1_CFG.replace(prim_path="/World/Origin4/Robot"))
# return the scene information
scene_entities = {"anymal_b": anymal_b, "anymal_c": anymal_c, "unitree_a": unitree_a, "unitree_go1": unitree_go1}
return scene_entities, origins
def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articulation], origins: torch.Tensor):
"""Runs the simulator by applying actions to the robot at every time-step"""
# Define simulation stepping
sim_dt = sim.get_physics_dt()
sim_time = 0.0
count = 0
# Simulate physics
while simulation_app.is_running():
# reset
if count % 200 == 0:
# reset counters
sim_time = 0.0
count = 0
# reset robots
for index, robot in enumerate(entities.values()):
# root state
root_state = robot.data.default_root_state.clone()
root_state[:, :3] += origins[index]
robot.write_root_state_to_sim(root_state)
# joint state
joint_pos, joint_vel = robot.data.default_joint_pos.clone(), robot.data.default_joint_vel.clone()
robot.write_joint_state_to_sim(joint_pos, joint_vel)
# reset the internal state
robot.reset()
print("[INFO]: Resetting robots state...")
# apply default actions to the quadrupedal robots
for robot in entities.values():
# generate random joint positions
joint_pos_target = robot.data.default_joint_pos + torch.randn_like(robot.data.joint_pos) * 0.01
# apply action to the robot
robot.set_joint_position_target(joint_pos_target)
# write data to sim
robot.write_data_to_sim()
# perform step
sim.step()
# update sim-time
sim_time += sim_dt
count += 1
# update buffers
for robot in entities.values():
robot.update(sim_dt)
def main():
"""Main function."""
# Initialize the simulation context
sim = sim_utils.SimulationContext(sim_utils.SimulationCfg(dt=0.01, substeps=1))
# Set main camera
sim.set_camera_view(eye=[2.5, 2.5, 2.5], target=[0.0, 0.0, 0.0])
# design scene
scene_entities, scene_origins = design_scene()
scene_origins = torch.tensor(scene_origins, device=sim.device)
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# Run the simulator
run_simulator(sim, scene_entities, scene_origins)
if __name__ == "__main__":
try:
# run the main execution
main()
except Exception as err:
carb.log_error(err)
carb.log_error(traceback.format_exc())
raise
finally:
# close sim app
simulation_app.close()
......@@ -3,7 +3,14 @@
#
# SPDX-License-Identifier: BSD-3-Clause
"""This script demonstrates how to create a simple stage in Isaac Sim with lights and a ground plane."""
"""This script demonstrates how to create a simple stage in Isaac Sim.
.. code-block:: bash
# Usage
./orbit.sh -p source/standalone/tutorials/00_sim/empty.py
"""
from __future__ import annotations
......@@ -13,7 +20,7 @@ import argparse
from omni.isaac.orbit.app import AppLauncher
# create argparser
parser = argparse.ArgumentParser(description="Create an empty stage.")
parser = argparse.ArgumentParser(description="Tutorial on creating an empty stage.")
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
......@@ -32,7 +39,7 @@ from omni.isaac.orbit.sim import SimulationCfg, SimulationContext
def main():
"""Spawns lights in the stage and sets the camera view."""
"""Main function."""
# Initialize the simulation context
sim_cfg = SimulationCfg(dt=0.01, substeps=1)
......
# Copyright (c) 2022-2023, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""This script demonstrates how to spawn prims into the scene.
.. code-block:: bash
# Usage
./orbit.sh -p source/standalone/tutorials/00_sim/objects.py
"""
from __future__ import annotations
"""Launch Isaac Sim Simulator first."""
import argparse
from omni.isaac.orbit.app import AppLauncher
# create argparser
parser = argparse.ArgumentParser(description="Tutorial on spawning prims into the scene.")
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
args_cli = parser.parse_args()
# launch omniverse app
app_launcher = AppLauncher(args_cli)
simulation_app = app_launcher.app
"""Rest everything follows."""
import traceback
import carb
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR
def design_scene():
"""Designs the scene by spawning ground plane, light, objects and meshes from usd files."""
# Ground-plane
cfg_ground = sim_utils.GroundPlaneCfg()
cfg_ground.func("/World/defaultGroundPlane", cfg_ground)
# spawn distant light
cfg_light_distant = sim_utils.DistantLightCfg(
intensity=3000.0,
color=(0.75, 0.75, 0.75),
)
cfg_light_distant.func("/World/lightDistant", cfg_light_distant, translation=(1, 0, 10))
# create a new xform prim for all objects to be spawned under
prim_utils.create_prim("/World/Objects", "Xform")
# spawn a red cone
cfg_cone = sim_utils.ConeCfg(
radius=0.15,
height=0.5,
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(1.0, 0.0, 0.0)),
)
cfg_cone.func("/World/Objects/Cone1", cfg_cone, translation=(-1.0, 1.0, 1.0))
cfg_cone.func("/World/Objects/Cone2", cfg_cone, translation=(-1.0, -1.0, 1.0))
# spawn a green cone with colliders and rigid body
cfg_cone_rigid = sim_utils.ConeCfg(
radius=0.15,
height=0.5,
rigid_props=sim_utils.RigidBodyPropertiesCfg(),
mass_props=sim_utils.MassPropertiesCfg(mass=1.0),
collision_props=sim_utils.CollisionPropertiesCfg(),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 1.0, 0.0)),
)
cfg_cone_rigid.func(
"/World/Objects/ConeRigid", cfg_cone_rigid, translation=(0.0, 0.0, 2.0), orientation=(0.5, 0.0, 0.5, 0.0)
)
# spawn a usd file of a table into the scene
cfg = sim_utils.UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Mounts/SeattleLabTable/table_instanceable.usd")
cfg.func("/World/Objects/Table", cfg, translation=(0.0, 0.0, 1.05))
def main():
"""Main function."""
# Initialize the simulation context
sim_cfg = sim_utils.SimulationCfg(dt=0.01, substeps=1)
sim = sim_utils.SimulationContext(sim_cfg)
# Set main camera
sim.set_camera_view([2.0, 0.0, 2.5], [-0.5, 0.0, 0.5])
# Design scene by adding assets to it
design_scene()
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# Simulate physics
while simulation_app.is_running():
# perform step
sim.step()
if __name__ == "__main__":
try:
# run the main execution
main()
except Exception as err:
carb.log_error(err)
carb.log_error(traceback.format_exc())
raise
finally:
# close sim app
simulation_app.close()
......@@ -3,14 +3,7 @@
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This script demonstrates how to simulate a legged robot.
We currently support the following robots:
* ANYmal-B (from ANYbotics)
* ANYmal-C (from ANYbotics)
* A1 (from Unitree Robotics)
"""This script demonstrates how to define and spawn a Cartpole Articulation
"""
from __future__ import annotations
......@@ -23,7 +16,9 @@ import argparse
from omni.isaac.orbit.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(description="This script demonstrates how to simulate a legged robot.")
parser = argparse.ArgumentParser(
description="This script demonstrates how to define and spawn a Cartpole Articulation."
)
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
......@@ -33,30 +28,20 @@ args_cli = parser.parse_args()
app_launcher = AppLauncher(args_cli)
simulation_app = app_launcher.app
"""Rest everything follows."""
import torch
import traceback
import carb
import omni.isaac.core.utils.prims as prim_utils
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.assets import Articulation
from omni.isaac.orbit.assets.config.anymal import ANYMAL_B_CFG, ANYMAL_C_CFG
from omni.isaac.orbit.assets.config.unitree import UNITREE_A1_CFG
from omni.isaac.orbit.assets.config import CARTPOLE_CFG
from omni.isaac.orbit.sim import SimulationContext
def main():
"""Main function."""
# Load kit helper
sim = SimulationContext(
sim_utils.SimulationCfg(device="cpu", use_gpu_pipeline=False, dt=0.005, physx=sim_utils.PhysxCfg(use_gpu=False))
)
# Set main camera
sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0])
# Spawn things into stage
def design_scene() -> tuple[dict, list[list[float]]]:
"""Designs the scene."""
# Ground-plane
cfg = sim_utils.GroundPlaneCfg()
cfg.func("/World/defaultGroundPlane", cfg)
......@@ -64,63 +49,80 @@ def main():
cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
cfg.func("/World/Light", cfg)
# Robots
# -- anymal-b
robot_b_cfg = ANYMAL_B_CFG
robot_b_cfg.spawn.func("/World/Anymal_b/Robot_1", robot_b_cfg.spawn, translation=(0.0, -1.5, 0.65))
robot_b_cfg.spawn.func("/World/Anymal_b/Robot_2", robot_b_cfg.spawn, translation=(0.0, -0.5, 0.65))
# -- anymal-c
robot_c_cfg = ANYMAL_C_CFG
robot_c_cfg.spawn.func("/World/Anymal_c/Robot_1", robot_c_cfg.spawn, translation=(1.5, -1.5, 0.65))
robot_c_cfg.spawn.func("/World/Anymal_c/Robot_2", robot_c_cfg.spawn, translation=(1.5, -0.5, 0.65))
# -- unitree a1
robot_a_cfg = UNITREE_A1_CFG
robot_a_cfg.spawn.func("/World/Unitree_A1/Robot_1", robot_a_cfg.spawn, translation=(1.5, 0.5, 0.42))
robot_a_cfg.spawn.func("/World/Unitree_A1/Robot_2", robot_a_cfg.spawn, translation=(1.5, 1.5, 0.42))
# create handles for the robots
robot_b = Articulation(robot_b_cfg.replace(prim_path="/World/Anymal_b/Robot.*"))
robot_c = Articulation(robot_c_cfg.replace(prim_path="/World/Anymal_c/Robot.*"))
robot_a = Articulation(robot_a_cfg.replace(prim_path="/World/Unitree_A1/Robot.*"))
# Create separate groups called "Origin1", "Origin2", "Origin3"
# Each group will have a mount and a robot on top of it
origins = [[0.0, 1.0, 0.0]]
# Play the simulator
sim.reset()
# Origin 1 with Cartpole
prim_utils.create_prim("/World/Origin", "Xform", translation=origins[0])
# -- Articulation
cartpole = Articulation(cfg=CARTPOLE_CFG.replace(prim_path="/World/Robot"))
# return the scene information
scene_entities = {"cartpole": cartpole}
return scene_entities, origins
# Now we are ready!
print("[INFO]: Setup complete...")
def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articulation], origins: torch.Tensor):
"""Runs the simulator by applying actions to the articulation at every time-step"""
# Define simulation stepping
sim_dt = sim.get_physics_dt()
sim_time = 0.0
count = 0
# Simulate physics
# Simulation loop
while simulation_app.is_running():
# reset
if count % 1000 == 0:
# reset counters
sim_time = 0.0
# Reset
if count % 500 == 0:
# reset counter
count = 0
# reset dof state
for robot in [robot_a, robot_b, robot_c]:
joint_pos, joint_vel = robot.data.default_joint_pos, robot.data.default_joint_vel
# reset the scene entities
for index, robot in enumerate(entities.values()):
# root state
# we offset the root state by the origin since the states are written in simulation world frame
# if this is not done, then the robots will be spawned at the (0, 0, 0) of the simulation world
root_state = robot.data.default_root_state.clone()
root_state[:, :3] += origins[index]
robot.write_root_state_to_sim(root_state)
# set joint positions with some noise
joint_pos, joint_vel = robot.data.default_joint_pos.clone(), robot.data.default_joint_vel.clone()
joint_pos += torch.rand_like(joint_pos) * 0.1
robot.write_joint_state_to_sim(joint_pos, joint_vel)
# clear internal buffers
robot.reset()
# reset command
print(">>>>>>>> Reset!")
# apply action to the robot
for robot in [robot_a, robot_b, robot_c]:
robot.set_joint_position_target(robot.data.default_joint_pos.clone())
print("[INFO]: Resetting robot state...")
# Apply random action
for robot in entities.values():
# generate random joint efforts
efforts = torch.randn_like(robot.data.joint_pos) * 5.0
# apply action to the robot
robot.set_joint_effort_target(efforts)
# write data to sim
robot.write_data_to_sim()
# perform step
# Perform step
sim.step()
# update sim-time
sim_time += sim_dt
# Increment counter
count += 1
# update buffers
for robot in [robot_a, robot_b, robot_c]:
# Update buffers
for robot in entities.values():
robot.update(sim_dt)
def main():
# Load kit helper
sim_cfg = sim_utils.SimulationCfg(device="cpu", use_gpu_pipeline=False)
sim = SimulationContext(sim_cfg)
# Set main camera
sim.set_camera_view([2.5, 1.0, 4.0], [0.0, 1.0, 2.0])
# Design scene
scene_entities, scene_origins = design_scene()
scene_origins = torch.tensor(scene_origins, device=sim.device)
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# Run the simulator
run_simulator(sim, scene_entities, scene_origins)
if __name__ == "__main__":
try:
# run the main execution
......
......@@ -5,6 +5,12 @@
"""
This script demonstrates how to import and use the YCB objects in Orbit
.. code-block:: bash
# Usage
./orbit.sh -p source/standalone/tutorials/01_assets/rigid_objects.py
"""
from __future__ import annotations
......@@ -41,25 +47,20 @@ from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR
from omni.isaac.orbit.utils.math import quat_mul, random_yaw_orientation, sample_cylinder
def main():
"""Main function."""
# Load kit helper
sim = SimulationContext(sim_utils.SimulationCfg(device="cpu", use_gpu_pipeline=False))
# Set main camera
sim.set_camera_view(eye=[1.5, 1.5, 1.5], target=[0.0, 0.0, 0.0])
# Spawn things into stage
def design_scene():
"""Designs the scene."""
# Ground-plane
cfg = sim_utils.GroundPlaneCfg()
cfg.func("/World/defaultGroundPlane", cfg)
# Lights-1
cfg = sim_utils.SphereLightCfg(intensity=600.0, color=(0.75, 0.75, 0.75), radius=2.5)
cfg.func("/World/Light/greyLight", cfg, translation=(4.5, 3.5, 10.0))
# Lights-2
cfg = sim_utils.SphereLightCfg(intensity=600.0, color=(1.0, 1.0, 1.0), radius=2.5)
cfg.func("/World/Light/whiteSphere", cfg, translation=(-4.5, 3.5, 10.0))
# Lights
cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
cfg.func("/World/Light", cfg)
# add rigid objects and return them
return add_rigid_objects()
def add_rigid_objects():
"""Adds rigid objects to the scene."""
# add YCB objects
ycb_usd_paths = {
"crackerBox": f"{ISAAC_NUCLEUS_DIR}/Props/YCB/Axis_Aligned_Physics/003_cracker_box.usd",
......@@ -77,12 +78,10 @@ def main():
# Create rigid object handler
rigid_object = RigidObject(cfg)
# Play the simulator
sim.reset()
return rigid_object
# Now we are ready!
print("[INFO]: Setup complete...")
def run_simulator(sim: sim_utils.SimulationContext, rigid_object: RigidObject):
# Define simulation stepping
sim_dt = sim.get_physics_dt()
sim_time = 0.0
......@@ -120,6 +119,22 @@ def main():
rigid_object.update(sim_dt)
def main():
"""Main function."""
# Load kit helper
sim = SimulationContext(sim_utils.SimulationCfg())
# Set main camera
sim.set_camera_view(eye=[1.5, 1.5, 1.5], target=[0.0, 0.0, 0.0])
# design scene
rigid_object = design_scene()
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# Run the simulator
run_simulator(sim, rigid_object)
if __name__ == "__main__":
try:
# run the main execution
......
# Copyright (c) 2022-2023, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This script demonstrates the FrameTransformer sensor by visualizing the frames that it creates.
.. code-block:: bash
# Usage
./orbit.sh -p source/standalone/tutorials/02_sensors/frame_transformer.py
"""
from __future__ import annotations
"""Launch Isaac Sim Simulator first."""
import argparse
from omni.isaac.orbit.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(
description="This script checks the FrameTransformer sensor by visualizing the frames that it creates."
)
AppLauncher.add_app_launcher_args(parser)
args_cli = parser.parse_args()
# launch omniverse app
app_launcher = AppLauncher(headless=args_cli.headless)
simulation_app = app_launcher.app
"""Rest everything follows."""
import math
import torch
import omni.isaac.orbit.sim as sim_utils
import omni.isaac.orbit.utils.math as math_utils
from omni.isaac.orbit.assets import Articulation
from omni.isaac.orbit.assets.config.anymal import ANYMAL_C_CFG
from omni.isaac.orbit.markers import VisualizationMarkers
from omni.isaac.orbit.markers.config import FRAME_MARKER_CFG
from omni.isaac.orbit.sensors import FrameTransformer, FrameTransformerCfg, OffsetCfg
from omni.isaac.orbit.sim import SimulationContext
def design_scene() -> tuple(FrameTransformer, Articulation):
"""Design the scene."""
# Populate scene
# -- Ground-plane
cfg = sim_utils.GroundPlaneCfg()
cfg.func("/World/defaultGroundPlane", cfg)
# -- Lights
cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
cfg.func("/World/Light", cfg)
# -- robot
robot_cfg = ANYMAL_C_CFG
robot_cfg.spawn.func("/World/Anymal_c/Robot", robot_cfg.spawn, translation=(1.5, -1.5, 0.65))
robot = Articulation(robot_cfg.replace(prim_path="/World/Anymal_c/Robot"))
# add frame transformer
frame_transformer = add_sensor()
return frame_transformer, robot
def add_sensor() -> FrameTransformer:
"""Adds FrameTransformer sensor to the scene."""
# define offset
rot_offset = math_utils.quat_from_euler_xyz(torch.zeros(1), torch.zeros(1), torch.tensor(-math.pi / 2))
pos_offset = math_utils.quat_apply(rot_offset, torch.tensor([0.08795, 0.01305, -0.33797]))
# Example using .* to get full body + LF_FOOT
frame_transformer_cfg = FrameTransformerCfg(
prim_path="/World/Anymal_c/Robot/base",
target_frames=[
FrameTransformerCfg.FrameCfg(prim_path="/World/Anymal_c/Robot/.*"),
FrameTransformerCfg.FrameCfg(
prim_path="/World/Anymal_c/Robot/LF_SHANK",
name="LF_FOOT",
offset=OffsetCfg(pos=tuple(pos_offset.tolist()), rot=tuple(rot_offset[0].tolist())),
),
],
debug_vis=False,
)
frame_transformer = FrameTransformer(frame_transformer_cfg)
return frame_transformer
def run_simulator(sim: sim_utils.SimulationContext, robot: Articulation, frame_transformer: FrameTransformer):
"""Run the simulator."""
# Define simulation stepping
sim_dt = sim.get_physics_dt()
sim_time = 0.0
count = 0
# We only want one visualization at a time. This visualizer will be used
# to step through each frame so the user can verify that the correct frame
# is being visualized as the frame names are printing to console
if not args_cli.headless:
cfg = FRAME_MARKER_CFG.replace(prim_path="/Visuals/FrameVisualizerFromScript")
cfg.markers["frame"].scale = (0.05, 0.05, 0.05)
transform_visualizer = VisualizationMarkers(cfg)
frame_index = 0
# Simulate physics
while simulation_app.is_running():
# perform this loop at policy control freq (50 Hz)
robot.set_joint_position_target(robot.data.default_joint_pos.clone())
robot.write_data_to_sim()
# perform step
sim.step()
# update sim-time
sim_time += sim_dt
count += 1
# read data from sim
robot.update(sim_dt)
frame_transformer.update(dt=sim_dt)
# Change the frame that we are visualizing to ensure that frame names
# are correctly associated with the frames
if not args_cli.headless:
if count % 50 == 0:
frame_names = frame_transformer.data.target_frame_names
frame_name = frame_names[frame_index]
print(f"Displaying {frame_index}: {frame_name}")
frame_index += 1
frame_index = frame_index % len(frame_names)
# visualize frame
pos = frame_transformer.data.target_pos_w[:, frame_index]
rot = frame_transformer.data.target_rot_w[:, frame_index]
transform_visualizer.visualize(pos, rot)
def main():
"""Main function."""
# Load kit helper
sim = SimulationContext(sim_utils.SimulationCfg(dt=0.005))
# Set main camera
sim.set_camera_view(eye=[2.5, 2.5, 2.5], target=[0.0, 0.0, 0.0])
# Design the scene
frame_transformer, robot = design_scene()
# Play the simulator
sim.reset()
# Now we are ready!
print("[INFO]: Setup complete...")
# Run the simulator
run_simulator(sim, robot, frame_transformer)
if __name__ == "__main__":
# Run the main function
main()
# Close the simulator
simulation_app.close()
# Copyright (c) 2022-2023, The ORBIT Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
This script demonstartes how to use the ray-caster sensor.
.. code-block:: bash
# Usage
./orbit.sh -p source/standalone/how_to_guides/02_sensors/ray_caster.py
"""
from __future__ import annotations
"""Launch Isaac Sim Simulator first."""
import argparse
from omni.isaac.kit import SimulationApp
# add argparse arguments
parser = argparse.ArgumentParser(description="Ray Caster Test Script")
parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.")
args_cli = parser.parse_args()
# launch omniverse app
config = {"headless": args_cli.headless}
simulation_app = SimulationApp(config)
"""Rest everything follows."""
import torch
import traceback
import carb
from omni.isaac.core.utils.viewports import set_camera_view
import omni.isaac.orbit.sim as sim_utils
from omni.isaac.orbit.assets import RigidObject, RigidObjectCfg
from omni.isaac.orbit.sensors.ray_caster import RayCaster, RayCasterCfg, patterns
from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR
from omni.isaac.orbit.utils.timer import Timer
def design_scene():
"""Design the scene."""
# Populate scene
# -- Rough terrain
cfg = sim_utils.UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Environments/Terrains/rough_plane.usd")
cfg.func("/World/ground", cfg)
# -- Light
cfg = sim_utils.DistantLightCfg(intensity=2000)
cfg.func("/World/light", cfg)
# -- Balls
cfg = sim_utils.SphereCfg(
radius=0.25,
rigid_props=sim_utils.RigidBodyPropertiesCfg(),
mass_props=sim_utils.MassPropertiesCfg(mass=0.5),
collision_props=sim_utils.CollisionPropertiesCfg(),
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0)),
)
cfg.func("/World/envs/env_0/ball", cfg)
cfg.func("/World/envs/env_1/ball", cfg)
cfg.func("/World/envs/env_2/ball", cfg)
cfg.func("/World/envs/env_3/ball", cfg)
# Setup rigid object
cfg = RigidObjectCfg(prim_path="/World/envs/env_.*/ball")
# Create rigid object handler
balls = RigidObject(cfg)
# Create a ray-caster sensor
ray_caster = add_sensor()
return ray_caster, balls
def add_sensor():
# Create a ray-caster sensor
ray_caster_cfg = RayCasterCfg(
prim_path="/World/envs/env_.*/ball",
mesh_prim_paths=["/World/ground"],
pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=(1.6, 1.0)),
attach_yaw_only=True,
debug_vis=not args_cli.headless,
)
ray_caster = RayCaster(cfg=ray_caster_cfg)
return ray_caster
def run_simulator(sim: sim_utils.SimulationContext, ray_caster: RayCaster, balls: RigidObject):
"""Run the simulator."""
# define an initial position of the sensor
ball_default_state = balls.data.default_root_state.clone()
ball_default_state[:, :3] = torch.rand_like(ball_default_state[:, :3]) * 10
# Create a counter for resetting the scene
step_count = 0
# Simulate physics
while simulation_app.is_running():
# Reset the scene
if step_count % 250 == 0:
# reset the balls
balls.write_root_state_to_sim(ball_default_state)
# reset the sensor
ray_caster.reset()
# reset the counter
step_count = 0
# Step simulation
sim.step()
# Update the ray-caster
with Timer(
f"Ray-caster update with {4} x {ray_caster.num_rays} rays with max height of"
f" {torch.max(ray_caster.data.pos_w).item():.2f}"
):
ray_caster.update(dt=sim.get_physics_dt(), force_recompute=True)
# Update counter
step_count += 1
def main():
"""Main function."""
# Load simulation context
sim_cfg = sim_utils.SimulationCfg()
sim = sim_utils.SimulationContext(sim_cfg)
# Set main camera
set_camera_view([0.0, 15.0, 15.0], [0.0, 0.0, -2.5])
# Design the scene
ray_caster, balls = design_scene()
# Play simulator
sim.reset()
# Print the sensor information
print(ray_caster)
# Run simulator
run_simulator(sim=sim, ray_caster=ray_caster, balls=balls)
if __name__ == "__main__":
try:
# Run the main function
main()
except Exception as err:
carb.log_error(err)
carb.log_error(traceback.format_exc())
raise
finally:
# close sim app
simulation_app.close()
......@@ -7,6 +7,12 @@
This script shows how to use the ray-cast camera sensor from the Orbit framework.
The camera sensor is based on using Warp kernels which do ray-casting against static meshes.
.. code-block:: bash
# Usage
./orbit.sh -p source/standalone/how_to_guides/02_sensors/ray_caster_camera.py
"""
"""Launch Isaac Sim Simulator first."""
......@@ -39,46 +45,33 @@ import omni.isaac.core.utils.prims as prim_utils
import omni.replicator.core as rep
import omni.isaac.orbit.sim as sim_utils
import omni.isaac.orbit.terrains as terrain_gen
from omni.isaac.orbit.sensors.ray_caster import RayCasterCamera, RayCasterCameraCfg, patterns
from omni.isaac.orbit.terrains.config.rough import ROUGH_TERRAINS_CFG
from omni.isaac.orbit.terrains.terrain_importer import TerrainImporter
from omni.isaac.orbit.utils import convert_dict_to_backend
from omni.isaac.orbit.utils.assets import ISAAC_NUCLEUS_DIR
from omni.isaac.orbit.utils.math import project_points, unproject_depth
def main():
"""Main function."""
# Load kit helper
sim = sim_utils.SimulationContext()
# Set main camera
sim.set_camera_view([2.5, 2.5, 3.5], [0.0, 0.0, 0.0])
def design_scene():
# Populate scene
# Handler for terrains importing
terrain_importer_cfg = terrain_gen.TerrainImporterCfg(
num_envs=args_cli.num_envs,
env_spacing=3.0,
prim_path="/World/ground",
max_init_terrain_level=None,
terrain_type="generator",
terrain_generator=ROUGH_TERRAINS_CFG.replace(curriculum=True, num_rows=4, num_cols=4),
debug_vis=True,
)
terrain = TerrainImporter(terrain_importer_cfg)
# -- Rough terrain
cfg = sim_utils.UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Environments/Terrains/rough_plane.usd")
cfg.func("/World/ground", cfg)
# Lights
cfg = sim_utils.DistantLightCfg(intensity=600.0, color=(0.75, 0.75, 0.75))
cfg.func("/World/Light", cfg)
# add and return sensor and terrain
return add_sensor()
def add_sensor():
# Camera base frames
prim_utils.create_prim("/World/envs", "Scope")
for index in range(args_cli.num_envs):
prim_utils.create_prim(f"/World/envs/env_{index}", "Xform", translation=terrain.env_origins[index])
prim_utils.create_prim(f"/World/envs/env_{index}/Camera", "Xform")
prim_utils.create_prim("/World/CameraSensor_00", "Xform")
prim_utils.create_prim("/World/CameraSensor_01", "Xform")
# Setup camera sensor
camera_cfg = RayCasterCameraCfg(
prim_path="/World/envs/env_.*/Camera",
prim_path="/World/CameraSensor_*",
mesh_prim_paths=["/World/ground"],
update_period=0,
offset=RayCasterCameraCfg.OffsetCfg(pos=(0.0, 0.0, 0.0), rot=(1.0, 0.0, 0.0, 0.0)),
......@@ -91,11 +84,13 @@ def main():
width=640,
),
)
# create xform because placement of camera directly under world is not supported
prim_utils.create_prim("/World/Camera", "Xform")
# Create camera
camera = RayCasterCamera(cfg=camera_cfg)
return camera
def run_simulator(sim: sim_utils.SimulationContext, camera: RayCasterCamera):
# Create replicator writer
output_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "ray_caster_camera")
rep_writer = rep.BasicWriter(output_dir=output_dir, frame_padding=3)
......@@ -105,9 +100,9 @@ def main():
# Set pose: There are two ways to set the pose of the camera.
# -- Option-1: Set pose using view
eyes = torch.tensor([[-1.0, 0, 2] * args_cli.num_envs], device=sim.device).reshape(-1, 3)
targets = torch.tensor([[0.0, 0.0, 0.0] * args_cli.num_envs], device=sim.device).reshape(-1, 3)
camera.set_world_poses_from_view(eyes + terrain.env_origins, targets + terrain.env_origins)
eyes = torch.tensor([[2.5, 2.5, 2.5], [-2.5, -2.5, 2.5]], device=sim.device)
targets = torch.tensor([[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], device=sim.device)
camera.set_world_poses_from_view(eyes, targets)
# -- Option-2: Set pose using ROS
# position = torch.tensor([[2.5, 2.5, 2.5]], device=sim.device)
# orientation = torch.tensor([[-0.17591989, 0.33985114, 0.82047325, -0.42470819]], device=sim.device)
......@@ -163,7 +158,23 @@ def main():
reproj_points = project_points(points_3d_cam, camera.data.intrinsic_matrices)
reproj_depths = reproj_points[..., -1].view(-1, im_width, im_height).transpose_(1, 2)
sim_depths = camera.data.output["distance_to_image_plane"].squeeze(-1)
torch.testing.assert_allclose(reproj_depths, sim_depths)
torch.testing.assert_close(reproj_depths, sim_depths)
def main():
"""Main function."""
# Load kit helper
sim = sim_utils.SimulationContext()
# Set main camera
sim.set_camera_view([2.5, 2.5, 3.5], [0.0, 0.0, 0.0])
# design the scene
camera = design_scene()
# Play simulator
sim.play()
# Print the sensor information
print(camera)
# Run simulator
run_simulator(sim=sim, camera=camera)
if __name__ == "__main__":
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -8,6 +8,12 @@ This script demonstrates how to use the inverse kinematics controller with the s
The differential IK controller can be configured in different modes. It uses the Jacobians computed by
PhysX. This helps perform parallelized computation of the inverse kinematics.
.. code-block:: bash
# Usage
./orbit.sh -p source/standalone/tutorials/05_controllers/ik_control.py
"""
from __future__ import annotations
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment