From 39f95498d4389e531e5cc8b7c1b772d193c2a08f Mon Sep 17 00:00:00 2001 From: AutonomousHansen <50837800+AutonomousHansen@users.noreply.github.com> Date: Tue, 17 Oct 2023 15:13:46 -0400 Subject: [PATCH] Adds method for simulation CLI arguments to the `AppLauncher` class (#160) # Description Adds `add_app_launcher_args` function to the `AppLauncher` class, which can be used to add AppLauncher-specific arguments to an existing ArgumentParser instance. Because everything we pass is then passed to SimulationApp as a config, the MR also adds the `check_config()` function. This compares the key values of a dict and raises a ValueError in the following cases: * if they have a key reserved for a SimulationApp config parameter but incorrect value types * if the value type is correct, it informs the user if a dict item they passed will be interpreted as a SimulationApp config parameter. Non-SimulationApp relevant keys are cleaned from the dict before it is passed to SimulationApp. Also separated out the giant `AppLauncher.__init__` function into a few sub-functions to make things more readable. ## Type of change - New feature (non-breaking change which adds functionality) ## Checklist - [x] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./orbit.sh --format` - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] I have updated the changelog and the corresponding version in the extension's `config/extension.toml` file --------- Signed-off-by: AutonomousHansen <50837800+AutonomousHansen@users.noreply.github.com> Co-authored-by: Mayank Mittal --- docs/source/refs/license.rst | 4 +- .../omni.isaac.orbit/config/extension.toml | 2 +- .../omni.isaac.orbit/docs/CHANGELOG.rst | 20 +- .../omni.isaac.orbit/omni/isaac/orbit/app.py | 521 +++++++++++++++--- .../test/app/test_argparser_launch.py | 49 ++ .../test/app/test_env_var_launch.py | 40 ++ .../test/app/test_kwarg_launch.py | 37 ++ source/standalone/demo/play_arms.py | 9 +- source/standalone/demo/play_camera.py | 6 +- source/standalone/demo/play_cloner.py | 6 +- source/standalone/demo/play_empty.py | 6 +- source/standalone/demo/play_ik_control.py | 6 +- source/standalone/demo/play_markers.py | 4 +- source/standalone/demo/play_quadrupeds.py | 6 +- .../standalone/demo/play_ridgeback_franka.py | 6 +- source/standalone/demo/play_rmpflow.py | 6 +- source/standalone/demo/play_scene.py | 6 +- source/standalone/demo/play_ycb_objects.py | 6 +- .../standalone/environments/random_agent.py | 6 +- .../environments/state_machine/play_lift.py | 4 +- .../teleoperation/teleop_se3_agent.py | 4 +- source/standalone/environments/zero_agent.py | 6 +- source/standalone/workflows/rl_games/play.py | 6 +- source/standalone/workflows/rl_games/train.py | 7 +- .../robomimic/collect_demonstrations.py | 8 +- source/standalone/workflows/robomimic/play.py | 6 +- source/standalone/workflows/rsl_rl/play.py | 6 +- source/standalone/workflows/rsl_rl/train.py | 7 +- source/standalone/workflows/sb3/play.py | 6 +- source/standalone/workflows/sb3/train.py | 6 +- source/standalone/workflows/skrl/play.py | 6 +- source/standalone/workflows/skrl/train.py | 7 +- 32 files changed, 700 insertions(+), 125 deletions(-) create mode 100644 source/extensions/omni.isaac.orbit/test/app/test_argparser_launch.py create mode 100644 source/extensions/omni.isaac.orbit/test/app/test_env_var_launch.py create mode 100644 source/extensions/omni.isaac.orbit/test/app/test_kwarg_launch.py diff --git a/docs/source/refs/license.rst b/docs/source/refs/license.rst index d86278862a..5e062627aa 100644 --- a/docs/source/refs/license.rst +++ b/docs/source/refs/license.rst @@ -1,7 +1,7 @@ License -======= +======== -NVIDIA Isaac Sim is available freely under `indivudal license +NVIDIA Isaac Sim is available freely under `individual license `_. For more information about its license terms, please check `here `_. The license files for all its dependencies and included assets are available in its diff --git a/source/extensions/omni.isaac.orbit/config/extension.toml b/source/extensions/omni.isaac.orbit/config/extension.toml index 7af08894bc..7ae012c7d4 100644 --- a/source/extensions/omni.isaac.orbit/config/extension.toml +++ b/source/extensions/omni.isaac.orbit/config/extension.toml @@ -1,7 +1,7 @@ [package] # Note: Semantic Versioning is used: https://semver.org/ -version = "0.9.9" +version = "0.9.10" # Description title = "ORBIT framework for Robot Learning" diff --git a/source/extensions/omni.isaac.orbit/docs/CHANGELOG.rst b/source/extensions/omni.isaac.orbit/docs/CHANGELOG.rst index 25d669ca49..6c353304f8 100644 --- a/source/extensions/omni.isaac.orbit/docs/CHANGELOG.rst +++ b/source/extensions/omni.isaac.orbit/docs/CHANGELOG.rst @@ -1,6 +1,24 @@ Changelog --------- +0.9.10 (2023-10-16) +~~~~~~~~~~~~~~~~~~~ + +Added +^^^^^ + +* Added `livestream` and `ros` CLI args to :class:`omni.isaac.orbit.app.AppLauncher` class. +* Added a static function :meth:`omni.isaac.orbit.app.AppLauncher.add_app_launcher_args`, which + appends the arguments needed for :class:`omni.isaac.orbit.app.AppLauncher` to the argument parser. + +Changed +^^^^^^^ + +* Within :class:`omni.isaac.orbit.app.AppLauncher`, removed `REMOTE_DEPLOYMENT` env-var processing + in the favor of ``HEADLESS`` and ``LIVESTREAM`` env-vars. These have clearer uses and better parity + with the CLI args. + + 0.9.9 (2023-10-12) ~~~~~~~~~~~~~~~~~~ @@ -28,8 +46,6 @@ Fixed * Fixed the boundedness of class objects that register callbacks into the simulator. These include devices, :class:`AssetBase`, :class:`SensorBase` and :class:`CommandGenerator`. The fix ensures that object gets deleted when the user deletes the object. - - 0.9.7 (2023-09-26) ~~~~~~~~~~~~~~~~~~ diff --git a/source/extensions/omni.isaac.orbit/omni/isaac/orbit/app.py b/source/extensions/omni.isaac.orbit/omni/isaac/orbit/app.py index e2a2371e63..6908ff6c7f 100644 --- a/source/extensions/omni.isaac.orbit/omni/isaac/orbit/app.py +++ b/source/extensions/omni.isaac.orbit/omni/isaac/orbit/app.py @@ -5,7 +5,7 @@ """Utility to configure the ``omni.isaac.kit.SimulationApp`` based on environment variables. -Based on the desired functionality, this class parses environment variables and input keyword arguments +Based on the desired functionality, this class parses environment variables and input CLI arguments to launch the simulator in various different modes. This includes with or without GUI, switching between different Omniverse remote clients, and enabling particular ROS bridges. Some of these require the extensions to be loaded in a specific order, otherwise a segmentation fault occurs. @@ -16,19 +16,23 @@ The following details the behavior of the class based on the environment variables: -* **Headless mode**: If the environment variable ``REMOTE_DEPLOYMENT>0``, then SimulationApp will be started in headless mode. +* **Headless mode**: If the environment variable ``HEADLESS=1``, then SimulationApp will be started in headless mode. + If ``LIVESTREAM={1,2,3}``, then it will supersede the ``HEADLESS`` envvar and force headlessness. -* **Livestreaming**: If the environment variable ``REMOTE_DEPLOYMENT={2,3,4}`` , then `livestream`_ is enabled. + * ``HEADLESS=1`` causes the app to run in headless mode. - * ``REMOTE_DEPLOYMENT=1`` does not enable livestreaming, though it causes the app to run in headless mode. - * ``REMOTE_DEPLOYMENT=2`` enables streaming via the Isaac `Native Livestream`_ extension. This allows users to +* **Livestreaming**: If the environment variable ``LIVESTREAM={1,2,3}`` , then `livestream`_ is enabled. Any + of the livestream modes being true forces the app to run in headless mode. + + * ``LIVESTREAM=1`` enables streaming via the Isaac `Native Livestream`_ extension. This allows users to connect through the Omniverse Streaming Client. - * ``REMOTE_DEPLOYMENT=3`` enables streaming via the `Websocket Livestream` extension. This allows users to + * ``LIVESTREAM=2`` enables streaming via the `Websocket Livestream`_ extension. This allows users to connect in a browser using the WebSocket protocol. - * ``REMOTE_DEPLOYMENT=4`` enables streaming via the `WebRTC Livestream` extension. This allows users to + * ``LIVESTREAM=3`` enables streaming via the `WebRTC Livestream`_ extension. This allows users to connect in a browser using the WebRTC protocol. -* **Viewport**: If the environment variable ``VIEWPORT_ENABLED`` is set to non-zero, then the following behavior happens: +* **Viewport**: If the environment variable ``VIEWPORT_ENABLED`` is set to non-zero, then the following + behavior happens: * ``VIEWPORT_ENABLED=1``: Ensures that the VIEWPORT member is set to true, to enable lightweight streaming when the full GUI is not needed (i.e. headless mode). @@ -52,7 +56,7 @@ .. code:: bash - export REMOTE_DEPLOYMENT=3 + export LIVESTREAM=3 export VIEWPORT_ENABLED=1 # run the python script ./orbit.sh -p source/standalone/demo/play_quadrupeds.py @@ -61,7 +65,7 @@ .. code:: bash - REMOTE_DEPLOYMENT=3 VIEWPORT_ENABLED=1 ./orbit.sh -p source/standalone/demo/play_quadrupeds.py + LIVESTREAM=3 VIEWPORT_ENABLED=1 ./orbit.sh -p source/standalone/demo/play_quadrupeds.py .. _SimulationApp: https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.kit/docs/index.html @@ -69,25 +73,69 @@ .. _`Native Livestream`: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/manual_livestream_clients.html#isaac-sim-setup-kit-remote .. _`Websocket Livestream`: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/manual_livestream_clients.html#isaac-sim-setup-livestream-webrtc .. _`WebRTC Livestream`: https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/manual_livestream_clients.html#isaac-sim-setup-livestream-websocket - """ from __future__ import annotations +import argparse import faulthandler import os import re import sys from typing import ClassVar +from typing_extensions import Literal -import carb from omni.isaac.kit import SimulationApp -__all__ = ["AppLauncher"] - class AppLauncher: - """A class to create the simulation application based on keyword arguments and environment variables.""" + """A utility class to launch Isaac Sim application based on command-line arguments and environment variables. + + The class resolves the simulation app settings that appear through environments variables, + command-line arguments (CLI) or as input keyword arguments. Based on these settings, it launches the + simulation app and configures the extensions to load (as a part of post-launch setup). + + The input arguments provided to the class are given higher priority than the values set + from the corresponding environment variables. This provides flexibility to deal with different + users' preferences. + + .. note:: + Explicitly defined arguments are only given priority when their value is set to something outside + their default configuration. For example, the ``livestream`` argument is 0 by default. It only + overrides the ``LIVESTREAM`` environment variable when ``livestream`` argument is set to a non-zero + value. In other words, if ``livestream=0``, then the value from the environment variable + ``LIVESTREAM`` is used. + + Usage: + + .. code:: python + + import argparser + + from omni.isaac.orbit.app import AppLauncher + + # add argparse arguments + parser = argparse.ArgumentParser() + # add your own arguments + # .... + # add app launcher arguments for cli + AppLauncher.add_app_launcher_args(parser) + # parse arguments + args = parser.parse_args() + + # launch omniverse isaac-sim app + # -- Option 1: Pass the settings as a Namespace object + app_launcher = AppLauncher(args).app + # -- Option 2: Pass the settings as keywords arguments + app_launcher = AppLauncher(headless=args.headless, livestream=args.livestream) + # -- Option 3: Pass the settings as a dictionary + app_launcher = AppLauncher(vars(args)) + # -- Option 4: Pass no settings + app_launcher = AppLauncher() + + # obtain the launched app + simulation_app = app_launcher.app + """ RENDER: ClassVar[bool] """ @@ -121,31 +169,388 @@ class AppLauncher: gym.make(render=app_launcher.RENDER, viewport=app_launcher.VIEWPORT) """ - def __init__(self, **kwargs): - """Parses environments variables and keyword arguments to create a `SimulationApp`_ instance. - - If the keyword argument ``headless`` is set to True, then the SimulationApp will be started in headless mode. - It will be given priority over the environment variable setting ``REMOTE_DEPLOYMENT=0``. + def __init__(self, launcher_args: argparse.Namespace | dict = None, **kwargs): + """Create a `SimulationApp`_ instance based on the input settings. Args: - **kwargs: Keyword arguments passed to the :class:`SimulationApp` from Isaac Sim. - A detailed description of the possible arguments is available in its `documentation`_. + launcher_args: Input arguments to parse using the AppLauncher and set into the SimulationApp. + Defaults to None, which is equivalent to passing an empty dictionary. A detailed description of + the possible arguments is available in the `SimulationApp`_ documentation. + **kwargs : Additional keyword arguments that will be merged into :attr:`launcher_args`. + They serve as a convenience for those who want to pass some arguments using the argparse + interface and others directly into the AppLauncher. Duplicated arguments with + the :attr:`launcher_args` will raise a ValueError. Raises: + ValueError: If there are common/duplicated arguments between ``launcher_args`` and ``kwargs``. + ValueError: If combination of ``launcher_args`` and ``kwargs`` are missing the necessary arguments + that are needed by the AppLauncher to resolve the desired app configuration. ValueError: If incompatible or undefined values are assigned to relevant environment values, - such as ``REMOTE_DEPLOYMENT`` and ``ROS_ENABLED`` + such as ``LIVESTREAM`` and ``ROS_ENABLED``. + .. _argparse.Namespace: https://docs.python.org/3/library/argparse.html?highlight=namespace#argparse.Namespace .. _SimulationApp: https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.kit/docs/index.html - .. _documentation: https://docs.omniverse.nvidia.com/py/isaacsim/source/extensions/omni.isaac.kit/docs/index.html """ # Enable call-stack on crash faulthandler.enable() - # Headless is always true for remote deployment - remote_deployment = int(os.environ.get("REMOTE_DEPLOYMENT", 0)) - # resolve headless execution of simulation app - headless = kwargs.get("headless", False) or remote_deployment - kwargs.update({"headless": headless}) + # We allow users to pass either a dict or an argparse.Namespace into + # __init__, anticipating that these will be all of the argparse arguments + # used by the calling script. Those which we appended via add_app_launcher_args + # will be used to control extension loading logic. Additional arguments are allowed, + # and will be passed directly to the SimulationApp initialization. + # + # We could potentially require users to enter each argument they want passed here + # as a kwarg, but this would require them to pass livestream, headless, ros, and + # any other options we choose to add here explicitly, and with the correct keywords. + # + # @hunter: I feel that this is cumbersone and could introduce error, and would prefer to do + # some sanity checking in the add_app_launcher_args function + if launcher_args is None: + launcher_args = {} + elif isinstance(launcher_args, argparse.Namespace): + launcher_args = launcher_args.__dict__ + + # Check that arguments are unique + if len(kwargs) > 0: + if not set(kwargs.keys()).isdisjoint(launcher_args.keys()): + overlapping_args = set(kwargs.keys()).intersection(launcher_args.keys()) + raise ValueError( + f"Input `launcher_args` and `kwargs` both provided common attributes: {overlapping_args}. " + "Please ensure that each argument is supplied to only one of them, as the AppLauncher cannot " + "discern priority between them." + ) + launcher_args.update(kwargs) + + # Define config members that are read from env-vars or keyword args + self._headless: bool # 0: GUI, 1: Headless + self._livestream: Literal[0, 1, 2, 3] # 0: Disabled, 1: Native, 2: Websocket, 3: WebRTC + self._ros: Literal[0, 1, 2] # 0: Disabled, 1: ROS1, 2: ROS2 + self._viewport: bool # 0: Disabled, 1: Enabled + + # Integrate env-vars and input keyword args into simulation app config + self._config_resolution(launcher_args) + # Create SimulationApp, passing the resolved self._config to it for initialization + self._create_app() + # Load IsaacSim extensions + self._load_extensions() + # Update global variables + self._update_globals() + + """ + Properties. + """ + + @property + def app(self) -> SimulationApp: + """The launched SimulationApp.""" + if self._app is not None: + return self._app + else: + raise RuntimeError("The `AppLauncher.app` member cannot be retrieved until the class is initialized.") + + """ + Operations. + """ + + @staticmethod + def add_app_launcher_args(parser: argparse.ArgumentParser) -> None: + """Utility function to configure AppLauncher arguments with an existing argument parser object. + + This function takes an ``argparse.ArgumentParser`` object and does some sanity checking on the existing + arguments for ingestion by the SimulationApp. It then appends custom command-line arguments relevant + to the SimulationApp to the input :class:`argparse.ArgumentParser` instance. This allows overriding the + environment variables using command-line arguments. + + Currently, it adds the following parameters to the argparser object: + + * ``headless`` (bool): If True, the app will be launched in headless (no-gui) mode. The values map the same + as that for the ``HEADLESS`` environment variable. If False, then headless mode is determined by the + ``HEADLESS`` environment variable. + * ``livestream`` (int): If one of {0, 1, 2, 3}, then livestreaming and headless mode is enabled. The values + map the same as that for the ``LIVESTREAM`` environment variable. If :obj:`-1`, then livestreaming is + determined by the ``LIVESTREAM`` environment variable. + * ``ros`` (int): If one of {0, 1, 2}, then the corresponding ROS bridge is enabled. The values + map the same as that for the ``ROS_ENABLED`` environment variable. If :obj:`-1`, then ROS bridge is + determined by the ``ROS_ENABLED`` environment variable. + + Args: + parser: An argument parser instance to be extended with the AppLauncher specific options. + """ + # If the passed parser has an existing _HelpAction when passed, + # we here remove the options which would invoke it, + # to be added back after the additional AppLauncher args + # have been added. This is equivalent to + # initially constructing the ArgParser with add_help=False, + # but this means we don't have to require that behavior + # in users and can handle it on our end. + # We do this because calling parse_known_args() will handle + # any -h/--help options being passed and then exit immediately, + # before the additional arguments can be added to the help readout. + parser_help = None + if len(parser._actions) > 0 and isinstance(parser._actions[0], argparse._HelpAction): # type: ignore + parser_help = parser._actions[0] + parser._option_string_actions.pop("-h") + parser._option_string_actions.pop("--help") + + # Parse known args for potential name collisions/type mismatches + # between the config fields SimulationApp expects and the ArgParse + # arguments that the user passed. + known, _ = parser.parse_known_args() + config = vars(known) + if len(config) == 0: + print( + "[Warn][AppLauncher]: There are no arguments attached to the ArgumentParser object. " + "If you have your own arguments, please load your own arguments before calling the " + "`AppLauncher.add_app_launcher_args` method. This allows the method to check the validity " + "of the arguments and perform checks for argument names." + ) + else: + AppLauncher._check_argparser_config_params(config) + + # Add custom arguments to the parser + arg_group = parser.add_argument_group("app_launcher arguments") + arg_group.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") + arg_group.add_argument( + "--livestream", + type=int, + default=-1, + choices={-1, 0, 1, 2, 3}, + help="Force enable livestreaming. Mapping corresponds to that for the `LIVESTREAM` environment variable.", + ) + arg_group.add_argument( + "--ros", + type=int, + default=-1, + choices={-1, 0, 1, 2}, + help="Enable ROS middleware. Mapping corresponds to that for the `ROS_ENABLED` environment variable", + ) + + # Corresponding to the beginning of the function, + # if we have removed -h/--help handling, we add it back. + if parser_help is not None: + parser._option_string_actions["-h"] = parser_help + parser._option_string_actions["--help"] = parser_help + + """ + Internal functions. + """ + + _APPLAUNCHER_CONFIG_TYPES: dict[str, list[type]] = { + "headless": [bool], + "livestream": [int], + "ros": [int], + } + """A dictionary of arguments added manually by the :meth:`AppLauncher.add_app_launcher_args` method. + + These are required to be passed to AppLauncher at initialization. They have corresponding environment + variables as detailed in the documentation. + """ + + # TODO: Find some internally managed NVIDIA list of these types. + # SimulationApp.DEFAULT_LAUNCHER_CONFIG almost works, except that + # it is ambiguous where the default types are None + _SIMULATIONAPP_CONFIG_TYPES: dict[str, list[type]] = { + "headless": [bool], + "active_gpu": [int, type(None)], + "physics_gpu": [int], + "multi_gpu": [bool], + "sync_loads": [bool], + "width": [int], + "height": [int], + "window_width": [int], + "window_height": [int], + "display_options": [int], + "subdiv_refinement_level": [int], + "renderer": [str], + "anti_aliasing": [int], + "samples_per_pixel_per_frame": [int], + "denoiser": [bool], + "max_bounces": [int], + "max_specular_transmission_bounces": [int], + "max_volume_bounces": [int], + "open_usd": [str, type(None)], + "livesync_usd": [str, type(None)], + "fast_shutdown": [bool], + "experience": [str], + } + """A dictionary containing the type of arguments passed to SimulationApp. + + This is used to check against name collisions for arguments passed to the :class:`AppLauncher` class + as well as for type checking. It corresponds closely to the :attr:`SimulationApp.DEFAULT_LAUNCHER_CONFIG`, + but specifically denotes where :obj:`None` types are allowed. + """ + + @staticmethod + def _check_argparser_config_params(config: dict) -> None: + """Checks that input argparser object has parameters with valid settings with no name conflicts. + + First, we inspect the dictionary to ensure that the passed ArgParser object is not attempting to add arguments + which should be assigned by calling :meth:`AppLauncher.add_app_launcher_args`. + + Then, we check that if the key corresponds to a config setting expected by SimulationApp, then the type of + that key's value corresponds to the type expected by the SimulationApp. If it passes the check, the function + prints out that the setting with be passed to the SimulationApp. Otherwise, we raise a ValueError exception. + + Args: + config: A configuration parameters which will be passed to the SimulationApp constructor. + + Raises: + ValueError: If a key is an already existing field in the configuration parameters but + should be added by calling the :meth:`AppLauncher.add_app_launcher_args. + ValueError: If keys corresponding to those used to initialize SimulationApp + (as found in :attr:`_SIMULATIONAPP_CONFIG_TYPES`) are of the wrong value type. + """ + # check that no config key conflicts with AppLauncher config names + applauncher_keys = set(AppLauncher._APPLAUNCHER_CONFIG_TYPES.keys()) + for key, value in config.items(): + if key in applauncher_keys: + raise ValueError( + f"The passed ArgParser object already has the field '{key}'. This field will be added by " + "AppLauncher.add_app_launcher_args(), and should not be added directly. Please remove the " + "argument or rename it to a non-conflicting name." + ) + # check that type of the passed keys are valid + simulationapp_keys = set(AppLauncher._SIMULATIONAPP_CONFIG_TYPES.keys()) + for key, value in config.items(): + if key in simulationapp_keys: + given_type = type(value) + expected_types = AppLauncher._SIMULATIONAPP_CONFIG_TYPES[key] + if type(value) not in set(expected_types): + raise ValueError( + f"Invalid value type for the argument '{key}': {given_type}. Expected one of {expected_types}, " + f"if intended to be ingested by the SimulationApp object. Please change the type if this " + "intended for the SimulationApp or change the name of the argument to avoid name conflicts." + ) + # Print out values which will be used + print(f"[INFO][AppLauncher]: The argument '{key}' will be used to configure the SimulationApp.") + + def _config_resolution(self, launcher_args: dict): + """Resolve the input arguments and environment variables. + + Args: + launcher_args: A dictionary of all input arguments passed to the class object. + """ + # Handle all control logic resolution + + # --LIVESTREAM logic-- + # + livestream_env = int(os.environ.get("LIVESTREAM", 0)) + livestream_arg = launcher_args.pop("livestream", -1) + livestream_valid_vals = {0, 1, 2, 3} + # Value checking on LIVESTREAM + if livestream_env not in livestream_valid_vals: + raise ValueError( + f"Invalid value for environment variable `LIVESTREAM`: {livestream_env} . " + f"Expected: {livestream_valid_vals}." + ) + # We allow livestream kwarg to supersede LIVESTREAM envvar + if livestream_arg >= 0: + if livestream_arg in livestream_valid_vals: + self._livestream = livestream_arg + # print info that we overrode the env-var + print( + f"[INFO][AppLauncher]: Input keyword argument `livestream={livestream_arg}` has overridden " + f"the environment variable `LIVESTREAM={livestream_env}`." + ) + else: + raise ValueError( + f"Invalid value for input keyword argument `livestream`: {livestream_arg} . " + f"Expected: {livestream_valid_vals}." + ) + else: + self._livestream = livestream_env + + # --HEADLESS logic-- + # + # Resolve headless execution of simulation app + # HEADLESS is initially passed as an int instead of + # the bool of headless_arg to avoid messy string processing, + headless_env = int(os.environ.get("HEADLESS", 0)) + headless_arg = launcher_args.pop("headless", False) + headless_valid_vals = {0, 1} + # Value checking on HEADLESS + if headless_env not in headless_valid_vals: + raise ValueError( + f"Invalid value for environment variable `HEADLESS`: {headless_env} . " + f"Expected: {headless_valid_vals}." + ) + # We allow headless kwarg to supersede HEADLESS envvar if headless_arg does not have the default value + # Note: Headless is always true when livestreaming + if headless_arg is True: + self._headless = headless_arg + elif self._livestream in {1, 2, 3}: + # we are always headless on the host machine + self._headless = True + # inform who has toggled the headless flag + if self._livestream == livestream_arg: + print( + f"[INFO][AppLauncher]: Input keyword argument `livestream={self._livestream}` has implicitly " + f"overridden the environment variable `HEADLESS={headless_env}` to True." + ) + elif self._livestream == livestream_env: + print( + f"[INFO][AppLauncher]: Environment variable `LIVESTREAM={self._livestream}` has implicitly " + f"overridden the environment variable `HEADLESS={headless_env}` to True." + ) + else: + # Headless needs to be a bool to be ingested by SimulationApp + self._headless = bool(headless_env) + # Headless needs to be passed to the SimulationApp so we keep it here + launcher_args["headless"] = self._headless + + # --ROS logic-- + # + ros_env = int(os.environ.get("ROS_ENABLED", 0)) + ros_arg = int(launcher_args.pop("ros", -1)) + ros_valid_vals = {0, 1, 2} + # Value checking on LIVESTREAM + if ros_env not in ros_valid_vals: + raise ValueError( + f"Invalid value for environment variable `ROS_ENABLED`: {ros_env} . Expected: {ros_valid_vals}." + ) + # We allow livestream kwarg to supersede LIVESTREAM envvar + if ros_arg >= 0: + if ros_arg in ros_valid_vals: + self._ros = ros_arg + # print info that we overrode the env-var + print( + f"[INFO][AppLauncher]: Input keyword argument `ros={ros_arg}` has overridden " + f"the environment variable `ROS_ENABLED={ros_env}`." + ) + else: + raise ValueError( + f"Invalid value for input keyword argument `ros`: {ros_arg} . Expected: {ros_valid_vals}." + ) + else: + self._ros = ros_env + + # --VIEWPORT logic-- + # + # off-screen rendering + viewport_env = int(os.environ.get("VIEWPORT_ENABLED", 0)) + viewport_valid_vals = {0, 1} + if viewport_env not in viewport_valid_vals: + raise ValueError( + f"Invalid value for environment variable `VIEWPORT_ENABLED`: {viewport_env} ." + f"Expected: {viewport_valid_vals} ." + ) + self._viewport = bool(viewport_env) + + # Check if input keywords contain an 'experience' file setting + # Note: since experience is taken as a separate argument by Simulation App, we store it separately + self._simulationapp_experience = launcher_args.pop("experience", "") + print(f"[INFO][AppLauncher]: Loading experience file: {self._simulationapp_experience} .") + # Remove all values from input keyword args which are not meant for SimulationApp + # Assign all the passed settings to a dictionary for the simulation app + self._simulationapp_config = { + key: launcher_args[key] + for key in set(AppLauncher._SIMULATIONAPP_CONFIG_TYPES.keys()) & set(launcher_args.keys()) + } + + def _create_app(self): + """Launch and create the SimulationApp based on the parsed simulation config.""" + # Initialize SimulationApp # hack sys module to make sure that the SimulationApp is initialized correctly # this is to avoid the warnings from the simulation app about not ok modules r = re.compile(".*orbit.*") @@ -157,18 +562,24 @@ def __init__(self, **kwargs): hacked_modules[key] = sys.modules[key] del sys.modules[key] # launch simulation app - self._app = SimulationApp(kwargs) + self._app = SimulationApp(self._simulationapp_config, experience=self._simulationapp_experience) # add orbit modules back to sys.modules for key, value in hacked_modules.items(): sys.modules[key] = value + def _load_extensions(self): + """Load correct extensions based on AppLauncher's resolved config member variables.""" # These have to be loaded after SimulationApp is initialized + import carb from omni.isaac.core.utils.extensions import enable_extension + from omni.isaac.version import get_version + # Read isaac sim version (this includes build tag, release tag etc.) + isaacsim_version = get_version() # Retrieve carb settings for modification carb_settings_iface = carb.settings.get_settings() - if remote_deployment >= 2: + if self._livestream >= 1: # Set carb settings to allow for livestreaming carb_settings_iface.set_bool("/app/livestream/enabled", True) carb_settings_iface.set_bool("/app/window/drawMouse", True) @@ -176,42 +587,37 @@ def __init__(self, **kwargs): carb_settings_iface.set_string("/app/livestream/proto", "ws") carb_settings_iface.set_int("/app/livestream/websocket/framerate_limit", 120) # Note: Only one livestream extension can be enabled at a time - if remote_deployment == 2: + if self._livestream == 1: # Enable Native Livestream extension # Default App: Streaming Client from the Omniverse Launcher enable_extension("omni.kit.livestream.native") enable_extension("omni.services.streaming.manager") - elif remote_deployment == 3: + elif self._livestream == 2: # Enable WebSocket Livestream extension # Default URL: http://localhost:8211/streaming/client/ enable_extension("omni.services.streamclient.websocket") - elif remote_deployment == 4: + elif self._livestream == 3: # Enable WebRTC Livestream extension # Default URL: http://localhost:8211/streaming/webrtc-client/ enable_extension("omni.services.streamclient.webrtc") else: - raise ValueError( - f"Invalid assignment for env variable `REMOTE_DEPLOYMENT`: {remote_deployment}. Expected 1, 2, 3, 4." - ) + raise ValueError(f"Invalid value for livestream: {self._livestream}. Expected: 1, 2, 3 .") # As of IsaacSim 2022.1.1, the ros extension has to be loaded # after the streaming extension or it will cause a segfault - ros = int(os.environ.get("ROS_ENABLED", 0)) # Note: Only one ROS bridge extension can be enabled at a time - if ros > 0: - if ros == 1: + if self._ros != 0: + if self._ros == 1: enable_extension("omni.isaac.ros_bridge") - elif ros == 2: + elif self._ros == 2: enable_extension("omni.isaac.ros2_bridge") else: - raise ValueError(f"Invalid assignment for env variable `ROS_ENABLED`: {ros}. Expected 1 or 2.") + raise ValueError(f"Invalid value for ros: {self._ros}. Expected: 1, 2 .") - # off-screen rendering - viewport = int(os.environ.get("VIEWPORT_ENABLED", 0)) # enable extensions for off-screen rendering # note: depending on the app file, some extensions might not be available in it. # Thus, we manually enable these extensions to make sure they are available. - if viewport > 0 or not headless: + if self._viewport != 0 or not self._headless: # note: enabling extensions is order-sensitive. please do not change the order! # extension to enable UI buttons (otherwise we get attribute errors) enable_extension("omni.kit.window.toolbar") @@ -227,11 +633,6 @@ def __init__(self, **kwargs): # note: moved here since it requires to have the viewport extension to be enabled first. enable_extension("omni.replicator.isaac") # enable urdf importer - # read isaac sim version (this includes build tag, release tag etc.) - # note: we do it once here because it reads the VERSION file from disk and is not expected to change. - from omni.isaac.version import get_version - - isaacsim_version = get_version() if int(isaacsim_version[2]) == 2022: enable_extension("omni.isaac.urdf") else: @@ -247,23 +648,11 @@ def __init__(self, **kwargs): "http://omniverse-content-production.s3-us-west-2.amazonaws.com/Assets/Isaac/2023.1.0", ) + def _update_globals(self): + """Updates global variables which depend upon AppLauncher's resolved config member variables.""" # update the global flags # TODO: Remove all these global flags. We don't need it anymore. # -- render GUI - if headless and (remote_deployment < 2): - self.RENDER = False - else: - self.RENDER = True + self.RENDER = not self._headless or self._livestream # -- render viewport - if not viewport: - self.VIEWPORT = False - else: - self.VIEWPORT = True - - @property - def app(self) -> SimulationApp: - """The launched SimulationApp.""" - if self._app is not None: - return self._app - else: - raise RuntimeError("The `AppLauncher.app` member cannot be retrieved until the class is initialized.") + self.VIEWPORT = self._viewport diff --git a/source/extensions/omni.isaac.orbit/test/app/test_argparser_launch.py b/source/extensions/omni.isaac.orbit/test/app/test_argparser_launch.py new file mode 100644 index 0000000000..b587c75e7f --- /dev/null +++ b/source/extensions/omni.isaac.orbit/test/app/test_argparser_launch.py @@ -0,0 +1,49 @@ +# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import argparse +import unittest +from unittest import mock + +from omni.isaac.orbit.app import AppLauncher + + +class TestAppLauncher(unittest.TestCase): + """Test launching of the simulation app using AppLauncher.""" + + @mock.patch("argparse.ArgumentParser.parse_args", return_value=argparse.Namespace(livestream=1)) + def test_livestream_launch_with_argparser(self, mock_args): + """Test launching with argparser arguments.""" + # create argparser + parser = argparse.ArgumentParser() + # add app launcher arguments + AppLauncher.add_app_launcher_args(parser) + # check that argparser has the mandatory arguments + for name in AppLauncher._APPLAUNCHER_CONFIG_TYPES: + self.assertTrue(parser._option_string_actions[f"--{name}"]) + # parse args + mock_args = parser.parse_args() + # everything defaults to None + app = AppLauncher(mock_args).app + + # import settings + import carb + + # acquire settings interface + carb_settings_iface = carb.settings.get_settings() + # check settings + # -- no-gui mode + self.assertEqual(carb_settings_iface.get("/app/window/enabled"), False) + # -- livestream + self.assertEqual(carb_settings_iface.get("/app/livestream/enabled"), True) + + # close the app on exit + app.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/source/extensions/omni.isaac.orbit/test/app/test_env_var_launch.py b/source/extensions/omni.isaac.orbit/test/app/test_env_var_launch.py new file mode 100644 index 0000000000..116f9358e0 --- /dev/null +++ b/source/extensions/omni.isaac.orbit/test/app/test_env_var_launch.py @@ -0,0 +1,40 @@ +# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import os +import unittest + +from omni.isaac.orbit.app import AppLauncher + + +class TestAppLauncher(unittest.TestCase): + """Test launching of the simulation app using AppLauncher.""" + + def test_livestream_launch_with_env_var(self): + """Test launching with no-keyword args but environment variables.""" + # manually set the settings as well to make sure they are set correctly + os.environ["LIVESTREAM"] = "1" + # everything defaults to None + app = AppLauncher().app + + # import settings + import carb + + # acquire settings interface + carb_settings_iface = carb.settings.get_settings() + # check settings + # -- no-gui mode + self.assertEqual(carb_settings_iface.get("/app/window/enabled"), False) + # -- livestream + self.assertEqual(carb_settings_iface.get("/app/livestream/enabled"), True) + + # close the app on exit + app.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/source/extensions/omni.isaac.orbit/test/app/test_kwarg_launch.py b/source/extensions/omni.isaac.orbit/test/app/test_kwarg_launch.py new file mode 100644 index 0000000000..f0d5092761 --- /dev/null +++ b/source/extensions/omni.isaac.orbit/test/app/test_kwarg_launch.py @@ -0,0 +1,37 @@ +# Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES, ETH Zurich, and University of Toronto +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import unittest + +from omni.isaac.orbit.app import AppLauncher + + +class TestAppLauncher(unittest.TestCase): + """Test launching of the simulation app using AppLauncher.""" + + def test_livestream_launch_with_kwarg(self): + """Test launching with headless and livestreaming arguments.""" + # everything defaults to None + app = AppLauncher(headless=True, livestream=1).app + + # import settings + import carb + + # acquire settings interface + carb_settings_iface = carb.settings.get_settings() + # check settings + # -- no-gui mode + self.assertEqual(carb_settings_iface.get("/app/window/enabled"), False) + # -- livestream + self.assertEqual(carb_settings_iface.get("/app/livestream/enabled"), True) + + # close the app on exit + app.close() + + +if __name__ == "__main__": + unittest.main() diff --git a/source/standalone/demo/play_arms.py b/source/standalone/demo/play_arms.py index 07a07c6524..29e37785be 100644 --- a/source/standalone/demo/play_arms.py +++ b/source/standalone/demo/play_arms.py @@ -27,19 +27,18 @@ parser = argparse.ArgumentParser( description="This script demonstrates how to use the physics engine to simulate a single-arm manipulator." ) -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") 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(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app -"""Rest everything follows.""" - - import torch import traceback diff --git a/source/standalone/demo/play_camera.py b/source/standalone/demo/play_camera.py index 56cfb24648..17ef3a424c 100644 --- a/source/standalone/demo/play_camera.py +++ b/source/standalone/demo/play_camera.py @@ -20,13 +20,15 @@ # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates how to use the camera sensor.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--gpu", action="store_true", default=False, help="Use GPU device for camera rendering output.") parser.add_argument("--draw", action="store_true", default=False, help="Draw the obtained pointcloud on viewport.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/demo/play_cloner.py b/source/standalone/demo/play_cloner.py index ebf43157da..d0d9e10980 100644 --- a/source/standalone/demo/play_cloner.py +++ b/source/standalone/demo/play_cloner.py @@ -20,13 +20,15 @@ # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates how to use the cloner API from Isaac Sim.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") 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.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/demo/play_empty.py b/source/standalone/demo/play_empty.py index 4e356fdc7b..8f3a9a2f22 100644 --- a/source/standalone/demo/play_empty.py +++ b/source/standalone/demo/play_empty.py @@ -16,11 +16,13 @@ # add argparse arguments parser = argparse.ArgumentParser(description="This script creates an empty stage in Isaac Sim.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/demo/play_ik_control.py b/source/standalone/demo/play_ik_control.py index 9c20cc8d9a..56615ce5c6 100644 --- a/source/standalone/demo/play_ik_control.py +++ b/source/standalone/demo/play_ik_control.py @@ -20,13 +20,15 @@ # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates how to use the inverse kinematics controller.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--robot", type=str, default="franka_panda", help="Name of the robot.") parser.add_argument("--num_envs", type=int, default=128, help="Number of environments to spawn.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/demo/play_markers.py b/source/standalone/demo/play_markers.py index d8b0f7ff9e..c969d0b4f7 100644 --- a/source/standalone/demo/play_markers.py +++ b/source/standalone/demo/play_markers.py @@ -18,7 +18,9 @@ parser = argparse.ArgumentParser( description="This script demonstrates how to create different types of markers in Orbit." ) -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app diff --git a/source/standalone/demo/play_quadrupeds.py b/source/standalone/demo/play_quadrupeds.py index 54d6f0d6ce..a1d5353faa 100644 --- a/source/standalone/demo/play_quadrupeds.py +++ b/source/standalone/demo/play_quadrupeds.py @@ -24,11 +24,13 @@ # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates how to simulate a legged robot.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/demo/play_ridgeback_franka.py b/source/standalone/demo/play_ridgeback_franka.py index 576f7fecef..66147ae930 100644 --- a/source/standalone/demo/play_ridgeback_franka.py +++ b/source/standalone/demo/play_ridgeback_franka.py @@ -26,12 +26,14 @@ parser = argparse.ArgumentParser( description="This script demonstrates how to simulate a mobile manipulator with dummy joints." ) -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") 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 args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/demo/play_rmpflow.py b/source/standalone/demo/play_rmpflow.py index 6decd7d61e..d2a29f3bd9 100644 --- a/source/standalone/demo/play_rmpflow.py +++ b/source/standalone/demo/play_rmpflow.py @@ -22,13 +22,15 @@ parser = argparse.ArgumentParser( description="This script demonstrates how to use the RMPFlow controller with the simulator." ) -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--robot", type=str, default="ur10", help="Name of the robot. Options: franka_panda, ur10.") parser.add_argument("--num_envs", type=int, default=5, help="Number of environments to spawn.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/demo/play_scene.py b/source/standalone/demo/play_scene.py index 97221232ab..92d091d159 100644 --- a/source/standalone/demo/play_scene.py +++ b/source/standalone/demo/play_scene.py @@ -19,12 +19,14 @@ # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates how to use the scene interface.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--num_envs", type=int, default=2, help="Number of environments to spawn.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/demo/play_ycb_objects.py b/source/standalone/demo/play_ycb_objects.py index 61d8397be3..be2b874270 100644 --- a/source/standalone/demo/play_ycb_objects.py +++ b/source/standalone/demo/play_ycb_objects.py @@ -18,11 +18,13 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Load YCB objects in Orbit and randomize their poses.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/environments/random_agent.py b/source/standalone/environments/random_agent.py index c0ec27368e..911afb7ffe 100644 --- a/source/standalone/environments/random_agent.py +++ b/source/standalone/environments/random_agent.py @@ -16,14 +16,16 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Random agent for Isaac Orbit environments.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--cpu", action="store_true", default=False, help="Use CPU pipeline.") parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/environments/state_machine/play_lift.py b/source/standalone/environments/state_machine/play_lift.py index a5836532d4..852a1ea4ce 100644 --- a/source/standalone/environments/state_machine/play_lift.py +++ b/source/standalone/environments/state_machine/play_lift.py @@ -18,9 +18,11 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Pick and lift state machine for lift environments.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--cpu", action="store_true", default=False, help="Use CPU pipeline.") parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app diff --git a/source/standalone/environments/teleoperation/teleop_se3_agent.py b/source/standalone/environments/teleoperation/teleop_se3_agent.py index cfe6cd709d..cd8be84f01 100644 --- a/source/standalone/environments/teleoperation/teleop_se3_agent.py +++ b/source/standalone/environments/teleoperation/teleop_se3_agent.py @@ -16,12 +16,14 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Keyboard teleoperation for Isaac Orbit environments.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--cpu", action="store_true", default=False, help="Use CPU pipeline.") parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to simulate.") parser.add_argument("--device", type=str, default="keyboard", help="Device for interacting with environment") parser.add_argument("--task", type=str, default=None, help="Name of the task.") parser.add_argument("--sensitivity", type=float, default=1.0, help="Sensitivity factor.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app diff --git a/source/standalone/environments/zero_agent.py b/source/standalone/environments/zero_agent.py index 0968b4891a..590f995957 100644 --- a/source/standalone/environments/zero_agent.py +++ b/source/standalone/environments/zero_agent.py @@ -16,14 +16,16 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Zero agent for Isaac Orbit environments.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--cpu", action="store_true", default=False, help="Use CPU pipeline.") parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/workflows/rl_games/play.py b/source/standalone/workflows/rl_games/play.py index f086ad6c2c..66316a56c7 100644 --- a/source/standalone/workflows/rl_games/play.py +++ b/source/standalone/workflows/rl_games/play.py @@ -16,7 +16,6 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Play a checkpoint of an RL agent from RL-Games.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--cpu", action="store_true", default=False, help="Use CPU pipeline.") parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") @@ -26,10 +25,13 @@ action="store_true", help="When no checkpoint provided, use the last saved model. Otherwise use the best saved model.", ) +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/workflows/rl_games/train.py b/source/standalone/workflows/rl_games/train.py index 2aa42067bb..d137ed6c8b 100644 --- a/source/standalone/workflows/rl_games/train.py +++ b/source/standalone/workflows/rl_games/train.py @@ -17,7 +17,6 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Train an RL agent with RL-Games.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--video", action="store_true", default=False, help="Record videos during training.") parser.add_argument("--video_length", type=int, default=200, help="Length of the recorded video (in steps).") parser.add_argument("--video_interval", type=int, default=2000, help="Interval between video recordings (in steps).") @@ -25,17 +24,19 @@ parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch the simulator -config = {"headless": args_cli.headless} # load cheaper kit config in headless if args_cli.headless: app_experience = f"{os.environ['EXP_PATH']}/omni.isaac.sim.python.gym.headless.kit" else: app_experience = f"{os.environ['EXP_PATH']}/omni.isaac.sim.python.kit" # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless, experience=app_experience) +app_launcher = AppLauncher(args_cli, experience=app_experience) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/workflows/robomimic/collect_demonstrations.py b/source/standalone/workflows/robomimic/collect_demonstrations.py index 940370dd19..57685405ad 100644 --- a/source/standalone/workflows/robomimic/collect_demonstrations.py +++ b/source/standalone/workflows/robomimic/collect_demonstrations.py @@ -16,18 +16,20 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Collect demonstrations for Isaac Orbit environments.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--cpu", action="store_true", default=False, help="Use CPU pipeline.") parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") parser.add_argument("--device", type=str, default="keyboard", help="Device for interacting with environment") parser.add_argument("--num_demos", type=int, default=1, help="Number of episodes to store in the dataset.") parser.add_argument("--filename", type=str, default="hdf_dataset", help="Basename of output file.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch the simulator -app_launcher = AppLauncher() -simulation_app = app_launcher.create_app(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) +simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/workflows/robomimic/play.py b/source/standalone/workflows/robomimic/play.py index fc7bbf788f..3e8b2d804d 100644 --- a/source/standalone/workflows/robomimic/play.py +++ b/source/standalone/workflows/robomimic/play.py @@ -16,14 +16,16 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Play policy trained using robomimic for Isaac Orbit environments.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--cpu", action="store_true", default=False, help="Use CPU pipeline.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") parser.add_argument("--checkpoint", type=str, default=None, help="Pytorch model checkpoint to load.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/workflows/rsl_rl/play.py b/source/standalone/workflows/rsl_rl/play.py index 4d6cc6a75f..4c7587dbfc 100644 --- a/source/standalone/workflows/rsl_rl/play.py +++ b/source/standalone/workflows/rsl_rl/play.py @@ -16,15 +16,17 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Play a checkpoint of an RL agent from RSL-RL.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--cpu", action="store_true", default=False, help="Use CPU pipeline.") parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") parser.add_argument("--checkpoint", type=str, default=None, help="Path to model checkpoint.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/workflows/rsl_rl/train.py b/source/standalone/workflows/rsl_rl/train.py index 79283b2dc8..d846a68a6f 100644 --- a/source/standalone/workflows/rsl_rl/train.py +++ b/source/standalone/workflows/rsl_rl/train.py @@ -17,7 +17,6 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Train an RL agent with RSL-RL.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--video", action="store_true", default=False, help="Record videos during training.") parser.add_argument("--video_length", type=int, default=200, help="Length of the recorded video (in steps).") parser.add_argument("--video_interval", type=int, default=2000, help="Interval between video recordings (in steps).") @@ -25,9 +24,11 @@ parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() -config = {"headless": args_cli.headless} # load cheaper kit config in headless if args_cli.headless: app_experience = f"{os.environ['EXP_PATH']}/omni.isaac.sim.python.gym.headless.kit" @@ -35,7 +36,7 @@ app_experience = f"{os.environ['EXP_PATH']}/omni.isaac.sim.python.kit" # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless, experience=app_experience) +app_launcher = AppLauncher(args_cli, experience=app_experience) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/workflows/sb3/play.py b/source/standalone/workflows/sb3/play.py index 694213c083..4325ba3afb 100644 --- a/source/standalone/workflows/sb3/play.py +++ b/source/standalone/workflows/sb3/play.py @@ -17,15 +17,17 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Play a checkpoint of an RL agent from Stable-Baselines3.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--cpu", action="store_true", default=False, help="Use CPU pipeline.") parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") parser.add_argument("--checkpoint", type=str, default=None, help="Path to model checkpoint.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/workflows/sb3/train.py b/source/standalone/workflows/sb3/train.py index 38dd09461c..eb35497ead 100644 --- a/source/standalone/workflows/sb3/train.py +++ b/source/standalone/workflows/sb3/train.py @@ -18,7 +18,6 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Train an RL agent with Stable-Baselines3.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--video", action="store_true", default=False, help="Record videos during training.") parser.add_argument("--video_length", type=int, default=200, help="Length of the recorded video (in steps).") parser.add_argument("--video_interval", type=int, default=2000, help="Interval between video recordings (in steps).") @@ -26,6 +25,9 @@ parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch the simulator @@ -36,7 +38,7 @@ else: app_experience = f"{os.environ['EXP_PATH']}/omni.isaac.sim.python.kit" # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless, experience=app_experience) +app_launcher = AppLauncher(args_cli, experience=app_experience) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/workflows/skrl/play.py b/source/standalone/workflows/skrl/play.py index d3fa3bd3f4..07918b80df 100644 --- a/source/standalone/workflows/skrl/play.py +++ b/source/standalone/workflows/skrl/play.py @@ -22,15 +22,17 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Play a checkpoint of an RL agent from skrl.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--cpu", action="store_true", default=False, help="Use CPU pipeline.") parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") parser.add_argument("--checkpoint", type=str, default=None, help="Path to model checkpoint.") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless) +app_launcher = AppLauncher(args_cli) simulation_app = app_launcher.app """Rest everything follows.""" diff --git a/source/standalone/workflows/skrl/train.py b/source/standalone/workflows/skrl/train.py index cd3e6c1296..da6117f1f0 100644 --- a/source/standalone/workflows/skrl/train.py +++ b/source/standalone/workflows/skrl/train.py @@ -22,7 +22,6 @@ # add argparse arguments parser = argparse.ArgumentParser(description="Train an RL agent with skrl.") -parser.add_argument("--headless", action="store_true", default=False, help="Force display off at all times.") parser.add_argument("--video", action="store_true", default=False, help="Record videos during training.") parser.add_argument("--video_length", type=int, default=200, help="Length of the recorded video (in steps).") parser.add_argument("--video_interval", type=int, default=2000, help="Interval between video recordings (in steps).") @@ -30,10 +29,12 @@ parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment") +# append AppLauncher cli args +AppLauncher.add_app_launcher_args(parser) +# parse the arguments args_cli = parser.parse_args() # launch the simulator -config = {"headless": args_cli.headless} # load cheaper kit config in headless if args_cli.headless: app_experience = f"{os.environ['EXP_PATH']}/omni.isaac.sim.python.gym.headless.kit" @@ -41,7 +42,7 @@ app_experience = f"{os.environ['EXP_PATH']}/omni.isaac.sim.python.kit" # launch omniverse app -app_launcher = AppLauncher(headless=args_cli.headless, experience=app_experience) +app_launcher = AppLauncher(args_cli, experience=app_experience) simulation_app = app_launcher.app """Rest everything follows."""