From 060125979f57272817cd939bf1cbbd6491dfad44 Mon Sep 17 00:00:00 2001 From: Octi Zhang Date: Sun, 27 Sep 2026 20:31:18 -0700 Subject: [PATCH 1/2] Move scripts, examples, and docs to launch_simulation Every script, example, tutorial, and benchmark starts the runtime with launch_simulation and builds runtime objects with instantiate. Scripts no longer import carb or omni.replicator, the converter tools preview in the visualizer chosen with --viz, and the check_* scripts that duplicated pytest coverage are deleted. AppLauncher shrinks to a shim that keeps old-style scripts running by starting Kit through launch_simulation, and show_stage_in_viewport has no callers left. --- .../api/lab_physx/isaaclab_physx.app.rst | 7 - docs/source/concepts/renderers.rst | 4 +- docs/source/concepts/sensors/ray_caster.rst | 2 +- docs/source/features/reproducibility.rst | 4 +- .../features/visualizer_tiled_camera.rst | 2 +- docs/source/how-to/add_sensors_on_robot.rst | 12 +- docs/source/how-to/create_empty.rst | 67 ++--- .../source/how-to/create_manager_base_env.rst | 8 +- docs/source/how-to/create_manager_rl_env.rst | 6 +- docs/source/how-to/create_scene.rst | 18 +- docs/source/how-to/index.rst | 2 +- docs/source/how-to/launch_app.rst | 192 +++++++------- docs/source/how-to/register_rl_env_gym.rst | 5 +- docs/source/how-to/run_articulation.rst | 2 +- docs/source/how-to/run_diff_ik.rst | 2 +- docs/source/how-to/run_rigid_object.rst | 2 +- docs/source/how-to/run_surface_gripper.rst | 2 +- docs/source/how-to/save_camera_output.rst | 40 ++- docs/source/how-to/spawn_prims.rst | 2 +- docs/source/how-to/wrap_rl_env.rst | 58 ++--- .../exporting_policies_with_leapp.rst | 2 +- docs/source/refs/issues.rst | 6 +- docs/source/refs/troubleshooting.rst | 2 +- examples/arl_robot_1.py | 1 - examples/bin_packing.py | 5 - examples/deformables.py | 4 - examples/demos/pick_and_place.py | 180 +++++--------- examples/demos/teapot_fill.py | 2 +- examples/markers.py | 4 - examples/multi_asset.py | 4 - examples/procedural_terrain.py | 5 - examples/sensors/cameras.py | 51 ++-- examples/sensors/multi_mesh_raycaster.py | 5 - .../sensors/multi_mesh_raycaster_camera.py | 67 +++-- examples/sensors/ppisp_camera.py | 122 +++++---- examples/sensors/tacsl_sensor.py | 120 +++++---- scripts/benchmarks/benchmark_load_robot.py | 14 +- .../benchmarks/benchmark_newton_raycast.py | 2 - .../benchmarks/benchmark_view_comparison.py | 22 +- .../benchmarks/benchmark_xform_prim_view.py | 33 +-- .../state_machine/lift_cube_sm.py | 112 ++++----- .../state_machine/lift_franka_soft.py | 1 - .../state_machine/open_cabinet_sm.py | 108 ++++---- .../teleoperation/teleop_replay_agent.py | 184 +++++--------- .../teleoperation/teleop_se3_agent.py | 85 +++---- .../isaaclab_mimic/annotate_demos.py | 60 ++--- .../isaaclab_mimic/consolidated_demo.py | 40 +-- .../isaaclab_mimic/generate_dataset.py | 55 ++-- .../locomanipulation_sdg/generate_data.py | 53 ++-- .../gr00t/rollout_policy.py | 34 +-- scripts/imitation_learning/robomimic/play.py | 41 ++- .../robomimic/robust_eval.py | 40 ++- scripts/imitation_learning/robomimic/train.py | 19 +- scripts/tools/check_instanceable.py | 115 ++++----- scripts/tools/convert_instanceable.py | 28 +-- scripts/tools/convert_mesh.py | 83 ++++--- scripts/tools/convert_mjcf.py | 24 +- scripts/tools/convert_urdf.py | 24 +- scripts/tools/cosmos/cosmos_prompt_gen.py | 1 - scripts/tools/hdf5_to_mp4.py | 1 - scripts/tools/mp4_to_hdf5.py | 1 - scripts/tools/process_meshes_to_obj.py | 2 - scripts/tools/record_demos.py | 95 ++++--- scripts/tools/replay_demos.py | 42 ++-- scripts/tutorials/00_sim/create_empty.py | 42 ++-- scripts/tutorials/00_sim/launch_app.py | 49 ++-- scripts/tutorials/00_sim/log_time.py | 62 +++-- scripts/tutorials/00_sim/spawn_prims.py | 46 ++-- scripts/tutorials/01_assets/add_new_robot.py | 50 ++-- .../tutorials/01_assets/run_articulation.py | 45 ++-- .../tutorials/01_assets/run_rigid_object.py | 45 ++-- .../01_assets/run_surface_gripper.py | 45 ++-- scripts/tutorials/02_scene/create_scene.py | 55 ++-- .../03_envs/create_cartpole_base_env.py | 68 +++-- .../tutorials/03_envs/create_cube_base_env.py | 82 +++--- .../03_envs/create_quadruped_base_env.py | 88 +++---- .../03_envs/policy_inference_in_usd.py | 4 +- .../tutorials/03_envs/run_cartpole_rl_env.py | 67 +++-- .../04_sensors/add_sensors_on_robot.py | 57 +++-- .../04_sensors/run_frame_transformer.py | 81 +++--- .../tutorials/04_sensors/run_ray_caster.py | 56 +++-- .../04_sensors/run_ray_caster_camera.py | 95 +++---- .../tutorials/04_sensors/run_usd_camera.py | 97 +++----- .../tutorials/05_controllers/run_diff_ik.py | 55 ++-- scripts/tutorials/05_controllers/run_osc.py | 59 ++--- .../run_tiled_camera_visualizer.py | 2 +- skills/user/migrate-2x-to-3x/SKILL.md | 2 +- .../migrate-2x-to-3x/supplemental-checks.md | 2 +- .../launch-simulation-scripts.major.rst | 21 ++ source/isaaclab/isaaclab/app/app_launcher.py | 17 +- .../test/assets/check_external_force.py | 131 ---------- .../test/assets/check_fixed_base_assets.py | 168 ------------- .../test/assets/check_ridgeback_franka.py | 171 ------------- .../cli/test_replay_demos_loop_termination.py | 19 +- .../isaaclab/test/devices/check_keyboard.py | 105 ++++---- .../misc/test_wheel_builder_smoke.py | 14 +- .../test/markers/check_markers_visibility.py | 53 ++-- .../test/scene/check_interactive_scene.py | 164 ------------ .../sensors/check_multi_mesh_ray_caster.py | 226 ----------------- .../isaaclab/test/sensors/check_ray_caster.py | 190 -------------- .../isaaclab/test/sim/test_mjcf_converter.py | 83 +++---- .../isaaclab/test/sim/test_urdf_converter.py | 46 ++-- .../check_height_field_subterrains.py | 14 +- .../test/terrains/check_mesh_subterrains.py | 15 +- .../test/terrains/check_terrain_importer.py | 215 ---------------- .../check_scene_xr_visualization.py | 75 +++--- .../sensors/benchmark_contact_sensor.py | 105 ++++---- .../sensors/benchmark_frame_transformer.py | 169 ++++++------- .../benchmark/sensors/benchmark_imu_pva.py | 157 ++++++------ .../sensors/benchmark_joint_wrench.py | 155 ++++++------ .../benchmark/sensors/benchmark_ray_caster.py | 235 +++++++++--------- .../changelog.d/launch-simulation-scripts.rst | 11 + .../isaaclab_physx/app/__init__.pyi | 3 +- .../isaaclab_physx/app/kit_stage.py | 28 --- .../renderers/isaac_rtx_renderer_utils.py | 48 +++- .../test/app/test_app_launcher_shim.py | 63 +++++ .../test_isaac_rtx_renderer_utils.py | 13 + .../test/sensors/check_contact_sensor.py | 187 -------------- .../test/sensors/check_pva_sensor.py | 192 -------------- tools/docs/media/capture_renderer_gallery.py | 16 +- .../media/visualizers/capture_visualizer.py | 2 +- 121 files changed, 2367 insertions(+), 4334 deletions(-) create mode 100644 source/isaaclab/changelog.d/launch-simulation-scripts.major.rst delete mode 100644 source/isaaclab/test/assets/check_external_force.py delete mode 100644 source/isaaclab/test/assets/check_fixed_base_assets.py delete mode 100644 source/isaaclab/test/assets/check_ridgeback_franka.py delete mode 100644 source/isaaclab/test/scene/check_interactive_scene.py delete mode 100644 source/isaaclab/test/sensors/check_multi_mesh_ray_caster.py delete mode 100644 source/isaaclab/test/sensors/check_ray_caster.py delete mode 100644 source/isaaclab/test/terrains/check_terrain_importer.py create mode 100644 source/isaaclab_physx/changelog.d/launch-simulation-scripts.rst create mode 100644 source/isaaclab_physx/test/app/test_app_launcher_shim.py delete mode 100644 source/isaaclab_physx/test/sensors/check_contact_sensor.py delete mode 100644 source/isaaclab_physx/test/sensors/check_pva_sensor.py diff --git a/docs/source/api/lab_physx/isaaclab_physx.app.rst b/docs/source/api/lab_physx/isaaclab_physx.app.rst index 40c4f022f4d4..675847d9526d 100644 --- a/docs/source/api/lab_physx/isaaclab_physx.app.rst +++ b/docs/source/api/lab_physx/isaaclab_physx.app.rst @@ -11,11 +11,6 @@ isaaclab\_physx.app KitStageBackend KitStageBackendCfg - .. rubric:: Functions - - .. autosummary:: - - show_stage_in_viewport Environment variables --------------------- @@ -164,7 +159,5 @@ Kit Stage :members: :show-inheritance: -.. autofunction:: show_stage_in_viewport - .. _livestream: https://docs.isaacsim.omniverse.nvidia.com/latest/installation/manual_livestream_clients.html .. _`WebRTC Livestream`: https://docs.isaacsim.omniverse.nvidia.com/latest/installation/manual_livestream_clients.html#isaac-sim-short-webrtc-streaming-client diff --git a/docs/source/concepts/renderers.rst b/docs/source/concepts/renderers.rst index 0e29fcde2551..d5164dc81024 100644 --- a/docs/source/concepts/renderers.rst +++ b/docs/source/concepts/renderers.rst @@ -350,14 +350,14 @@ Configure the behavior through :class:`~isaaclab_physx.renderers.IsaacRtxRendere renderer_cfg = IsaacRtxRendererCfg(enable_scene_partitioning=False) Scene partitioning and the all-environment spectator view are separate controls. -:class:`~isaaclab.app.AppLauncher` enables spectator support before RTX startup only +:class:`~isaaclab_physx.app.KitLauncher` enables spectator support before RTX startup only when the Kit viewport is enabled or Kit visualization, recording, livestreaming, or XR is requested. Regular headless training and camera-sensor runs keep it disabled so tiled cameras are not exposed to the spectator mode's world-space layout constraints. ``global_settings.show_all_partitions_by_default`` maps to that same process-global RTX setting; it is not a separate feature. Its default value of ``None`` preserves the -launch-time choice made by :class:`~isaaclab.app.AppLauncher`. An explicit value overrides +launch-time choice made by :class:`~isaaclab_physx.app.KitLauncher`. An explicit value overrides that setting when the Isaac RTX renderer is constructed. When enabled, environments must remain spatially separated because overlapping partition bounds can make content leak into another environment or disappear. When disabled, the Kit viewport displays only the diff --git a/docs/source/concepts/sensors/ray_caster.rst b/docs/source/concepts/sensors/ray_caster.rst index e952c4131ef7..20dbd1484c56 100644 --- a/docs/source/concepts/sensors/ray_caster.rst +++ b/docs/source/concepts/sensors/ray_caster.rst @@ -44,7 +44,7 @@ Querying the sensor for data can be done at simulation run time like any other s . . # Simulate physics - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): . . . diff --git a/docs/source/features/reproducibility.rst b/docs/source/features/reproducibility.rst index 872ab360a8ab..2a7e3a005fe1 100644 --- a/docs/source/features/reproducibility.rst +++ b/docs/source/features/reproducibility.rst @@ -23,8 +23,8 @@ depending on the manager-based or direct environment implementation respectively The ``--deterministic`` flag ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The ``--deterministic`` flag is provided by :meth:`isaaclab.app.AppLauncher.add_app_launcher_args`. -:class:`~isaaclab.app.app_launcher.AppLauncher` publishes ``/isaaclab/render/deterministic``. +The ``--deterministic`` flag is provided by :func:`isaaclab.app.add_launcher_args`. +:class:`~isaaclab_physx.app.KitLauncher` publishes ``/isaaclab/render/deterministic``. The Isaac RTX backend reads it on init and applies :func:`isaaclab_physx.renderers.isaac_rtx_renderer_utils.apply_isaac_rtx_determinism_settings`. diff --git a/docs/source/features/visualizer_tiled_camera.rst b/docs/source/features/visualizer_tiled_camera.rst index 3346847e3b50..e993318d1778 100644 --- a/docs/source/features/visualizer_tiled_camera.rst +++ b/docs/source/features/visualizer_tiled_camera.rst @@ -34,7 +34,7 @@ This guide is accompanied by the ``run_tiled_camera_visualizer.py`` script in .. literalinclude:: ../../../scripts/tutorials/07_visualizers/run_tiled_camera_visualizer.py :language: python - :emphasize-lines: 72-78,81-83,89-96,107-109 + :emphasize-lines: 72-78,81-83,93-99,107-109 :linenos: See `Examples`_ below for the two ways the script can be run, and `Usage`_ for the diff --git a/docs/source/how-to/add_sensors_on_robot.rst b/docs/source/how-to/add_sensors_on_robot.rst index 00141da0e87c..0911250f22db 100644 --- a/docs/source/how-to/add_sensors_on_robot.rst +++ b/docs/source/how-to/add_sensors_on_robot.rst @@ -39,7 +39,7 @@ The tutorial corresponds to the ``add_sensors_on_robot.py`` script in the .. literalinclude:: ../../../scripts/tutorials/04_sensors/add_sensors_on_robot.py :language: python - :emphasize-lines: 72-95, 143-153, 167-168 + :emphasize-lines: 74-96, 147-160, 177-178 :linenos: @@ -90,6 +90,16 @@ and ``"front_cam"`` is the name of the prim associated with the camera sensor. :start-at: camera = CameraCfg( :end-before: height_scanner = RayCasterCfg( +Camera sensors need the simulator's rendering extensions, also when running without a visualizer. +:func:`~app.launch_simulation` enables them automatically when the configuration it is given contains a +camera. Since this script passes only the simulation configuration and the camera lives in the scene +configuration, the script requests camera rendering explicitly after parsing its arguments: + +.. literalinclude:: ../../../scripts/tutorials/04_sensors/add_sensors_on_robot.py + :language: python + :start-at: args_cli = parser.parse_args() + :end-at: args_cli.enable_cameras = True + Height scanner -------------- diff --git a/docs/source/how-to/create_empty.rst b/docs/source/how-to/create_empty.rst index 29db20fa87d3..2e115ad65fb2 100644 --- a/docs/source/how-to/create_empty.rst +++ b/docs/source/how-to/create_empty.rst @@ -6,8 +6,8 @@ Creating an empty scene .. currentmodule:: isaaclab This tutorial shows how to launch and control Isaac Sim simulator from a standalone Python script. It sets up an -empty scene in Isaac Lab and introduces the two main classes used in the framework, :class:`app.AppLauncher` and -:class:`sim.SimulationContext`. +empty scene in Isaac Lab and introduces the two main entry points used in the framework, the +:func:`app.launch_simulation` context manager and the :class:`sim.SimulationContext` class. Please review `Isaac Sim Workflows`_ prior to beginning this tutorial to get an initial understanding of working with the simulator. @@ -23,40 +23,37 @@ The tutorial corresponds to the ``create_empty.py`` script in the ``scripts/tuto .. literalinclude:: ../../../scripts/tutorials/00_sim/create_empty.py :language: python - :emphasize-lines: 18-30,34,40-44,46-47,51-54,60-61 + :emphasize-lines: 18-27,31,37-44,46-47,51-54 :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. +Parsing the command-line arguments +---------------------------------- -This can be done by importing the :class:`app.AppLauncher` class. This utility class wraps around -:class:`isaacsim.SimulationApp` class to launch the simulator. It provides mechanisms to -configure the simulator using command-line arguments and environment variables. +A standalone script starts by parsing its command-line arguments. The simulator itself is not started yet: +it is launched later, once we know which runtime the simulation configuration needs. -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. +The launch-related command-line options are added to a user-defined :class:`argparse.ArgumentParser` +by passing the parser instance to the :func:`app.add_launcher_args` function. It appends options such as +``--device`` to choose the simulation device, ``--visualizer`` (or ``--viz``) to open a visualizer window, +and ``--livestream`` to stream the simulator. These options are later handed to +:func:`app.launch_simulation`. .. literalinclude:: ../../../scripts/tutorials/00_sim/create_empty.py :language: python :start-at: import argparse - :end-at: simulation_app = app_launcher.app + :end-at: args_cli = parser.parse_args() 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 the following module: +Isaac Lab configuration classes and utilities can be imported before the simulator is launched. Modules that +need the running simulator, such as Isaac Sim's ``omni.*`` and ``isaacsim.*`` modules or Isaac Lab classes that +work on the USD stage, are imported later, after the simulator has been launched, as shown in the following +tutorials. Here we import the following module: * :mod:`isaaclab.sim`: A sub-package in Isaac Lab for all the core simulator-related operations. @@ -82,9 +79,17 @@ For this tutorial, we set the physics and rendering time step to 0.01 seconds. T by passing these quantities to the :class:`sim.SimulationCfg`, which is then used to create an instance of the simulation context. +Before the simulation context can be created, the simulator runtime must be launched. This is done with the +:func:`app.launch_simulation` context manager. It inspects the given configuration together with the parsed +command-line arguments and starts only the runtime that they need: the default PhysX physics backend runs +inside Isaac Sim (Kit), so Isaac Sim is started here, while a kitless backend such as Newton needs no Isaac Sim +at all. The simulation runs headless unless a visualizer is requested, for example with ``--viz kit``. +Everything that uses the simulator runs inside the ``with`` block, and the runtime is closed automatically +when the block exits. + .. literalinclude:: ../../../scripts/tutorials/00_sim/create_empty.py :language: python - :start-at: # Initialize the simulation context + :start-at: # Configure the simulation :end-at: sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) @@ -112,10 +117,11 @@ properly. :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. +After playing the simulation timeline, we set up a simple simulation loop where the simulator is stepped repeatedly. +The loop condition :meth:`sim.SimulationContext.is_headless_or_exist_active_visualizer` keeps it running +forever when no visualizer is open, and until the last visualizer window is closed otherwise. +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:: ../../../scripts/tutorials/00_sim/create_empty.py :language: python @@ -125,13 +131,8 @@ set to True. Exiting the simulation ---------------------- -Lastly, the simulation application is stopped and its window is closed by calling -:meth:`isaacsim.SimulationApp.close` method. - -.. literalinclude:: ../../../scripts/tutorials/00_sim/create_empty.py - :language: python - :start-at: # close sim app - :end-at: simulation_app.close() +There is no explicit shutdown call. When the simulation loop ends and the ``with launch_simulation(...)`` +block exits, the simulator runtime is stopped and its window is closed. The Code Execution @@ -153,7 +154,7 @@ you can either close the window, or press ``Ctrl+C`` in the terminal. :alt: result of create_empty.py 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, omit the visualizer +earlier by the :func:`app.add_launcher_args` function. To run the script headless, omit the visualizer selection: .. code-block:: bash diff --git a/docs/source/how-to/create_manager_base_env.rst b/docs/source/how-to/create_manager_base_env.rst index cb9c8bf3352f..029f273aba13 100644 --- a/docs/source/how-to/create_manager_base_env.rst +++ b/docs/source/how-to/create_manager_base_env.rst @@ -41,7 +41,7 @@ directory. .. literalinclude:: ../../../scripts/tutorials/03_envs/create_cartpole_base_env.py :language: python - :emphasize-lines: 47-51, 54-71, 74-108, 111-130, 135-139, 144, 148, 153-154, 160-161 + :emphasize-lines: 50-54, 57-74, 77-111, 114-132, 138-144, 153, 159 :linenos: The Code Explained @@ -181,6 +181,12 @@ method to step the environment. Both these functions return the observation and which may contain additional information provided by the environment. These can be used by an agent for decision-making. +Before the environment is created, its configuration is passed to :func:`app.launch_simulation`, which +launches the simulator runtime that the configuration needs and closes it when the ``with`` block exits. +Inside this block, :func:`~isaaclab.utils.instantiate` constructs the environment from the class named by +the configuration's :attr:`~envs.ManagerBasedEnvCfg.class_type` attribute. The class is resolved lazily, so the environment +class, which works on the USD stage, is only imported once the simulator runtime is running. + The :class:`envs.ManagerBasedEnv` class does not have any notion of terminations since that concept is specific for episodic tasks. Thus, the user is responsible for defining the termination condition for the environment. In this tutorial, we reset the simulation at regular intervals. diff --git a/docs/source/how-to/create_manager_rl_env.rst b/docs/source/how-to/create_manager_rl_env.rst index ee489da6d71d..69db784ca888 100644 --- a/docs/source/how-to/create_manager_rl_env.rst +++ b/docs/source/how-to/create_manager_rl_env.rst @@ -51,7 +51,7 @@ The script for running the environment ``run_cartpole_rl_env.py`` is present in .. literalinclude:: ../../../scripts/tutorials/03_envs/run_cartpole_rl_env.py :language: python - :emphasize-lines: 38-42, 56-57 + :emphasize-lines: 45-52, 67 :linenos: @@ -147,8 +147,8 @@ Running the simulation loop --------------------------- Coming back to the ``run_cartpole_rl_env.py`` script, the simulation loop is similar to the previous tutorial. -The only difference is that we create an instance of :class:`envs.ManagerBasedRLEnv` instead of the -:class:`envs.ManagerBasedEnv`. Consequently, now the :meth:`envs.ManagerBasedRLEnv.step` method returns additional signals +The only difference is that the configuration's ``class_type`` creates an instance of :class:`envs.ManagerBasedRLEnv` +instead of the :class:`envs.ManagerBasedEnv`. Consequently, now the :meth:`envs.ManagerBasedRLEnv.step` method returns additional signals such as the reward and termination status. The information dictionary also maintains logging of quantities such as the reward contribution from individual terms, the termination status of each term, the episode length etc. diff --git a/docs/source/how-to/create_scene.rst b/docs/source/how-to/create_scene.rst index e3f67178607c..173fb677cd2e 100644 --- a/docs/source/how-to/create_scene.rst +++ b/docs/source/how-to/create_scene.rst @@ -42,7 +42,7 @@ This tutorial corresponds to the ``create_scene.py`` script within .. literalinclude:: ../../../scripts/tutorials/02_scene/create_scene.py :language: python - :emphasize-lines: 50-63, 68-70, 91-92, 99-100, 105-106, 116-118 + :emphasize-lines: 50-63, 68-70, 97-98, 105-106, 111-112, 125-127 :linenos: @@ -103,15 +103,21 @@ Scene instantiation ------------------- Unlike before where we called the ``design_scene`` function to create the scene, we now -create an instance of the :class:`scene.InteractiveScene` class and pass in the configuration -object to its constructor. While creating the configuration instance of ``CartpoleSceneCfg`` -we specify how many environment copies we want to create using the ``num_envs`` argument. -This will be used to clone the scene for each environment. +create an instance of the :class:`scene.InteractiveScene` class from the configuration object. +The configuration names the class it builds in its :attr:`~scene.InteractiveSceneCfg.class_type` +attribute, so :func:`~isaaclab.utils.instantiate` constructs the scene from it. While creating the +configuration instance of ``CartpoleSceneCfg`` we specify how many environment copies we want to +create using the ``num_envs`` argument. This will be used to clone the scene for each environment. .. literalinclude:: ../../../scripts/tutorials/02_scene/create_scene.py :language: python :start-at: # Design scene - :end-at: scene = InteractiveScene(scene_cfg) + :end-at: scene = instantiate(scene_cfg) + +The configuration class ``CartpoleSceneCfg`` can be defined at the top of the script, but the +:class:`scene.InteractiveScene` class works on the USD stage of the running simulator. The +``class_type`` attribute resolves the class lazily, so the scene class is only imported when it is +constructed inside the ``with launch_simulation(...)`` block, once the simulator runtime has been launched. Accessing scene elements ------------------------ diff --git a/docs/source/how-to/index.rst b/docs/source/how-to/index.rst index d6ed14fbb84d..986d37653bf2 100644 --- a/docs/source/how-to/index.rst +++ b/docs/source/how-to/index.rst @@ -54,7 +54,7 @@ How-to Guides .. container:: guide-entry - :doc:`Deep-dive into AppLauncher ` + :doc:`Deep-dive into launching the simulation ` Configure and launch simulation applications from Python and the command line. diff --git a/docs/source/how-to/launch_app.rst b/docs/source/how-to/launch_app.rst index 655ab30c4708..972e8c298895 100644 --- a/docs/source/how-to/launch_app.rst +++ b/docs/source/how-to/launch_app.rst @@ -1,23 +1,31 @@ :orphan: -Deep-dive into AppLauncher -========================== +Deep-dive into the simulation launcher +====================================== .. currentmodule:: isaaclab -In this tutorial, we will dive into the :class:`app.AppLauncher` class to configure the simulator using +In this tutorial, we will dive into how a standalone script launches the simulator and configures it using CLI arguments and environment variables (envars). Particularly, we will demonstrate how to use -:class:`~app.AppLauncher` to enable livestreaming and configure the :class:`isaacsim.simulation_app.SimulationApp` -instance it wraps, while also allowing user-provided options. - -The :class:`~app.AppLauncher` is a wrapper for :class:`~isaacsim.simulation_app.SimulationApp` to simplify -its configuration. The :class:`~isaacsim.simulation_app.SimulationApp` has many extensions that must be -loaded to enable different capabilities, and some of these extensions are order- and inter-dependent. -Additionally, there are startup options such as ``headless`` which must be set at instantiation time, -and which have an implied relationship with some extensions, e.g. the livestreaming extensions. -The :class:`~app.AppLauncher` presents an interface that can handle these extensions and startup -options in a portable manner across a variety of use cases. To achieve this, we offer CLI and envar -flags which can be merged with user-defined CLI args, while passing forward arguments intended +:func:`app.add_launcher_args` and :func:`app.launch_simulation` to enable livestreaming and to configure +the :class:`isaacsim.simulation_app.SimulationApp` instance that runs Isaac Sim, while also allowing +user-provided options. + +Launching is split into two steps. First, :func:`app.add_launcher_args` appends the launch-related options +to the script's own :class:`argparse.ArgumentParser`. Then, the :func:`app.launch_simulation` context manager +takes the simulation configuration and the parsed arguments, and starts only the runtime that they need. +The default PhysX physics backend, Kit-based RTX cameras, the Kit visualizer (``--visualizer kit``) and +livestreaming all run inside Isaac Sim (Kit), so for these :func:`~app.launch_simulation` starts a +:class:`~isaacsim.simulation_app.SimulationApp`. A kitless configuration, for example Newton physics +without a Kit visualizer, starts no Isaac Sim at all. The runtime is closed automatically when the +``with`` block exits. + +The :class:`~isaacsim.simulation_app.SimulationApp` has many extensions that must be loaded to enable +different capabilities, and some of these extensions are order- and inter-dependent. Additionally, there are +startup options such as ``headless`` which must be set at instantiation time, and which have an implied +relationship with some extensions, e.g. the livestreaming extensions. The launcher handles these extensions +and startup options in a portable manner across a variety of use cases. To achieve this, we offer CLI and +envar flags which can be merged with user-defined CLI args, while passing forward arguments intended for :class:`~isaacsim.simulation_app.SimulationApp`. @@ -32,7 +40,7 @@ The tutorial corresponds to the ``launch_app.py`` script in the .. literalinclude:: ../../../scripts/tutorials/00_sim/launch_app.py :language: python - :emphasize-lines: 18-40 + :emphasize-lines: 19-37, 69-75 :linenos: The Code Explained @@ -41,34 +49,42 @@ The Code Explained Adding arguments to the argparser ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -:class:`~app.AppLauncher` is designed to be compatible with custom CLI args that users need for +The launcher is designed to be compatible with custom CLI args that users need for their own scripts, while still providing a portable CLI interface. In this tutorial, a standard :class:`argparse.ArgumentParser` is instantiated and given the script-specific ``--size`` argument, as well as the arguments ``--height`` and ``--width``. The latter are ingested by :class:`~isaacsim.simulation_app.SimulationApp`. -The argument ``--size`` is not used by :class:`~app.AppLauncher`, but will merge seamlessly -with the :class:`~app.AppLauncher` interface. In-script arguments can be merged with the -:class:`~app.AppLauncher` interface via the :meth:`~app.AppLauncher.add_app_launcher_args` method, -which will return a modified :class:`~argparse.ArgumentParser` with the :class:`~app.AppLauncher` -arguments appended. This can then be processed into an :class:`argparse.Namespace` using the -standard :meth:`argparse.ArgumentParser.parse_args` method and passed directly to -:class:`~app.AppLauncher` for instantiation. +The argument ``--size`` is not used by the launcher, but merges seamlessly with the launcher +interface. In-script arguments are merged with the launcher interface via the +:func:`~app.add_launcher_args` function, which appends the launcher arguments to the given +:class:`~argparse.ArgumentParser`. This can then be processed into an :class:`argparse.Namespace` using the +standard :meth:`argparse.ArgumentParser.parse_args` method. .. literalinclude:: ../../../scripts/tutorials/00_sim/launch_app.py :language: python :start-at: import argparse - :end-at: simulation_app = app_launcher.app + :end-at: args_cli = parser.parse_args() -The above illustrates one of several ways of passing arguments to :class:`~app.AppLauncher`. -Please consult its documentation page to see further options. +Launching the simulator +^^^^^^^^^^^^^^^^^^^^^^^ + +The parsed arguments are passed, together with the simulation configuration, to +:func:`~app.launch_simulation`. The configuration tells it which runtime is needed (here, the default +PhysX physics backend, which runs inside Isaac Sim), and the arguments configure that runtime. +Everything that uses the simulator runs inside the ``with`` block. + +.. literalinclude:: ../../../scripts/tutorials/00_sim/launch_app.py + :language: python + :start-at: # Configure the simulation + :end-at: sim = sim_utils.SimulationContext(sim_cfg) Understanding the output of --help ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ While executing the script, we can pass the ``--help`` argument and see the combined outputs of the -custom arguments and the non-deprecated options from :class:`~app.AppLauncher`. +custom arguments and the launcher options (abbreviated below). .. tab-set:: @@ -78,33 +94,35 @@ custom arguments and the non-deprecated options from :class:`~app.AppLauncher`. uv run python scripts/tutorials/00_sim/launch_app.py --help - [INFO] Using python from: /isaac-sim/python.sh - [INFO][AppLauncher]: The argument 'width' will be used to configure the SimulationApp. - [INFO][AppLauncher]: The argument 'height' will be used to configure the SimulationApp. - usage: launch_app.py [-h] [--size SIZE] [--width WIDTH] [--height HEIGHT] [--livestream {0,1,2}] - [] [--visualizer VISUALIZER] [--verbose] [--experience EXPERIENCE] + usage: launch_app.py [-h] [--size SIZE] [--width WIDTH] [--height HEIGHT] [--livestream {0,1,2}] [--xr] + [--device DEVICE] [--visualizer VISUALIZER] [--verbose] [--info] [--experience EXPERIENCE] + ... - Tutorial on running IsaacSim via the AppLauncher. + Tutorial on configuring the simulator launch. options: - -h, --help show this help message and exit - --size SIZE Side-length of cuboid - --width WIDTH Width of the viewport and generated images. Defaults to 1280 - --height HEIGHT Height of the viewport and generated images. Defaults to 720 - - app_launcher arguments: - --livestream {0,1,2} - Force enable livestreaming. Mapping corresponds to that for the "LIVESTREAM" environment variable. - Enable cameras when running without a GUI. - --visualizer VISUALIZER, --viz VISUALIZER - Visualizer backends as CSV (e.g., kit,newton,rerun,viser) or none. - --verbose Enable verbose terminal logging from the SimulationApp. - --experience EXPERIENCE - The experience file to load when launching the SimulationApp. - - * If an empty string is provided, the experience file is determined based on the launch mode. - * If a relative path is provided, it is resolved relative to the `apps` folder in Isaac Sim and - Isaac Lab (in that order). + -h, --help show this help message and exit + --size SIZE Side-length of cuboid + --width WIDTH Width of the viewport and generated images. Defaults to 1280 + --height HEIGHT Height of the viewport and generated images. Defaults to 720 + + launcher arguments: + Arguments for the KitLauncher. For more details, please check the documentation. + + --livestream {0,1,2} Force enable livestreaming. Mapping corresponds to that for the `LIVESTREAM` environment + variable. + --xr Enable XR mode for VR/AR applications. + --device DEVICE The device to run the simulation on. Can be "cpu", "cuda", "cuda:N", where N is the device ID + --visualizer VISUALIZER, --viz VISUALIZER + Visualizer backends to enable as CSV (e.g., kit,newton,rerun,viser). + --verbose Enable verbose-level log output from the SimulationApp. + --info Enable info-level log output from the SimulationApp. + --experience EXPERIENCE + The experience file to load when launching the SimulationApp. If an empty string is provided, + the experience file is determined from the resolved visualizer and XR settings. If a relative + path is provided, it is resolved relative to the `apps` folder in Isaac Sim and Isaac Lab (in + that order). + ... .. tab-item:: isaaclab.sh / isaaclab.bat @@ -112,58 +130,58 @@ custom arguments and the non-deprecated options from :class:`~app.AppLauncher`. ./isaaclab.sh -p scripts/tutorials/00_sim/launch_app.py --help - [INFO] Using python from: /isaac-sim/python.sh - [INFO][AppLauncher]: The argument 'width' will be used to configure the SimulationApp. - [INFO][AppLauncher]: The argument 'height' will be used to configure the SimulationApp. - usage: launch_app.py [-h] [--size SIZE] [--width WIDTH] [--height HEIGHT] [--livestream {0,1,2}] - [] [--visualizer VISUALIZER] [--verbose] [--experience EXPERIENCE] + usage: launch_app.py [-h] [--size SIZE] [--width WIDTH] [--height HEIGHT] [--livestream {0,1,2}] [--xr] + [--device DEVICE] [--visualizer VISUALIZER] [--verbose] [--info] [--experience EXPERIENCE] + ... - Tutorial on running IsaacSim via the AppLauncher. + Tutorial on configuring the simulator launch. options: - -h, --help show this help message and exit - --size SIZE Side-length of cuboid - --width WIDTH Width of the viewport and generated images. Defaults to 1280 - --height HEIGHT Height of the viewport and generated images. Defaults to 720 - - app_launcher arguments: - --livestream {0,1,2} - Force enable livestreaming. Mapping corresponds to that for the "LIVESTREAM" environment variable. - Enable cameras when running without a GUI. - --visualizer VISUALIZER, --viz VISUALIZER - Visualizer backends as CSV (e.g., kit,newton,rerun,viser) or none. - --verbose Enable verbose terminal logging from the SimulationApp. - --experience EXPERIENCE - The experience file to load when launching the SimulationApp. - - * If an empty string is provided, the experience file is determined based on the launch mode. - * If a relative path is provided, it is resolved relative to the `apps` folder in Isaac Sim and - Isaac Lab (in that order). + -h, --help show this help message and exit + --size SIZE Side-length of cuboid + --width WIDTH Width of the viewport and generated images. Defaults to 1280 + --height HEIGHT Height of the viewport and generated images. Defaults to 720 + + launcher arguments: + Arguments for the KitLauncher. For more details, please check the documentation. + + --livestream {0,1,2} Force enable livestreaming. Mapping corresponds to that for the `LIVESTREAM` environment + variable. + --xr Enable XR mode for VR/AR applications. + --device DEVICE The device to run the simulation on. Can be "cpu", "cuda", "cuda:N", where N is the device ID + --visualizer VISUALIZER, --viz VISUALIZER + Visualizer backends to enable as CSV (e.g., kit,newton,rerun,viser). + --verbose Enable verbose-level log output from the SimulationApp. + --info Enable info-level log output from the SimulationApp. + --experience EXPERIENCE + The experience file to load when launching the SimulationApp. If an empty string is provided, + the experience file is determined from the resolved visualizer and XR settings. If a relative + path is provided, it is resolved relative to the `apps` folder in Isaac Sim and Isaac Lab (in + that order). + ... This readout details the ``--size``, ``--height``, and ``--width`` arguments defined in the script directly, -as well as the :class:`~app.AppLauncher` arguments. +as well as the launcher arguments. -The ``[INFO]`` messages preceding the help output also reads out which of these arguments are going -to be interpreted as arguments to the :class:`~isaacsim.simulation_app.SimulationApp` instance which the -:class:`~app.AppLauncher` class wraps. In this case, it is ``--height`` and ``--width``. These -are classified as such because they match the name and type of an argument which can be processed -by :class:`~isaacsim.simulation_app.SimulationApp`. Please refer to the `specification`_ for such arguments -for more examples. +Script arguments whose name and type match an argument of +:class:`~isaacsim.simulation_app.SimulationApp`, in this case ``--height`` and ``--width``, are forwarded +to the :class:`~isaacsim.simulation_app.SimulationApp` instance when :func:`~app.launch_simulation` +starts Isaac Sim. Please refer to the `specification`_ for such arguments for more examples. Using environment variables ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -As noted in the help message, :class:`~app.AppLauncher` arguments such as ``--livestream`` +As noted in the help message, launcher arguments such as ``--livestream`` have corresponding environment variables (envar) as well. These are detailed in :mod:`isaaclab.app` documentation. Providing any of these arguments through CLI is equivalent to running the script in a shell environment where the corresponding envar is set. -The support for :class:`~app.AppLauncher` envars are simply a convenience to provide session-persistent +The support for launcher envars is simply a convenience to provide session-persistent configurations, and can be set in the user's ``${HOME}/.bashrc`` for persistent settings between sessions. In the case where these arguments are provided from the CLI, they will override their corresponding envar, as we will demonstrate later in this tutorial. -These arguments can be used with any script that starts the simulation using :class:`~app.AppLauncher`. +These arguments can be used with any script that starts the simulation using :func:`~app.launch_simulation`. Camera and offscreen rendering support is configured automatically. @@ -245,7 +263,7 @@ process can be killed by pressing ``Ctrl+C`` in the launching terminal. :figwidth: 100% :alt: result of launch_app.py -Now, let's look at how :class:`~app.AppLauncher` handles conflicting commands: +Now, let's look at how the launcher handles conflicting commands: .. tab-set:: :sync-group: os @@ -314,7 +332,7 @@ in our envars, CLI args such as ``--livestream`` take precedence in determining be killed by pressing ``Ctrl+C`` in the launching terminal. Finally, we will examine passing arguments to :class:`~isaacsim.simulation_app.SimulationApp` through -:class:`~app.AppLauncher`: +:func:`~app.launch_simulation`: .. tab-set:: :sync-group: os diff --git a/docs/source/how-to/register_rl_env_gym.rst b/docs/source/how-to/register_rl_env_gym.rst index c67c5a3eab5c..31c7356c18fa 100644 --- a/docs/source/how-to/register_rl_env_gym.rst +++ b/docs/source/how-to/register_rl_env_gym.rst @@ -8,8 +8,7 @@ Registering an Environment .. currentmodule:: isaaclab In the previous tutorial, we learned how to create a custom cartpole environment. We manually -created an instance of the environment by importing the environment class and its configuration -class. +created an instance of the environment from the class named by its configuration's ``class_type``. .. dropdown:: Environment creation in the previous tutorial :icon: code @@ -17,7 +16,7 @@ class. .. literalinclude:: ../../../scripts/tutorials/03_envs/run_cartpole_rl_env.py :language: python :start-at: # create environment configuration - :end-at: env = ManagerBasedRLEnv(cfg=env_cfg) + :end-at: env = instantiate(env_cfg) While straightforward, this approach is not scalable as we have a large suite of environments. In this tutorial, we will show how to use the :meth:`gymnasium.register` method to register diff --git a/docs/source/how-to/run_articulation.rst b/docs/source/how-to/run_articulation.rst index 7fdd6dd53795..9cd1ff3c3b13 100644 --- a/docs/source/how-to/run_articulation.rst +++ b/docs/source/how-to/run_articulation.rst @@ -29,7 +29,7 @@ directory. .. literalinclude:: ../../../scripts/tutorials/01_assets/run_articulation.py :language: python - :emphasize-lines: 58-69, 91-104, 108-111, 116-117 + :emphasize-lines: 53-64, 86-104, 106-112, 117-118 :linenos: diff --git a/docs/source/how-to/run_diff_ik.rst b/docs/source/how-to/run_diff_ik.rst index e084871794a6..024a25e6629a 100644 --- a/docs/source/how-to/run_diff_ik.rst +++ b/docs/source/how-to/run_diff_ik.rst @@ -29,7 +29,7 @@ The tutorial corresponds to the ``run_diff_ik.py`` script in the .. literalinclude:: ../../../scripts/tutorials/05_controllers/run_diff_ik.py :language: python - :emphasize-lines: 98-100, 121-136, 155-157, 161-171 + :emphasize-lines: 98-100, 121-136, 156-158, 162-175 :linenos: diff --git a/docs/source/how-to/run_rigid_object.rst b/docs/source/how-to/run_rigid_object.rst index 44bd533c7d29..9cfd1c5d5b24 100644 --- a/docs/source/how-to/run_rigid_object.rst +++ b/docs/source/how-to/run_rigid_object.rst @@ -26,7 +26,7 @@ The tutorial corresponds to the ``run_rigid_object.py`` script in the ``scripts/ .. literalinclude:: ../../../scripts/tutorials/01_assets/run_rigid_object.py :language: python - :emphasize-lines: 55-74, 76-78, 98-108, 111-112, 118-119, 132-134, 139-140 + :emphasize-lines: 49-68, 70-72, 92-104, 107-108, 114-115, 131-133, 138-139 :linenos: diff --git a/docs/source/how-to/run_surface_gripper.rst b/docs/source/how-to/run_surface_gripper.rst index 71a74fde5844..fdae26d197b6 100644 --- a/docs/source/how-to/run_surface_gripper.rst +++ b/docs/source/how-to/run_surface_gripper.rst @@ -25,7 +25,7 @@ directory. .. literalinclude:: ../../../scripts/tutorials/01_assets/run_surface_gripper.py :language: python - :emphasize-lines: 61-85, 124-125, 128-142, 147-150 + :emphasize-lines: 58-82, 126-127, 130-144, 149-152 :linenos: diff --git a/docs/source/how-to/save_camera_output.rst b/docs/source/how-to/save_camera_output.rst index 9c3661f36a0a..83c648bebc98 100644 --- a/docs/source/how-to/save_camera_output.rst +++ b/docs/source/how-to/save_camera_output.rst @@ -16,40 +16,30 @@ directory. .. literalinclude:: ../../../scripts/tutorials/04_sensors/run_usd_camera.py :language: python - :emphasize-lines: 174-182, 232-250, 254-267 + :emphasize-lines: 173-175, 224-229, 231-250 :linenos: -Saving using Replicator Basic Writer ------------------------------------- +Saving the images to file +------------------------- -.. note:: - The BasicWriter is part of the Omniverse Replicator ecosystem and is specific to the default - Isaac RTX renderer backend. Other renderer backends may require different save workflows. - -.. note:: - The ``colorize_*`` arguments below are set on - :attr:`~isaaclab.sensors.camera.CameraCfg.renderer_cfg` (an - :class:`~isaaclab_physx.renderers.IsaacRtxRendererCfg`); the same-named - fields on :class:`~isaaclab.sensors.camera.CameraCfg` are deprecated. - -To save camera outputs, we use the basic write class from Omniverse Replicator. This class allows us to save the -images in a numpy format. For more information on the basic writer, please check the -`documentation `_. +To save camera outputs, we use the :func:`~isaaclab.sensors.camera.utils.save_images_to_file` utility. +It writes a batch of images as a PNG file and does not depend on the renderer backend. The script creates +the output folder once, before the simulation loop: .. literalinclude:: ../../../scripts/tutorials/04_sensors/run_usd_camera.py :language: python - :start-at: rep_writer = rep.BasicWriter( - :end-before: # Camera positions, targets, orientations + :start-at: # Create the output directory + :end-at: os.makedirs(output_dir, exist_ok=True) -While stepping the simulator, the images can be saved to the defined folder. Since the BasicWriter only supports -saving data using NumPy format, we first need to convert the PyTorch sensors to NumPy arrays before packing -them in a dictionary and writing with the BasicWriter. +While stepping the simulator, the 8-bit color outputs (the RGB image and the colorized segmentations) of the +camera at ``camera_index`` are saved as one PNG file per data type and frame. Floating-point outputs, such as +depth and normals, are not saved. .. literalinclude:: ../../../scripts/tutorials/04_sensors/run_usd_camera.py :language: python - :start-at: # Save images from camera at camera_index - :end-at: rep_writer.write(rep_output) + :start-at: # Save the 8-bit color images + :end-at: save_images_to_file(data.torch[camera_index Projection into 3D Space @@ -77,7 +67,7 @@ to create a point cloud from the depth image and transform it to the world frame :start-at: # Derive pointcloud from camera at camera_index :end-before: # In the first few steps, things are still being instanced and Camera.data -The resulting point cloud can be visualized using the :mod:`isaacsim.util.debug_draw` extension from Isaac Sim. +The resulting point cloud can be visualized using :class:`~isaaclab.markers.VisualizationMarkers`. This makes it easy to visualize the point cloud in the 3D space. .. literalinclude:: ../../../scripts/tutorials/04_sensors/run_usd_camera.py @@ -101,7 +91,7 @@ To run the accompanying script, execute the following command: The simulation should start, and you can observe different objects falling down. An output folder will be created -in the ``IsaacLab/scripts/tutorials/04_sensors`` directory, where the images will be saved. Additionally, +in the ``IsaacLab/scripts/tutorials/04_sensors`` directory, where the images will be saved as PNG files. Additionally, you should see the point cloud in the 3D space drawn on the viewport. To stop the simulation, close the window, or use ``Ctrl+C`` in the terminal. diff --git a/docs/source/how-to/spawn_prims.rst b/docs/source/how-to/spawn_prims.rst index 334eca949ab4..110523b80429 100644 --- a/docs/source/how-to/spawn_prims.rst +++ b/docs/source/how-to/spawn_prims.rst @@ -39,7 +39,7 @@ Let's take a look at the Python script: .. literalinclude:: ../../../scripts/tutorials/00_sim/spawn_prims.py :language: python - :emphasize-lines: 40-88, 100-101 + :emphasize-lines: 38-86, 100-101 :linenos: diff --git a/docs/source/how-to/wrap_rl_env.rst b/docs/source/how-to/wrap_rl_env.rst index a5195607cdba..2174ddab4b75 100644 --- a/docs/source/how-to/wrap_rl_env.rst +++ b/docs/source/how-to/wrap_rl_env.rst @@ -21,27 +21,20 @@ For example, here is how you would wrap an environment to enforce that reset is .. code-block:: python - """Launch Isaac Sim Simulator first.""" - - - from isaaclab.app import AppLauncher - - # launch omniverse app in headless mode - app_launcher = AppLauncher(headless=True) - simulation_app = app_launcher.app - - """Rest everything follows.""" - import gymnasium as gym + from isaaclab.app import launch_simulation + import isaaclab_tasks # noqa: F401 from isaaclab_tasks.utils import load_cfg_from_registry - # create base environment cfg = load_cfg_from_registry("Isaac-Reach-Franka", "env_cfg_entry_point") - env = gym.make("Isaac-Reach-Franka", cfg=cfg) - # wrap environment to enforce that reset is called before step - env = gym.wrappers.OrderEnforcing(env) + # start the simulation runtime this configuration needs, and stop it on exit + with launch_simulation(cfg): + # create base environment + env = gym.make("Isaac-Reach-Franka", cfg=cfg) + # wrap environment to enforce that reset is called before step + env = gym.wrappers.OrderEnforcing(env) Wrapper for recording videos @@ -93,33 +86,26 @@ for 200 steps, and saves it in the ``videos`` folder at a step interval of 1500 .. code:: python - """Launch Isaac Sim Simulator first.""" - - - from isaaclab.app import AppLauncher - - # launch omniverse app in headless mode with off-screen rendering - app_launcher = AppLauncher(headless=True, enable_cameras=True) - simulation_app = app_launcher.app - - """Rest everything follows.""" - import gymnasium as gym + from isaaclab.app import launch_simulation + # adjust camera resolution and pose env_cfg.viewer.resolution = (640, 480) env_cfg.viewer.eye = (1.0, 1.0, 1.0) env_cfg.viewer.lookat = (0.0, 0.0, 0.0) - # create isaac-env instance - # set render mode to rgb_array to obtain images on render calls - env = gym.make(task_name, cfg=env_cfg, render_mode="rgb_array") - # wrap for video recording - video_kwargs = { - "video_folder": "videos/train", - "step_trigger": lambda step: step % 1500 == 0, - "video_length": 200, - } - env = gym.wrappers.RecordVideo(env, **video_kwargs) + # start the runtime with off-screen rendering enabled for the video frames + with launch_simulation(env_cfg, {"enable_cameras": True}): + # create isaac-env instance + # set render mode to rgb_array to obtain images on render calls + env = gym.make(task_name, cfg=env_cfg, render_mode="rgb_array") + # wrap for video recording + video_kwargs = { + "video_folder": "videos/train", + "step_trigger": lambda step: step % 1500 == 0, + "video_length": 200, + } + env = gym.wrappers.RecordVideo(env, **video_kwargs) Wrapper for learning frameworks diff --git a/docs/source/policy_deployment/05_leapp/exporting_policies_with_leapp.rst b/docs/source/policy_deployment/05_leapp/exporting_policies_with_leapp.rst index d1752232cde4..59a0e2ff991c 100644 --- a/docs/source/policy_deployment/05_leapp/exporting_policies_with_leapp.rst +++ b/docs/source/policy_deployment/05_leapp/exporting_policies_with_leapp.rst @@ -204,7 +204,7 @@ CLI Options ^^^^^^^^^^^ The export command accepts the following common LEAPP-specific arguments in addition to -backend-specific and AppLauncher arguments: +backend-specific and launcher arguments (see :func:`~isaaclab.app.add_launcher_args`): .. list-table:: :widths: 30 15 55 diff --git a/docs/source/refs/issues.rst b/docs/source/refs/issues.rst index 8d17d43e06cc..7c8a73550cb2 100644 --- a/docs/source/refs/issues.rst +++ b/docs/source/refs/issues.rst @@ -265,6 +265,6 @@ GLIBCXX errors in conda environments Some workflows exit with an ``OSError`` indicating ``version 'GLIBCXX_3.4.30' not found`` when running from a conda environment. The issue appears to stem from importing torch or -torch-related packages, such as tensorboard, prior to launching ``AppLauncher``. As a -workaround, ensure that all torch imports happen after the ``AppLauncher`` instance has been -created, which should resolve the error. +torch-related packages, such as tensorboard, before Isaac Sim starts. As a workaround, ensure +that all torch imports happen inside :func:`~isaaclab.app.launch_simulation`, after the runtime has +started, which should resolve the error. diff --git a/docs/source/refs/troubleshooting.rst b/docs/source/refs/troubleshooting.rst index 8a7374863836..77c3e3af707f 100644 --- a/docs/source/refs/troubleshooting.rst +++ b/docs/source/refs/troubleshooting.rst @@ -312,7 +312,7 @@ Livestreaming and WebRTC ``NVST_R_BUSY`` / ``NVST_R_INTERNAL_ERROR`` on ``LIVESTREAM=1`` or ``LIVESTREAM=2`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -:class:`~isaaclab.app.AppLauncher` pins TCP port ``49100`` for WebRTC signaling whenever +:class:`~isaaclab_physx.app.KitLauncher` pins TCP port ``49100`` for WebRTC signaling whenever ``LIVESTREAM=1`` (public network) or ``LIVESTREAM=2`` (private network) is set. If a previous livestream process is still bound to that port, the new session fails to start with: diff --git a/examples/arl_robot_1.py b/examples/arl_robot_1.py index f125c89dcc41..43787ccf9706 100644 --- a/examples/arl_robot_1.py +++ b/examples/arl_robot_1.py @@ -69,7 +69,6 @@ def main(): robot_cfg.actuators["thrusters"].dt = sim_cfg.dt robot = instantiate(robot_cfg) - # Play the simulator sim.reset() # Create Lee position controller diff --git a/examples/bin_packing.py b/examples/bin_packing.py index fd7589002e93..e560f904ba9c 100644 --- a/examples/bin_packing.py +++ b/examples/bin_packing.py @@ -395,7 +395,6 @@ def main(): None: The function drives the simulation for its side-effects. """ with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: - # Load kit helper sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device, physics=physics_cfg) sim = sim_utils.SimulationContext(sim_cfg) # Set main camera @@ -408,14 +407,10 @@ def main(): with Timer("[INFO] Time to create scene: "): scene = instantiate(scene_cfg) - # Play the simulator sim.reset() - # Now we are ready! print("[INFO]: Setup complete...") - # Run the simulator run_simulator(sim, scene) if __name__ == "__main__": - # run the main execution main() diff --git a/examples/deformables.py b/examples/deformables.py index 4d0b78622b2d..9336fb661834 100644 --- a/examples/deformables.py +++ b/examples/deformables.py @@ -247,7 +247,6 @@ def main(): soft_contact_kd=1.0e0, soft_contact_mu=0.01, ) - # Initialize the simulation context sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device, physics=physics_cfg) sim = sim_utils.SimulationContext(sim_cfg) # Set main camera @@ -255,14 +254,11 @@ def main(): scene_cfg = DeformablesSceneCfg(num_envs=1, env_spacing=0.0) scene = instantiate(scene_cfg) - # Play the simulator sim.reset() - # Now we are ready! print("[INFO]: Setup complete...") run_simulator(sim, scene.deformable_objects) print("[INFO]: Simulation complete...") if __name__ == "__main__": - # run the main function main() diff --git a/examples/demos/pick_and_place.py b/examples/demos/pick_and_place.py index ba7dba01e4e3..4da27b871ae5 100644 --- a/examples/demos/pick_and_place.py +++ b/examples/demos/pick_and_place.py @@ -13,7 +13,7 @@ import torch import warp as wp -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation parser = argparse.ArgumentParser(description="Keyboard control for Isaac Lab Pick and Place.") parser.add_argument("--num_envs", type=int, default=32, help="Number of environments to spawn.") @@ -24,30 +24,25 @@ choices=["isaacsim_physx"], help="Physics backend.", ) -AppLauncher.add_app_launcher_args(parser) -parser.set_defaults(visualizer=["kit"]) +add_launcher_args(parser) +# surface grippers only run on CPU, and the launcher applies --device to the environment +parser.set_defaults(visualizer=["kit"], device="cpu") args_cli = parser.parse_args() if args_cli.num_envs < 1: parser.error("--num_envs must be at least 1.") if args_cli.max_steps == 0 or args_cli.max_steps < -1: parser.error("--max_steps must be positive or -1.") -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -# Kit modules must be imported after AppLauncher starts SimulationApp. from isaaclab_physx.assets import SurfaceGripperCfg -import carb -import omni - import isaaclab.sim as sim_utils from isaaclab.assets import ArticulationCfg, AssetBaseCfg, RigidObjectCfg +from isaaclab.devices import Se3KeyboardCfg from isaaclab.envs import DirectRLEnv, DirectRLEnvCfg from isaaclab.markers import SPHERE_MARKER_CFG from isaaclab.scene import InteractiveSceneCfg from isaaclab.sim import SimulationCfg -from isaaclab.utils import configclass, index_fill_, replace +from isaaclab.utils import configclass, index_fill_, instantiate, replace from isaaclab.utils.math import sample_uniform from isaaclab_assets.robots.pick_and_place import PICK_AND_PLACE_CFG @@ -138,7 +133,8 @@ class PickAndPlaceEnv(DirectRLEnv): """Example environment for a PickAndPlace robot using suction-cups. This example follows what would be typically done in a DirectRL pipeline. - Here we substitute the policy by keyboard inputs. + Here we substitute the policy by keyboard inputs. The 4-D action holds the x and y efforts, + the z effort and the gripper command (-1 open, 1 close, 0 idle). """ cfg: PickAndPlaceEnvCfg @@ -162,113 +158,37 @@ def __init__(self, cfg: PickAndPlaceEnvCfg, render_mode: str | None = None, **kw self.go_to_cube = torch.zeros(self.num_envs, dtype=torch.bool, device=self.device) self.go_to_target = torch.zeros(self.num_envs, dtype=torch.bool, device=self.device) self.target_pos = torch.zeros((self.num_envs, 3), device=self.device, dtype=torch.float32) - self.instant_controls = torch.zeros((self.num_envs, 3), device=self.device, dtype=torch.float32) - self.permanent_controls = torch.zeros((self.num_envs, 1), device=self.device, dtype=torch.float32) # Visual marker for the target self.set_debug_vis(self.cfg.debug_vis) - # Sets up the keyboard callback and settings - self.set_up_keyboard() - - def set_up_keyboard(self) -> None: - """Register keyboard controls.""" - self._input = carb.input.acquire_input_interface() - self._keyboard = omni.appwindow.get_default_app_window().get_keyboard() - self._sub_keyboard = self._input.subscribe_to_keyboard_events(self._keyboard, self._on_keyboard_event) - self._instant_key_controls = { - "Q": torch.tensor([0, 0, -1]), - "E": torch.tensor([0, 0, 1]), - "ZEROS": torch.tensor([0, 0, 0]), - } - self._permanent_key_controls = { - "W": torch.tensor([-200.0], device=self.device), - "S": torch.tensor([100.0], device=self.device), - } - self._auto_aim_cube = "A" - self._auto_aim_target = "D" - - # Task description: - print("Keyboard set up!") - print("The simulation is ready for you to try it out!") - print("Your goal is pick up the purple cube and to drop it on the red sphere!") - print(f"Number of environments: {self.num_envs}") - print("Use the following controls to interact with ALL environments simultaneously:") - print("Press the 'A' key to have all grippers track the cube position.") - print("Press the 'D' key to have all grippers track the target position") - print("Press the 'W' or 'S' keys to move all gantries UP or DOWN respectively") - print("Press 'Q' or 'E' to OPEN or CLOSE all grippers respectively") - - def _on_keyboard_event(self, event: carb.input.KeyboardEvent) -> bool: - """Update controls from a keyboard event.""" - if event.type == carb.input.KeyboardEventType.KEY_PRESS: - if event.input.name == self._auto_aim_target: - self.go_to_target[:] = True - self.go_to_cube[:] = False - if event.input.name == self._auto_aim_cube: - self.go_to_cube[:] = True - self.go_to_target[:] = False - if event.input.name in self._instant_key_controls: - self.go_to_cube[:] = False - self.go_to_target[:] = False - self.instant_controls[:] = self._instant_key_controls[event.input.name] - if event.input.name in self._permanent_key_controls: - self.go_to_cube[:] = False - self.go_to_target[:] = False - self.permanent_controls[:] = self._permanent_key_controls[event.input.name] - # On key release, all robots stop moving - elif event.type == carb.input.KeyboardEventType.KEY_RELEASE: - self.go_to_cube[:] = False - self.go_to_target[:] = False - self.instant_controls[:] = self._instant_key_controls["ZEROS"] - return True + def auto_aim(self, cube: bool) -> None: + """Make all grippers track the cube (``cube=True``) or the target position.""" + self.go_to_cube[:] = cube + self.go_to_target[:] = not cube def _pre_physics_step(self, actions: torch.Tensor) -> None: # Store the actions self.actions = actions.clone() def _apply_action(self) -> None: - # We use the keyboard outputs as an action. - # Process each environment independently - if self.go_to_cube.any(): - # Effort based proportional controller to track the cube position - head_pos_x = self.pick_and_place.data.joint_pos.torch[self.go_to_cube, self._x_dof_idx[0]] - head_pos_y = self.pick_and_place.data.joint_pos.torch[self.go_to_cube, self._y_dof_idx[0]] - cube_pos_x = ( - self.cube.data.root_pos_w.torch[self.go_to_cube, 0] - self.scene.env_origins[self.go_to_cube, 0] - ) - cube_pos_y = ( - self.cube.data.root_pos_w.torch[self.go_to_cube, 1] - self.scene.env_origins[self.go_to_cube, 1] - ) - d_cube_robot_x = cube_pos_x - head_pos_x - d_cube_robot_y = cube_pos_y - head_pos_y - self.instant_controls[self.go_to_cube] = torch.stack( - [d_cube_robot_x * 5.0, d_cube_robot_y * 5.0, torch.zeros_like(d_cube_robot_x)], dim=1 - ) - if self.go_to_target.any(): - # Effort based proportional controller to track the target position - head_pos_x = self.pick_and_place.data.joint_pos.torch[self.go_to_target, self._x_dof_idx[0]] - head_pos_y = self.pick_and_place.data.joint_pos.torch[self.go_to_target, self._y_dof_idx[0]] - target_pos_x = self.target_pos[self.go_to_target, 0] - target_pos_y = self.target_pos[self.go_to_target, 1] - d_target_robot_x = target_pos_x - head_pos_x - d_target_robot_y = target_pos_y - head_pos_y - self.instant_controls[self.go_to_target] = torch.stack( - [d_target_robot_x * 5.0, d_target_robot_y * 5.0, torch.zeros_like(d_target_robot_x)], dim=1 - ) + xy_efforts = self.actions[:, :2].clone() + # Manual x/y input cancels auto-aim + manual = xy_efforts.any(dim=1) + self.go_to_cube &= ~manual + self.go_to_target &= ~manual + # Effort based proportional controller to track the cube or the target position + head_pos_xy = self.pick_and_place.data.joint_pos.torch[:, [self._x_dof_idx[0], self._y_dof_idx[0]]] + cube_pos_xy = self.cube.data.root_pos_w.torch[:, :2] - self.scene.env_origins[:, :2] + for mask, goal_xy in ((self.go_to_cube, cube_pos_xy), (self.go_to_target, self.target_pos[:, :2])): + xy_efforts[mask] = (goal_xy[mask] - head_pos_xy[mask]) * 5.0 # Set the joint effort targets for the picker - self.pick_and_place.set_joint_effort_target_index( - target=self.instant_controls[:, 0].unsqueeze(dim=1), joint_ids=self._x_dof_idx - ) - self.pick_and_place.set_joint_effort_target_index( - target=self.instant_controls[:, 1].unsqueeze(dim=1), joint_ids=self._y_dof_idx - ) - self.pick_and_place.set_joint_effort_target_index( - target=self.permanent_controls[:, 0].unsqueeze(dim=1), joint_ids=self._z_dof_idx - ) + self.pick_and_place.set_joint_effort_target_index(target=xy_efforts[:, 0:1], joint_ids=self._x_dof_idx) + self.pick_and_place.set_joint_effort_target_index(target=xy_efforts[:, 1:2], joint_ids=self._y_dof_idx) + self.pick_and_place.set_joint_effort_target_index(target=self.actions[:, 2:3], joint_ids=self._z_dof_idx) # Set the gripper command - self.gripper.set_grippers_command(self.instant_controls[:, 2]) + self.gripper.set_grippers_command(self.actions[:, 3]) def _get_observations(self) -> dict[str, torch.Tensor]: # Get the observations @@ -398,21 +318,39 @@ def main() -> None: """Run the interactive surface-gripper demo.""" env_cfg = PickAndPlaceEnvCfg() env_cfg.scene.num_envs = args_cli.num_envs - pick_and_place = PickAndPlaceEnv(env_cfg) - pick_and_place.reset() - step_count = 0 - try: - while simulation_app.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): - with torch.inference_mode(): - actions = torch.zeros((pick_and_place.num_envs, 4), device=pick_and_place.device) - pick_and_place.step(actions) - step_count += 1 - finally: - pick_and_place.close() + with launch_simulation(env_cfg, args_cli): + pick_and_place = PickAndPlaceEnv(env_cfg) + pick_and_place.reset() + actions = torch.zeros((pick_and_place.num_envs, 4), device=pick_and_place.device) + teleop = None + if pick_and_place.sim.has_gui: + teleop_cfg = Se3KeyboardCfg(pos_sensitivity=10.0, sim_device=pick_and_place.device) + teleop = instantiate(teleop_cfg) + teleop.add_callback("N", lambda: pick_and_place.auto_aim(cube=True)) + teleop.add_callback("M", lambda: pick_and_place.auto_aim(cube=False)) + print(teleop) + print("Pick up the purple cube and drop it on the red sphere, in ALL environments at once.") + print("\tW/S and A/D move the gantries, Q/E latch them UP/DOWN, K toggles the grippers.") + print("\tN/M make the grippers track the cube/target position.") + step_count = 0 + try: + while pick_and_place.sim.is_headless_or_exist_active_visualizer() and ( + args_cli.max_steps < 0 or step_count < args_cli.max_steps + ): + if teleop is not None: + cmd = teleop.advance() + actions[:, :2] = cmd[:2] + # Latch the gantry height effort; the z joint moves up for negative effort + if cmd[2] != 0: + actions[:, 2] = -200.0 if cmd[2] > 0 else 100.0 + # Se3Keyboard reports +1 for open, the surface gripper uses -1 for open + actions[:, 3] = -cmd[6] + with torch.inference_mode(): + pick_and_place.step(actions) + step_count += 1 + finally: + pick_and_place.close() if __name__ == "__main__": - try: - main() - finally: - simulation_app.close() + main() diff --git a/examples/demos/teapot_fill.py b/examples/demos/teapot_fill.py index 2899e87a0109..fea8a0d9caaf 100644 --- a/examples/demos/teapot_fill.py +++ b/examples/demos/teapot_fill.py @@ -823,7 +823,7 @@ def main() -> None: ) # Resolve after launching so Kit runs never import USD modules before - # AppLauncher; Newton-only runs still use standalone omni.client. + # Kit starts; Newton-only runs still use standalone omni.client. container_usd = retrieve_file_path(args_cli.container_usd) if {"kit", "newton_rtx"}.intersection(args_cli.visualizer or []): island_usd = retrieve_optional_visual_asset(args_cli.island_usd, "kitchen island") diff --git a/examples/markers.py b/examples/markers.py index 4a8bd7f11f57..402b1cbc3ca0 100644 --- a/examples/markers.py +++ b/examples/markers.py @@ -96,7 +96,6 @@ def define_markers() -> "VisualizationMarkers": def main(): """Main function.""" with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: - # Load kit helper sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device, physics=physics_cfg) sim = sim_utils.SimulationContext(sim_cfg) # Set main camera @@ -128,9 +127,7 @@ def main(): marker_locations = torch.stack([x_grid, y_grid, z_grid], dim=1) marker_indices = torch.arange(my_visualizer.num_prototypes).repeat(num_markers_per_type) - # Play the simulator sim.reset() - # Now we are ready! print("[INFO]: Setup complete...") # Yaw angle @@ -155,5 +152,4 @@ def main(): if __name__ == "__main__": - # run the main function main() diff --git a/examples/multi_asset.py b/examples/multi_asset.py index fc9e08820519..4b18df7688a7 100644 --- a/examples/multi_asset.py +++ b/examples/multi_asset.py @@ -242,14 +242,10 @@ def main(): with Timer("[INFO] Time to create scene: "): scene = instantiate(scene_cfg) - # Play the simulator sim.reset() - # Now we are ready! print("[INFO]: Setup complete...") - # Run the simulator run_simulator(sim, scene) if __name__ == "__main__": - # run the main execution main() diff --git a/examples/procedural_terrain.py b/examples/procedural_terrain.py index 86d236a78cb0..278ad110b534 100644 --- a/examples/procedural_terrain.py +++ b/examples/procedural_terrain.py @@ -160,21 +160,16 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, AssetBas def main(): """Main function.""" with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: - # Initialize the simulation context sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device, physics=physics_cfg) sim = sim_utils.SimulationContext(sim_cfg) # Set main camera sim.set_camera_view(eye=[15.0, 15.0, 15.0], target=[0.0, 0.0, 0.0]) # design scene scene_entities, scene_origins = design_scene() - # 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__": - # run the main function main() diff --git a/examples/sensors/cameras.py b/examples/sensors/cameras.py index 0f0dfe0641eb..9a0efda16d9f 100644 --- a/examples/sensors/cameras.py +++ b/examples/sensors/cameras.py @@ -10,19 +10,20 @@ # Usage uvx --from 'isaaclab[isaacsim]' isaaclab example camera - # Usage in headless mode - uvx --from 'isaaclab[isaacsim]' isaaclab example camera --headless + # Usage without a visualizer window + uvx --from 'isaaclab[isaacsim]' isaaclab example camera --viz none """ import argparse from pathlib import Path +from typing import TYPE_CHECKING import matplotlib.pyplot as plt import numpy as np import torch -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation parser = argparse.ArgumentParser(description="Example on using the different camera sensor implementations.") parser.add_argument("--num_envs", type=int, default=4, help="Number of environments to spawn.") @@ -38,7 +39,7 @@ choices=["isaacsim_physx"], help="Physics backend.", ) -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) parser.set_defaults(visualizer=["kit"]) args_cli = parser.parse_args() if args_cli.log_interval < 1: @@ -50,22 +51,20 @@ # Camera sensors require the rendering extensions in headless and viewport-free launches. args_cli.enable_cameras = True -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - - -# Simulator-dependent imports must follow AppLauncher initialization. import isaaclab.sim as sim_utils from isaaclab.assets import ArticulationCfg, AssetBaseCfg -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors import CameraCfg, RayCasterCameraCfg from isaaclab.sensors.ray_caster import patterns from isaaclab.terrains import TerrainImporterCfg -from isaaclab.utils import configclass, replace +from isaaclab.utils import configclass, instantiate, replace from isaaclab.terrains.config.rough import ROUGH_TERRAINS_CFG # isort:skip from isaaclab_assets.robots.anymal import ANYMAL_C_CFG # isort: skip +if TYPE_CHECKING: + from isaaclab.scene import InteractiveScene + @configclass class SensorsSceneCfg(InteractiveSceneCfg): @@ -161,7 +160,7 @@ def save_images_grid( plt.close() -def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene) -> None: +def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") -> None: """Run the simulator.""" # Define simulation stepping sim_dt = sim.get_physics_dt() @@ -170,7 +169,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene) -> if args_cli.save: args_cli.output_dir.mkdir(parents=True, exist_ok=True) - while simulation_app.is_running() and (args_cli.max_steps < 0 or count < args_cli.max_steps): + while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or count < args_cli.max_steps): # Reset if count % 500 == 0: # reset the scene entities @@ -236,24 +235,18 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene) -> def main() -> None: """Run the camera example.""" - # Initialize the simulation context sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device, use_fabric=not args_cli.disable_fabric) - sim = sim_utils.SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0]) - # design scene - scene_cfg = SensorsSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) - scene = InteractiveScene(scene_cfg) - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run the simulator - run_simulator(sim, scene) + with launch_simulation(sim_cfg, args_cli): + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0]) + # design scene + scene_cfg = SensorsSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) + scene = instantiate(scene_cfg) + sim.reset() + print("[INFO]: Setup complete...") + run_simulator(sim, scene) if __name__ == "__main__": - # run the main function main() - # close sim app - simulation_app.close() diff --git a/examples/sensors/multi_mesh_raycaster.py b/examples/sensors/multi_mesh_raycaster.py index 574abf924253..e2d190ff9ccc 100644 --- a/examples/sensors/multi_mesh_raycaster.py +++ b/examples/sensors/multi_mesh_raycaster.py @@ -315,7 +315,6 @@ def main(): with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: from isaaclab.scene import InteractiveScene - # Initialize the simulation context sim_cfg = sim_utils.SimulationCfg( dt=0.005, device=args_cli.device, @@ -332,14 +331,10 @@ def main(): if args_cli.asset_type == "objects": randomize_shape_color(scene_cfg.asset.prim_path.format(ENV_REGEX_NS="/World/envs/env_.*")) - # Play the simulator sim.reset() - # Now we are ready! print("[INFO]: Setup complete...") - # Run the simulator run_simulator(sim, scene) if __name__ == "__main__": - # run the main function main() diff --git a/examples/sensors/multi_mesh_raycaster_camera.py b/examples/sensors/multi_mesh_raycaster_camera.py index a13a7aad4e27..b7c248cd132c 100644 --- a/examples/sensors/multi_mesh_raycaster_camera.py +++ b/examples/sensors/multi_mesh_raycaster_camera.py @@ -24,10 +24,11 @@ import argparse import random +from typing import TYPE_CHECKING import torch -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation parser = argparse.ArgumentParser(description="Example on using the multi-mesh raycaster sensor.") parser.add_argument("--num_envs", type=int, default=16, help="Number of environments to spawn.") @@ -46,7 +47,7 @@ choices=["isaacsim_physx"], help="Physics backend.", ) -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) parser.set_defaults(visualizer=["kit"]) args_cli = parser.parse_args() if args_cli.log_interval < 1: @@ -54,26 +55,22 @@ if args_cli.max_steps == 0 or args_cli.max_steps < -1: parser.error("--max_steps must be positive or -1.") -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - - -# Simulator-dependent imports must follow AppLauncher initialization. from isaaclab_physx.sim.schemas import PhysxRigidBodyCfg -from pxr import Gf, Sdf - import isaaclab.sim as sim_utils from isaaclab.assets import Articulation, AssetBaseCfg, RigidObjectCfg from isaaclab.markers.config import VisualizationMarkersCfg -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors.ray_caster import MultiMeshRayCasterCameraCfg, patterns -from isaaclab.utils import configclass, replace +from isaaclab.utils import configclass, instantiate, replace from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR from isaaclab_assets.robots.allegro import ALLEGRO_HAND_CFG from isaaclab_assets.robots.anymal import ANYMAL_D_CFG +if TYPE_CHECKING: + from isaaclab.scene import InteractiveScene + RAY_CASTER_MARKER_CFG = VisualizationMarkersCfg( markers={ "hit": sim_utils.SphereCfg( @@ -106,7 +103,7 @@ height=120, width=240, ), - debug_vis=not args_cli.headless, + debug_vis=bool(args_cli.visualizer), visualizer_cfg=replace(RAY_CASTER_MARKER_CFG, prim_path="/Visuals/RayCaster"), ) @@ -130,7 +127,7 @@ height=120, width=240, ), - debug_vis=not args_cli.headless, + debug_vis=bool(args_cli.visualizer), visualizer_cfg=replace(RAY_CASTER_MARKER_CFG, prim_path="/Visuals/RayCaster"), ) @@ -187,7 +184,7 @@ height=120, width=240, ), - debug_vis=not args_cli.headless, + debug_vis=bool(args_cli.visualizer), visualizer_cfg=replace(RAY_CASTER_MARKER_CFG, prim_path="/Visuals/RayCaster"), ) else: @@ -220,6 +217,7 @@ class RaycasterSensorSceneCfg(InteractiveSceneCfg): def randomize_shape_color(prim_path_expr: str) -> None: """Randomize the color of the geometry.""" + from pxr import Gf, Sdf stage = sim_utils.get_current_stage() # resolve prim paths for spawning and cloning @@ -235,12 +233,12 @@ def randomize_shape_color(prim_path_expr: str) -> None: scale_spec.default = Gf.Vec3f(random.uniform(0.5, 1.5), random.uniform(0.5, 1.5), random.uniform(0.5, 1.5)) -def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene) -> None: +def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") -> None: """Run the simulator.""" sim_dt = sim.get_physics_dt() count = 0 - while simulation_app.is_running() and (args_cli.max_steps < 0 or count < args_cli.max_steps): + while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or count < args_cli.max_steps): if count % 500 == 0: root_pose = scene["asset"].data.default_root_pose.torch.clone() root_pose[:, :3] += scene.env_origins @@ -269,36 +267,29 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene) -> scene.update(sim_dt) if count % args_cli.log_interval == 0: - hits = scene["ray_caster"].data.ray_hits_w.torch - valid = torch.isfinite(hits).all(dim=-1) - print(f"[INFO] step={count} ray hit rate={valid.float().mean().item():.1%}") + depth = scene["ray_caster"].data.output["distance_to_image_plane"].torch + print(f"[INFO] step={count} ray hit rate={torch.isfinite(depth).float().mean().item():.1%}") def main() -> None: """Run the multi-mesh ray-caster camera example.""" - # Initialize the simulation context sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0]) - # design scene - scene_cfg = RaycasterSensorSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0, replicate_physics=True) - scene = InteractiveScene(scene_cfg) + with launch_simulation(sim_cfg, args_cli): + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0]) + # design scene + scene_cfg = RaycasterSensorSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0, replicate_physics=True) + scene = instantiate(scene_cfg) - if args_cli.asset_type == "objects": - randomize_shape_color(scene_cfg.asset.prim_path.format(ENV_REGEX_NS="/World/envs/env_.*")) + if args_cli.asset_type == "objects": + randomize_shape_color(scene_cfg.asset.prim_path.format(ENV_REGEX_NS="/World/envs/env_.*")) - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run the simulator - run_simulator(sim, scene) + sim.reset() + print("[INFO]: Setup complete...") + run_simulator(sim, scene) if __name__ == "__main__": - try: - main() - finally: - simulation_app.close() + main() diff --git a/examples/sensors/ppisp_camera.py b/examples/sensors/ppisp_camera.py index 3bbfa0ec112d..43f406df87ae 100644 --- a/examples/sensors/ppisp_camera.py +++ b/examples/sensors/ppisp_camera.py @@ -19,11 +19,13 @@ """ +from __future__ import annotations + import argparse import os -from typing import Any +from typing import TYPE_CHECKING, Any -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR DEFAULT_INPUT_SCENE = f"{ISAAC_NUCLEUS_DIR}/Samples/Scene_ParticleField/valiant_auto.usdz" @@ -89,10 +91,13 @@ default=None, help="Directory to write comparison images. Defaults to ./output/ppisp_camera.", ) -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) # Camera output is required, but the example does not need an interactive visualizer. parser.set_defaults(enable_cameras=True) args_cli = parser.parse_args() +# the Gaussian particle-field scene (and remote scene URLs) need Kit's USD plugins and asset resolver, +# even with the Newton renderer, so always start Kit +args_cli.require_kit = True if "://" not in args_cli.input_scene: args_cli.input_scene = os.path.abspath(os.path.expanduser(args_cli.input_scene)) if not os.path.exists(args_cli.input_scene): @@ -112,28 +117,23 @@ if args_cli.save_interval < 1: parser.error("--save_interval must be at least 1.") -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - - import matplotlib.pyplot as plt import numpy as np import torch -from isaaclab_ppisp._demo_utils import ( - find_ppisp_camera_bindings, - format_available_ppisp_cameras, - order_ppisp_bindings_by_camera, -) from isaaclab_ppisp.cfg import PpispCfg, ppisp_cfg_from_usd_camera -from pxr import Usd, UsdGeom - import isaaclab.sim as sim_utils from isaaclab.assets import AssetBaseCfg, RigidObjectCfg -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg -from isaaclab.sensors import Camera, CameraCfg +from isaaclab.scene import InteractiveSceneCfg +from isaaclab.sensors import CameraCfg from isaaclab.sim.spawners.materials import UsdPhysicsRigidBodyMaterialCfg -from isaaclab.utils import configclass +from isaaclab.utils import configclass, instantiate + +if TYPE_CHECKING: + from pxr import Usd + + from isaaclab.scene import InteractiveScene + from isaaclab.sensors import Camera @configclass @@ -192,6 +192,12 @@ def make_sim_cfg() -> sim_utils.SimulationCfg: def resolve_source_camera_binding(source_stage: Usd.Stage) -> tuple[str, Usd.Prim | None, Usd.Prim]: """Resolve the source camera and PPISP camera binding from CLI or source stage metadata.""" + from isaaclab_ppisp._demo_utils import ( + find_ppisp_camera_bindings, + format_available_ppisp_cameras, + order_ppisp_bindings_by_camera, + ) + ppisp_bindings = order_ppisp_bindings_by_camera(source_stage, find_ppisp_camera_bindings(source_stage)) if not ppisp_bindings: raise RuntimeError("No cameras with PPISP camera attributes found in input scene.") @@ -255,6 +261,8 @@ def source_camera_path_to_env_regex(source_stage: Usd.Stage, source_camera_prim_ def bake_source_camera_pose_to_envs(source_stage: Usd.Stage, source_camera_prim_path: str) -> None: """Bake the selected USD camera pose at ``camera_time_code`` into duplicated env camera prims.""" + from pxr import Usd, UsdGeom + default_prim = source_stage.GetDefaultPrim() if not default_prim: raise RuntimeError("Input scene must have a defaultPrim so it can be referenced under each env.") @@ -352,25 +360,24 @@ def create_duplicated_env_scene() -> InteractiveScene: """Create a production-style duplicated-env scene for tiled camera rendering.""" scene_cfg = PpispCameraSceneCfg(num_envs=args_cli.num_envs, env_spacing=args_cli.env_spacing) scene_cfg.input_scene.spawn = sim_utils.UsdFileCfg(usd_path=args_cli.input_scene) - scene = InteractiveScene(scene_cfg) + scene = instantiate(scene_cfg) print(f"[INFO] Referenced input scene into {args_cli.num_envs} env(s).", flush=True) return scene def make_camera(camera_prim_path: str, *, ppisp_cfg: PpispCfg | None, width: int, height: int) -> Camera: """Create a baseline or PPISP camera sensor for the duplicated-env camera batch.""" - return Camera( - CameraCfg( - prim_path=camera_prim_path, - update_period=0.0, - height=height, - width=width, - data_types=["rgb"], - spawn=None, - isp_cfg=ppisp_cfg, - renderer_cfg=make_renderer_cfg(), - ) + camera_cfg = CameraCfg( + prim_path=camera_prim_path, + update_period=0.0, + height=height, + width=width, + data_types=["rgb"], + spawn=None, + isp_cfg=ppisp_cfg, + renderer_cfg=make_renderer_cfg(), ) + return instantiate(camera_cfg) def save_images_grid( @@ -451,7 +458,7 @@ def run_simulator(sim: sim_utils.SimulationContext, baseline_camera: Camera, ppi count = 0 reported_shape = False - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): sim.step() baseline_camera.update(sim_dt) ppisp_camera.update(sim_dt) @@ -520,33 +527,38 @@ def run_simulator(sim: sim_utils.SimulationContext, baseline_camera: Camera, ppi def main() -> None: """Main function.""" - source_stage = Usd.Stage.Open(args_cli.input_scene) - if source_stage is None: - raise RuntimeError(f"Failed to open input scene: {args_cli.input_scene}") - source_camera_prim_path, render_product_prim, ppisp_camera_prim = resolve_source_camera_binding(source_stage) - ppisp_cfg = make_ppisp_cfg(ppisp_camera_prim, len(find_ppisp_camera_bindings(source_stage))) - camera_prim_path = source_camera_path_to_env_regex(source_stage, source_camera_prim_path) - width, height = resolve_image_shape(render_product_prim) - - sim_utils.create_new_stage() sim_cfg = make_sim_cfg() - sim = sim_utils.SimulationContext(sim_cfg) - sim.set_camera_view(eye=[2.5, 2.5, 2.5], target=[0.0, 0.0, 0.0]) - - scene = create_duplicated_env_scene() - if args_cli.renderer == "newton_renderer": - bake_source_camera_pose_to_envs(source_stage, source_camera_prim_path) - baseline_camera = make_camera(camera_prim_path, ppisp_cfg=None, width=width, height=height) - ppisp_camera = make_camera(camera_prim_path, ppisp_cfg=ppisp_cfg, width=width, height=height) - print(f"[INFO] Duplicated-env camera regex: {camera_prim_path}", flush=True) - print(f"[INFO] Rendering {width}x{height} from source camera {source_camera_prim_path}.", flush=True) - - sim.reset() - print("[INFO]: Setup complete. Saving comparison images during simulation.", flush=True) - run_simulator(sim, baseline_camera, ppisp_camera) - del scene + with launch_simulation(sim_cfg, args_cli): + # USD must load after Kit starts + from isaaclab_ppisp._demo_utils import find_ppisp_camera_bindings + + from pxr import Usd + + source_stage = Usd.Stage.Open(args_cli.input_scene) + if source_stage is None: + raise RuntimeError(f"Failed to open input scene: {args_cli.input_scene}") + source_camera_prim_path, render_product_prim, ppisp_camera_prim = resolve_source_camera_binding(source_stage) + ppisp_cfg = make_ppisp_cfg(ppisp_camera_prim, len(find_ppisp_camera_bindings(source_stage))) + camera_prim_path = source_camera_path_to_env_regex(source_stage, source_camera_prim_path) + width, height = resolve_image_shape(render_product_prim) + + sim_utils.create_new_stage() + sim = sim_utils.SimulationContext(sim_cfg) + sim.set_camera_view(eye=[2.5, 2.5, 2.5], target=[0.0, 0.0, 0.0]) + + scene = create_duplicated_env_scene() + if args_cli.renderer == "newton_renderer": + bake_source_camera_pose_to_envs(source_stage, source_camera_prim_path) + baseline_camera = make_camera(camera_prim_path, ppisp_cfg=None, width=width, height=height) + ppisp_camera = make_camera(camera_prim_path, ppisp_cfg=ppisp_cfg, width=width, height=height) + print(f"[INFO] Duplicated-env camera regex: {camera_prim_path}", flush=True) + print(f"[INFO] Rendering {width}x{height} from source camera {source_camera_prim_path}.", flush=True) + + sim.reset() + print("[INFO]: Setup complete. Saving comparison images during simulation.", flush=True) + run_simulator(sim, baseline_camera, ppisp_camera) + del scene if __name__ == "__main__": main() - simulation_app.close() diff --git a/examples/sensors/tacsl_sensor.py b/examples/sensors/tacsl_sensor.py index 10dd1c1d6ee5..5d0c85a518a5 100644 --- a/examples/sensors/tacsl_sensor.py +++ b/examples/sensors/tacsl_sensor.py @@ -26,10 +26,10 @@ import argparse import math import os +from typing import TYPE_CHECKING -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# Add argparse arguments parser = argparse.ArgumentParser(description="TacSL tactile sensor example.") parser.add_argument("--num_envs", type=int, default=2, help="Number of environments to spawn.") parser.add_argument( @@ -69,9 +69,7 @@ help="Type of contact object to use.", ) -# Append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) -# Parse the arguments +add_launcher_args(parser) args_cli = parser.parse_args() if args_cli.num_envs < 1: parser.error("--num_envs must be at least 1.") @@ -79,11 +77,9 @@ parser.error("--max_steps must be positive or -1.") if not args_cli.use_tactile_rgb and not args_cli.use_tactile_ff: args_cli.use_tactile_ff = True - -# Launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - +# the tactile RGB sensor renders through an RTX camera, which needs camera rendering in headless runs +if args_cli.use_tactile_rgb: + args_cli.enable_cameras = True import cv2 import numpy as np @@ -95,9 +91,9 @@ import isaaclab.sim as sim_utils from isaaclab.assets import ArticulationCfg, AssetBaseCfg, RigidObjectCfg -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors import CameraCfg -from isaaclab.utils import configclass +from isaaclab.utils import configclass, instantiate from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR from isaaclab_contrib.sensors.tacsl_sensor import VisuoTactileSensorCfg @@ -106,6 +102,9 @@ from isaaclab_assets.sensors import GELSIGHT_R15_CFG +if TYPE_CHECKING: + from isaaclab.scene import InteractiveScene + @configclass class TactileSensorsSceneCfg(InteractiveSceneCfg): @@ -314,7 +313,7 @@ def save_viz_helper( cv2.imwrite(os.path.join(tactile_rgb_image_dir, f"{count:04d}.png"), tactile_rgb_tiled) -def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene) -> None: +def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") -> None: """Run the simulator.""" sim_dt = sim.get_physics_dt() count = 0 @@ -340,7 +339,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene) -> entity_list.append("contact_object") total_steps = 0 - while simulation_app.is_running() and (args_cli.max_steps < 0 or total_steps < args_cli.max_steps): + while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or total_steps < args_cli.max_steps): if count == 122: # Reset robot and contact object positions count = 0 @@ -382,61 +381,54 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene) -> def main() -> None: """Run the tactile-sensor example.""" - # Initialize simulation - # Note: We set the gpu_collision_stack_size to prevent buffer overflow in contact-rich environments. - sim_cfg = sim_utils.SimulationCfg( - dt=0.005, - device=args_cli.device, - physics=PhysxCfg(gpu_collision_stack_size=2**30), - ) - sim = sim_utils.SimulationContext(sim_cfg) - - # Set main camera - sim.set_camera_view(eye=(0.5, 0.6, 1.0), target=(-0.1, 0.1, 0.5)) - - # Create scene based on contact object type - if args_cli.contact_object_type == "cube": - scene_cfg = CubeTactileSceneCfg(num_envs=args_cli.num_envs, env_spacing=0.2) - # disabled force field for cube contact object because a SDF collision mesh cannot - # be created for the Shape Prims - scene_cfg.tactile_sensor.enable_force_field = False - elif args_cli.contact_object_type == "nut": - scene_cfg = NutTactileSceneCfg(num_envs=args_cli.num_envs, env_spacing=0.2) - elif args_cli.contact_object_type == "none": - scene_cfg = TactileSensorsSceneCfg(num_envs=args_cli.num_envs, env_spacing=0.2) - scene_cfg.tactile_sensor.contact_object_prim_path_expr = None - # this flag is to visualize the tactile sensor points - scene_cfg.tactile_sensor.debug_vis = True - else: - raise ValueError( - f"Invalid contact object type: '{args_cli.contact_object_type}'. Must be 'none', 'cube', or 'nut'." - ) + sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device, physics=PhysxCfg()) + with launch_simulation(sim_cfg, args_cli) as physics_cfg: + # Note: We set the gpu_collision_stack_size to prevent buffer overflow in contact-rich environments. + physics_cfg.gpu_collision_stack_size = 2**30 + sim = sim_utils.SimulationContext(sim_cfg) + + # Set main camera + sim.set_camera_view(eye=(0.5, 0.6, 1.0), target=(-0.1, 0.1, 0.5)) + + # Create scene based on contact object type + if args_cli.contact_object_type == "cube": + scene_cfg = CubeTactileSceneCfg(num_envs=args_cli.num_envs, env_spacing=0.2) + # disabled force field for cube contact object because a SDF collision mesh cannot + # be created for the Shape Prims + scene_cfg.tactile_sensor.enable_force_field = False + elif args_cli.contact_object_type == "nut": + scene_cfg = NutTactileSceneCfg(num_envs=args_cli.num_envs, env_spacing=0.2) + elif args_cli.contact_object_type == "none": + scene_cfg = TactileSensorsSceneCfg(num_envs=args_cli.num_envs, env_spacing=0.2) + scene_cfg.tactile_sensor.contact_object_prim_path_expr = None + # this flag is to visualize the tactile sensor points + scene_cfg.tactile_sensor.debug_vis = True + else: + raise ValueError( + f"Invalid contact object type: '{args_cli.contact_object_type}'. Must be 'none', 'cube', or 'nut'." + ) - scene = InteractiveScene(scene_cfg) + scene = instantiate(scene_cfg) - # Initialize simulation - sim.reset() + sim.reset() - # The tactile RGB path internally uses an RTX camera that may request only non-color render products. - # Isaac RTX can disable color rendering for that case, which makes the Kit viewport black even though - # the sensor images are produced correctly. Keep color rendering enabled when a Kit viewport is active. - visualizers = args_cli.visualizer - if isinstance(visualizers, str): - visualizers = [token.strip() for token in visualizers.split(",")] - if args_cli.use_tactile_rgb and "kit" in visualizers: - print("[INFO]: Keeping RTX color rendering enabled for Kit viewport visualization.") - sim.set_setting("/rtx/sdg/force/disableColorRender", False) + # The tactile RGB path internally uses an RTX camera that may request only non-color render products. + # Isaac RTX can disable color rendering for that case, which makes the Kit viewport black even though + # the sensor images are produced correctly. Keep color rendering enabled when a Kit viewport is active. + visualizers = args_cli.visualizer + if isinstance(visualizers, str): + visualizers = [token.strip() for token in visualizers.split(",")] + if args_cli.use_tactile_rgb and "kit" in visualizers: + print("[INFO]: Keeping RTX color rendering enabled for Kit viewport visualization.") + sim.set_setting("/rtx/sdg/force/disableColorRender", False) - print("[INFO]: Setup complete...") + print("[INFO]: Setup complete...") - # Get initial render - scene["tactile_sensor"].get_initial_render() - # Run simulation - run_simulator(sim, scene) + # Get initial render + scene["tactile_sensor"].get_initial_render() + # Run simulation + run_simulator(sim, scene) if __name__ == "__main__": - try: - main() - finally: - simulation_app.close() + main() diff --git a/scripts/benchmarks/benchmark_load_robot.py b/scripts/benchmarks/benchmark_load_robot.py index 872fca6e5983..6be38a059aa4 100644 --- a/scripts/benchmarks/benchmark_load_robot.py +++ b/scripts/benchmarks/benchmark_load_robot.py @@ -22,9 +22,8 @@ import time from typing import TYPE_CHECKING -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# add argparse arguments parser = argparse.ArgumentParser(description="Benchmark loading different robots.") parser.add_argument("--num_envs", type=int, default=32, help="Number of robots to simulate.") parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") @@ -43,13 +42,9 @@ help="Benchmark output formatter, defaults omniperf", ) parser.add_argument("--output_path", type=str, default=".", help="Path to output benchmark results.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) -# parse the arguments +add_launcher_args(parser) args_cli, _ = parser.parse_known_args() -"""Rest everything follows.""" - # Start the timer for imports imports_time_begin = time.perf_counter_ns() @@ -60,7 +55,6 @@ # runtime classes (``SimulationContext``, ``InteractiveScene``) eagerly import ``pxr`` and must therefore # be imported only after the simulation app has been launched (see :func:`main`). import isaaclab.sim as sim_utils -from isaaclab.app import launch_simulation from isaaclab.assets import ArticulationCfg, AssetBaseCfg from isaaclab.benchmark import BaseIsaacLabBenchmark, SingleMeasurement from isaaclab.physics import PhysicsCfg @@ -174,7 +168,6 @@ def main( from isaaclab.scene import InteractiveScene from isaaclab.sim import SimulationContext - # Load kit helper # The default MJWarp configuration needs additional constraint capacity and solver tuning for bipeds. if ( args_cli.robot in ("h1", "g1") @@ -210,7 +203,6 @@ def main( # Start the timer for reset reset_time_begin = time.perf_counter_ns() - # Play the simulator sim.reset() # Stop the timer for reset reset_time_end = time.perf_counter_ns() @@ -230,7 +222,6 @@ def main( "startup", measurement=SingleMeasurement(name="Imports Time", value=imports_time_ms, unit="ms") ) - # Run the simulator run_simulator(sim, scene, benchmark) # Finalize benchmark @@ -277,5 +268,4 @@ def main( ] }, ) - # run the main function main(scene_cfg, physics_cfg, app_start_time_ms, benchmark) diff --git a/scripts/benchmarks/benchmark_newton_raycast.py b/scripts/benchmarks/benchmark_newton_raycast.py index cd24441d4eae..5ec35008dcea 100644 --- a/scripts/benchmarks/benchmark_newton_raycast.py +++ b/scripts/benchmarks/benchmark_newton_raycast.py @@ -17,8 +17,6 @@ ./isaaclab.sh -p scripts/benchmarks/benchmark_newton_raycast.py --num_envs 1024 --headless """ -"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" - import argparse from isaaclab.app import add_launcher_args, launch_simulation diff --git a/scripts/benchmarks/benchmark_view_comparison.py b/scripts/benchmarks/benchmark_view_comparison.py index e40e7af8c33b..02c7c8804e99 100644 --- a/scripts/benchmarks/benchmark_view_comparison.py +++ b/scripts/benchmarks/benchmark_view_comparison.py @@ -27,7 +27,7 @@ import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation parser = argparse.ArgumentParser(description="Benchmark FrameView backends and PhysX RigidBodyView.") @@ -52,28 +52,20 @@ help="Directory to save profile results. Default: ./profile_results", ) -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) args_cli = parser.parse_args() -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything follows.""" - import cProfile import time import torch import warp as wp -from pxr import Gf - import isaaclab.sim as sim_utils from isaaclab.sim.views import FrameView try: from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg - from isaaclab_newton.sim.views import NewtonSiteFrameView HAS_NEWTON = True except ImportError: @@ -88,6 +80,8 @@ @torch.no_grad() def benchmark_usd_or_fabric(view_type: str, num_iterations: int) -> dict[str, float]: """Benchmark USD or Fabric FrameView.""" + from pxr import Gf + timing_results = {} print(" Setting up scene") @@ -138,6 +132,10 @@ def benchmark_usd_or_fabric(view_type: str, num_iterations: int) -> dict[str, fl @torch.no_grad() def benchmark_newton(num_iterations: int) -> dict[str, float]: """Benchmark Newton FrameView.""" + from isaaclab_newton.sim.views import NewtonSiteFrameView + + from pxr import Gf + from isaaclab.assets import RigidObjectCfg from isaaclab.scene import InteractiveScene, InteractiveSceneCfg from isaaclab.sim import SimulationCfg, build_simulation_context @@ -446,4 +444,6 @@ def main(): if __name__ == "__main__": - main() + # each backend builds its own simulation context; the USD, Fabric, and PhysX views need Kit + with launch_simulation(None, args_cli): + main() diff --git a/scripts/benchmarks/benchmark_xform_prim_view.py b/scripts/benchmarks/benchmark_xform_prim_view.py index 1f2006ed84a0..ced4ae042372 100644 --- a/scripts/benchmarks/benchmark_xform_prim_view.py +++ b/scripts/benchmarks/benchmark_xform_prim_view.py @@ -21,7 +21,7 @@ import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation parser = argparse.ArgumentParser(description="Benchmark FrameView performance across backends.") parser.add_argument("--num_envs", type=int, default=100, help="Number of environments to simulate.") @@ -29,14 +29,9 @@ parser.add_argument("--profile", action="store_true", help="Enable cProfile profiling.") parser.add_argument("--profile_dir", type=str, default="./profile_results", help="Directory for .prof files.") -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) args_cli = parser.parse_args() -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything follows.""" - import cProfile import time from typing import Literal @@ -44,17 +39,12 @@ import torch import warp as wp from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg -from isaaclab_newton.sim.views import NewtonSiteFrameView -from isaaclab_physx.sim.views import FabricFrameView - -from pxr import Gf import isaaclab.sim as sim_utils from isaaclab.assets import RigidObjectCfg -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sim import SimulationCfg, build_simulation_context -from isaaclab.sim.views import UsdFrameView -from isaaclab.utils import configclass +from isaaclab.utils import configclass, instantiate @configclass @@ -82,6 +72,14 @@ def benchmark_frame_view( # noqa: C901 num_iterations: int, ) -> tuple[dict[str, float], dict[str, torch.Tensor]]: """Benchmark get/set world/local poses for the given FrameView backend.""" + # the view runtime classes import USD, which must load after Kit starts + from isaaclab_newton.sim.views import NewtonSiteFrameView + from isaaclab_physx.sim.views import FabricFrameView + + from pxr import Gf + + from isaaclab.sim.views import UsdFrameView + timing_results: dict[str, float] = {} computed_results: dict[str, torch.Tensor] = {} device = args_cli.device @@ -97,7 +95,8 @@ def benchmark_frame_view( # noqa: C901 ctx = build_simulation_context(device=device, sim_cfg=newton_cfg, add_ground_plane=True) sim = ctx.__enter__() sim._app_control_on_stop_handle = None - InteractiveScene(_NewtonSceneCfg(num_envs=num_envs, env_spacing=2.0)) + scene_cfg = _NewtonSceneCfg(num_envs=num_envs, env_spacing=2.0) + instantiate(scene_cfg) stage = sim_utils.get_current_stage() for i in range(num_envs): @@ -473,4 +472,6 @@ def main(): if __name__ == "__main__": - main() + # each backend builds its own simulation context; the USD and Fabric views need Kit + with launch_simulation(None, args_cli): + main() diff --git a/scripts/environments/state_machine/lift_cube_sm.py b/scripts/environments/state_machine/lift_cube_sm.py index 1dcb92fa04ba..986b0827aaa8 100644 --- a/scripts/environments/state_machine/lift_cube_sm.py +++ b/scripts/environments/state_machine/lift_cube_sm.py @@ -15,29 +15,19 @@ """ -"""Launch Omniverse Toolkit first.""" - import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# add argparse arguments parser = argparse.ArgumentParser(description="Pick and lift state machine for lift environments.") parser.add_argument( "--disable_fabric", action="store_true", default=False, help="Disable fabric and use USD I/O operations." ) 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) +add_launcher_args(parser) # parse the arguments, forwarding unrecognized ones as Hydra-style task config overrides args_cli, hydra_overrides = parser.parse_known_args() -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything else.""" - from collections.abc import Sequence import gymnasium as gym @@ -258,58 +248,56 @@ def main(): use_fabric=not args_cli.disable_fabric, overrides=hydra_overrides, ) - # create environment - env = gym.make("IsaacContrib-Lift-Cube-Franka-IK-Abs", cfg=env_cfg) - # reset environment at start - env.reset() - - # create action buffers (position + quaternion) - actions = torch.zeros(env.unwrapped.action_space.shape, device=env.unwrapped.device) - actions[:, 3] = 1.0 - # desired object orientation (we only do position control of object) - desired_orientation = torch.zeros((env.unwrapped.num_envs, 4), device=env.unwrapped.device) - desired_orientation[:, 1] = 1.0 - # create state machine - pick_sm = PickAndLiftSm( - env_cfg.sim.dt * env_cfg.decimation, env.unwrapped.num_envs, env.unwrapped.device, position_threshold=0.01 - ) + with launch_simulation(env_cfg, args_cli): + # create environment + env = gym.make("IsaacContrib-Lift-Cube-Franka-IK-Abs", cfg=env_cfg) + # reset environment at start + env.reset() + + # create action buffers (position + quaternion) + actions = torch.zeros(env.unwrapped.action_space.shape, device=env.unwrapped.device) + actions[:, 3] = 1.0 + # desired object orientation (we only do position control of object) + desired_orientation = torch.zeros((env.unwrapped.num_envs, 4), device=env.unwrapped.device) + desired_orientation[:, 1] = 1.0 + # create state machine + pick_sm = PickAndLiftSm( + env_cfg.sim.dt * env_cfg.decimation, env.unwrapped.num_envs, env.unwrapped.device, position_threshold=0.01 + ) - while simulation_app.is_running(): - # run everything in inference mode - with torch.inference_mode(): - # step environment - dones = env.step(actions)[-2] - - # observations - # -- end-effector frame - ee_frame_sensor = env.unwrapped.scene["ee_frame"] - tcp_rest_position = ( - ee_frame_sensor.data.target_pos_w.torch[..., 0, :].clone() - env.unwrapped.scene.env_origins - ) - tcp_rest_orientation = ee_frame_sensor.data.target_quat_w.torch[..., 0, :].clone() - # -- object frame - object_data: RigidObjectData = env.unwrapped.scene["object"].data - object_position = object_data.root_pos_w.torch - env.unwrapped.scene.env_origins - # -- target object frame - desired_position = env.unwrapped.command_manager.get_command("object_pose")[..., :3] - - # advance state machine - actions = pick_sm.compute( - torch.cat([tcp_rest_position, tcp_rest_orientation], dim=-1), - torch.cat([object_position, desired_orientation], dim=-1), - torch.cat([desired_position, desired_orientation], dim=-1), - ) - - # reset state machine - if dones.any(): - pick_sm.reset_idx(dones.nonzero(as_tuple=False).squeeze(-1)) - - # close the environment - env.close() + while env.unwrapped.sim.is_headless_or_exist_active_visualizer(): + # run everything in inference mode + with torch.inference_mode(): + # step environment + dones = env.step(actions)[-2] + + # observations + # -- end-effector frame + ee_frame_sensor = env.unwrapped.scene["ee_frame"] + tcp_rest_position = ( + ee_frame_sensor.data.target_pos_w.torch[..., 0, :].clone() - env.unwrapped.scene.env_origins + ) + tcp_rest_orientation = ee_frame_sensor.data.target_quat_w.torch[..., 0, :].clone() + # -- object frame + object_data: RigidObjectData = env.unwrapped.scene["object"].data + object_position = object_data.root_pos_w.torch - env.unwrapped.scene.env_origins + # -- target object frame + desired_position = env.unwrapped.command_manager.get_command("object_pose")[..., :3] + + # advance state machine + actions = pick_sm.compute( + torch.cat([tcp_rest_position, tcp_rest_orientation], dim=-1), + torch.cat([object_position, desired_orientation], dim=-1), + torch.cat([desired_position, desired_orientation], dim=-1), + ) + + # reset state machine + if dones.any(): + pick_sm.reset_idx(dones.nonzero(as_tuple=False).squeeze(-1)) + + # close the environment + env.close() if __name__ == "__main__": - # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/environments/state_machine/lift_franka_soft.py b/scripts/environments/state_machine/lift_franka_soft.py index 4b0d6d15a017..0fc991fa4e8d 100644 --- a/scripts/environments/state_machine/lift_franka_soft.py +++ b/scripts/environments/state_machine/lift_franka_soft.py @@ -35,7 +35,6 @@ import isaaclab_tasks # noqa: F401 from isaaclab_tasks.utils import resolve_task_config, setup_preset_cli -# add argparse arguments parser = argparse.ArgumentParser(description="Pick and lift a deformable with a robotic arm.") parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to simulate.") parser.add_argument("--num_steps", type=int, default=1000, help="Number of environment steps to run.") diff --git a/scripts/environments/state_machine/open_cabinet_sm.py b/scripts/environments/state_machine/open_cabinet_sm.py index 55550ee7a702..474dd8d6363c 100644 --- a/scripts/environments/state_machine/open_cabinet_sm.py +++ b/scripts/environments/state_machine/open_cabinet_sm.py @@ -15,29 +15,19 @@ """ -"""Launch Omniverse Toolkit first.""" - import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# add argparse arguments parser = argparse.ArgumentParser(description="Pick and lift state machine for cabinet environments.") parser.add_argument( "--disable_fabric", action="store_true", default=False, help="Disable fabric and use USD I/O operations." ) 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) +add_launcher_args(parser) # parse the arguments, forwarding unrecognized ones as Hydra-style task config overrides args_cli, hydra_overrides = parser.parse_known_args() -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything else.""" - from collections.abc import Sequence import gymnasium as gym @@ -277,54 +267,54 @@ def main(): use_fabric=not args_cli.disable_fabric, overrides=hydra_overrides, ) - # create environment - env = gym.make("IsaacContrib-Open-Drawer-Franka-IK-Abs", cfg=env_cfg) - # reset environment at start - env.reset() - - # create action buffers (position + quaternion) - actions = torch.zeros(env.unwrapped.action_space.shape, device=env.unwrapped.device) - actions[:, 3] = 1.0 - # desired object orientation (we only do position control of object) - desired_orientation = torch.zeros((env.unwrapped.num_envs, 4), device=env.unwrapped.device) - desired_orientation[:, 1] = 1.0 - # create state machine - open_sm = OpenDrawerSm(env_cfg.sim.dt * env_cfg.decimation, env.unwrapped.num_envs, env.unwrapped.device) - - while simulation_app.is_running(): - # run everything in inference mode - with torch.inference_mode(): - # step environment - dones = env.step(actions)[-2] - - # observations - # -- end-effector frame - ee_frame_tf: FrameTransformer = env.unwrapped.scene["ee_frame"] - tcp_rest_position = ee_frame_tf.data.target_pos_w.torch[..., 0, :].clone() - env.unwrapped.scene.env_origins - tcp_rest_orientation = ee_frame_tf.data.target_quat_w.torch[..., 0, :].clone() - # -- handle frame - cabinet_frame_tf: FrameTransformer = env.unwrapped.scene["cabinet_frame"] - cabinet_position = ( - cabinet_frame_tf.data.target_pos_w.torch[..., 0, :].clone() - env.unwrapped.scene.env_origins - ) - cabinet_orientation = cabinet_frame_tf.data.target_quat_w.torch[..., 0, :].clone() - - # advance state machine - actions = open_sm.compute( - torch.cat([tcp_rest_position, tcp_rest_orientation], dim=-1), - torch.cat([cabinet_position, cabinet_orientation], dim=-1), - ) - - # reset state machine - if dones.any(): - open_sm.reset_idx(dones.nonzero(as_tuple=False).squeeze(-1)) - - # close the environment - env.close() + with launch_simulation(env_cfg, args_cli): + # create environment + env = gym.make("IsaacContrib-Open-Drawer-Franka-IK-Abs", cfg=env_cfg) + # reset environment at start + env.reset() + + # create action buffers (position + quaternion) + actions = torch.zeros(env.unwrapped.action_space.shape, device=env.unwrapped.device) + actions[:, 3] = 1.0 + # desired object orientation (we only do position control of object) + desired_orientation = torch.zeros((env.unwrapped.num_envs, 4), device=env.unwrapped.device) + desired_orientation[:, 1] = 1.0 + # create state machine + open_sm = OpenDrawerSm(env_cfg.sim.dt * env_cfg.decimation, env.unwrapped.num_envs, env.unwrapped.device) + + while env.unwrapped.sim.is_headless_or_exist_active_visualizer(): + # run everything in inference mode + with torch.inference_mode(): + # step environment + dones = env.step(actions)[-2] + + # observations + # -- end-effector frame + ee_frame_tf: FrameTransformer = env.unwrapped.scene["ee_frame"] + tcp_rest_position = ( + ee_frame_tf.data.target_pos_w.torch[..., 0, :].clone() - env.unwrapped.scene.env_origins + ) + tcp_rest_orientation = ee_frame_tf.data.target_quat_w.torch[..., 0, :].clone() + # -- handle frame + cabinet_frame_tf: FrameTransformer = env.unwrapped.scene["cabinet_frame"] + cabinet_position = ( + cabinet_frame_tf.data.target_pos_w.torch[..., 0, :].clone() - env.unwrapped.scene.env_origins + ) + cabinet_orientation = cabinet_frame_tf.data.target_quat_w.torch[..., 0, :].clone() + + # advance state machine + actions = open_sm.compute( + torch.cat([tcp_rest_position, tcp_rest_orientation], dim=-1), + torch.cat([cabinet_position, cabinet_orientation], dim=-1), + ) + + # reset state machine + if dones.any(): + open_sm.reset_idx(dones.nonzero(as_tuple=False).squeeze(-1)) + + # close the environment + env.close() if __name__ == "__main__": - # run the main execution main() - # close sim app - simulation_app.close() diff --git a/scripts/environments/teleoperation/teleop_replay_agent.py b/scripts/environments/teleoperation/teleop_replay_agent.py index f7577dc1b3a7..90641a98eacc 100644 --- a/scripts/environments/teleoperation/teleop_replay_agent.py +++ b/scripts/environments/teleoperation/teleop_replay_agent.py @@ -37,7 +37,7 @@ inner loop. ``_run_single_replay`` returns its populated :class:`_RunStats`, and the outer batch driver in :func:`main` moves on to the next replay (or returns from ``main`` when the - batch is done; ``__main__`` then calls ``simulation_app.close()``). + batch is done; the simulation runtime is closed when ``main`` leaves ``launch_simulation``). The signals are: 1. **The recorded operator STOP**, replayed via the @@ -220,7 +220,7 @@ values, which only happen to match record-time semantics when the anchor never moved. - The full incantation also needs ``AppLauncher``'s ``--xr`` flag + The full incantation also needs the launcher's ``--xr`` flag plus a few simulator-side carb settings to flip the AR profile and load the teleop XR bridge (the replay path skips both for the headless-CI default; we have not yet promoted them to a single @@ -248,11 +248,11 @@ instance/session stay alive. """ -"""Launch Isaac Sim Simulator first.""" +"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation parser = argparse.ArgumentParser( description=( @@ -341,7 +341,7 @@ help=( "Path to a CloudXR ``.env`` file, or a shorthand: 'cloudxrjs' (Quest/Pico) or 'avp'" " (Apple Vision Pro). Default is None -- CloudXR is not launched. Pair with" - " AppLauncher's ``--xr`` and simulator-side AR-profile settings for spectate-on-headset" + " the launcher's ``--xr`` and simulator-side AR-profile settings for spectate-on-headset" " replay; see the script docstring for the full command." ), ) @@ -371,18 +371,18 @@ " replay." ), ) -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) # forward unrecognized args as Hydra-style task config overrides args_cli, hydra_overrides = parser.parse_known_args() -app_launcher_args = vars(args_cli) # Enable external camera rendering by default so the replay mimics the production teleop env (perf # parity with live camera rendering); ``--disable_external_cameras`` turns it off. The # ``--enable_cameras`` CLI flag was removed in Isaac Lab 3.0 (see #6656), so pass the intent to -# AppLauncher as a kwarg; this selects a camera-rendering experience that provides RTX/DLSS. +# the launcher; this selects a camera-rendering experience that provides RTX/DLSS. # Everywhere else we read ``args_cli.disable_external_cameras`` directly. -app_launcher = AppLauncher(app_launcher_args, enable_cameras=not args_cli.disable_external_cameras) -simulation_app = app_launcher.app +args_cli.enable_cameras = not args_cli.disable_external_cameras +# the stage-load wait and the RTX settings use Kit APIs directly +args_cli.require_kit = True """Rest everything follows.""" @@ -402,9 +402,6 @@ import gymnasium as gym import torch from isaaclab_physx.renderers import IsaacRtxRendererGlobalSettingsCfg -from isaaclab_physx.renderers.isaac_rtx_renderer_utils import ( - apply_isaac_rtx_global_settings, -) from isaaclab_teleop import IsaacTeleopDevice, create_isaac_teleop_device, poll_control_events from isaaclab.devices.openxr import remove_camera_configs @@ -1033,25 +1030,24 @@ def _rtx_rendering_requested(args: argparse.Namespace) -> bool: return external_cameras or ("kit" in visualizers) or bool(getattr(args, "xr", False)) -def _ensure_replicator_loaded() -> None: - """Enable ``omni.replicator.core`` so RTX/DLSS global settings can be applied. +def _apply_rtx_settings() -> None: + """Apply the RTX/DLSS global settings used for replay rendering. - :func:`apply_isaac_rtx_global_settings` sets the antialiasing mode through - ``omni.replicator.core``, which ships with the SDG/rendering extensions. Some Kit - experiences (e.g. the Kit-viewport-only app selected by ``--visualizer kit`` without - cameras or XR) do not preload it, so enable it on demand via the extension manager - before applying RTX settings. Idempotent when the extension is already enabled. + Only call this when an RTX render pipeline actually runs this session (Kit visualizer, + external cameras, or XR), see :func:`_rtx_rendering_requested`; a headless replay that renders + nothing neither needs them nor has the extensions loaded to apply them. """ - import omni.kit.app + from isaaclab_physx.renderers.isaac_rtx_renderer_utils import apply_isaac_rtx_global_settings - omni.kit.app.get_app().get_extension_manager().set_extension_enabled_immediate("omni.replicator.core", True) + apply_isaac_rtx_global_settings( + IsaacRtxRendererGlobalSettingsCfg(antialiasing_mode="DLSS"), + ) def _prepare_env_cfg( task: str, num_envs: int, device: str, - apply_rtx_settings: bool = False, overrides: Sequence[str] = (), ) -> tuple[ManagerBasedRLEnvCfg, object | None]: """Build and tweak an env config suitable for non-interactive replay. @@ -1086,11 +1082,6 @@ def _prepare_env_cfg( task: Registered task name to load the env config for. num_envs: Number of parallel environments. device: Simulation device (e.g. ``"cuda:0"``). - apply_rtx_settings: Whether an RTX render pipeline will actually run this - session (Kit visualizer, external cameras, or XR). Only then are the - RTX/DLSS global settings applied; a headless replay that renders - nothing neither needs them nor has the extensions loaded to apply - them. See :func:`_rtx_rendering_requested`. overrides: Hydra-style ``key=value`` overrides forwarded to :func:`parse_env_cfg`. Returns: @@ -1119,15 +1110,6 @@ def _prepare_env_cfg( # renders them for production parity; otherwise strip them for a lighter headless replay. if args_cli.disable_external_cameras: env_cfg = remove_camera_configs(env_cfg) - # The RTX/DLSS global settings only matter when an RTX render pipeline actually runs. - # ``apply_isaac_rtx_global_settings`` uses ``omni.replicator`` (part of the SDG/rendering - # extensions), which some experiences do not preload, so ensure it is loaded first. A - # pure-headless replay renders nothing, so skip both. - if apply_rtx_settings: - _ensure_replicator_loaded() - apply_isaac_rtx_global_settings( - IsaacRtxRendererGlobalSettingsCfg(antialiasing_mode="DLSS"), - ) return env_cfg, success_term @@ -1189,67 +1171,6 @@ def _process_success_condition( """ -def _wait_for_stage_load(simulation_app, max_wait_s: float) -> None: - """Block until the USD stage finishes resolving every referenced asset. - - Polls :meth:`omni.usd.UsdContext.get_stage_loading_status`. The third element of - the returned tuple is the count of assets still pending; when it reaches - zero the stage is fully streamed in and the renderer pipeline is ready to draw - against it. After the count reaches zero this function pumps an additional - :data:`_RENDERER_SETTLE_FRAMES` ``simulation_app.update()`` calls so shaders, - materials, and articulation views finish warming up before the caller begins - consuming MCAP frames or stepping the env. - - Args: - simulation_app: The :class:`isaaclab.app.SimulationApp` instance whose - event loop to pump while waiting. - max_wait_s: Upper bound on how long to spin on a non-zero loading count - before warning and returning. Acts as a safety net for misconfigured - scenes (missing assets, slow Nucleus); a successful run typically - completes well within this bound. - - The function is best-effort: when ``omni.usd`` is unavailable (e.g. when - running outside a simulator context) it returns immediately so callers do not - need a separate code path. - """ - try: - import omni.usd - except (ImportError, ModuleNotFoundError): - logger.warning("omni.usd not available; skipping deterministic stage-load wait") - return - - print("Waiting for USD stage to finish loading...") - start_s = time.monotonic() - last_progress_log_s = start_s - while simulation_app.is_running(): - context = omni.usd.get_context() - if context is None: - break - # get_stage_loading_status -> (message, count_loaded, count_loading) - _, _, count_loading = context.get_stage_loading_status() - if count_loading == 0: - break - elapsed_s = time.monotonic() - start_s - if elapsed_s >= max_wait_s: - logger.warning( - "Stage still reports %d assets pending after %.1fs; proceeding anyway. Replay may race the renderer.", - count_loading, - max_wait_s, - ) - break - if time.monotonic() - last_progress_log_s >= 5.0: - print(f" stage loading: {count_loading} assets pending (elapsed {elapsed_s:.1f}s)") - last_progress_log_s = time.monotonic() - simulation_app.update() - - elapsed_s = time.monotonic() - start_s - print(f"Stage load complete after {elapsed_s:.1f}s; settling renderer for {_RENDERER_SETTLE_FRAMES} frames...") - for _ in range(_RENDERER_SETTLE_FRAMES): - if not simulation_app.is_running(): - return - simulation_app.update() - - def _run_single_replay( env: gym.Env, isaac_teleop_cfg, @@ -1361,7 +1282,10 @@ def _patched_sim_render(): # at the cost of doing nothing measurable (the wait returns # immediately on a fully-loaded stage anyway). if run_index == 0: - _wait_for_stage_load(simulation_app, args_cli.max_stage_load_wait_s) + from isaaclab_physx.renderers.isaac_rtx_renderer_utils import wait_for_stage_load + + print("Waiting for USD stage to finish loading...") + wait_for_stage_load(args_cli.max_stage_load_wait_s, settle_frames=_RENDERER_SETTLE_FRAMES) # Optional extra wall-clock buffer on top of the deterministic # wait. Useful as an escape hatch when the deterministic check @@ -1372,7 +1296,10 @@ def _patched_sim_render(): " before consuming MCAP frames." ) buffer_start_s = time.monotonic() - while simulation_app.is_running() and time.monotonic() - buffer_start_s < args_cli.replay_start_delay_s: + while ( + env.sim.is_headless_or_exist_active_visualizer() + and time.monotonic() - buffer_start_s < args_cli.replay_start_delay_s + ): env.sim.render() print( @@ -1394,7 +1321,7 @@ def _patched_sim_render(): # subsequent renders are excluded. last_active_end_s: float | None = None - while simulation_app.is_running(): + while env.sim.is_headless_or_exist_active_visualizer(): try: with torch.inference_mode(): # Wall-clock safety cap. Only hit when the recording @@ -1551,7 +1478,7 @@ def main() -> int: multi-run batches start essentially instantly. Per-replay control flow (see :func:`_run_single_replay` for details): - * Pre-loop warmup: ``_wait_for_stage_load`` polls + * Pre-loop warmup: ``wait_for_stage_load`` polls ``omni.usd.UsdContext.get_stage_loading_status`` until the simulator reports zero pending assets, then renders a fixed number of settle frames (only on ``run_index == 0``). An optional @@ -1589,8 +1516,8 @@ def main() -> int: The simulator is left running between replays so a fresh :class:`IsaacTeleopDevice` can be constructed without reloading - the USD stage; ``__main__`` calls ``simulation_app.close()`` - after the whole batch finishes. + the USD stage; ``launch_simulation`` closes it after the whole + batch finishes. Stats output: Each iteration where ``env.step()`` ran contributes one CPU @@ -1613,13 +1540,37 @@ def main() -> int: ``--max_replay_duration_s``, otherwise ``1`` (any failure or incomplete run). """ + if args_cli.num_replays < 1: + raise ValueError(f"--num_replays must be >= 1; got {args_cli.num_replays}") + + env_cfg, success_term = _prepare_env_cfg( + args_cli.task, + args_cli.num_envs, + args_cli.device, + overrides=hydra_overrides, + ) + + if not hasattr(env_cfg, "isaac_teleop") or env_cfg.isaac_teleop is None: + raise ValueError( + f"Task '{args_cli.task}' does not configure an IsaacTeleop pipeline. " + "MCAP replay requires env_cfg.isaac_teleop to be set." + ) + + with launch_simulation(env_cfg, args_cli): + exit_code = _run_replay_batch(env_cfg, success_term) + return exit_code + + +def _run_replay_batch(env_cfg: ManagerBasedRLEnvCfg, success_term: object | None) -> int: + """Build the env, run the ``--num_replays`` replays, and report the stats; see :func:`main`. + + Returns: + The host process exit code, see :func:`main`. + """ env: gym.Env | None = None cloudxr_launcher = None all_runs: list[_RunStats] = [] - if args_cli.num_replays < 1: - raise ValueError(f"--num_replays must be >= 1; got {args_cli.num_replays}") - try: # CloudXR launch is hoisted to the agent (batch scope) so it # survives across per-run device teardown; per-run lifecycles @@ -1632,19 +1583,8 @@ def main() -> int: _resolve_cloudxr_env(args_cli.cloudxr_env), args_cli.auto_launch_cloudxr ) - env_cfg, success_term = _prepare_env_cfg( - args_cli.task, - args_cli.num_envs, - args_cli.device, - apply_rtx_settings=_rtx_rendering_requested(args_cli), - overrides=hydra_overrides, - ) - - if not hasattr(env_cfg, "isaac_teleop") or env_cfg.isaac_teleop is None: - raise ValueError( - f"Task '{args_cli.task}' does not configure an IsaacTeleop pipeline. " - "MCAP replay requires env_cfg.isaac_teleop to be set." - ) + if _rtx_rendering_requested(args_cli): + _apply_rtx_settings() env = gym.make(args_cli.task, cfg=env_cfg).unwrapped @@ -1658,7 +1598,7 @@ def main() -> int: ) all_runs.append(run_stats) print(f"Replay {run_idx + 1}/{args_cli.num_replays} outcome: {run_stats.outcome}") - if not simulation_app.is_running(): + if not env.sim.is_headless_or_exist_active_visualizer(): # The simulator was closed externally mid-batch; stop the # outer loop rather than spawning a fresh device against # a dead app. @@ -1683,6 +1623,4 @@ def main() -> int: if __name__ == "__main__": exit_code = main() - simulation_app.update() - simulation_app.close() sys.exit(exit_code) diff --git a/scripts/environments/teleoperation/teleop_se3_agent.py b/scripts/environments/teleoperation/teleop_se3_agent.py index 9e3c17da02f8..e2f6efdb831b 100644 --- a/scripts/environments/teleoperation/teleop_se3_agent.py +++ b/scripts/environments/teleoperation/teleop_se3_agent.py @@ -16,7 +16,7 @@ The script automatically detects which stack to use based on the environment config. """ -"""Launch Isaac Sim Simulator first.""" +"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" # Isaac Lab does not use Warp autodiff; skipping adjoint codegen roughly halves the # time spent building kernels on a cold kernel cache. @@ -28,7 +28,7 @@ import sys from collections.abc import Callable -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.utils.string import list_intersection, string_to_callable from isaaclab_tasks.utils import setup_preset_cli @@ -86,19 +86,18 @@ ), ) -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_launcher_args(parser) # parse the arguments args_cli, hydra_args = setup_preset_cli(parser) -app_launcher_args = vars(args_cli) - # Enable external camera rendering by default (``--disable_external_cameras`` turns it off). The # ``--enable_cameras`` CLI flag was removed in Isaac Lab 3.0 (see #6656), so pass the intent to -# AppLauncher as a kwarg; this selects a camera-rendering experience that provides RTX/DLSS support. +# the launcher; this selects a camera-rendering experience that provides RTX/DLSS support. # Everywhere else we read ``args_cli.disable_external_cameras`` directly. -app_launcher = AppLauncher(app_launcher_args, enable_cameras=not args_cli.disable_external_cameras) -simulation_app = app_launcher.app +args_cli.enable_cameras = not args_cli.disable_external_cameras +# the teleop input devices and the RTX settings use Kit APIs directly +args_cli.require_kit = True # Call an external callback if requested. remaining_args_env_registration = None @@ -118,11 +117,7 @@ import gymnasium as gym import torch from isaaclab_physx.renderers import IsaacRtxRendererGlobalSettingsCfg -from isaaclab_physx.renderers.isaac_rtx_renderer_utils import ( - apply_isaac_rtx_global_settings, -) -from isaaclab.devices import Se3Gamepad, Se3GamepadCfg, Se3Keyboard, Se3KeyboardCfg, Se3SpaceMouse, Se3SpaceMouseCfg from isaaclab.devices.openxr import remove_camera_configs from isaaclab.devices.teleop_device_factory import create_teleop_device from isaaclab.envs import ManagerBasedRLEnvCfg @@ -175,22 +170,10 @@ def _rtx_rendering_requested(args: argparse.Namespace) -> bool: return external_cameras or ("kit" in visualizers) or bool(getattr(args, "xr", False)) -def _ensure_replicator_loaded() -> None: - """Enable ``omni.replicator.core`` so RTX/DLSS global settings can be applied. - - :func:`apply_isaac_rtx_global_settings` sets the antialiasing mode through - ``omni.replicator.core``, which ships with the SDG/rendering extensions. Some Kit - experiences (e.g. the Kit-viewport-only app selected by ``--visualizer kit`` without - cameras or XR) do not preload it, so enable it on demand via the extension manager - before applying RTX settings. Idempotent when the extension is already enabled. - """ - import omni.kit.app - - omni.kit.app.get_app().get_extension_manager().set_extension_enabled_immediate("omni.replicator.core", True) - - def _create_builtin_device(device_name: str, sensitivity: float) -> object | None: """Create a built-in teleop device by name, or return None if unrecognized.""" + from isaaclab.devices import Se3Gamepad, Se3GamepadCfg, Se3Keyboard, Se3KeyboardCfg, Se3SpaceMouse, Se3SpaceMouseCfg + name = device_name.lower() if name == "keyboard": return Se3Keyboard(Se3KeyboardCfg(pos_sensitivity=0.05 * sensitivity, rot_sensitivity=0.05 * sensitivity)) @@ -235,6 +218,8 @@ def _make_control_keyboard(teleop_interface, use_isaac_teleop: bool, has_window: """ if not use_isaac_teleop or not has_window: return None + from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg + try: keyboard = Se3Keyboard(Se3KeyboardCfg(pos_sensitivity=0.0, rot_sensitivity=0.0)) keyboard.add_callback("B", teleop_interface.request_start) @@ -282,14 +267,6 @@ def main() -> None: # noqa: C901 not teleop_device_explicitly_set and hasattr(env_cfg, "isaac_teleop") and env_cfg.isaac_teleop is not None ) - from isaaclab_teleop import XrCameraFeedSession - - camera_feed_session = XrCameraFeedSession.prepare( - env_cfg, - enabled=args_cli.xr and use_isaac_teleop, - camera_rendering_enabled=not args_cli.disable_external_cameras, - ) - # XR-rendering setup (camera removal + DLSS) is only needed for the Kit XR # path. Without --xr, IsaacTeleop runs standalone (I/O only) and renders # normally, so gate on --xr alone. @@ -298,11 +275,29 @@ def main() -> None: # noqa: C901 # strip them so the XR headset view is the sole render product. if args_cli.disable_external_cameras: env_cfg = remove_camera_configs(env_cfg) + + with launch_simulation(env_cfg, args_cli): + run_teleoperation(env_cfg, use_isaac_teleop) + + +def run_teleoperation(env_cfg: ManagerBasedRLEnvCfg, use_isaac_teleop: bool) -> None: # noqa: C901 + """Create the environment and teleop device, then run the teleoperation loop until the app is closed.""" + from isaaclab_physx.renderers.isaac_rtx_renderer_utils import apply_isaac_rtx_global_settings + from isaaclab_teleop import XrCameraFeedSession + + from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg + + teleop_device_explicitly_set = args_cli.teleop_device is not None + + camera_feed_session = XrCameraFeedSession.prepare( + env_cfg, + enabled=args_cli.xr and use_isaac_teleop, + camera_rendering_enabled=not args_cli.disable_external_cameras, + ) + # Apply the RTX/DLSS global settings when an RTX render pipeline will run (Kit visualizer, - # external cameras, or XR). ``apply_isaac_rtx_global_settings`` uses ``omni.replicator``, - # which some experiences do not preload, so ensure it is loaded first. + # external cameras, or XR). if _rtx_rendering_requested(args_cli): - _ensure_replicator_loaded() apply_isaac_rtx_global_settings( IsaacRtxRendererGlobalSettingsCfg( antialiasing_mode="DLSS", @@ -325,7 +320,6 @@ def main() -> None: # noqa: C901 ) except Exception as e: logger.error(f"Failed to create environment: {e}") - simulation_app.close() return # Flags for controlling teleoperation flow @@ -425,7 +419,6 @@ def stop_teleoperation() -> None: " Built-in devices: keyboard, spacemouse, gamepad." ) env.close() - simulation_app.close() return for key, callback in teleoperation_callbacks.items(): try: @@ -446,13 +439,11 @@ def stop_teleoperation() -> None: except Exception as e: logger.error(f"Failed to create teleop device: {e}") env.close() - simulation_app.close() return if teleop_interface is None: logger.error("Failed to create teleop interface") env.close() - simulation_app.close() return print(f"Using teleop device: {teleop_interface}") @@ -464,7 +455,9 @@ def stop_teleoperation() -> None: # Optional keyboard for headset-free IsaacTeleop control. Kept in a local so its # carb input subscription is not garbage-collected; a headless run auto-starts # (in ``run_loop``) without it. - control_keyboard = _make_control_keyboard(teleop_interface, use_isaac_teleop, app_launcher.has_window) # noqa: F841 + # a local window exists (GUI or livestream) unless XR runs headless without a viewport + has_window = env.sim.has_gui and not env.sim.get_setting("/isaaclab/xr/auto_start") + control_keyboard = _make_control_keyboard(teleop_interface, use_isaac_teleop, has_window) # noqa: F841 def run_loop(): """Inner function to run the teleop loop with access to nonlocal variables.""" @@ -484,7 +477,7 @@ def run_loop(): print(f"{stack_name} teleoperation started. Press 'R' to reset the environment.") # simulate environment - while simulation_app.is_running(): + while env.sim.is_headless_or_exist_active_visualizer(): try: # run everything in inference mode with torch.inference_mode(): @@ -542,7 +535,3 @@ def run_loop(): if __name__ == "__main__": # run the main function main() - # env.close() already closes the USD stage via sim.clear_instance(). - # Pump the event loop so the viewport processes closure, then close the app. - simulation_app.update() - simulation_app.close() diff --git a/scripts/imitation_learning/isaaclab_mimic/annotate_demos.py b/scripts/imitation_learning/isaaclab_mimic/annotate_demos.py index 21b86ea8a435..b59794c1edd5 100644 --- a/scripts/imitation_learning/isaaclab_mimic/annotate_demos.py +++ b/scripts/imitation_learning/isaaclab_mimic/annotate_demos.py @@ -10,13 +10,12 @@ import argparse import math -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.utils.string import list_intersection, string_to_callable -# Launching Isaac Sim Simulator first. +# Parse CLI first so we can decide whether to launch Isaac Sim Kit. -# add argparse arguments parser = argparse.ArgumentParser(description="Annotate demonstrations for Isaac Lab environments.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") parser.add_argument( @@ -37,15 +36,9 @@ ) parser.add_argument("--external_callback", default=None, help="Fully qualified path to an externally defined callback.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) -# parse the arguments +add_launcher_args(parser) args_cli, remaining_args = parser.parse_known_args() -# launch the simulator -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - # Call an external callback if requested. remaining_args_env_registration = None if args_cli.external_callback: @@ -61,29 +54,27 @@ if unrecognized_args: parser.error(f"unrecognized arguments: {' '.join(unrecognized_args)}") -"""Rest everything follows.""" - import contextlib import os +from typing import TYPE_CHECKING import gymnasium as gym import torch -import isaaclab_mimic.envs # noqa: F401 - -# Only enables inputs if this script is NOT headless mode -if not args_cli.headless and not os.environ.get("HEADLESS", 0): - from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg - -from isaaclab.envs import ManagerBasedRLMimicEnv from isaaclab.envs.mdp.recorders.recorders_cfg import ActionStateRecorderManagerCfg from isaaclab.managers import RecorderTerm, RecorderTermCfg, TerminationTermCfg from isaaclab.utils import configclass from isaaclab.utils.datasets import EpisodeData, HDF5DatasetFileHandler +import isaaclab_mimic.envs # noqa: F401 + import isaaclab_tasks # noqa: F401 from isaaclab_tasks.utils.parse_cfg import parse_env_cfg +if TYPE_CHECKING: + # the environment class loads USD, so it is only imported at runtime once the simulation is launched + from isaaclab.envs import ManagerBasedRLMimicEnv + is_paused = False current_action_index = 0 marked_subtask_action_indices = [] @@ -227,6 +218,20 @@ def main(): env_cfg.recorders.dataset_export_dir_path = output_dir env_cfg.recorders.dataset_filename = output_file_name + with launch_simulation(env_cfg, args_cli): + return annotate_dataset(env_cfg, dataset_file_handler, success_term) + + +def annotate_dataset(env_cfg, dataset_file_handler: HDF5DatasetFileHandler, success_term: TerminationTermCfg) -> int: + """Create the environment and annotate every episode of the loaded dataset. + + Returns: + The number of successfully annotated episodes. + """ + global is_paused, current_action_index, marked_subtask_action_indices + + from isaaclab.envs import ManagerBasedRLMimicEnv + # create environment from loaded config env: ManagerBasedRLMimicEnv = gym.make(args_cli.task, cfg=env_cfg).unwrapped @@ -276,8 +281,10 @@ def main(): # reset environment env.reset() - # Only enables inputs if this script is NOT headless mode - if not args_cli.headless and not os.environ.get("HEADLESS", 0): + # Only enables inputs if this script runs with a GUI + if env.sim.has_gui: + from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg + keyboard_interface = Se3Keyboard(Se3KeyboardCfg(pos_sensitivity=0.1, rot_sensitivity=0.1)) keyboard_interface.add_callback("N", play_cb) keyboard_interface.add_callback("B", pause_cb) @@ -291,7 +298,7 @@ def main(): processed_episode_count = 0 successful_task_count = 0 # Counter for successful task completions with contextlib.suppress(KeyboardInterrupt) and torch.inference_mode(): - while simulation_app.is_running() and not simulation_app.is_exiting(): + while env.sim.is_headless_or_exist_active_visualizer(): # Iterate over the episodes in the loaded dataset file for episode_index, episode_name in enumerate(dataset_file_handler.get_episode_names()): processed_episode_count += 1 @@ -335,7 +342,7 @@ def main(): def replay_episode( - env: ManagerBasedRLMimicEnv, + env: "ManagerBasedRLMimicEnv", episode: EpisodeData, success_term: TerminationTermCfg | None = None, ) -> bool: @@ -380,7 +387,7 @@ def replay_episode( def annotate_episode_in_auto_mode( - env: ManagerBasedRLMimicEnv, + env: "ManagerBasedRLMimicEnv", episode: EpisodeData, success_term: TerminationTermCfg | None = None, ) -> bool: @@ -425,7 +432,7 @@ def annotate_episode_in_auto_mode( def annotate_episode_in_manual_mode( - env: ManagerBasedRLMimicEnv, + env: "ManagerBasedRLMimicEnv", episode: EpisodeData, success_term: TerminationTermCfg | None = None, subtask_term_signal_names: dict[str, list[str]] = {}, @@ -533,9 +540,6 @@ def annotate_episode_in_manual_mode( if __name__ == "__main__": - # run the main function successful_task_count = main() - # close sim app - simulation_app.close() # exit with the number of successful task completions as return code exit(successful_task_count) diff --git a/scripts/imitation_learning/isaaclab_mimic/consolidated_demo.py b/scripts/imitation_learning/isaaclab_mimic/consolidated_demo.py index 56eeb2480800..34ab9e282e09 100644 --- a/scripts/imitation_learning/isaaclab_mimic/consolidated_demo.py +++ b/scripts/imitation_learning/isaaclab_mimic/consolidated_demo.py @@ -7,13 +7,12 @@ Script to record teleoperated demos and run mimic dataset generation in real-time. """ -# Launching Isaac Sim Simulator first. +# Parse CLI first so we can decide whether to launch Isaac Sim Kit. import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# add argparse arguments parser = argparse.ArgumentParser( description="Record demonstrations and run mimic dataset generation for Isaac Lab environments." ) @@ -60,16 +59,11 @@ default=None, help="File path to export generated episodes by mimic.", ) -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) # parse the arguments, forwarding unrecognized ones as Hydra-style task config overrides args_cli, hydra_overrides = parser.parse_known_args() - -# launch the simulator -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything follows.""" +# the teleop input devices are Kit input devices, so the Kit runtime is required +args_cli.require_kit = True import asyncio import contextlib @@ -81,16 +75,12 @@ import numpy as np import torch -from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg, Se3SpaceMouse, Se3SpaceMouseCfg -from isaaclab.envs import ManagerBasedRLMimicEnv from isaaclab.envs.mdp.recorders.recorders_cfg import ActionStateRecorderManagerCfg from isaaclab.managers import DatasetExportMode, RecorderTerm, RecorderTermCfg from isaaclab.utils import configclass from isaaclab.utils.datasets import HDF5DatasetFileHandler import isaaclab_mimic.envs # noqa: F401 -from isaaclab_mimic.datagen.data_generator import DataGenerator -from isaaclab_mimic.datagen.datagen_info_pool import DataGenInfoPool import isaaclab_tasks # noqa: F401 from isaaclab_tasks.utils.parse_cfg import parse_env_cfg @@ -198,6 +188,9 @@ async def run_teleop_robot( should_reset_teleop_instance = False # create controller if needed if teleop_interface is None: + # the teleop devices need the Kit runtime, which is running by the time this coroutine executes + from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg, Se3SpaceMouse, Se3SpaceMouseCfg + if args_cli.teleop_device.lower() == "keyboard": teleop_interface = Se3Keyboard(Se3KeyboardCfg(pos_sensitivity=0.2, rot_sensitivity=0.5)) elif args_cli.teleop_device.lower() == "spacemouse": @@ -264,6 +257,8 @@ async def run_data_generator( ): """Run data generator.""" global num_success, num_failures, num_attempts + from isaaclab_mimic.datagen.data_generator import DataGenerator + data_generator = DataGenerator(env=env.unwrapped, src_demo_datagen_info_pool=shared_datagen_info_pool) idle_action = torch.zeros(env.unwrapped.action_space.shape)[0] while True: @@ -400,6 +395,19 @@ def main(): env_cfg.recorders.dataset_filename = generated_output_file_name env_cfg.recorders.dataset_export_mode = DatasetExportMode.EXPORT_SUCCEEDED_ONLY + with launch_simulation(env_cfg, args_cli): + run_consolidated_demo(env_cfg, success_term) + + +def run_consolidated_demo(env_cfg, success_term): + """Create the environment and run teleoperated recording alongside real-time mimic data generation.""" + # the environment and data generation classes load USD, so they are imported once the simulation is launched + from isaaclab.envs import ManagerBasedRLMimicEnv + + from isaaclab_mimic.datagen.datagen_info_pool import DataGenInfoPool + + num_envs = args_cli.num_envs + # create environment env = gym.make(args_cli.task, cfg=env_cfg) @@ -470,5 +478,3 @@ def main(): main() except KeyboardInterrupt: print("\nProgram interrupted by user. Exiting...") - # close sim app - simulation_app.close() diff --git a/scripts/imitation_learning/isaaclab_mimic/generate_dataset.py b/scripts/imitation_learning/isaaclab_mimic/generate_dataset.py index dfcfabe60fa1..df527dd9b97f 100644 --- a/scripts/imitation_learning/isaaclab_mimic/generate_dataset.py +++ b/scripts/imitation_learning/isaaclab_mimic/generate_dataset.py @@ -7,14 +7,11 @@ Main data generation script. """ -"""Launch Isaac Sim Simulator first.""" - import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation, scan from isaaclab.utils.string import list_intersection, string_to_callable -# add argparse arguments parser = argparse.ArgumentParser(description="Generate demonstrations for Isaac Lab environments.") parser.add_argument("--task", type=str, default=None, help="Name of the task.") parser.add_argument("--generation_num_trials", type=int, help="Number of demos to be generated.", default=None) @@ -54,17 +51,14 @@ help="Fully qualified path to an externally defined callback.", ) -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) -# parse the arguments +add_launcher_args(parser) args_cli, remaining_args = parser.parse_known_args() if args_cli.max_num_failures is not None and args_cli.max_num_failures < 1: parser.error("--max_num_failures must be positive") # Mimic environments may use camera observations or an RTX renderer. Request # rendering support here so callers do not need a legacy CLI flag. -app_launcher = AppLauncher(args_cli, enable_cameras=True) -simulation_app = app_launcher.app +args_cli.enable_cameras = True # Call an external callback if requested. remaining_args_env_registration = None @@ -77,22 +71,21 @@ if unrecognized_args: parser.error(f"unrecognized arguments: {' '.join(unrecognized_args)}") -"""Rest everything follows.""" - import asyncio import inspect import logging +import os import random import gymnasium as gym import numpy as np import torch -from isaaclab.envs import ManagerBasedRLMimicEnv +from isaaclab.utils.datasets import HDF5DatasetFileHandler import isaaclab_mimic.envs # noqa: F401 -from isaaclab_mimic.datagen.generation import env_loop, setup_async_generation, setup_env_config -from isaaclab_mimic.datagen.utils import get_env_name_from_dataset, setup_output_paths + +from isaaclab_tasks.utils.parse_cfg import parse_env_cfg # import logger logger = logging.getLogger(__name__) @@ -101,12 +94,36 @@ def main(): num_envs = args_cli.num_envs - # Setup output paths and get env name - output_dir, output_file_name = setup_output_paths(args_cli.output_file) + # Get env name task_name = args_cli.task if task_name: task_name = args_cli.task.split(":")[-1] - env_name = task_name or get_env_name_from_dataset(args_cli.input_file) + if not task_name: + if not os.path.exists(args_cli.input_file): + raise FileNotFoundError(f"The dataset file {args_cli.input_file} does not exist.") + dataset_file_handler = HDF5DatasetFileHandler() + dataset_file_handler.open(args_cli.input_file) + task_name = dataset_file_handler.get_env_name() + if task_name is None: + raise ValueError("Environment name not found in dataset") + env_name = task_name + + # Launch the runtime the task needs before loading the mimic helpers, which import USD + with launch_simulation(parse_env_cfg(env_name, device=args_cli.device, num_envs=num_envs), args_cli): + generate(env_name) + + +def generate(env_name: str): + """Configure and create the environment, then run the (optionally SkillGen-based) data generation.""" + from isaaclab.envs import ManagerBasedRLMimicEnv + + from isaaclab_mimic.datagen.generation import env_loop, setup_async_generation, setup_env_config + from isaaclab_mimic.datagen.utils import setup_output_paths + + num_envs = args_cli.num_envs + + # Setup output paths + output_dir, output_file_name = setup_output_paths(args_cli.output_file) # Configure environment env_cfg, success_term = setup_env_config( @@ -118,6 +135,8 @@ def main(): generation_num_trials=args_cli.generation_num_trials, dataset_compression=not args_cli.disable_dataset_compression, ) + # resolve the automatic physics and renderer selections of this config the same way the launch did + scan(env_cfg, args_cli) if args_cli.max_num_failures is not None: env_cfg.datagen_config.max_num_failures = args_cli.max_num_failures @@ -221,5 +240,3 @@ def main(): main() except KeyboardInterrupt: print("\nProgram interrupted by user. Exiting...") - # Close sim app - simulation_app.close() diff --git a/scripts/imitation_learning/locomanipulation_sdg/generate_data.py b/scripts/imitation_learning/locomanipulation_sdg/generate_data.py index 399f89be47c3..dd199c990c63 100644 --- a/scripts/imitation_learning/locomanipulation_sdg/generate_data.py +++ b/scripts/imitation_learning/locomanipulation_sdg/generate_data.py @@ -5,10 +5,12 @@ """Script to replay demonstrations with Isaac Lab environments.""" +from __future__ import annotations + import argparse import os -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation, scan parser = argparse.ArgumentParser(description="Locomanipulation SDG") parser.add_argument("--task", type=str, help="The Isaac Lab locomanipulation SDG task to load for data generation.") @@ -122,26 +124,24 @@ help="Set the Sim GUI viewport to the robot_pov_cam sensor view at the start of each episode.", ) -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) # forward unrecognized args as Hydra-style task config overrides args_cli, hydra_overrides = parser.parse_known_args() -app_launcher = AppLauncher(args_cli, enable_cameras=True) -simulation_app = app_launcher.app +args_cli.enable_cameras = True +# the task config imports USD and the script uses Kit APIs directly, so the Kit runtime is always launched +args_cli.require_kit = True import enum import random from dataclasses import dataclass +from typing import TYPE_CHECKING import gymnasium as gym import numpy as np import torch import warp as wp -import omni.kit -import omni.kit.viewport.utility -import omni.usd - from isaaclab.managers import DatasetExportMode from isaaclab.utils import configclass from isaaclab.utils.datasets import EpisodeData, HDF5DatasetFileHandler @@ -156,19 +156,17 @@ if args_cli.seed is not None: configure_seed(args_cli.seed) -from isaaclab_mimic.locomanipulation_sdg.envs.locomanipulation_sdg_env import LocomanipulationSDGEnv -from isaaclab_mimic.locomanipulation_sdg.occupancy_map_utils import ( - OccupancyMap, - OccupancyMapDataValue, - merge_occupancy_maps, - occupancy_map_add_to_stage, -) -from isaaclab_mimic.locomanipulation_sdg.path_utils import ParameterizedPath, plan_path -from isaaclab_mimic.locomanipulation_sdg.scene_utils import RelativePose, place_randomly from isaaclab_mimic.locomanipulation_sdg.transform_utils import transform_inv, transform_mul, transform_relative_pose from isaaclab_tasks.utils import parse_env_cfg +if TYPE_CHECKING: + # these modules load USD, so they are only imported at runtime once the simulation is launched + from isaaclab_mimic.locomanipulation_sdg.envs.locomanipulation_sdg_env import LocomanipulationSDGEnv + from isaaclab_mimic.locomanipulation_sdg.occupancy_map_utils import OccupancyMap + from isaaclab_mimic.locomanipulation_sdg.path_utils import ParameterizedPath + from isaaclab_mimic.locomanipulation_sdg.scene_utils import RelativePose + class LocomanipulationSDGDataGenerationState(enum.IntEnum): """States for the locomanipulation SDG data generation state machine.""" @@ -310,6 +308,8 @@ def sync_simulation_state(env: LocomanipulationSDGEnv): def _set_sensor_camera_view(): """Set the Sim GUI viewport to display the robot_pov_cam sensor view.""" + import omni.kit.viewport.utility + viewport = omni.kit.viewport.utility.get_active_viewport() if viewport is not None: cam_prim_path = "/World/envs/env_0/Robot/torso_link/d435_link/camera" @@ -437,6 +437,14 @@ def setup_navigation_scene( Returns: NavigationScene or None if the navigation scene setup failed. """ + from isaaclab_mimic.locomanipulation_sdg.occupancy_map_utils import ( + OccupancyMap, + OccupancyMapDataValue, + merge_occupancy_maps, + occupancy_map_add_to_stage, + ) + from isaaclab_mimic.locomanipulation_sdg.path_utils import ParameterizedPath, plan_path + from isaaclab_mimic.locomanipulation_sdg.scene_utils import RelativePose, place_randomly background_fixture = env.get_background_fixture() if background_fixture is not None: @@ -517,7 +525,7 @@ def clear_nav_map_buffer(world_xy: torch.Tensor | np.ndarray): if draw_visualization: occupancy_map_add_to_stage( occupancy_map, - stage=omni.usd.get_context().get_stage(), + stage=env.sim.stage, path="/OccupancyMap", z_offset=0.01, draw_path=base_path_helper.points, @@ -920,7 +928,7 @@ def replay( recording_step = 0 # Main simulation loop with state machine - while simulation_app.is_running() and not simulation_app.is_exiting(): + while env.sim.is_headless_or_exist_active_visualizer(): if current_state != previous_state: print(f"State changed: {current_state.name}, Recording step: {recording_step}", flush=True) previous_state = current_state @@ -998,7 +1006,8 @@ def replay( if __name__ == "__main__": - with torch.no_grad(): + # the task config module imports USD, so the Kit runtime is launched before the config is parsed + with torch.no_grad(), launch_simulation(None, args_cli): # Create environment if args_cli.task is not None: env_name = args_cli.task.split(":")[-1] @@ -1006,6 +1015,8 @@ def replay( raise ValueError("Task/env name was not specified nor found in the dataset.") env_cfg = parse_env_cfg(env_name, device=args_cli.device, num_envs=1, overrides=hydra_overrides) + # resolve the config's automatic physics and renderer selections for the launched runtime + scan(env_cfg, args_cli) env_cfg.sim.device = "cpu" env_cfg.recorders.dataset_export_dir_path = os.path.dirname(args_cli.output_file) env_cfg.recorders.dataset_filename = os.path.basename(args_cli.output_file) @@ -1085,5 +1096,3 @@ def replay( if getattr(env, "viewport_camera_controller", None) is not None: env.viewport_camera_controller.update_view_to_world() env.close() - - simulation_app.close() diff --git a/scripts/imitation_learning/locomanipulation_sdg/gr00t/rollout_policy.py b/scripts/imitation_learning/locomanipulation_sdg/gr00t/rollout_policy.py index da6980202e1b..7a53fdc01293 100644 --- a/scripts/imitation_learning/locomanipulation_sdg/gr00t/rollout_policy.py +++ b/scripts/imitation_learning/locomanipulation_sdg/gr00t/rollout_policy.py @@ -5,10 +5,12 @@ """Script to replay demonstrations with Isaac Lab environments.""" +from __future__ import annotations + import argparse import os -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation, scan parser = argparse.ArgumentParser(description="Disjoint navigation") parser.add_argument("--task", type=str, help="The Isaac Lab disjoint navigation task to load for data generation.") @@ -30,12 +32,13 @@ help="Quaternion order the policy uses: 'xyzw' (current Isaac Lab) or 'wxyz' (legacy). " "Converts env observations/actions to match. Default is 'xyzw'.", ) -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) # forward unrecognized args as Hydra-style task config overrides args_cli, hydra_overrides = parser.parse_known_args() +# the task config imports USD, so the Kit runtime is always launched before the config is parsed +args_cli.require_kit = True -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +from typing import TYPE_CHECKING import gymnasium as gym import torch @@ -46,12 +49,6 @@ from isaaclab.utils.math import convert_quat import isaaclab_mimic.locomanipulation_sdg.envs # noqa: F401 -from isaaclab_mimic.locomanipulation_sdg.envs.locomanipulation_sdg_env import LocomanipulationSDGEnv -from isaaclab_mimic.locomanipulation_sdg.occupancy_map_utils import ( - OccupancyMap, - merge_occupancy_maps, -) -from isaaclab_mimic.locomanipulation_sdg.scene_utils import RelativePose, place_randomly from isaaclab_mimic.locomanipulation_sdg.transform_utils import ( transform_inv, transform_mul, @@ -59,6 +56,12 @@ from isaaclab_tasks.utils import parse_env_cfg +if TYPE_CHECKING: + # these modules load USD, so they are only imported at runtime once the simulation is launched + from isaaclab_mimic.locomanipulation_sdg.envs.locomanipulation_sdg_env import LocomanipulationSDGEnv + from isaaclab_mimic.locomanipulation_sdg.occupancy_map_utils import OccupancyMap + from isaaclab_mimic.locomanipulation_sdg.scene_utils import RelativePose + def _clone_state(state: dict) -> dict: """Deep clone state dict so we do not mutate the episode's stored initial state. @@ -220,6 +223,9 @@ def setup_navigation_scene( Tuple of (occupancy_map, base_goal). First element is None; base_goal is the goal pose for the policy relative to the end fixture. """ + from isaaclab_mimic.locomanipulation_sdg.occupancy_map_utils import OccupancyMap, merge_occupancy_maps + from isaaclab_mimic.locomanipulation_sdg.scene_utils import RelativePose, place_randomly + # Create base occupancy map occupancy_map = merge_occupancy_maps( [ @@ -335,7 +341,7 @@ def eval_policy( action_idx = 0 inference_interval = 16 - while simulation_app.is_running() and not simulation_app.is_exiting(): + while env.sim.is_headless_or_exist_active_visualizer(): if step % inference_interval == 0: model_input, dummy_action = build_model_input(env, base_goal, policy_quat_format) action_dict = policy.policy.get_action(model_input) @@ -368,7 +374,7 @@ def eval_policy( if __name__ == "__main__": - with torch.no_grad(): + with torch.no_grad(), launch_simulation(None, args_cli): env_name = args_cli.task.split(":")[-1] if args_cli.task is not None else None if env_name is None: raise ValueError("Task/env name was not specified nor found in the dataset.") @@ -376,6 +382,8 @@ def eval_policy( policy = Policy(model_path=args_cli.model_path, embodiment_tag=args_cli.embodiment_tag) env_cfg = parse_env_cfg(env_name, device=args_cli.device, num_envs=1, overrides=hydra_overrides) + # resolve the config's automatic physics and renderer selections for the launched runtime + scan(env_cfg, args_cli) env_cfg.sim.device = args_cli.device # Drop the SDG output-data recorder term: it pulls env._locomanipulation_sdg_output_data, # which is only populated by the data-generation state machine, not during policy rollout. @@ -399,5 +407,3 @@ def eval_policy( env.reset() env.close() - - simulation_app.close() diff --git a/scripts/imitation_learning/robomimic/play.py b/scripts/imitation_learning/robomimic/play.py index 1f381bb693ea..cce66eb4181e 100644 --- a/scripts/imitation_learning/robomimic/play.py +++ b/scripts/imitation_learning/robomimic/play.py @@ -17,16 +17,10 @@ norm_factor_max: If provided, maximum value of the action space normalization factor. """ -"""Launch Isaac Sim Simulator first.""" - - import argparse -from isaaclab.app import AppLauncher, scan +from isaaclab.app import add_launcher_args, launch_simulation -from isaaclab_tasks.utils import resolve_task_config - -# add argparse arguments parser = argparse.ArgumentParser(description="Evaluate robomimic policy for Isaac Lab environment.") parser.add_argument( "--disable_fabric", action="store_true", default=False, help="Disable fabric and use USD I/O operations." @@ -43,25 +37,13 @@ "--norm_factor_max", type=float, default=None, help="Optional: maximum value of the normalization factor." ) -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) -# parse the arguments, forwarding unrecognized ones as Hydra-style task config overrides +add_launcher_args(parser) +# parse the arguments, forwarding unrecognized ones as Hydra-style task config overrides. +# ``overrides`` must be passed explicitly to the config parser: this script keeps its own flags in +# ``sys.argv`` rather than stripping them, so letting Hydra fall back to reading ``sys.argv`` makes it +# reject them. args_cli, hydra_overrides = parser.parse_known_args() -# launch omniverse app -# Only enable rendering for tasks that actually declare Kit camera sensors: this script also -# plays policies trained on low-dimensional observations, which should not pay for the RTX -# renderer. ``resolve_task_config`` is safe to call before Kit is launched, and ``scan`` is the -# same detection ``launch_simulation`` uses, so this matches how camera enabling is resolved -# elsewhere now that the ``--enable_cameras`` flag is gone. -# ``overrides`` must be passed explicitly: this script keeps its own flags in ``sys.argv`` rather -# than stripping them, so letting Hydra fall back to reading ``sys.argv`` makes it reject them. -env_cfg_for_scan, _ = resolve_task_config(args_cli.task, "", overrides=hydra_overrides) -app_launcher = AppLauncher(args_cli, enable_cameras=scan(env_cfg_for_scan, args_cli).has_kit_camera) -simulation_app = app_launcher.app - -"""Rest everything follows.""" - import copy import random @@ -163,6 +145,14 @@ def main(): success_term = env_cfg.terminations.success env_cfg.terminations.success = None + # Launch the runtime the task needs. Camera rendering is only enabled for tasks that declare Kit + # camera sensors, so policies trained on low-dimensional observations do not pay for the RTX renderer. + with launch_simulation(env_cfg, args_cli): + run_policy(env_cfg, success_term) + + +def run_policy(env_cfg, success_term): + """Create the environment and evaluate the policy rollouts.""" # Create environment env = gym.make(args_cli.task, cfg=env_cfg).unwrapped @@ -193,7 +183,4 @@ def main(): if __name__ == "__main__": - # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/imitation_learning/robomimic/robust_eval.py b/scripts/imitation_learning/robomimic/robust_eval.py index 32a875541733..805fa8bc2a8d 100644 --- a/scripts/imitation_learning/robomimic/robust_eval.py +++ b/scripts/imitation_learning/robomimic/robust_eval.py @@ -24,15 +24,10 @@ disable_fabric: Whether to disable fabric and use USD I/O operations. """ -"""Launch Isaac Sim Simulator first.""" - import argparse -from isaaclab.app import AppLauncher, scan - -from isaaclab_tasks.utils import resolve_task_config +from isaaclab.app import add_launcher_args, launch_simulation -# add argparse arguments parser = argparse.ArgumentParser(description="Evaluate robomimic policy for Isaac Lab environment.") parser.add_argument( "--disable_fabric", action="store_true", default=False, help="Disable fabric and use USD I/O operations." @@ -60,25 +55,13 @@ "--norm_factor_max", type=float, default=None, help="Optional: maximum value of the normalization factor." ) -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) -# parse the arguments, forwarding unrecognized ones as Hydra-style task config overrides +add_launcher_args(parser) +# parse the arguments, forwarding unrecognized ones as Hydra-style task config overrides. +# ``overrides`` must be passed explicitly to the config parser: this script keeps its own flags in +# ``sys.argv`` rather than stripping them, so letting Hydra fall back to reading ``sys.argv`` makes it +# reject them. args_cli, hydra_overrides = parser.parse_known_args() -# launch omniverse app -# Only enable rendering for tasks that actually declare Kit camera sensors: this script also -# evaluates policies trained on low-dimensional observations, which should not pay for the RTX -# renderer. ``resolve_task_config`` is safe to call before Kit is launched, and ``scan`` is the -# same detection ``launch_simulation`` uses, so this matches how camera enabling is resolved -# elsewhere now that the ``--enable_cameras`` flag is gone. -# ``overrides`` must be passed explicitly: this script keeps its own flags in ``sys.argv`` rather -# than stripping them, so letting Hydra fall back to reading ``sys.argv`` makes it reject them. -env_cfg_for_scan, _ = resolve_task_config(args_cli.task, "", overrides=hydra_overrides) -app_launcher = AppLauncher(args_cli, enable_cameras=scan(env_cfg_for_scan, args_cli).has_kit_camera) -simulation_app = app_launcher.app - -"""Rest everything follows.""" - import copy import os import pathlib @@ -247,6 +230,14 @@ def main() -> None: # Set evaluation settings env_cfg.eval_mode = True + # Launch the runtime the task needs. Camera rendering is only enabled for tasks that declare Kit + # camera sensors, so policies trained on low-dimensional observations do not pay for the RTX renderer. + with launch_simulation(env_cfg, args_cli): + evaluate_models(env_cfg, success_term) + + +def evaluate_models(env_cfg, success_term) -> None: + """Create the environment and evaluate every checkpoint under each evaluation setting.""" # Create environment env = gym.make(args_cli.task, cfg=env_cfg).unwrapped @@ -338,7 +329,4 @@ def main() -> None: if __name__ == "__main__": - # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/imitation_learning/robomimic/train.py b/scripts/imitation_learning/robomimic/train.py index 7fb6c9da7168..36c78375c8c5 100644 --- a/scripts/imitation_learning/robomimic/train.py +++ b/scripts/imitation_learning/robomimic/train.py @@ -43,16 +43,6 @@ This file has been modified from the original robomimic version to integrate with IsaacLab. """ -"""Launch Isaac Sim Simulator first.""" - -from isaaclab.app import AppLauncher - -# launch omniverse app -app_launcher = AppLauncher(headless=True) -simulation_app = app_launcher.app - -"""Rest everything follows.""" - import argparse import importlib import json @@ -78,6 +68,8 @@ from robomimic.utils.log_utils import DataLogger, PrintLogger from torch.utils.data import DataLoader +from isaaclab.app import launch_simulation + import isaaclab_tasks # noqa: F401 import isaaclab_tasks.contrib.locomanip_pick_place # noqa: F401 import isaaclab_tasks.contrib.pick_place # noqa: F401 @@ -451,7 +443,6 @@ def main(args: argparse.Namespace): args = parser.parse_args() - # run training - main(args) - # close sim app - simulation_app.close() + # run training inside a headless Isaac Sim runtime, which validation rollouts in Isaac Lab environments need + with launch_simulation(None, {"headless": True}): + main(args) diff --git a/scripts/tools/check_instanceable.py b/scripts/tools/check_instanceable.py index 8cd5cce3e894..9cc0018c120b 100644 --- a/scripts/tools/check_instanceable.py +++ b/scripts/tools/check_instanceable.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: BSD-3-Clause """ -This script uses the cloner API to check if asset has been instanced properly. +This script uses the interactive scene cloner to check if asset has been instanced properly. An asset path may be a local file or a Nucleus/HTTPS URL; remote assets are downloaded before use. @@ -21,31 +21,31 @@ Output from the above commands: ```bash ->>> Cloning time (cloner.clone): 0.648198 seconds +>>> Cloning time (scene creation): 0.648198 seconds >>> Setup time (sim.reset): : 5.843589 seconds [#clones: 4096, physics: True] Asset: : 6.491870 seconds ->>> Cloning time (cloner.clone): 0.693133 seconds +>>> Cloning time (scene creation): 0.693133 seconds >>> Setup time (sim.reset): 50.860526 seconds [#clones: 4096, physics: True] Asset: : 51.553743 seconds ->>> Cloning time (cloner.clone) : 0.687201 seconds +>>> Cloning time (scene creation) : 0.687201 seconds >>> Setup time (sim.reset) : 6.302215 seconds [#clones: 4096, physics: False] Asset: : 6.989500 seconds ->>> Cloning time (cloner.clone) : 0.678150 seconds +>>> Cloning time (scene creation) : 0.678150 seconds >>> Setup time (sim.reset) : 52.854054 seconds [#clones: 4096, physics: False] Asset: : 53.532287 seconds ``` """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command line first.""" import argparse import contextlib -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # add argparse arguments parser = argparse.ArgumentParser("Utility to empirically check if asset in instanced properly.") @@ -53,83 +53,64 @@ parser.add_argument("-n", "--num_clones", type=int, default=128, help="Number of clones to spawn.") parser.add_argument("-s", "--spacing", type=float, default=1.5, help="Spacing between instances in a grid.") parser.add_argument("-p", "--physics", action="store_true", default=False, help="Clone assets using physics cloner.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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.""" - -from isaaclab.sim.utils import enable_extension - -enable_extension("isaacsim.core.cloner") - -from isaacsim.core.cloner import GridCloner - import isaaclab.sim as sim_utils +from isaaclab.assets import AssetBaseCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sim import SimulationCfg, SimulationContext -from isaaclab.utils import Timer +from isaaclab.utils import Timer, instantiate from isaaclab.utils.assets import check_file_path, retrieve_file_path def main(): - """Spawns the USD asset robot and clones it using Isaac Gym Cloner API.""" + """Spawns the USD asset and clones it through the interactive scene.""" # check valid file path if not check_file_path(args_cli.input): raise ValueError(f"Invalid file path: {args_cli.input}") # Load kit helper - sim = SimulationContext(SimulationCfg(dt=0.01)) - - # get stage handle - stage = sim_utils.get_current_stage() - - # Fabric and PhysX GPU buffers are configured through SimulationCfg/PhysxCfg defaults. - # enable hydra scene-graph instancing - # this is needed to visualize the scene when fabric is enabled - sim.set_setting("/persistent/omnihydra/useSceneGraphInstancing", True) - - # Create interface to clone the scene - cloner = GridCloner(spacing=args_cli.spacing, stage=stage) - cloner.define_base_env("/World/envs") - stage.DefinePrim("/World/envs/env_0", "Xform") - # Spawn things into stage - sim_utils.create_prim("/World/Light", "DistantLight") - - # Everything under the namespace "/World/envs/env_0" will be cloned. - # Resolve through retrieve_file_path so Nucleus/HTTPS inputs are downloaded first; applying - # os.path.abspath() to a URL would prepend the working directory and corrupt it. - sim_utils.create_prim("/World/envs/env_0/Asset", "Xform", usd_path=retrieve_file_path(args_cli.input)) - # Clone the scene - num_clones = args_cli.num_clones - - # Create a timer to measure the cloning time - with Timer(f"[#clones: {num_clones}, physics: {args_cli.physics}] Asset: {args_cli.input}"): - # Clone the scene - with Timer(">>> Cloning time (cloner.clone)"): - cloner.define_base_env("/World/envs") - envs_prim_paths = cloner.generate_paths("/World/envs/env", num_paths=num_clones) - _ = cloner.clone( - source_prim_path="/World/envs/env_0", prim_paths=envs_prim_paths, replicate_physics=args_cli.physics - ) - # Play the simulator - with Timer(">>> Setup time (sim.reset)"): - sim.reset() - - # Simulate scene (if not headless) - if not args_cli.headless: - with contextlib.suppress(KeyboardInterrupt): - while sim.is_playing(): - # perform step - sim.step() + sim_cfg = SimulationCfg(dt=0.01) + with launch_simulation(sim_cfg, args_cli): + sim = SimulationContext(sim_cfg) + + # Fabric and PhysX GPU buffers are configured through SimulationCfg/PhysxCfg defaults. + # enable hydra scene-graph instancing + # this is needed to visualize the scene when fabric is enabled + sim.set_setting("/persistent/omnihydra/useSceneGraphInstancing", True) + + num_clones = args_cli.num_clones + scene_cfg = InteractiveSceneCfg( + num_envs=num_clones, env_spacing=args_cli.spacing, replicate_physics=args_cli.physics + ) + scene_cfg.light = AssetBaseCfg(prim_path="/World/Light", spawn=sim_utils.DistantLightCfg()) + # Resolve through retrieve_file_path so Nucleus/HTTPS inputs are downloaded first; applying + # os.path.abspath() to a URL would prepend the working directory and corrupt it. + scene_cfg.asset = AssetBaseCfg( + prim_path="{ENV_REGEX_NS}/Asset", spawn=sim_utils.UsdFileCfg(usd_path=retrieve_file_path(args_cli.input)) + ) + + # Create a timer to measure the cloning time + with Timer(f"[#clones: {num_clones}, physics: {args_cli.physics}] Asset: {args_cli.input}"): + # Clone the scene + with Timer(">>> Cloning time (scene creation)"): + instantiate(scene_cfg) + # Play the simulator + with Timer(">>> Setup time (sim.reset)"): + sim.reset() + + # Simulate scene (if a GUI is open) + if sim.has_gui: + with contextlib.suppress(KeyboardInterrupt): + while sim.is_playing(): + # perform step + sim.step() if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tools/convert_instanceable.py b/scripts/tools/convert_instanceable.py index 527fcc71e54d..0c6e5151fd66 100644 --- a/scripts/tools/convert_instanceable.py +++ b/scripts/tools/convert_instanceable.py @@ -28,13 +28,10 @@ """ -"""Launch Isaac Sim Simulator first.""" - import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# add argparse arguments parser = argparse.ArgumentParser(description="Utility to convert a URDF or mesh into an Instanceable asset.") parser.add_argument("input", type=str, help="The path to the input directory.") parser.add_argument("output", type=str, help="The path to directory to store converted instanceable files.") @@ -71,24 +68,21 @@ help="The mass (in kg) to assign to the converted asset. If not provided, then no mass is added.", ) -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) -# parse the arguments +add_launcher_args(parser) args_cli = parser.parse_args() - -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything follows.""" +# the URDF importer and mesh asset converter are Kit extensions +args_cli.require_kit = True import os -from isaaclab.sim.converters import MeshConverter, MeshConverterCfg, UrdfConverter, UrdfConverterCfg +from isaaclab.sim.converters import MeshConverterCfg, UrdfConverterCfg from isaaclab.sim.schemas import schemas_cfg def main(): + # the mesh converter imports Kit modules, so load it after Kit starts + from isaaclab.sim.converters import MeshConverter, UrdfConverter + # Define conversion time given conversion_type = args_cli.conversion_type.lower() # Warning if conversion type input is not valid @@ -154,7 +148,5 @@ def main(): if __name__ == "__main__": - # run the main function - main() - # close sim app - simulation_app.close() + with launch_simulation(None, args_cli): + main() diff --git a/scripts/tools/convert_mesh.py b/scripts/tools/convert_mesh.py index ef4692c7db7f..e1b7bc0b8456 100644 --- a/scripts/tools/convert_mesh.py +++ b/scripts/tools/convert_mesh.py @@ -37,13 +37,10 @@ """ -"""Launch Isaac Sim Simulator first.""" - - import argparse -from isaaclab.app import AppLauncher -from isaaclab.utils import to_dict +from isaaclab.app import add_launcher_args, launch_simulation +from isaaclab.utils import instantiate, to_dict # Define collision approximation choices (must be defined before parser) _valid_collision_approx = [ @@ -57,7 +54,6 @@ "none", ] -# add argparse arguments parser = argparse.ArgumentParser(description="Utility to convert a mesh file into USD format.") parser.add_argument("input", type=str, help="The path to the input mesh file.") parser.add_argument("output", type=str, help="The path to store the USD file.") @@ -80,22 +76,18 @@ default=None, help="The mass (in kg) to assign to the converted asset. If not provided, then no mass is added.", ) -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) -# parse the arguments +add_launcher_args(parser) args_cli = parser.parse_args() - -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything follows.""" +# the mesh converter uses the Kit asset converter extension, which the Isaac Sim PhysX runtime provides +args_cli.physics = "isaacsim_physx" import os -from isaaclab_physx.app import show_stage_in_viewport - -from isaaclab.sim.converters import MeshConverter, MeshConverterCfg +import isaaclab.sim as sim_utils +from isaaclab.assets import AssetBaseCfg +from isaaclab.physics import PhysicsCfg +from isaaclab.scene import InteractiveSceneCfg +from isaaclab.sim.converters import MeshConverterCfg from isaaclab.sim.schemas import schemas_cfg from isaaclab.utils.assets import check_file_path from isaaclab.utils.dict import print_dict @@ -114,6 +106,36 @@ } +def preview(usd_path: str, physics_cfg: PhysicsCfg) -> None: + """Open the converted asset in the visualizer selected on the command line. + + Args: + usd_path: Path of the generated USD file to display. + physics_cfg: Physics config resolved by :func:`~isaaclab.app.launch_simulation`. + """ + visualizers = args_cli.visualizer or [] + if not visualizers: + return + + # The physics backend ingests the USD stage and every visualizer renders the shared scene data, + # so no backend-specific code is needed here. Physics is not stepped -- the + # asset is shown in its imported pose until the visualizer window is closed. + sim = sim_utils.SimulationContext(sim_utils.SimulationCfg(device=args_cli.device, physics=physics_cfg)) + scene_cfg = InteractiveSceneCfg(num_envs=1, env_spacing=0.0) + scene_cfg.light = AssetBaseCfg( + prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75)) + ) + scene_cfg.asset = AssetBaseCfg(prim_path="/World/ConvertedAsset", spawn=sim_utils.UsdFileCfg(usd_path=usd_path)) + _scene = instantiate(scene_cfg) + sim.reset() + + # Checked per visualizer rather than through ``SimulationContext.is_headless_or_exist_active_visualizer``: + # that predicate also reports True for an empty visualizer list (headless stepping), and ``render`` + # drops visualizers once they close, so the preview would never exit. + while any(viz.is_running() and not viz.is_closed for viz in sim.visualizers): + sim.render() + + def main(): # check valid file path mesh_path = args_cli.input @@ -173,21 +195,20 @@ def main(): print("-" * 80) print("-" * 80) - # Create Mesh converter and import the file - mesh_converter = MeshConverter(mesh_converter_cfg) - # print output - print("Mesh importer output:") - print(f"Generated USD file: {mesh_converter.usd_path}") - print("-" * 80) - print("-" * 80) + with launch_simulation(PhysicsCfg(), args_cli) as physics_cfg: + # the mesh converter imports Kit modules, so load it after Kit starts + from isaaclab.sim.converters import MeshConverter + + # Create Mesh converter and import the file + mesh_converter = MeshConverter(mesh_converter_cfg) + # print output + print("Mesh importer output:") + print(f"Generated USD file: {mesh_converter.usd_path}") + print("-" * 80) + print("-" * 80) - # Show the converted asset if the launch resolved to a window or livestream - if AppLauncher.has_gui(): - show_stage_in_viewport(mesh_converter.usd_path) + preview(mesh_converter.usd_path, physics_cfg) if __name__ == "__main__": - # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tools/convert_mjcf.py b/scripts/tools/convert_mjcf.py index b5359563075a..4e54e171f33d 100644 --- a/scripts/tools/convert_mjcf.py +++ b/scripts/tools/convert_mjcf.py @@ -33,11 +33,9 @@ """ -"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" - import argparse -from isaaclab.app import AppLauncher, add_launcher_args, launch_simulation +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.utils import instantiate, to_dict from isaaclab.utils.version import standalone_importers_available @@ -90,14 +88,6 @@ # runtime provides; without it the preview builds a simulation with no physics manager. args_cli.physics = "isaacsim_physx" if args_cli.require_kit else "newton_mjwarp" -# Report the missing importer before converting anything. Without this the launcher reports only -# that Isaac Sim is absent, which does not mention the wheel that would make this run kitlessly. -if args_cli.require_kit and not AppLauncher.is_available(): - raise ImportError( - "MJCF conversion requires either the full Isaac Sim runtime or the standalone" - " 'isaacsim-asset-isolated' importer wheel, but neither is installed." - ) - import os # noqa: E402 import isaaclab.sim as sim_utils # noqa: E402 @@ -120,16 +110,8 @@ def preview(usd_path: str, physics_cfg: PhysicsCfg) -> None: if not visualizers: return - if "kit" in visualizers: - # a Kit app that resolved without a GUI has no viewport to display the asset in - if AppLauncher.has_gui(): - from isaaclab_physx.app import show_stage_in_viewport - - show_stage_in_viewport(usd_path) - return - - # Kitless preview: the physics backend ingests the USD stage and every visualizer renders the - # shared scene data, so no backend-specific code is needed here. Physics is not stepped -- the + # The physics backend ingests the USD stage and every visualizer renders the shared scene data, + # so no backend-specific code is needed here. Physics is not stepped -- the # asset is shown in its imported pose until the visualizer window is closed. sim = sim_utils.SimulationContext(sim_utils.SimulationCfg(device=args_cli.device, physics=physics_cfg)) scene_cfg = InteractiveSceneCfg(num_envs=1, env_spacing=0.0) diff --git a/scripts/tools/convert_urdf.py b/scripts/tools/convert_urdf.py index 77906ba17ba7..dd3dde789151 100644 --- a/scripts/tools/convert_urdf.py +++ b/scripts/tools/convert_urdf.py @@ -33,11 +33,9 @@ """ -"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" - import argparse -from isaaclab.app import AppLauncher, add_launcher_args, launch_simulation +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.utils import instantiate, to_dict from isaaclab.utils.version import standalone_importers_available @@ -94,14 +92,6 @@ # runtime provides; without it the preview builds a simulation with no physics manager. args_cli.physics = "isaacsim_physx" if args_cli.require_kit else "newton_mjwarp" -# Report the missing importer before converting anything. Without this the launcher reports only -# that Isaac Sim is absent, which does not mention the wheel that would make this run kitlessly. -if args_cli.require_kit and not AppLauncher.is_available(): - raise ImportError( - "URDF conversion requires either the full Isaac Sim runtime or the standalone" - " 'isaacsim-asset-isolated' importer wheel, but neither is installed." - ) - import os # noqa: E402 import isaaclab.sim as sim_utils # noqa: E402 @@ -124,16 +114,8 @@ def preview(usd_path: str, physics_cfg: PhysicsCfg) -> None: if not visualizers: return - if "kit" in visualizers: - # a Kit app that resolved without a GUI has no viewport to display the asset in - if AppLauncher.has_gui(): - from isaaclab_physx.app import show_stage_in_viewport - - show_stage_in_viewport(usd_path) - return - - # Kitless preview: the physics backend ingests the USD stage and every visualizer renders the - # shared scene data, so no backend-specific code is needed here. Physics is not stepped -- the + # The physics backend ingests the USD stage and every visualizer renders the shared scene data, + # so no backend-specific code is needed here. Physics is not stepped -- the # asset is shown in its imported pose until the visualizer window is closed. sim = sim_utils.SimulationContext(sim_utils.SimulationCfg(device=args_cli.device, physics=physics_cfg)) scene_cfg = InteractiveSceneCfg(num_envs=1, env_spacing=0.0) diff --git a/scripts/tools/cosmos/cosmos_prompt_gen.py b/scripts/tools/cosmos/cosmos_prompt_gen.py index 32db884adc56..2530625d8d1e 100644 --- a/scripts/tools/cosmos/cosmos_prompt_gen.py +++ b/scripts/tools/cosmos/cosmos_prompt_gen.py @@ -68,7 +68,6 @@ def generate_prompt(templates_path: str): def main(): - # Parse command line arguments args = parse_args() prompts = [generate_prompt(args.templates_path) for _ in range(args.num_prompts)] diff --git a/scripts/tools/hdf5_to_mp4.py b/scripts/tools/hdf5_to_mp4.py index a0f4f7eafd0b..5c93b7cbae10 100644 --- a/scripts/tools/hdf5_to_mp4.py +++ b/scripts/tools/hdf5_to_mp4.py @@ -199,7 +199,6 @@ def get_num_demos(hdf5_file): def main(): """Main function to convert all demonstrations to MP4 videos.""" - # Parse command line arguments args = parse_args() # Create output directory if it doesn't exist diff --git a/scripts/tools/mp4_to_hdf5.py b/scripts/tools/mp4_to_hdf5.py index 61f7b5b0b40b..17b37b74fb26 100644 --- a/scripts/tools/mp4_to_hdf5.py +++ b/scripts/tools/mp4_to_hdf5.py @@ -132,7 +132,6 @@ def process_video_and_demo(f_in, f_out, video_path, orig_demo_id, new_demo_id): def main(): """Main function to create a new dataset with augmented videos.""" - # Parse command line arguments args = parse_args() # Get list of MP4 videos diff --git a/scripts/tools/process_meshes_to_obj.py b/scripts/tools/process_meshes_to_obj.py index 2c5be04c0e5c..2e5371225bae 100644 --- a/scripts/tools/process_meshes_to_obj.py +++ b/scripts/tools/process_meshes_to_obj.py @@ -17,7 +17,6 @@ def parse_cli_args(): """Parse the input command line arguments.""" - # add argparse arguments parser = argparse.ArgumentParser("Utility to convert all mesh files to `.obj` in given folders.") parser.add_argument("input_dir", type=str, help="The input directory from which to load meshes.") parser.add_argument( @@ -84,7 +83,6 @@ def convert_meshes(source_folders: list[str], destination_folders: list[str]): if __name__ == "__main__": - # Parse command line arguments args = parse_cli_args() # Run conversion convert_meshes([args.input_dir], [args.output_dir]) diff --git a/scripts/tools/record_demos.py b/scripts/tools/record_demos.py index 6f69a2fe7b12..88bb0bd871a5 100644 --- a/scripts/tools/record_demos.py +++ b/scripts/tools/record_demos.py @@ -29,7 +29,7 @@ (default: 10) """ -"""Launch Isaac Sim Simulator first.""" +"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" # Isaac Lab does not use Warp autodiff; skipping adjoint codegen roughly halves the # time spent building kernels on a cold kernel cache. @@ -43,9 +43,8 @@ import sys from typing import TYPE_CHECKING -from isaaclab.app import AppLauncher - -# Isaac Lab AppLauncher +# Isaac Lab simulation launcher +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.utils import replace from isaaclab.utils.string import list_intersection, string_to_callable @@ -54,6 +53,8 @@ if TYPE_CHECKING: from isaaclab_teleop import XrCameraFeedSession + from isaaclab_mimic.ui.instruction_display import InstructionDisplay + # add argparse arguments parser = argparse.ArgumentParser(description="Record demonstrations for Isaac Lab environments.") parser.add_argument("--task", type=str, required=True, help="Name of the task.") @@ -135,8 +136,8 @@ " and improve XR performance)." ), ) -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_launcher_args(parser) # parse the arguments args_cli, hydra_args = setup_preset_cli(parser) @@ -144,15 +145,13 @@ if args_cli.task is None: parser.error("--task is required") -app_launcher_args = vars(args_cli) - -# launch the simulator # Enable external camera rendering by default (``--disable_external_cameras`` turns it off). The # ``--enable_cameras`` CLI flag was removed in Isaac Lab 3.0 (see #6656), so pass the intent to -# AppLauncher as a kwarg; this selects a camera-rendering experience that provides RTX/DLSS support. +# the launcher; this selects a camera-rendering experience that provides RTX/DLSS support. # Everywhere else we read ``args_cli.disable_external_cameras`` directly. -app_launcher = AppLauncher(args_cli, enable_cameras=not args_cli.disable_external_cameras) -simulation_app = app_launcher.app +args_cli.enable_cameras = not args_cli.disable_external_cameras +# the recording UI, the teleop input devices, and the RTX settings use Kit APIs directly +args_cli.require_kit = True # Call an external callback if requested. remaining_args_env_registration = None @@ -176,22 +175,14 @@ import gymnasium as gym import torch from isaaclab_physx.renderers import IsaacRtxRendererGlobalSettingsCfg -from isaaclab_physx.renderers.isaac_rtx_renderer_utils import ( - apply_isaac_rtx_global_settings, -) -import omni.ui as ui - -from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg, Se3SpaceMouse, Se3SpaceMouseCfg from isaaclab.devices.openxr import remove_camera_configs from isaaclab.devices.teleop_device_factory import create_teleop_device from isaaclab.envs import DirectRLEnvCfg, ManagerBasedRLEnvCfg from isaaclab.envs.mdp.recorders.recorders_cfg import ActionStateRecorderManagerCfg -from isaaclab.envs.ui import EmptyWindow from isaaclab.managers import DatasetExportMode import isaaclab_mimic.envs # noqa: F401 -from isaaclab_mimic.ui.instruction_display import InstructionDisplay, show_subtask_instructions import isaaclab_tasks # noqa: F401 from isaaclab_tasks.utils import resolve_task_config @@ -245,20 +236,6 @@ def _rtx_rendering_requested(args: argparse.Namespace) -> bool: return external_cameras or ("kit" in visualizers) or bool(getattr(args, "xr", False)) -def _ensure_replicator_loaded() -> None: - """Enable ``omni.replicator.core`` so RTX/DLSS global settings can be applied. - - :func:`apply_isaac_rtx_global_settings` sets the antialiasing mode through - ``omni.replicator.core``, which ships with the SDG/rendering extensions. Some Kit - experiences (e.g. the Kit-viewport-only app selected by ``--visualizer kit`` without - cameras or XR) do not preload it, so enable it on demand via the extension manager - before applying RTX settings. Idempotent when the extension is already enabled. - """ - import omni.kit.app - - omni.kit.app.get_app().get_extension_manager().set_extension_enabled_immediate("omni.replicator.core", True) - - class RateLimiter: """Convenience class for enforcing rates in loops.""" @@ -372,14 +349,6 @@ def create_environment_config( # them (defaulted on) so cameras render alongside the XR view. if args_cli.disable_external_cameras: env_cfg = remove_camera_configs(env_cfg) - # Apply the RTX/DLSS global settings when an RTX render pipeline will run (Kit visualizer, - # external cameras, or XR). ``apply_isaac_rtx_global_settings`` uses ``omni.replicator``, - # which some experiences do not preload, so ensure it is loaded first. - if _rtx_rendering_requested(args_cli): - _ensure_replicator_loaded() - apply_isaac_rtx_global_settings( - IsaacRtxRendererGlobalSettingsCfg(antialiasing_mode="DLSS"), - ) # modify configuration such that the environment runs indefinitely until # the goal is reached or other termination conditions are met @@ -421,6 +390,8 @@ def create_environment(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg) -> gym.En def _create_builtin_device(device_name: str) -> object | None: """Create a built-in teleop device by name, or return None if unrecognized.""" + from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg, Se3SpaceMouse, Se3SpaceMouseCfg + name = device_name.lower() if name == "keyboard": return Se3Keyboard(Se3KeyboardCfg(pos_sensitivity=0.2, rot_sensitivity=0.5)) @@ -484,6 +455,8 @@ def setup_teleop_device(callbacks: dict[str, Callable], use_isaac_teleop: bool = for key, callback in callbacks.items(): teleop_interface.add_callback(key, callback) else: + from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg + # No --teleop_device and no isaac_teleop: fall back to keyboard teleop_interface = Se3Keyboard(Se3KeyboardCfg(pos_sensitivity=0.2, rot_sensitivity=0.5)) for key, callback in callbacks.items(): @@ -499,7 +472,7 @@ def setup_teleop_device(callbacks: dict[str, Callable], use_isaac_teleop: bool = return teleop_interface -def setup_ui(label_text: str, env: gym.Env) -> InstructionDisplay: +def setup_ui(label_text: str, env: gym.Env) -> "InstructionDisplay": """Set up the user interface elements. Creates instruction display and UI window with labels for showing information @@ -512,6 +485,12 @@ def setup_ui(label_text: str, env: gym.Env) -> InstructionDisplay: Returns: InstructionDisplay: The configured instruction display object """ + import omni.ui as ui + + from isaaclab.envs.ui import EmptyWindow + + from isaaclab_mimic.ui.instruction_display import InstructionDisplay + instruction_display = InstructionDisplay(args_cli.xr) if not args_cli.xr: window = EmptyWindow(env, "Instruction") @@ -561,7 +540,7 @@ def process_success_condition(env: gym.Env, success_term: object | None, success def handle_reset( env: gym.Env, success_step_count: int, - instruction_display: InstructionDisplay, + instruction_display: "InstructionDisplay", label_text: str, teleop_interface: object | None = None, ) -> int: @@ -681,7 +660,11 @@ def stop_recording_instance(): # turns it into one env reset) and pauses the session -- binding it straight to # ``reset_recording_instance`` would reset the env twice. control_keyboard = None - if use_isaac_teleop and app_launcher.has_window: + # a local window exists (GUI or livestream) unless XR runs headless without a viewport + has_window = env.sim.has_gui and not env.sim.get_setting("/isaaclab/xr/auto_start") + if use_isaac_teleop and has_window: + from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg + try: control_keyboard = Se3Keyboard(Se3KeyboardCfg(pos_sensitivity=0.0, rot_sensitivity=0.0)) control_keyboard.add_callback("B", teleop_interface.request_start) @@ -719,8 +702,10 @@ def inner_loop(): if use_isaac_teleop: from isaaclab_teleop import poll_control_events + from isaaclab_mimic.ui.instruction_display import show_subtask_instructions + with contextlib.suppress(KeyboardInterrupt), torch.inference_mode(), camera_feed_session.bind(env): - while simulation_app.is_running(): + while env.sim.is_headless_or_exist_active_visualizer(): # Get teleop command (may be None while waiting for session start) action = teleop_interface.advance() @@ -830,8 +815,22 @@ def main() -> None: global env_cfg # Make env_cfg available to setup_teleop_device env_cfg, success_term, use_isaac_teleop = create_environment_config(output_dir, output_file_name) + with launch_simulation(env_cfg, args_cli): + record_demos(success_term, use_isaac_teleop) + + +def record_demos(success_term: object | None, use_isaac_teleop: bool) -> None: + """Create the environment and record demonstrations until the session ends.""" + from isaaclab_physx.renderers.isaac_rtx_renderer_utils import apply_isaac_rtx_global_settings from isaaclab_teleop import XrCameraFeedSession + # Apply the RTX/DLSS global settings when an RTX render pipeline will run (Kit visualizer, + # external cameras, or XR). + if _rtx_rendering_requested(args_cli): + apply_isaac_rtx_global_settings( + IsaacRtxRendererGlobalSettingsCfg(antialiasing_mode="DLSS"), + ) + camera_feed_session = XrCameraFeedSession.prepare( env_cfg, enabled=args_cli.xr and use_isaac_teleop, @@ -873,7 +872,3 @@ def main() -> None: if __name__ == "__main__": # run the main function main() - # env.close() already closes the USD stage via sim.clear_instance(). - # Pump the event loop so the viewport processes closure, then close the app. - simulation_app.update() - simulation_app.close() diff --git a/scripts/tools/replay_demos.py b/scripts/tools/replay_demos.py index 92bc7ea2599b..839a24b9a067 100644 --- a/scripts/tools/replay_demos.py +++ b/scripts/tools/replay_demos.py @@ -4,9 +4,6 @@ # SPDX-License-Identifier: BSD-3-Clause """Script to replay demonstrations with Isaac Lab environments.""" -"""Launch Isaac Sim Simulator first.""" - - # Isaac Lab does not use Warp autodiff; skipping adjoint codegen roughly halves the # time spent building kernels on a cold kernel cache. import warp as wp @@ -16,12 +13,11 @@ import argparse import sys -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.utils.string import list_intersection, string_to_callable from isaaclab_tasks.utils import setup_preset_cli -# add argparse arguments parser = argparse.ArgumentParser(description="Replay demonstrations in Isaac Lab environments.") parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to replay episodes.") parser.add_argument("--task", type=str, default=None, help="Force to use the specified task.") @@ -59,15 +55,10 @@ ) parser.add_argument("--external_callback", default=None, help="Fully qualified path to an externally defined callback.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) -# parse the arguments +add_launcher_args(parser) args_cli, hydra_args = setup_preset_cli(parser) -# args_cli.headless = True - -# launch the simulator -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +# the pause/resume keyboard is a Kit input device, so the Kit runtime is required +args_cli.require_kit = True # Call an external callback if requested. remaining_args_env_registration = None @@ -79,15 +70,12 @@ hydra_args = list_intersection(hydra_args, remaining_args_env_registration) sys.argv = [sys.argv[0]] + hydra_args -"""Rest everything follows.""" - import contextlib import os import gymnasium as gym import torch -from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg from isaaclab.utils.datasets import EpisodeData, HDF5DatasetFileHandler import isaaclab_tasks # noqa: F401 @@ -159,7 +147,7 @@ def replay_episodes_loop( # noqa: C901 failed_demo_ids: list[int] = [] with contextlib.suppress(KeyboardInterrupt) and torch.inference_mode(): - while simulation_app.is_running() and not simulation_app.is_exiting(): + while env.sim.is_headless_or_exist_active_visualizer(): env_episode_data_map = {index: EpisodeData() for index in range(num_envs)} first_loop = True has_next_action = True @@ -305,6 +293,23 @@ def main(): env_cfg.recorders = {} env_cfg.terminations = {} + with launch_simulation(env_cfg, args_cli): + replay_dataset(env_cfg, dataset_file_handler, episode_count, episode_indices_to_replay, success_term) + + +def replay_dataset( + env_cfg, + dataset_file_handler: HDF5DatasetFileHandler, + episode_count: int, + episode_indices_to_replay: list[int], + success_term, +): + """Create the environment and replay the selected episodes of the dataset.""" + # the keyboard device needs the Kit runtime, which is running at this point + from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg + + num_envs = args_cli.num_envs + # create environment from loaded config env = gym.make(args_cli.task, cfg=env_cfg).unwrapped @@ -361,7 +366,4 @@ def main(): if __name__ == "__main__": - # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/00_sim/create_empty.py b/scripts/tutorials/00_sim/create_empty.py index 9729145bddbb..90adb1125343 100644 --- a/scripts/tutorials/00_sim/create_empty.py +++ b/scripts/tutorials/00_sim/create_empty.py @@ -12,22 +12,19 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # create argparser parser = argparse.ArgumentParser(description="Tutorial on creating an empty stage.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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.""" @@ -37,25 +34,26 @@ def main(): """Main function.""" - # Initialize the simulation context - sim_cfg = SimulationCfg(dt=0.01) - sim = SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) + # Configure the simulation + sim_cfg = SimulationCfg(dt=0.01, device=args_cli.device) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") + # Play the simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") - # Simulate physics - while simulation_app.is_running(): - # perform step - sim.step() + # Simulate physics + while sim.is_headless_or_exist_active_visualizer(): + # perform step + sim.step() if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/00_sim/launch_app.py b/scripts/tutorials/00_sim/launch_app.py index fb4c203db448..514a4e530c74 100644 --- a/scripts/tutorials/00_sim/launch_app.py +++ b/scripts/tutorials/00_sim/launch_app.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: BSD-3-Clause """ -This script demonstrates how to run IsaacSim via the AppLauncher +This script demonstrates how to configure the simulator launch through command-line arguments. .. code-block:: bash @@ -13,15 +13,15 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # create argparser -parser = argparse.ArgumentParser(description="Tutorial on running IsaacSim via the AppLauncher.") +parser = argparse.ArgumentParser(description="Tutorial on configuring the simulator launch.") parser.add_argument("--size", type=float, default=1.0, help="Side-length of cuboid") # SimulationApp arguments https://docs.omniverse.nvidia.com/py/isaacsim/source/isaacsim.simulation_app/docs/index.html?highlight=simulationapp#isaacsim.simulation_app.SimulationApp parser.add_argument( @@ -31,13 +31,10 @@ "--height", type=int, default=720, help="Height of the viewport and generated images. Defaults to 720" ) -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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.""" @@ -69,28 +66,30 @@ def design_scene(): def main(): """Main function.""" - # Initialize the simulation context + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(device=args_cli.device, dt=0.01) - 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]) + # Launch the simulator runtime that the configuration needs; the launcher arguments + # (including --width and --height) configure it, and it is closed when the block exits + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + 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() + # Design scene by adding assets to it + design_scene() - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") + # Play the simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") - # Simulate physics - while simulation_app.is_running(): - # perform step - sim.step() + # Simulate physics + while sim.is_headless_or_exist_active_visualizer(): + # perform step + sim.step() if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/00_sim/log_time.py b/scripts/tutorials/00_sim/log_time.py index b8d2e480f812..bc5309238ee6 100644 --- a/scripts/tutorials/00_sim/log_time.py +++ b/scripts/tutorials/00_sim/log_time.py @@ -14,23 +14,20 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse import os -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # create argparser parser = argparse.ArgumentParser(description="Tutorial on creating logs from within the docker container.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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.""" @@ -52,33 +49,34 @@ def main(): os.mkdir(log_dir_path) print(f"[INFO] Logging experiment to directory: {log_dir_path}") - # Initialize the simulation context - sim_cfg = SimulationCfg(dt=0.01) - sim = SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) - - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - - # Prepare to count sim_time - sim_dt = sim.get_physics_dt() - sim_time = 0.0 - - # Open logging file - with open(os.path.join(log_dir_path, "log.txt"), "w") as log_file: - # Simulate physics - while simulation_app.is_running(): - log_file.write(f"{sim_time}" + "\n") - # perform step - sim.step() - sim_time += sim_dt + # Configure the simulation + sim_cfg = SimulationCfg(dt=0.01, device=args_cli.device) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) + + # Play the simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + + # Prepare to count sim_time + sim_dt = sim.get_physics_dt() + sim_time = 0.0 + + # Open logging file + with open(os.path.join(log_dir_path, "log.txt"), "w") as log_file: + # Simulate physics + while sim.is_headless_or_exist_active_visualizer(): + log_file.write(f"{sim_time}" + "\n") + # perform step + sim.step() + sim_time += sim_dt if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/00_sim/spawn_prims.py b/scripts/tutorials/00_sim/spawn_prims.py index cfc0a87a2afd..a2e1d0d147dc 100644 --- a/scripts/tutorials/00_sim/spawn_prims.py +++ b/scripts/tutorials/00_sim/spawn_prims.py @@ -12,22 +12,19 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # create argparser parser = argparse.ArgumentParser(description="Tutorial on spawning prims into the scene.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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.""" @@ -92,26 +89,27 @@ def design_scene(): def main(): """Main function.""" - # Initialize the simulation context + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device) - 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 - 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() + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + 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 + design_scene() + # Play the simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + + # Simulate physics + while sim.is_headless_or_exist_active_visualizer(): + # perform step + sim.step() if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/01_assets/add_new_robot.py b/scripts/tutorials/01_assets/add_new_robot.py index 6dd1c222966c..96d414971fa6 100644 --- a/scripts/tutorials/01_assets/add_new_robot.py +++ b/scripts/tutorials/01_assets/add_new_robot.py @@ -4,24 +4,21 @@ # SPDX-License-Identifier: BSD-3-Clause import argparse +from typing import TYPE_CHECKING -from isaaclab.app import AppLauncher -from isaaclab.utils import replace +from isaaclab.app import add_launcher_args, launch_simulation +from isaaclab.utils import instantiate, replace # add argparse arguments parser = argparse.ArgumentParser( description="This script demonstrates adding a custom robot to an Isaac Lab environment." ) parser.add_argument("--num_envs", type=int, default=1, help="Number of environments to spawn.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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 numpy as np import torch from isaaclab_newton.sim.schemas import NewtonArticulationCfg @@ -31,9 +28,12 @@ from isaaclab.actuators import ImplicitActuatorCfg from isaaclab.assets import AssetBaseCfg from isaaclab.assets.articulation import ArticulationCfg -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR +if TYPE_CHECKING: + from isaaclab.scene import InteractiveScene + JETBOT_CONFIG = ArticulationCfg( spawn=sim_utils.UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Robots/NVIDIA/Jetbot/jetbot.usd"), actuators={"wheel_acts": ImplicitActuatorCfg(joint_names_expr=[".*"], damping=None, stiffness=None)}, @@ -104,7 +104,7 @@ class NewRobotsSceneCfg(InteractiveSceneCfg): Dofbot = replace(DOFBOT_CONFIG, prim_path="{ENV_REGEX_NS}/Dofbot") -def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): +def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): sim_dt = sim.get_physics_dt() sim_time = 0.0 count = 0 @@ -114,7 +114,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): straight_action = torch.tensor([[10.0, 10.0]], device=sim.device).repeat(scene.num_envs, 1) turn_action = torch.tensor([[5.0, -5.0]], device=sim.device).repeat(scene.num_envs, 1) - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # reset if count % 500 == 0: # reset counters @@ -174,21 +174,23 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): def main(): """Main function.""" - # Initialize the simulation context + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) - sim.set_camera_view([3.5, 0.0, 3.2], [0.0, 0.0, 0.5]) - # Design scene - scene_cfg = NewRobotsSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) - scene = InteractiveScene(scene_cfg) - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run the simulator - run_simulator(sim, scene) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = sim_utils.SimulationContext(sim_cfg) + sim.set_camera_view([3.5, 0.0, 3.2], [0.0, 0.0, 0.5]) + # Design scene + scene_cfg = NewRobotsSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) + scene = instantiate(scene_cfg) + # Play the simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + # Run the simulator + run_simulator(sim, scene) if __name__ == "__main__": main() - simulation_app.close() diff --git a/scripts/tutorials/01_assets/run_articulation.py b/scripts/tutorials/01_assets/run_articulation.py index 47dae172fd66..ce52f3d1e167 100644 --- a/scripts/tutorials/01_assets/run_articulation.py +++ b/scripts/tutorials/01_assets/run_articulation.py @@ -12,25 +12,21 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.utils import clone # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on spawning and interacting with an articulation.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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 torch @@ -82,7 +78,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articula sim_dt = sim.get_physics_dt() count = 0 # Simulation loop - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # Reset if count % 500 == 0: # reset counter @@ -124,24 +120,25 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articula def main(): """Main function.""" - # Load kit helper + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(device=args_cli.device) - sim = SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.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) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.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__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/01_assets/run_rigid_object.py b/scripts/tutorials/01_assets/run_rigid_object.py index 52149192dbb1..4290065dbca0 100644 --- a/scripts/tutorials/01_assets/run_rigid_object.py +++ b/scripts/tutorials/01_assets/run_rigid_object.py @@ -13,24 +13,20 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on spawning and interacting with a rigid object.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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 torch @@ -87,7 +83,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, RigidObj sim_time = 0.0 count = 0 # Simulate physics - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # reset if count % 250 == 0: # reset counters @@ -124,24 +120,25 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, RigidObj def main(): """Main function.""" - # Load kit helper + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(device=args_cli.device) - sim = SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view(eye=[1.5, 0.0, 1.0], 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) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view(eye=[1.5, 0.0, 1.0], 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__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/01_assets/run_surface_gripper.py b/scripts/tutorials/01_assets/run_surface_gripper.py index 873435f51d76..0fcabf692299 100644 --- a/scripts/tutorials/01_assets/run_surface_gripper.py +++ b/scripts/tutorials/01_assets/run_surface_gripper.py @@ -14,26 +14,22 @@ currently only supported on the CPU. """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.utils import clone # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on spawning and interacting with a Surface Gripper.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_launcher_args(parser) # tutorials should open Kit visualizer by default parser.set_defaults(visualizer=["kit"]) # 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 torch @@ -102,7 +98,7 @@ def run_simulator( sim_dt = sim.get_physics_dt() count = 0 # Simulation loop - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # Reset if count % 500 == 0: # reset counter @@ -169,24 +165,25 @@ def run_simulator( def main(): """Main function.""" - # Load kit helper + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(device=args_cli.device) - sim = SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view([2.75, 7.5, 10.0], [2.75, 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) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view([2.75, 7.5, 10.0], [2.75, 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__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/02_scene/create_scene.py b/scripts/tutorials/02_scene/create_scene.py index 8d7db5dc9883..40c69509dca7 100644 --- a/scripts/tutorials/02_scene/create_scene.py +++ b/scripts/tutorials/02_scene/create_scene.py @@ -12,34 +12,34 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse +from typing import TYPE_CHECKING -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on using the interactive scene interface.") 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) +# append simulation launcher cli args +add_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 torch import isaaclab.sim as sim_utils from isaaclab.assets import ArticulationCfg, AssetBaseCfg -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sim import SimulationContext -from isaaclab.utils import configclass, replace +from isaaclab.utils import configclass, instantiate, replace + +if TYPE_CHECKING: + from isaaclab.scene import InteractiveScene ## # Pre-defined configs @@ -63,7 +63,7 @@ class CartpoleSceneCfg(InteractiveSceneCfg): cartpole: ArticulationCfg = replace(CARTPOLE_CFG, prim_path="{ENV_REGEX_NS}/Robot") -def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): +def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): """Runs the simulation loop.""" # Extract scene entities # note: we only do this here for readability. @@ -72,7 +72,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): sim_dt = sim.get_physics_dt() count = 0 # Simulation loop - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # Reset if count % 500 == 0: # reset counter @@ -114,24 +114,25 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): def main(): """Main function.""" - # Load kit helper + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(device=args_cli.device) - sim = SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0]) - # Design scene - scene_cfg = CartpoleSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) - scene = InteractiveScene(scene_cfg) - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run the simulator - run_simulator(sim, scene) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0]) + # Design scene + scene_cfg = CartpoleSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) + scene = instantiate(scene_cfg) + # Play the simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + # Run the simulator + run_simulator(sim, scene) if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/03_envs/create_cartpole_base_env.py b/scripts/tutorials/03_envs/create_cartpole_base_env.py index 35da1550678a..82bd6ad649be 100644 --- a/scripts/tutorials/03_envs/create_cartpole_base_env.py +++ b/scripts/tutorials/03_envs/create_cartpole_base_env.py @@ -13,26 +13,22 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on creating a cartpole base environment.") parser.add_argument("--num_envs", type=int, default=16, help="Number of environments to spawn.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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 math @@ -40,12 +36,12 @@ import torch import isaaclab.envs.mdp as mdp -from isaaclab.envs import ManagerBasedEnv, ManagerBasedEnvCfg +from isaaclab.envs import ManagerBasedEnvCfg from isaaclab.managers import EventTermCfg as EventTerm from isaaclab.managers import ObservationGroupCfg as ObsGroup from isaaclab.managers import ObservationTermCfg as ObsTerm from isaaclab.managers import SceneEntityCfg -from isaaclab.utils import configclass +from isaaclab.utils import configclass, instantiate from isaaclab.visualizers import VisualizerCfg from isaaclab_tasks.core.cartpole.cartpole_manager_env_cfg import CartpoleSceneCfg @@ -142,34 +138,34 @@ def main(): env_cfg = CartpoleEnvCfg() env_cfg.scene.num_envs = args_cli.num_envs env_cfg.sim.device = args_cli.device - # setup base environment - env = ManagerBasedEnv(cfg=env_cfg) - - # simulate physics - count = 0 - while simulation_app.is_running(): - with torch.inference_mode(): - # reset - if count % 300 == 0: - count = 0 - env.reset() - print("-" * 80) - print("[INFO]: Resetting environment...") - # sample random actions - joint_efforts = torch.randn_like(env.action_manager.action) - # step the environment - obs, _ = env.step(joint_efforts) - # print current orientation of pole - print("[Env 0]: Pole joint: ", obs["policy"][0][1].item()) - # update counter - count += 1 - - # close the environment - env.close() + # Launch the simulator runtime that the configuration needs + with launch_simulation(env_cfg, args_cli): + # setup base environment + env = instantiate(env_cfg) + + # simulate physics + count = 0 + while env.sim.is_headless_or_exist_active_visualizer(): + with torch.inference_mode(): + # reset + if count % 300 == 0: + count = 0 + env.reset() + print("-" * 80) + print("[INFO]: Resetting environment...") + # sample random actions + joint_efforts = torch.randn_like(env.action_manager.action) + # step the environment + obs, _ = env.step(joint_efforts) + # print current orientation of pole + print("[Env 0]: Pole joint: ", obs["policy"][0][1].item()) + # update counter + count += 1 + + # close the environment + env.close() if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/03_envs/create_cube_base_env.py b/scripts/tutorials/03_envs/create_cube_base_env.py index b9dc0b43d48a..38d8d3d213a0 100644 --- a/scripts/tutorials/03_envs/create_cube_base_env.py +++ b/scripts/tutorials/03_envs/create_cube_base_env.py @@ -28,26 +28,23 @@ from __future__ import annotations -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse +from typing import TYPE_CHECKING -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on creating a floating cube environment.") parser.add_argument("--num_envs", type=int, default=64, help="Number of environments to spawn.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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 torch @@ -56,16 +53,19 @@ import isaaclab.envs.mdp as mdp import isaaclab.sim as sim_utils from isaaclab.assets import AssetBaseCfg, RigidObject, RigidObjectCfg -from isaaclab.envs import ManagerBasedEnv, ManagerBasedEnvCfg +from isaaclab.envs import ManagerBasedEnvCfg from isaaclab.managers import ActionTerm, ActionTermCfg, SceneEntityCfg from isaaclab.managers import EventTermCfg as EventTerm from isaaclab.managers import ObservationGroupCfg as ObsGroup from isaaclab.managers import ObservationTermCfg as ObsTerm from isaaclab.scene import InteractiveSceneCfg from isaaclab.terrains import TerrainImporterCfg -from isaaclab.utils import configclass +from isaaclab.utils import configclass, instantiate from isaaclab.visualizers import VisualizerCfg +if TYPE_CHECKING: + from isaaclab.envs import ManagerBasedEnv + ## # Custom action term ## @@ -315,39 +315,39 @@ def main(): # setup base environment env_cfg = CubeEnvCfg() - env = ManagerBasedEnv(cfg=env_cfg) - - # setup target position commands - target_position = torch.rand(env.num_envs, 3, device=env.device) * 2 - target_position[:, 2] += 2.0 - # offset all targets so that they move to the world origin - target_position -= env.scene.env_origins - - # simulate physics - count = 0 - obs, _ = env.reset() - while simulation_app.is_running(): - with torch.inference_mode(): - # reset - if count % 300 == 0: - count = 0 - obs, _ = env.reset() - print("-" * 80) - print("[INFO]: Resetting environment...") - # step env - obs, _ = env.step(target_position) - # print mean squared position error between target and current position - error = torch.linalg.norm(obs["policy"] - target_position).mean().item() - print(f"[Step: {count:04d}]: Mean position error: {error:.4f}") - # update counter - count += 1 - - # close the environment - env.close() + # Launch the simulator runtime that the configuration needs + with launch_simulation(env_cfg, args_cli): + env = instantiate(env_cfg) + + # setup target position commands + target_position = torch.rand(env.num_envs, 3, device=env.device) * 2 + target_position[:, 2] += 2.0 + # offset all targets so that they move to the world origin + target_position -= env.scene.env_origins + + # simulate physics + count = 0 + obs, _ = env.reset() + while env.sim.is_headless_or_exist_active_visualizer(): + with torch.inference_mode(): + # reset + if count % 300 == 0: + count = 0 + obs, _ = env.reset() + print("-" * 80) + print("[INFO]: Resetting environment...") + # step env + obs, _ = env.step(target_position) + # print mean squared position error between target and current position + error = torch.linalg.norm(obs["policy"] - target_position).mean().item() + print(f"[Step: {count:04d}]: Mean position error: {error:.4f}") + # update counter + count += 1 + + # close the environment + env.close() if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/03_envs/create_quadruped_base_env.py b/scripts/tutorials/03_envs/create_quadruped_base_env.py index ece8ddbed3f7..a1d721ed768c 100644 --- a/scripts/tutorials/03_envs/create_quadruped_base_env.py +++ b/scripts/tutorials/03_envs/create_quadruped_base_env.py @@ -17,26 +17,23 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse +from typing import TYPE_CHECKING -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on creating a quadruped base environment.") parser.add_argument("--num_envs", type=int, default=64, help="Number of environments to spawn.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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 torch @@ -44,7 +41,7 @@ import isaaclab.envs.mdp as mdp import isaaclab.sim as sim_utils from isaaclab.assets import ArticulationCfg, AssetBaseCfg -from isaaclab.envs import ManagerBasedEnv, ManagerBasedEnvCfg +from isaaclab.envs import ManagerBasedEnvCfg from isaaclab.managers import EventTermCfg as EventTerm from isaaclab.managers import ObservationGroupCfg as ObsGroup from isaaclab.managers import ObservationTermCfg as ObsTerm @@ -52,10 +49,13 @@ from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors import RayCasterCfg, patterns from isaaclab.terrains import TerrainImporterCfg -from isaaclab.utils import configclass, replace +from isaaclab.utils import configclass, instantiate, replace from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR, check_file_path, read_file from isaaclab.utils.noise import UniformNoiseCfg as Unoise +if TYPE_CHECKING: + from isaaclab.envs import ManagerBasedEnv + ## # Pre-defined configs ## @@ -68,7 +68,7 @@ ## -def constant_commands(env: ManagerBasedEnv) -> torch.Tensor: +def constant_commands(env: "ManagerBasedEnv") -> torch.Tensor: """The generated command from the command generator.""" return torch.tensor([[1, 0, 0]], device=env.device).repeat(env.num_envs, 1) @@ -205,41 +205,41 @@ def main(): """Main function.""" # setup base environment env_cfg = QuadrupedEnvCfg() - env = ManagerBasedEnv(cfg=env_cfg) - - # load level policy - policy_path = ISAACLAB_NUCLEUS_DIR + "/Policies/ANYmal-C/HeightScan/policy.pt" - # check if policy file exists - if not check_file_path(policy_path): - raise FileNotFoundError(f"Policy file '{policy_path}' does not exist.") - file_bytes = read_file(policy_path) - # jit load the policy - policy = torch.jit.load(file_bytes).to(env.device).eval() - - # simulate physics - count = 0 - obs, _ = env.reset() - while simulation_app.is_running(): - with torch.inference_mode(): - # reset - if count % 1000 == 0: - obs, _ = env.reset() - count = 0 - print("-" * 80) - print("[INFO]: Resetting environment...") - # infer action - action = policy(obs["policy"]) - # step env - obs, _ = env.step(action) - # update counter - count += 1 - - # close the environment - env.close() + # Launch the simulator runtime that the configuration needs + with launch_simulation(env_cfg, args_cli): + env = instantiate(env_cfg) + + # load level policy + policy_path = ISAACLAB_NUCLEUS_DIR + "/Policies/ANYmal-C/HeightScan/policy.pt" + # check if policy file exists + if not check_file_path(policy_path): + raise FileNotFoundError(f"Policy file '{policy_path}' does not exist.") + file_bytes = read_file(policy_path) + # jit load the policy + policy = torch.jit.load(file_bytes).to(env.device).eval() + + # simulate physics + count = 0 + obs, _ = env.reset() + while env.sim.is_headless_or_exist_active_visualizer(): + with torch.inference_mode(): + # reset + if count % 1000 == 0: + obs, _ = env.reset() + count = 0 + print("-" * 80) + print("[INFO]: Resetting environment...") + # infer action + action = policy(obs["policy"]) + # step env + obs, _ = env.step(action) + # update counter + count += 1 + + # close the environment + env.close() if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/03_envs/policy_inference_in_usd.py b/scripts/tutorials/03_envs/policy_inference_in_usd.py index 61e61013ee57..e2d284bbc39e 100644 --- a/scripts/tutorials/03_envs/policy_inference_in_usd.py +++ b/scripts/tutorials/03_envs/policy_inference_in_usd.py @@ -19,6 +19,7 @@ import argparse from isaaclab.app import add_launcher_args, launch_simulation +from isaaclab.utils import instantiate # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on inferencing a policy on an H1 robot in a warehouse.") @@ -32,7 +33,6 @@ import torch -from isaaclab.envs import ManagerBasedRLEnv from isaaclab.terrains import TerrainImporterCfg from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR, read_file @@ -62,7 +62,7 @@ def main(): with launch_simulation(env_cfg, args_cli): # create environment - env = ManagerBasedRLEnv(cfg=env_cfg) + env = instantiate(env_cfg) # run inference with the policy obs, _ = env.reset() diff --git a/scripts/tutorials/03_envs/run_cartpole_rl_env.py b/scripts/tutorials/03_envs/run_cartpole_rl_env.py index 2f36a26b0c5a..f796ff596b5a 100644 --- a/scripts/tutorials/03_envs/run_cartpole_rl_env.py +++ b/scripts/tutorials/03_envs/run_cartpole_rl_env.py @@ -15,33 +15,28 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation +from isaaclab.utils import instantiate # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on running the cartpole RL environment.") parser.add_argument("--num_envs", type=int, default=16, help="Number of environments to spawn.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_launcher_args(parser) # tutorials should open Kit visualizer by default parser.set_defaults(visualizer=["kit"]) # parse the arguments, forwarding unrecognized ones as Hydra-style task config overrides args_cli, hydra_overrides = parser.parse_known_args() -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - """Rest everything follows.""" import torch -from isaaclab.envs import ManagerBasedRLEnv - from isaaclab_tasks.utils import parse_env_cfg @@ -51,34 +46,34 @@ def main(): env_cfg = parse_env_cfg( "Isaac-Cartpole", device=args_cli.device, num_envs=args_cli.num_envs, overrides=hydra_overrides ) - # setup RL environment - env = ManagerBasedRLEnv(cfg=env_cfg) - - # simulate physics - count = 0 - while simulation_app.is_running(): - with torch.inference_mode(): - # reset - if count % 300 == 0: - count = 0 - env.reset() - print("-" * 80) - print("[INFO]: Resetting environment...") - # sample random actions - joint_efforts = torch.randn_like(env.action_manager.action) - # step the environment - obs, rew, terminated, truncated, info = env.step(joint_efforts) - # print current orientation of pole - print("[Env 0]: Pole joint: ", obs["policy"][0][1].item()) - # update counter - count += 1 - - # close the environment - env.close() + # Launch the simulator runtime that the configuration needs + with launch_simulation(env_cfg, args_cli): + # setup RL environment + env = instantiate(env_cfg) + + # simulate physics + count = 0 + while env.sim.is_headless_or_exist_active_visualizer(): + with torch.inference_mode(): + # reset + if count % 300 == 0: + count = 0 + env.reset() + print("-" * 80) + print("[INFO]: Resetting environment...") + # sample random actions + joint_efforts = torch.randn_like(env.action_manager.action) + # step the environment + obs, rew, terminated, truncated, info = env.step(joint_efforts) + # print current orientation of pole + print("[Env 0]: Pole joint: ", obs["policy"][0][1].item()) + # update counter + count += 1 + + # close the environment + env.close() if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/04_sensors/add_sensors_on_robot.py b/scripts/tutorials/04_sensors/add_sensors_on_robot.py index 23e2f709e06f..623b7ae9780c 100644 --- a/scripts/tutorials/04_sensors/add_sensors_on_robot.py +++ b/scripts/tutorials/04_sensors/add_sensors_on_robot.py @@ -19,23 +19,22 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse +from typing import TYPE_CHECKING -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on adding sensors on a robot.") 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) +# append simulation launcher cli args +add_launcher_args(parser) # parse the arguments args_cli = parser.parse_args() - -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +# The scene has a camera sensor, which requires the rendering extensions in headless launches. +args_cli.enable_cameras = True """Rest everything follows.""" @@ -43,9 +42,12 @@ import isaaclab.sim as sim_utils from isaaclab.assets import ArticulationCfg, AssetBaseCfg -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors import CameraCfg, ContactSensorCfg, RayCasterCfg, patterns -from isaaclab.utils import configclass, replace +from isaaclab.utils import configclass, instantiate, replace + +if TYPE_CHECKING: + from isaaclab.scene import InteractiveScene ## # Pre-defined configs @@ -94,7 +96,7 @@ class SensorsSceneCfg(InteractiveSceneCfg): ) -def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): +def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): """Run the simulator.""" # Define simulation stepping sim_dt = sim.get_physics_dt() @@ -102,7 +104,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): count = 0 # Simulate physics - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # Reset if count % 500 == 0: # reset counter @@ -161,24 +163,25 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): def main(): """Main function.""" - # Initialize the simulation context + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0]) - # Design scene - scene_cfg = SensorsSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) - scene = InteractiveScene(scene_cfg) - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run the simulator - run_simulator(sim, scene) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0]) + # Design scene + scene_cfg = SensorsSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) + scene = instantiate(scene_cfg) + # Play the simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + # Run the simulator + run_simulator(sim, scene) if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/04_sensors/run_frame_transformer.py b/scripts/tutorials/04_sensors/run_frame_transformer.py index afc28c4f3f58..2a40e144f173 100644 --- a/scripts/tutorials/04_sensors/run_frame_transformer.py +++ b/scripts/tutorials/04_sensors/run_frame_transformer.py @@ -13,44 +13,38 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # 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) +add_launcher_args(parser) args_cli = parser.parse_args() -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - """Rest everything follows.""" import math +from typing import TYPE_CHECKING import torch -from isaaclab.sim.utils import enable_extension - -enable_extension("isaacsim.util.debug_draw") - -from isaacsim.util.debug_draw import _debug_draw as omni_debug_draw - import isaaclab.sim as sim_utils import isaaclab.utils.math as math_utils -from isaaclab.assets import Articulation, AssetBaseCfg -from isaaclab.markers import VisualizationMarkers +from isaaclab.assets import AssetBaseCfg from isaaclab.markers.config import FRAME_MARKER_CFG -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg -from isaaclab.sensors import FrameTransformer, FrameTransformerCfg, OffsetCfg -from isaaclab.sim import SimulationContext -from isaaclab.utils import configclass, replace +from isaaclab.scene import InteractiveSceneCfg +from isaaclab.sensors import FrameTransformerCfg, OffsetCfg +from isaaclab.utils import configclass, instantiate, replace + +if TYPE_CHECKING: + from isaaclab.assets import Articulation + from isaaclab.scene import InteractiveScene + from isaaclab.sensors import FrameTransformer ## # Pre-defined configs @@ -87,7 +81,7 @@ class FrameTransformerSceneCfg(InteractiveSceneCfg): ) -def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): +def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): """Run the simulator.""" # Define simulation stepping sim_dt = sim.get_physics_dt() @@ -101,19 +95,16 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): # 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: + if sim.has_gui: cfg = replace(FRAME_MARKER_CFG, prim_path="/Visuals/FrameVisualizerFromScript") cfg.markers["frame"].scale = (0.1, 0.1, 0.1) - transform_visualizer = VisualizationMarkers(cfg) - # debug drawing for lines connecting the frame - draw_interface = omni_debug_draw.acquire_debug_draw_interface() + transform_visualizer = instantiate(cfg) else: transform_visualizer = None - draw_interface = None frame_index = 0 # Simulate physics - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # perform this loop at policy control freq (50 Hz) robot.set_joint_position_target_index(target=robot.data.default_joint_pos.torch.clone()) robot.write_data_to_sim() @@ -128,7 +119,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): # Change the frame that we are visualizing to ensure that frame names # are correctly associated with the frames - if not args_cli.headless: + if sim.has_gui: if count % 50 == 0: # get frame names frame_names = frame_transformer.data.target_frame_names @@ -146,33 +137,29 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): transform_visualizer.visualize( torch.cat([source_pos, target_pos], dim=0), torch.cat([source_quat, target_quat], dim=0) ) - # draw the line connecting the frames - draw_interface.clear_lines() - # plain color for lines - lines_colors = [[1.0, 1.0, 0.0, 1.0]] * source_pos.shape[0] - line_thicknesses = [5.0] * source_pos.shape[0] - draw_interface.draw_lines(source_pos.tolist(), target_pos.tolist(), lines_colors, line_thicknesses) def main(): """Main function.""" - # Load kit helper + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device) - sim = SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view(eye=[2.5, 2.5, 2.5], target=[0.0, 0.0, 0.0]) - # Design scene - scene = InteractiveScene(FrameTransformerSceneCfg(num_envs=1, env_spacing=0.0, replicate_physics=False)) - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run the simulator - run_simulator(sim, scene) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view(eye=[2.5, 2.5, 2.5], target=[0.0, 0.0, 0.0]) + # Design scene + scene_cfg = FrameTransformerSceneCfg(num_envs=1, env_spacing=0.0, replicate_physics=False) + scene = instantiate(scene_cfg) + # Play the simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + # Run the simulator + run_simulator(sim, scene) if __name__ == "__main__": # Run the main function main() - # Close the simulator - simulation_app.close() diff --git a/scripts/tutorials/04_sensors/run_ray_caster.py b/scripts/tutorials/04_sensors/run_ray_caster.py index 6673c625926e..3cd003d28800 100644 --- a/scripts/tutorials/04_sensors/run_ray_caster.py +++ b/scripts/tutorials/04_sensors/run_ray_caster.py @@ -13,21 +13,20 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse +from typing import TYPE_CHECKING -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation +from isaaclab.utils import instantiate # add argparse arguments parser = argparse.ArgumentParser(description="Ray Caster Test Script") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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.""" @@ -35,12 +34,15 @@ import isaaclab.sim as sim_utils from isaaclab.assets import RigidObject, RigidObjectCfg -from isaaclab.sensors.ray_caster import RayCaster, RayCasterCfg, patterns +from isaaclab.sensors.ray_caster import RayCasterCfg, patterns from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR from isaaclab.utils.timer import Timer +if TYPE_CHECKING: + from isaaclab.sensors.ray_caster import RayCaster -def define_sensor() -> RayCaster: + +def define_sensor() -> "RayCaster": """Defines the ray-caster sensor to add to the scene.""" # Create a ray-caster sensor ray_caster_cfg = RayCasterCfg( @@ -48,9 +50,10 @@ def define_sensor() -> RayCaster: mesh_prim_paths=["/World/ground"], pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=(2.0, 2.0)), ray_alignment="yaw", - debug_vis=not args_cli.headless, + # draw the ray hits only when a visualizer is requested + debug_vis=bool(args_cli.visualizer), ) - ray_caster = RayCaster(cfg=ray_caster_cfg) + ray_caster = instantiate(ray_caster_cfg) return ray_caster @@ -104,7 +107,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): # Create a counter for resetting the scene step_count = 0 # Simulate physics - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # Reset the scene if step_count % 250 == 0: # reset the balls @@ -130,23 +133,24 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): def main(): """Main function.""" - # Load simulation context + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view([0.0, 15.0, 15.0], [0.0, 0.0, -2.5]) - # Design scene - scene_entities = design_scene() - # Play simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run simulator - run_simulator(sim=sim, scene_entities=scene_entities) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view([0.0, 15.0, 15.0], [0.0, 0.0, -2.5]) + # Design scene + scene_entities = design_scene() + # Play simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + # Run simulator + run_simulator(sim=sim, scene_entities=scene_entities) if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/04_sensors/run_ray_caster_camera.py b/scripts/tutorials/04_sensors/run_ray_caster_camera.py index 755106a5c8f9..0222e47814c7 100644 --- a/scripts/tutorials/04_sensors/run_ray_caster_camera.py +++ b/scripts/tutorials/04_sensors/run_ray_caster_camera.py @@ -15,39 +15,40 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation +from isaaclab.utils import instantiate # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates how to use the ray-cast camera sensor.") parser.add_argument("--num_envs", type=int, default=16, help="Number of environments to generate.") parser.add_argument("--save", action="store_true", default=False, help="Save the obtained data to disk.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_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 os -from typing import Any +from typing import TYPE_CHECKING import torch import isaaclab.sim as sim_utils -from isaaclab.sensors.ray_caster import RayCasterCamera, RayCasterCameraCfg, patterns -from isaaclab.utils import convert_dict_to_backend +from isaaclab.sensors.camera.utils import save_images_to_file +from isaaclab.sensors.ray_caster import RayCasterCameraCfg, patterns from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR from isaaclab.utils.math import project_points, unproject_depth +if TYPE_CHECKING: + from isaaclab.sensors.ray_caster import RayCasterCamera -def define_sensor() -> RayCasterCamera: + +def define_sensor() -> "RayCasterCamera": """Defines the ray-cast camera sensor to add to the scene.""" # Camera base frames # In contras to the USD camera, we associate the sensor to the prims at these locations. @@ -71,7 +72,7 @@ def define_sensor() -> RayCasterCamera: ), ) # Create camera - camera = RayCasterCamera(cfg=camera_cfg) + camera = instantiate(camera_cfg) return camera @@ -97,14 +98,9 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): # extract entities for simplified notation camera: RayCasterCamera = scene_entities["camera"] - # Create the Replicator writer only when saving. The ray-cast camera itself - # is Warp-based and does not require Replicator or RTX rendering extensions. - rep_writer: Any | None = None - if args_cli.save: - import omni.replicator.core as rep - - 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) + # Create the output directory + output_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "ray_caster_camera") + os.makedirs(output_dir, exist_ok=True) # Set pose: There are two ways to set the pose of the camera. # -- Option-1: Set pose using view @@ -117,7 +113,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): # camera.set_world_poses(position, orientation, indices=[0], convention="ros") # Simulate physics - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # Step simulation sim.step() # Update camera data @@ -130,58 +126,47 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): # Extract camera data if args_cli.save: - # Extract camera data + # Save the depth image of the first camera, normalized to [0, 1], as a PNG file camera_index = 0 - # note: BasicWriter only supports saving data in numpy format, so we need to convert the data to numpy. - single_cam_data = convert_dict_to_backend( - {k: v[camera_index] for k, v in camera.data.output.items()}, backend="numpy" - ) - # Pack data back into replicator format to save them using its writer - rep_output = {"annotators": {}} - for key, data in single_cam_data.items(): - info = camera.data.info.get(key) - if info is not None: - rep_output["annotators"][key] = {"render_product": {"data": data, **info}} - else: - rep_output["annotators"][key] = {"render_product": {"data": data}} - # Save images - rep_output["trigger_outputs"] = {"on_time": camera.frame[camera_index]} - assert rep_writer is not None - rep_writer.write(rep_output) + depth = camera.data.output["distance_to_image_plane"].torch[camera_index : camera_index + 1] + depth = torch.nan_to_num(depth, posinf=0.0) + file_path = os.path.join(output_dir, f"depth_{camera.frame[camera_index]:03d}.png") + save_images_to_file(depth / depth.max().clamp(min=1e-6), file_path) # Pointcloud in world frame points_3d_cam = unproject_depth( - camera.data.output["distance_to_image_plane"], camera.data.intrinsic_matrices + camera.data.output["distance_to_image_plane"].torch, camera.data.intrinsic_matrices.torch ) # Check methods are valid im_height, im_width = camera.image_shape # -- project points to (u, v, d) - reproj_points = project_points(points_3d_cam, camera.data.intrinsic_matrices) + reproj_points = project_points(points_3d_cam, camera.data.intrinsic_matrices.torch) 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) + sim_depths = camera.data.output["distance_to_image_plane"].torch.squeeze(-1) torch.testing.assert_close(reproj_depths, sim_depths) def main(): """Main function.""" - # Load kit helper + # Configure the simulation sim_cfg = sim_utils.SimulationCfg() - sim = sim_utils.SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view([2.5, 2.5, 3.5], [0.0, 0.0, 0.0]) - # Design scene - scene_entities = design_scene() - # Play simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run simulator - run_simulator(sim=sim, scene_entities=scene_entities) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view([2.5, 2.5, 3.5], [0.0, 0.0, 0.0]) + # Design scene + scene_entities = design_scene() + # Play simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + # Run simulator + run_simulator(sim=sim, scene_entities=scene_entities) if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/04_sensors/run_usd_camera.py b/scripts/tutorials/04_sensors/run_usd_camera.py index 6edd2659f760..e27340379465 100644 --- a/scripts/tutorials/04_sensors/run_usd_camera.py +++ b/scripts/tutorials/04_sensors/run_usd_camera.py @@ -6,8 +6,7 @@ """ This script shows how to use the camera sensor from the Isaac Lab framework. -The camera sensor is created and interfaced through the Omniverse Replicator API. However, instead of using -the simulator or OpenGL convention for the camera, we use the robotics or ROS convention. +Instead of using the simulator or OpenGL convention for the camera, we use the robotics or ROS convention. .. code-block:: bash @@ -19,11 +18,12 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse +from typing import TYPE_CHECKING -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates how to use the camera sensor.") @@ -49,17 +49,13 @@ " The viewport will always initialize with the perspective of camera 0." ), ) -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +# append simulation launcher cli args +add_launcher_args(parser) # parse the arguments args_cli = parser.parse_args() # Camera sensors require the rendering extensions in headless and viewport-free launches. args_cli.enable_cameras = True -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - """Rest everything follows.""" import os @@ -69,18 +65,19 @@ import torch from isaaclab_physx.renderers import IsaacRtxRendererCfg -import omni.replicator.core as rep - import isaaclab.sim as sim_utils from isaaclab.assets import RigidObject, RigidObjectCfg from isaaclab.markers import VisualizationMarkers from isaaclab.markers.config import RAY_CASTER_MARKER_CFG -from isaaclab.sensors.camera import Camera, CameraCfg -from isaaclab.sensors.camera.utils import create_pointcloud_from_depth -from isaaclab.utils import convert_dict_to_backend, replace +from isaaclab.sensors.camera import CameraCfg +from isaaclab.sensors.camera.utils import create_pointcloud_from_depth, save_images_to_file +from isaaclab.utils import instantiate, replace + +if TYPE_CHECKING: + from isaaclab.sensors.camera import Camera -def define_sensor() -> Camera: +def define_sensor() -> "Camera": """Defines the camera sensor to add to the scene.""" # Setup camera sensor # In contrast to the ray-cast camera, we spawn the prim at these locations. @@ -110,7 +107,7 @@ def define_sensor() -> Camera: ), ) # Create camera - camera = Camera(cfg=camera_cfg) + camera = instantiate(camera_cfg) return camera @@ -173,15 +170,9 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): # extract entities for simplified notation camera: Camera = scene_entities["camera"] - # Create replicator writer + # Create the output directory output_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "output", "camera") - rep_writer = rep.BasicWriter( - output_dir=output_dir, - frame_padding=0, - colorize_instance_id_segmentation=camera.cfg.renderer_cfg.colorize_instance_id_segmentation, - colorize_instance_segmentation=camera.cfg.renderer_cfg.colorize_instance_segmentation, - colorize_semantic_segmentation=camera.cfg.renderer_cfg.colorize_semantic_segmentation, - ) + os.makedirs(output_dir, exist_ok=True) # Camera positions, targets, orientations camera_positions = torch.tensor([[2.5, 2.5, 2.5], [-2.5, -2.5, 2.5]], device=sim.device) @@ -200,14 +191,14 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): # Index of the camera to use for visualization and saving camera_index = args_cli.camera_id - # Create the markers for the --draw option outside of is_running() loop + # Create the markers for the --draw option outside of the simulation loop if sim.get_setting("/isaaclab/has_gui") and args_cli.draw: cfg = replace(RAY_CASTER_MARKER_CFG, prim_path="/Visuals/CameraPointCloud") cfg.markers["hit"].radius = 0.002 pc_markers = VisualizationMarkers(cfg) # Simulate physics - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # Step simulation sim.step() # Update camera data @@ -231,24 +222,11 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): # Extract camera data if args_cli.save: - # Save images from camera at camera_index - # note: BasicWriter only supports saving data in numpy format, so we need to convert the data to numpy. - single_cam_data = convert_dict_to_backend( - {k: v[camera_index] for k, v in camera.data.output.items()}, backend="numpy" - ) - - # Pack data back into replicator format to save them using its writer - rep_output = {"annotators": {}} - for key, data in single_cam_data.items(): - info = camera.data.info.get(key) - if info is not None: - rep_output["annotators"][key] = {"render_product": {"data": data, **info}} - else: - rep_output["annotators"][key] = {"render_product": {"data": data}} - # Save images - # Note: We need to provide On-time data for Replicator to save the images. - rep_output["trigger_outputs"] = {"on_time": camera.frame[camera_index]} - rep_writer.write(rep_output) + # Save the 8-bit color images (rgb and colorized segmentations) from camera at camera_index + for key, data in camera.data.output.items(): + if data.torch.dtype == torch.uint8: + file_path = os.path.join(output_dir, f"{key}_{camera.frame[camera_index]}.png") + save_images_to_file(data.torch[camera_index : camera_index + 1].float() / 255.0, file_path) # Draw pointcloud if there is a GUI and --draw has been passed if ( @@ -274,23 +252,24 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): def main(): """Main function.""" - # Load simulation context + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) - # Design scene - scene_entities = design_scene() - # Play simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run simulator - run_simulator(sim, scene_entities) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) + # Design scene + scene_entities = design_scene() + # Play simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + # Run simulator + run_simulator(sim, scene_entities) if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/05_controllers/run_diff_ik.py b/scripts/tutorials/05_controllers/run_diff_ik.py index 8ab65fa8ba89..00176c88569f 100644 --- a/scripts/tutorials/05_controllers/run_diff_ik.py +++ b/scripts/tutorials/05_controllers/run_diff_ik.py @@ -16,25 +16,22 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse +from typing import TYPE_CHECKING -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on using the differential IK controller.") 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) +# append simulation launcher cli args +add_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 torch @@ -45,11 +42,14 @@ from isaaclab.managers import SceneEntityCfg from isaaclab.markers import VisualizationMarkers from isaaclab.markers.config import FRAME_MARKER_CFG -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg -from isaaclab.utils import clone, configclass, replace +from isaaclab.scene import InteractiveSceneCfg +from isaaclab.utils import clone, configclass, instantiate, replace from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR from isaaclab.utils.math import subtract_frame_transforms +if TYPE_CHECKING: + from isaaclab.scene import InteractiveScene + ## # Pre-defined configs ## @@ -89,7 +89,7 @@ class TableTopSceneCfg(InteractiveSceneCfg): raise ValueError(f"Robot {args_cli.robot} is not supported. Valid: franka_panda, ur10") -def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): +def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): """Runs the simulation loop.""" # Extract scene entities # note: we only do this here for readability. @@ -139,7 +139,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): sim_dt = sim.get_physics_dt() count = 0 # Simulation loop - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # reset if count % 150 == 0: # reset time @@ -193,24 +193,25 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): def main(): """Main function.""" - # Load kit helper + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) - # Design scene - scene_cfg = TableTopSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) - scene = InteractiveScene(scene_cfg) - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run the simulator - run_simulator(sim, scene) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) + # Design scene + scene_cfg = TableTopSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) + scene = instantiate(scene_cfg) + # Play the simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + # Run the simulator + run_simulator(sim, scene) if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/05_controllers/run_osc.py b/scripts/tutorials/05_controllers/run_osc.py index 53746d246baa..90cf8d63a18c 100644 --- a/scripts/tutorials/05_controllers/run_osc.py +++ b/scripts/tutorials/05_controllers/run_osc.py @@ -16,24 +16,21 @@ """ -"""Launch Isaac Sim Simulator first.""" +"""Parse the command-line arguments first.""" import argparse +from typing import TYPE_CHECKING -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation # add argparse arguments parser = argparse.ArgumentParser(description="Tutorial on using the operational space controller.") 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) +# append simulation launcher cli args +add_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 torch @@ -43,9 +40,9 @@ from isaaclab.controllers import OperationalSpaceController, OperationalSpaceControllerCfg from isaaclab.markers import VisualizationMarkers from isaaclab.markers.config import FRAME_MARKER_CFG -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors import ContactSensorCfg -from isaaclab.utils import clone, configclass, replace +from isaaclab.utils import clone, configclass, instantiate, replace from isaaclab.utils.math import ( combine_frame_transforms, matrix_from_quat, @@ -54,6 +51,9 @@ subtract_frame_transforms, ) +if TYPE_CHECKING: + from isaaclab.scene import InteractiveScene + ## # Pre-defined configs ## @@ -105,7 +105,7 @@ class SceneCfg(InteractiveSceneCfg): robot.spawn.rigid_props.disable_gravity = True -def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): +def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): """Runs the simulation loop. Args: @@ -209,7 +209,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): count = 0 # Simulation loop - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # reset every 500 steps if count % 500 == 0: # reset joint state to default @@ -281,7 +281,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): # Update robot states def update_states( sim: sim_utils.SimulationContext, - scene: InteractiveScene, + scene: "InteractiveScene", robot: Articulation, ee_frame_idx: int, arm_joint_ids: list[int], @@ -377,7 +377,7 @@ def update_states( # Update the target commands def update_target( sim: sim_utils.SimulationContext, - scene: InteractiveScene, + scene: "InteractiveScene", osc: OperationalSpaceController, root_pose_w: torch.tensor, ee_target_set: torch.tensor, @@ -466,24 +466,25 @@ def convert_to_task_frame(osc: OperationalSpaceController, command: torch.tensor def main(): """Main function.""" - # Load kit helper + # Configure the simulation sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) - # Design scene - scene_cfg = SceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) - scene = InteractiveScene(scene_cfg) - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run the simulator - run_simulator(sim, scene) + # Launch the simulator runtime that the configuration needs + with launch_simulation(sim_cfg, args_cli): + # Initialize the simulation context + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) + # Design scene + scene_cfg = SceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) + scene = instantiate(scene_cfg) + # Play the simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + # Run the simulator + run_simulator(sim, scene) if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/tutorials/07_visualizers/run_tiled_camera_visualizer.py b/scripts/tutorials/07_visualizers/run_tiled_camera_visualizer.py index 9dbd9bf4d3f5..c055955309f3 100644 --- a/scripts/tutorials/07_visualizers/run_tiled_camera_visualizer.py +++ b/scripts/tutorials/07_visualizers/run_tiled_camera_visualizer.py @@ -133,7 +133,7 @@ def _resolve_task(args_cli: argparse.Namespace) -> str: parser = argparse.ArgumentParser(description="Showcase the Kit/Newton visualizer tiled camera panel.") 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 +# append simulation launcher cli args add_launcher_args(parser) args_cli, hydra_args = setup_preset_cli(parser) args_cli.task = _resolve_task(args_cli) diff --git a/skills/user/migrate-2x-to-3x/SKILL.md b/skills/user/migrate-2x-to-3x/SKILL.md index b56e5a75beb7..eaf47298b53b 100644 --- a/skills/user/migrate-2x-to-3x/SKILL.md +++ b/skills/user/migrate-2x-to-3x/SKILL.md @@ -29,7 +29,7 @@ Do not copy migration tables into answers from memory. Read the official migrati - For custom `.kit` files, either list every direct Isaac Sim extension dependency explicitly or enable each extension before importing its Python module. - Do not add extension enables to plain Python modules that can be imported before Kit starts unless the code is guarded so it runs only inside a launched Kit app. 5. Apply the smallest focused migration change. -6. Run a targeted smoke test or import test. For direct Isaac Sim imports, ensure the test or script starts `AppLauncher` before enabling extensions. For example, run an app-launching test with `uv run --extra isaacsim --with pytest python -m pytest PATH_TO_TEST`. +6. Run a targeted smoke test or import test. For direct Isaac Sim imports, ensure the script enters `isaaclab.app.launch_simulation` (tests: `isaaclab.test.utils.launch_test_simulation`) before enabling extensions. For example, run an app-launching test with `uv run --extra isaacsim --with pytest python -m pytest PATH_TO_TEST`. 7. If the official docs are missing a recurring migration issue, update `docs/source/migration/migrating_to_isaaclab_3-0.rst` instead of expanding this skill with standalone documentation. ## Validation diff --git a/skills/user/migrate-2x-to-3x/supplemental-checks.md b/skills/user/migrate-2x-to-3x/supplemental-checks.md index e769ec4ebed6..43b7bb0838fb 100644 --- a/skills/user/migrate-2x-to-3x/supplemental-checks.md +++ b/skills/user/migrate-2x-to-3x/supplemental-checks.md @@ -13,7 +13,7 @@ Use these checks to route investigation, not as standalone migration docs: | Symptom or old pattern | Current source of truth | | --- | --- | | Task names include the old Gym version suffix, such as `-v0` | Current task docs and the environment catalog; use suffixless task names in examples | -| Visualizer launch behavior changed | `docs/source/migration/migrating_to_isaaclab_3-0.rst` and `source/isaaclab/isaaclab/app/app_launcher.py` | +| Visualizer launch behavior changed | `docs/source/migration/migrating_to_isaaclab_3-0.rst` and `source/isaaclab/isaaclab/app/sim_launcher.py` | | Camera examples include obsolete launch options | Current sensor, renderer, and visualization docs; camera support is automatic | | Backend-specific physics or schema cfgs | `docs/source/concepts/backend_architecture.rst` and `docs/source/concepts/schema_cfgs.rst` | | Imports of PhysX/Newton schema cfgs from `isaaclab.sim.schemas` | Move backend-specific imports to `isaaclab_physx.sim.schemas` or `isaaclab_newton.sim.schemas`; core forwarding shims are deprecated | diff --git a/source/isaaclab/changelog.d/launch-simulation-scripts.major.rst b/source/isaaclab/changelog.d/launch-simulation-scripts.major.rst new file mode 100644 index 000000000000..cbb7705e984e --- /dev/null +++ b/source/isaaclab/changelog.d/launch-simulation-scripts.major.rst @@ -0,0 +1,21 @@ +Changed +^^^^^^^ + +* Changed every script, example, tutorial, and benchmark to start the simulation runtime with + :func:`~isaaclab.app.launch_simulation` and to construct runtime objects from their configs with + :func:`~isaaclab.utils.instantiate`. +* Changed the ``convert_mesh``, ``convert_urdf``, and ``convert_mjcf`` tools to preview the converted asset in the + visualizer selected with ``--viz`` (including ``kit``) instead of a Kit-only viewport. +* Changed the ``run_usd_camera`` and ``run_ray_caster_camera`` tutorials to save images as PNG files with + :func:`~isaaclab.utils.save_images_to_file` instead of Replicator writers. +* Changed ``pick_and_place.py`` to use :class:`~isaaclab.devices.Se3Keyboard`. + +Removed +^^^^^^^ + +* **Breaking:** Reduced the deprecated ``isaaclab.app.AppLauncher`` to a shim for scripts that still use it. + ``AppLauncher(args).app`` still starts Isaac Sim / Kit through :func:`~isaaclab.app.launch_simulation`, and + ``AppLauncher.add_app_launcher_args`` still adds the launcher arguments. ``is_available``, ``has_gui``, + ``has_window``, and ``device`` were removed: use ``isaaclab_physx.app.KitLauncher.is_available()``, + :attr:`~isaaclab.sim.SimulationContext.has_gui`, and the simulation config's ``device``. +* Removed the ``check_*`` scripts under the core and PhysX test folders, which duplicated pytest coverage. diff --git a/source/isaaclab/isaaclab/app/app_launcher.py b/source/isaaclab/isaaclab/app/app_launcher.py index db4d9ff8bef6..84fa4fced585 100644 --- a/source/isaaclab/isaaclab/app/app_launcher.py +++ b/source/isaaclab/isaaclab/app/app_launcher.py @@ -12,8 +12,7 @@ import logging from typing import Any -from .settings_manager import get_settings_manager -from .sim_launcher import _KIT_LAUNCHER, add_launcher_args, launch_simulation +from .sim_launcher import add_launcher_args, launch_simulation logger = logging.getLogger(__name__) @@ -56,8 +55,6 @@ def __init__(self, launcher_args: argparse.Namespace | dict | None = None, **kwa logger.warning("AppLauncher starts Kit, which cannot run the 'newton_rtx' visualizer; skipping it.") args["visualizer"] = [v for v in args["visualizer"] if v != "newton_rtx"] _RUNTIME.enter_context(launch_simulation(None, args)) - self.device = args.get("device") - self.has_window = not args["headless"] or bool(args.get("livestream", 0)) import omni.kit.app @@ -67,15 +64,3 @@ def __init__(self, launcher_args: argparse.Namespace | dict | None = None, **kwa def add_app_launcher_args(parser: argparse.ArgumentParser) -> None: """Add the launcher arguments, see :func:`~isaaclab.app.add_launcher_args`.""" add_launcher_args(parser) - - @staticmethod - def is_available() -> bool: - """Return whether the full Isaac Sim runtime is importable, i.e. Kit can be launched.""" - from ..utils.string import string_to_callable - - return string_to_callable(_KIT_LAUNCHER).is_available() - - @staticmethod - def has_gui() -> bool: - """Return whether the launched app has an interactive GUI (window, livestream, or XR).""" - return bool(get_settings_manager().get("/isaaclab/has_gui")) diff --git a/source/isaaclab/test/assets/check_external_force.py b/source/isaaclab/test/assets/check_external_force.py deleted file mode 100644 index a3342919d03d..000000000000 --- a/source/isaaclab/test/assets/check_external_force.py +++ /dev/null @@ -1,131 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -This script checks if the external force is applied correctly on the robot. - -.. code-block:: bash - - # Usage to apply force on base - uv run python source/isaaclab/test/assets/check_external_force.py --body base --force 1000 - # Usage to apply force on legs - uv run python source/isaaclab/test/assets/check_external_force.py --body .*_SHANK --force 100 -""" - -"""Launch Isaac Sim Simulator first.""" - - -import argparse - -from isaaclab.app import AppLauncher -from isaaclab.utils import replace - -# add argparse arguments -parser = argparse.ArgumentParser(description="This script demonstrates how to external force on a legged robot.") -parser.add_argument("--body", default="base", type=str, help="Name of the body to apply force on.") -parser.add_argument("--force", default=1000.0, type=float, help="Force to apply on the body.") -# 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 torch - -import isaaclab.sim as sim_utils -from isaaclab.assets import Articulation -from isaaclab.sim import SimulationContext - -## -# Pre-defined configs -## -from isaaclab_assets.robots.anymal import ANYMAL_C_CFG # isort:skip - - -def main(): - """Main function.""" - - # Load kit helper - sim = SimulationContext(sim_utils.SimulationCfg(dt=0.005)) - # 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 - # Ground-plane - cfg = sim_utils.GroundPlaneCfg() - cfg.func("/World/defaultGroundPlane", cfg) - # Lights - cfg = sim_utils.DistantLightCfg(intensity=1000.0, color=(0.75, 0.75, 0.75)) - cfg.func("/World/Light/greyLight", cfg) - - # Robots - robot_cfg = ANYMAL_C_CFG - robot_cfg.spawn.func("/World/Anymal_c/Robot_1", robot_cfg.spawn, translation=(0.0, -0.5, 0.65)) - robot_cfg.spawn.func("/World/Anymal_c/Robot_2", robot_cfg.spawn, translation=(0.0, 0.5, 0.65)) - # create handles for the robots - robot = Articulation(replace(robot_cfg, prim_path="/World/Anymal_c/Robot[^/]*")) - - # Play the simulator - sim.reset() - - # Find bodies to apply the force - body_ids, body_names = robot.find_bodies(args_cli.body) - # Sample a large force - external_wrench_b = torch.zeros(robot.num_instances, len(body_ids), 6, device=sim.device) - external_wrench_b[..., 1] = args_cli.force - - # Now we are ready! - print("[INFO]: Setup complete...") - print("[INFO]: Applying force on the robot: ", args_cli.body, " -> ", body_names) - - # 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 % 100 == 0: - # reset counters - sim_time = 0.0 - count = 0 - # reset root state - root_state = robot.data.default_root_state.torch.clone() - root_state[0, :2] = torch.tensor([0.0, -0.5], device=sim.device) - root_state[1, :2] = torch.tensor([0.0, 0.5], device=sim.device) - robot.write_root_pose_to_sim(root_state[:, :7]) - robot.write_root_velocity_to_sim(root_state[:, 7:]) - # reset dof state - joint_pos, joint_vel = robot.data.default_joint_pos.torch, robot.data.default_joint_vel.torch - robot.write_joint_state_to_sim(joint_pos, joint_vel) - robot.reset() - # apply force - robot.permanent_wrench_composer.set_forces_and_torques( - external_wrench_b[..., :3], external_wrench_b[..., 3:], body_ids=body_ids - ) - # reset command - print(">>>>>>>> Reset!") - # apply action to the robot - robot.set_joint_position_target(robot.data.default_joint_pos.torch.clone()) - robot.write_data_to_sim() - # perform step - sim.step() - # update sim-time - sim_time += sim_dt - count += 1 - # update buffers - robot.update(sim_dt) - - -if __name__ == "__main__": - # run the main function - main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab/test/assets/check_fixed_base_assets.py b/source/isaaclab/test/assets/check_fixed_base_assets.py deleted file mode 100644 index 944c6e96fd50..000000000000 --- a/source/isaaclab/test/assets/check_fixed_base_assets.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -This script demonstrates fixed-base API for different robots. - -.. code-block:: bash - - # Usage - uv run python source/isaaclab/test/assets/check_fixed_base_assets.py - -""" - -"""Launch Isaac Sim Simulator first.""" - -import argparse - -from isaaclab.app import AppLauncher -from isaaclab.utils import replace - -# add argparse arguments -parser = argparse.ArgumentParser(description="This script checks the fixed-base API for different 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 isaaclab.sim as sim_utils -from isaaclab.assets import Articulation - -## -# Pre-defined configs -## -from isaaclab_assets import ANYMAL_C_CFG, FRANKA_PANDA_CFG # isort:skip - - -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=2.0) - - # Origin 1 with Franka Panda - sim_utils.create_prim("/World/Origin1", "Xform", translation=origins[0]) - # -- Robot - franka = Articulation(replace(FRANKA_PANDA_CFG, prim_path="/World/Origin1/Robot")) - - # Origin 2 with Anymal C - sim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1]) - # -- Robot - robot_cfg = replace(ANYMAL_C_CFG, prim_path="/World/Origin2/Robot") - robot_cfg.spawn.fix_root_link = True - anymal_c = Articulation(robot_cfg) - - # return the scene information - scene_entities = { - "franka": franka, - "anymal_c": anymal_c, - } - return scene_entities, origins - - -def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articulation], origins: torch.Tensor): - """Runs the simulation loop.""" - # 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.torch.clone() - root_state[:, :3] += origins[index] - root_state[:, :2] += torch.randn_like(root_state[:, :2]) * 0.25 - robot.write_root_pose_to_sim(root_state[:, :7]) - robot.write_root_velocity_to_sim(root_state[:, 7:]) - # joint state - joint_pos, joint_vel = ( - robot.data.default_joint_pos.torch.clone(), - robot.data.default_joint_vel.torch.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 name, robot in entities.items(): - if count % 200 == 0: - print("Name: ", name, "is_fixed_base: ", robot.is_fixed_base) - # generate random joint positions - joint_pos_target = robot.data.default_joint_pos.torch + torch.randn_like(robot.data.joint_pos.torch) * 0.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 - 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)) - # 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__": - # run the main function - main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab/test/assets/check_ridgeback_franka.py b/source/isaaclab/test/assets/check_ridgeback_franka.py deleted file mode 100644 index 78eaa6e5bb00..000000000000 --- a/source/isaaclab/test/assets/check_ridgeback_franka.py +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -This script demonstrates how to simulate a mobile manipulator. - -.. code-block:: bash - - # Usage - uv run python source/isaaclab/test/assets/check_ridgeback_franka.py - -""" - -"""Launch Isaac Sim Simulator first.""" - -import argparse - -from isaaclab.app import AppLauncher -from isaaclab.utils import replace - -# add argparse arguments -parser = argparse.ArgumentParser( - description="This script demonstrates how to simulate a mobile manipulator with dummy joints." -) -# 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 torch - -import isaaclab.sim as sim_utils -from isaaclab.assets import Articulation - -## -# Pre-defined configs -## -from isaaclab_assets.robots.ridgeback_franka import RIDGEBACK_FRANKA_PANDA_CFG # isort:skip - - -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() - - -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)) - robot_cfg.spawn.func("/World/Robot_2", robot_cfg.spawn, translation=(0.0, 1.0, 0.0)) - # -- Create interface - robot = Articulation(cfg=replace(robot_cfg, prim_path="/World/Robot[^/]*")) - - return robot - - -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.torch.clone() - - # 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: - # reset counters - sim_time = 0.0 - ep_step_count = 0 - # reset dof state - joint_pos, joint_vel = ( - robot.data.default_joint_pos.torch.clone(), - robot.data.default_joint_vel.torch.clone(), - ) - robot.write_joint_state_to_sim(joint_pos, joint_vel) - # reset internals - robot.reset() - # reset command - actions = torch.rand_like(robot.data.default_joint_pos.torch) + robot.data.default_joint_pos.torch - # -- base - actions[:, 0:3] = 0.0 - # -- gripper - actions[:, -2:] = 0.04 - print("[INFO]: Resetting robots state...") - # change the gripper action - if ep_step_count % 200 == 0: - # flip command for the gripper - actions[:, -2:] = 0.0 if actions[0, -2] > 0.0 else 0.04 - # change the base action - # -- forward and backward (x-axis) - if ep_step_count == 200: - actions[:, :3] = 0.0 - actions[:, 0] = 1.0 - if ep_step_count == 300: - actions[:, :3] = 0.0 - actions[:, 0] = -1.0 - # -- right and left (y-axis) - if ep_step_count == 400: - actions[:, :3] = 0.0 - actions[:, 1] = 1.0 - if ep_step_count == 500: - actions[:, :3] = 0.0 - actions[:, 1] = -1.0 - # -- turn right and left (z-axis) - if ep_step_count == 600: - actions[:, :3] = 0.0 - actions[:, 2] = 1.0 - if ep_step_count == 700: - actions[:, :3] = 0.0 - actions[:, 2] = -1.0 - if ep_step_count == 900: - actions[:, :3] = 0.0 - actions[:, 2] = 1.0 - # change the arm action - if ep_step_count % 100: - actions[:, 3:10] = torch.rand(robot.num_instances, 7, device=robot.device) - actions[:, 3:10] += robot.data.default_joint_pos[:, 3:10] - # apply action - robot.set_joint_velocity_target(actions[:, :3], joint_ids=[0, 1, 2]) - robot.set_joint_position_target(actions[:, 3:], joint_ids=[3, 4, 5, 6, 7, 8, 9, 10, 11]) - robot.write_data_to_sim() - # perform step - sim.step() - # update sim-time - sim_time += sim_dt - ep_step_count += 1 - # update buffers - 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__": - # run the main function - main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab/test/cli/test_replay_demos_loop_termination.py b/source/isaaclab/test/cli/test_replay_demos_loop_termination.py index d390abc14fc1..3cc4366afe37 100644 --- a/source/isaaclab/test/cli/test_replay_demos_loop_termination.py +++ b/source/isaaclab/test/cli/test_replay_demos_loop_termination.py @@ -28,8 +28,8 @@ _REPLAY_DEMOS_PATH = Path(__file__).resolve().parents[4] / "scripts" / "tools" / "replay_demos.py" -def _load_replay_episodes_loop(simulation_app): - """Compile ``replay_episodes_loop`` from the script source, bound to the given app stub.""" +def _load_replay_episodes_loop(): + """Compile ``replay_episodes_loop`` from the script source.""" source = _REPLAY_DEMOS_PATH.read_text() tree = ast.parse(source) func = next(node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "replay_episodes_loop") @@ -38,19 +38,21 @@ def _load_replay_episodes_loop(simulation_app): "torch": torch, "EpisodeData": EpisodeData, "HDF5DatasetFileHandler": HDF5DatasetFileHandler, - "simulation_app": simulation_app, "is_paused": False, } exec(compile(ast.Module(body=[func], type_ignores=[]), str(_REPLAY_DEMOS_PATH), "exec"), namespace) return namespace["replay_episodes_loop"] -class _SimulationAppStub: - def is_running(self): +class _SimStub: + def is_headless_or_exist_active_visualizer(self): return True - def is_exiting(self): - return False + def reset(self): + pass + + def render(self): + pass class _EnvStub: @@ -59,6 +61,7 @@ class _EnvStub: device = "cpu" def __init__(self): + self.sim = _SimStub() self.stepped_actions: list[torch.Tensor] = [] def reset_to(self, state, env_ids, is_relative=True): @@ -90,7 +93,7 @@ def test_replay_loop_does_not_step_after_the_recorded_actions(): ) idle_action = torch.zeros(1, recorded_actions.shape[-1]) env = _EnvStub() - replay_episodes_loop = _load_replay_episodes_loop(_SimulationAppStub()) + replay_episodes_loop = _load_replay_episodes_loop() replayed_episode_count, _, _ = replay_episodes_loop( env, diff --git a/source/isaaclab/test/devices/check_keyboard.py b/source/isaaclab/test/devices/check_keyboard.py index 439aeb92795a..a4e9bf0fca4b 100644 --- a/source/isaaclab/test/devices/check_keyboard.py +++ b/source/isaaclab/test/devices/check_keyboard.py @@ -10,20 +10,18 @@ It is possible to add additional callbacks to it for user-defined operations. """ -"""Launch Isaac Sim Simulator first.""" +import argparse +from isaaclab.app import add_launcher_args, launch_simulation -from isaaclab.app import AppLauncher - -# launch omniverse app -app_launcher = AppLauncher() -simulation_app = app_launcher.app - -"""Rest everything follows.""" +parser = argparse.ArgumentParser(description="Check the keyboard teleoperation device.") +add_launcher_args(parser) +# keyboard input needs the Kit window, so open the Kit visualizer by default +parser.set_defaults(visualizer=["kit"]) +args_cli = parser.parse_args() import sys -from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg from isaaclab.sim import SimulationCfg, SimulationContext @@ -35,55 +33,56 @@ def print_cb(): def quit_cb(): """Dummy callback function executed when the key 'ESC' is pressed.""" print("Quit callback") - simulation_app.close() + SimulationContext.instance().stop() def main(): - # Load kit helper - sim = SimulationContext(SimulationCfg(dt=0.01)) - - # Create teleoperation interface - teleop_interface = Se3Keyboard(Se3KeyboardCfg(pos_sensitivity=0.1, rot_sensitivity=0.1)) - # Add teleoperation callbacks - # available key buttons: https://docs.omniverse.nvidia.com/kit/docs/carbonite/latest/docs/python/carb.html?highlight=keyboardeventtype#carb.input.KeyboardInput - teleop_interface.add_callback("L", print_cb) - teleop_interface.add_callback("ESCAPE", quit_cb) - - print("Press 'L' to print a message. Press 'ESC' to quit.") - - # Check that the framework doesn't hold excessive strong references. - if sys.getrefcount(teleop_interface) >= 10: - raise RuntimeError("Possible reference leak for teleoperation interface.") - - # Reset interface internals - teleop_interface.reset() - - # Play simulation - sim.reset() - - # Simulate - while simulation_app.is_running(): - # If simulation is stopped, then exit. - if sim.is_stopped(): - break - # If simulation is paused, then skip. - if not sim.is_playing(): + sim_cfg = SimulationCfg(dt=0.01, device=args_cli.device) + with launch_simulation(sim_cfg, args_cli): + # the keyboard device uses Kit's input interface, so import it once Kit is running + from isaaclab.devices import Se3Keyboard, Se3KeyboardCfg + + sim = SimulationContext(sim_cfg) + + # Create teleoperation interface + teleop_interface = Se3Keyboard(Se3KeyboardCfg(pos_sensitivity=0.1, rot_sensitivity=0.1)) + # Add teleoperation callbacks + # available key buttons: https://docs.omniverse.nvidia.com/kit/docs/carbonite/latest/docs/python/carb.html?highlight=keyboardeventtype#carb.input.KeyboardInput + teleop_interface.add_callback("L", print_cb) + teleop_interface.add_callback("ESCAPE", quit_cb) + + print("Press 'L' to print a message. Press 'ESC' to quit.") + + # Check that the framework doesn't hold excessive strong references. + if sys.getrefcount(teleop_interface) >= 10: + raise RuntimeError("Possible reference leak for teleoperation interface.") + + # Reset interface internals + teleop_interface.reset() + + # Play simulation + sim.reset() + + # Simulate + while sim.is_headless_or_exist_active_visualizer(): + # If simulation is stopped, then exit. + if sim.is_stopped(): + break + # If simulation is paused, then skip. + if not sim.is_playing(): + sim.step() + continue + # get keyboard command + delta_pose, gripper_command = teleop_interface.advance() + # print command + if gripper_command: + print(f"Gripper command: {gripper_command}") + # step simulation sim.step() - continue - # get keyboard command - delta_pose, gripper_command = teleop_interface.advance() - # print command - if gripper_command: - print(f"Gripper command: {gripper_command}") - # step simulation - sim.step() - # check if simulator is stopped - if sim.is_stopped(): - break + # check if simulator is stopped + if sim.is_stopped(): + break if __name__ == "__main__": - # run the main function main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab/test/install_ci/misc/test_wheel_builder_smoke.py b/source/isaaclab/test/install_ci/misc/test_wheel_builder_smoke.py index 5063bd2830d1..86a745279991 100644 --- a/source/isaaclab/test/install_ci/misc/test_wheel_builder_smoke.py +++ b/source/isaaclab/test/install_ci/misc/test_wheel_builder_smoke.py @@ -13,7 +13,7 @@ - from isaaclab import __version__ -> verify version matches wheel filename - inspect the wheel and isaaclab.__path__ -> verify the core package has a flat layout - from isaaclab import _deprioritize_prebundle_paths -> verify wheel exports path sanitizer - - from isaaclab.app import AppLauncher -> verify importable + - from isaaclab.app import launch_simulation -> verify importable - from isaaclab.envs import VideoRecorderCfg -> verify importable - from isaaclab_assets.robots.allegro import ALLEGRO_HAND_CFG -> verify importable - from isaaclab.scene import InteractiveSceneCfg -> verify importable @@ -28,8 +28,7 @@ from isaaclab.programs import DEMOS, EXAMPLES, _run_script def fail_launch(*args, **kwargs): raise AssertionError(f'{sys.argv[0]} launched simulation while handling --help') - app.AppLauncher.__init__ = fail_launch - app.launch_simulation = fail_launch + app.launch_simulation = fail_launch for command, catalog in (('demo', DEMOS), ('example', EXAMPLES)): for program in catalog: sys.argv = [f'isaaclab {command} {program.name}', '--help'] @@ -161,16 +160,16 @@ def test_isaaclab_package_has_flat_layout(self): # from isaaclab import _deprioritize_prebundle_paths def test_isaaclab_prebundle_path_sanitizer_exported(self): - """Verify the wheel exports the prebundle path sanitizer used by AppLauncher.""" + """Verify the wheel exports the prebundle path sanitizer used by the Kit launcher.""" result = self.run_in_uv_env( ["python", "-c", "from isaaclab import _deprioritize_prebundle_paths; _deprioritize_prebundle_paths()"] ) assert result.returncode == 0, f"import path sanitizer failed:\n{result.stdout}\n{result.stderr}" - # from isaaclab.app import AppLauncher + # from isaaclab.app import launch_simulation def test_isaaclab_app_importable(self): - """Verify isaaclab.app and AppLauncher are importable.""" - result = self.run_in_uv_env(["python", "-c", "from isaaclab.app import AppLauncher"]) + """Verify the simulation launcher is importable from isaaclab.app.""" + result = self.run_in_uv_env(["python", "-c", "from isaaclab.app import launch_simulation"]) assert result.returncode == 0, f"import isaaclab.app failed:\n{result.stdout}\n{result.stderr}" # from isaaclab.envs import VideoRecorderCfg @@ -224,7 +223,6 @@ def fail_launch(*args, **kwargs): raise AssertionError(f"{sys.argv[0]} launched simulation while handling --help") -app.AppLauncher.__init__ = fail_launch app.launch_simulation = fail_launch for command, catalog in (("demo", DEMOS), ("example", EXAMPLES)): diff --git a/source/isaaclab/test/markers/check_markers_visibility.py b/source/isaaclab/test/markers/check_markers_visibility.py index dc778ee30c6d..96d588140838 100644 --- a/source/isaaclab/test/markers/check_markers_visibility.py +++ b/source/isaaclab/test/markers/check_markers_visibility.py @@ -17,32 +17,27 @@ """ -"""Launch Isaac Sim Simulator first.""" - +from __future__ import annotations import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# add argparse arguments parser = argparse.ArgumentParser(description="Check if the debug markers are visible from the camera.") 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 +add_launcher_args(parser) args_cli = parser.parse_args() -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything follows.""" +from typing import TYPE_CHECKING import isaaclab.sim as sim_utils from isaaclab.assets import ArticulationCfg, AssetBaseCfg -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors import RayCasterCfg, patterns -from isaaclab.utils import configclass, replace +from isaaclab.utils import configclass, instantiate, replace + +if TYPE_CHECKING: + from isaaclab.scene import InteractiveScene ## # Pre-defined configs @@ -89,7 +84,7 @@ def run_simulator( count = 0 # Simulate physics - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): # Reset if count % 500 == 0: # reset counter @@ -128,24 +123,18 @@ def run_simulator( def main(): """Main function.""" - # Initialize the simulation context - sim_cfg = sim_utils.SimulationCfg(dt=0.005) - sim = sim_utils.SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0]) - # design scene - scene_cfg = SensorsSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) - scene = InteractiveScene(scene_cfg) - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run the simulator - run_simulator(sim, scene) + sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device) + with launch_simulation(sim_cfg, args_cli): + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view(eye=[3.5, 3.5, 3.5], target=[0.0, 0.0, 0.0]) + # design scene + scene_cfg = SensorsSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) + scene = instantiate(scene_cfg) + sim.reset() + print("[INFO]: Setup complete...") + run_simulator(sim, scene) if __name__ == "__main__": - # run the main function main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab/test/scene/check_interactive_scene.py b/source/isaaclab/test/scene/check_interactive_scene.py deleted file mode 100644 index 85a2ab4886b3..000000000000 --- a/source/isaaclab/test/scene/check_interactive_scene.py +++ /dev/null @@ -1,164 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -This script demonstrates how to use the scene interface to quickly setup a scene with multiple -articulated robots and sensors. -""" - -"""Launch Isaac Sim Simulator first.""" - - -import argparse - -from isaaclab.app import AppLauncher - -# add argparse arguments -parser = argparse.ArgumentParser(description="This script demonstrates how to use the scene interface.") -parser.add_argument("--num_envs", type=int, default=2, help="Number of environments to spawn.") -AppLauncher.add_app_launcher_args(parser) -args_cli = parser.parse_args() - -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything follows.""" - -import isaaclab.sim as sim_utils -from isaaclab.assets import AssetBaseCfg -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg -from isaaclab.sensors.ray_caster import RayCasterCfg, patterns -from isaaclab.sim import SimulationContext -from isaaclab.terrains import TerrainImporterCfg -from isaaclab.utils import configclass, replace -from isaaclab.utils.timer import Timer - -## -# Pre-defined configs -## -from isaaclab_assets.robots.anymal import ANYMAL_C_CFG # isort: skip - - -@configclass -class MySceneCfg(InteractiveSceneCfg): - """Example scene configuration.""" - - # terrain - flat terrain plane - terrain = TerrainImporterCfg( - prim_path="/World/ground", - terrain_type="plane", - ) - - # articulation - robot 1 - robot_1 = replace(ANYMAL_C_CFG, prim_path="{ENV_REGEX_NS}/Robot_1") - # articulation - robot 2 - robot_2 = replace(ANYMAL_C_CFG, prim_path="{ENV_REGEX_NS}/Robot_2") - robot_2.init_state.pos = (0.0, 1.0, 0.6) - - # sensor - ray caster attached to the base of robot 1 that scans the ground - height_scanner = RayCasterCfg( - prim_path="{ENV_REGEX_NS}/Robot_1/base", - offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)), - ray_alignment="yaw", - pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), - debug_vis=True, - mesh_prim_paths=["/World/ground"], - ) - - # extras - light - light = AssetBaseCfg( - prim_path="/World/light", - spawn=sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75)), - init_state=AssetBaseCfg.InitialStateCfg(pos=(0.0, 0.0, 500.0)), - ) - - -def main(): - """Main function.""" - - # Load kit helper - sim = SimulationContext(sim_utils.SimulationCfg(dt=0.005)) - # Set main camera - sim.set_camera_view(eye=[5, 5, 5], target=[0.0, 0.0, 0.0]) - - # Spawn things into stage - with Timer("Setup scene"): - scene = InteractiveScene(MySceneCfg(num_envs=args_cli.num_envs, env_spacing=5.0, lazy_sensor_update=False)) - - # Check that parsing happened as expected - assert len(scene.env_prim_paths) == args_cli.num_envs, "Number of environments does not match." - assert scene.terrain is not None, "Terrain not found." - assert len(scene.articulations) == 2, "Number of robots does not match." - assert len(scene.sensors) == 1, "Number of sensors does not match." - assert len(scene.extras) == 1, "Number of extras does not match." - - # Play the simulator - with Timer("Time taken to play the simulator"): - sim.reset() - - # Now we are ready! - print("[INFO]: Setup complete...") - - # default joint targets - robot_1_actions = scene.articulations["robot_1"].data.default_joint_pos.torch.clone() - robot_2_actions = scene.articulations["robot_2"].data.default_joint_pos.torch.clone() - # Define simulation stepping - sim_dt = sim.get_physics_dt() - sim_time = 0.0 - count = 0 - # Simulate physics - while simulation_app.is_running(): - # If simulation is stopped, then exit. - if sim.is_stopped(): - break - # If simulation is paused, then skip. - if not sim.is_playing(): - sim.step() - continue - # reset - if count % 50 == 0: - # reset counters - sim_time = 0.0 - count = 0 - # reset root state - root_state = scene.articulations["robot_1"].data.default_root_state.torch.clone() - root_state[:, :3] += scene.env_origins - joint_pos = scene.articulations["robot_1"].data.default_joint_pos.torch - joint_vel = scene.articulations["robot_1"].data.default_joint_vel.torch - # -- set root state - # -- robot 1 - scene.articulations["robot_1"].write_root_pose_to_sim(root_state[:, :7]) - scene.articulations["robot_1"].write_root_velocity_to_sim(root_state[:, 7:]) - scene.articulations["robot_1"].write_joint_state_to_sim(joint_pos, joint_vel) - # -- robot 2 - root_state[:, 1] += 1.0 - scene.articulations["robot_2"].write_root_pose_to_sim(root_state[:, :7]) - scene.articulations["robot_2"].write_root_velocity_to_sim(root_state[:, 7:]) - scene.articulations["robot_2"].write_joint_state_to_sim(joint_pos, joint_vel) - # reset buffers - scene.reset() - print(">>>>>>>> Reset!") - # perform this loop at policy control freq (50 Hz) - for _ in range(4): - # set joint targets - scene.articulations["robot_1"].set_joint_position_target(robot_1_actions) - scene.articulations["robot_2"].set_joint_position_target(robot_2_actions) - # write data to sim - scene.write_data_to_sim() - # perform step - sim.step() - # read data from sim - scene.update(sim_dt) - # update sim-time - sim_time += sim_dt * 4 - count += 1 - - -if __name__ == "__main__": - # run the main function - main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab/test/sensors/check_multi_mesh_ray_caster.py b/source/isaaclab/test/sensors/check_multi_mesh_ray_caster.py deleted file mode 100644 index af226a74ce90..000000000000 --- a/source/isaaclab/test/sensors/check_multi_mesh_ray_caster.py +++ /dev/null @@ -1,226 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - - -""" -This script shows how to use the multi-mesh ray caster from the Isaac Lab framework. - -.. code-block:: bash - - # Usage - uv run python source/isaaclab/test/sensors/check_multi_mesh_ray_caster.py - -""" - -"""Launch Isaac Sim Simulator first.""" - -import argparse - -from isaaclab.app import AppLauncher -from isaaclab.assets import AssetBaseCfg -from isaaclab.sim import SpawnerCfg - -# add argparse arguments -parser = argparse.ArgumentParser(description="Ray Caster Test Script") -parser.add_argument("--num_envs", type=int, default=16, help="Number of environments to clone.") -parser.add_argument("--num_objects", type=int, default=0, help="Number of additional objects to clone.") -parser.add_argument( - "--terrain_type", - type=str, - default="generator", - help="Type of terrain to import. Can be 'generator' or 'usd' or 'plane'.", -) -# 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 random - -import numpy as np -import torch - -import isaaclab.sim as sim_utils -import isaaclab.terrains as terrain_gen -from isaaclab import cloner as lab_cloner -from isaaclab.assets import RigidObject, RigidObjectCfg -from isaaclab.sensors.ray_caster import MultiMeshRayCaster, MultiMeshRayCasterCfg, patterns -from isaaclab.sim import SimulationCfg, SimulationContext -from isaaclab.terrains.config.rough import ROUGH_TERRAINS_CFG -from isaaclab.terrains.terrain_importer import TerrainImporter -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR -from isaaclab.utils.math import quat_from_euler_xyz -from isaaclab.utils.timer import Timer - - -def design_scene(sim: SimulationContext, num_envs: int = 2048): - """Design the scene.""" - # Create interface to clone the scene - # Create environment clones using Lab's cloner utilities - env_fmt = "/World/envs/env_{}" - env_ids = np.arange(num_envs, dtype=np.int64) - env_origins, _ = lab_cloner.grid_transforms(num_envs, spacing=10.0) - # Everything under the namespace "/World/envs/env_0" will be cloned - sim.stage.DefinePrim("/World/envs/env_0", "Xform") - # Define the scene - # -- Light - cfg = sim_utils.DistantLightCfg(intensity=2000) - cfg.func("/World/light", cfg) - # -- Balls - cfg = sim_utils.SphereCfg( - radius=0.25, - rigid_props=sim_utils.UsdPhysicsRigidBodyCfg(), - mass_props=sim_utils.MassCfg(mass=0.5), - collision_props=sim_utils.UsdPhysicsCollisionCfg(), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0)), - ) - cfg.func("/World/envs/env_0/ball", cfg, translation=(0.0, 0.0, 5.0)) - - for i in range(args_cli.num_objects): - object = sim_utils.CuboidCfg( - size=(0.5 + random.random() * 0.5, 0.5 + random.random() * 0.5, 0.1 + random.random() * 0.05), - rigid_props=sim_utils.UsdPhysicsRigidBodyCfg(), - mass_props=sim_utils.MassCfg(mass=0.5), - collision_props=sim_utils.UsdPhysicsCollisionCfg(), - visual_material=sim_utils.PreviewSurfaceCfg( - diffuse_color=(0.0 + i / args_cli.num_objects, 0.0, 1.0 - i / args_cli.num_objects) - ), - ) - object.func( - f"/World/envs/env_0/object_{i}", - object, - translation=(0.0 + random.random(), 0.0 + random.random(), 1.0), - orientation=quat_from_euler_xyz(torch.zeros(1), torch.zeros(1), torch.rand(1) * torch.pi)[0].numpy(), - ) - - # Clone the scene - envs_prim_paths = [f"/World/envs/env_{i}" for i in range(num_envs)] - lab_cloner.usd_replicate(sim.stage, [env_fmt.format(0)], [env_fmt], env_ids, positions=env_origins) - # Publish the manually authored environment declaration for sensor queries. - asset = AssetBaseCfg(prim_path=env_fmt.format("[^/]+"), spawn=SpawnerCfg(spawn_path=env_fmt.format(0))) - plan = lab_cloner.make_clone_plan((asset,), ((0,),), num_envs, positions=env_origins) - sim.set_clone_plan(plan) - # PhysX-only optimization: filter collisions across env clones. Skip on Newton — - # PhysxSceneAPI isn't applied there and the cloner helper is PhysX-specific. - physics_scene_path = next( - (prim.GetPrimPath().pathString for prim in sim.stage.Traverse() if "PhysxSceneAPI" in prim.GetAppliedSchemas()), - None, - ) - if physics_scene_path is not None: - lab_cloner.filter_collisions( - sim.stage, - physics_scene_path, - "/World/collisions", - prim_paths=envs_prim_paths, - global_paths=["/World/ground"], - ) - - -def main(): - """Main function.""" - # Load kit helper - sim = SimulationContext(SimulationCfg()) - # Set main camera - sim.set_camera_view([0.0, 30.0, 25.0], [0.0, 0.0, -2.5]) - - # Parameters - num_envs = args_cli.num_envs - # Design the scene - design_scene(sim=sim, num_envs=num_envs) - # Handler for terrains importing - terrain_importer_cfg = terrain_gen.TerrainImporterCfg( - prim_path="/World/ground", - terrain_type=args_cli.terrain_type, - terrain_generator=ROUGH_TERRAINS_CFG, - usd_path=f"{ISAAC_NUCLEUS_DIR}/Environments/Terrains/rough_plane.usd", - max_init_terrain_level=0, - num_envs=1, - env_spacing=10.0, - ) - _ = TerrainImporter(terrain_importer_cfg) - - mesh_targets: list[MultiMeshRayCasterCfg.RaycastTargetCfg] = [ - MultiMeshRayCasterCfg.RaycastTargetCfg(prim_expr="/World/ground", track_mesh_transforms=False), - ] - if args_cli.num_objects != 0: - mesh_targets.append( - MultiMeshRayCasterCfg.RaycastTargetCfg(prim_expr="{ENV_REGEX_NS}/object_[^/]*", track_mesh_transforms=True) - ) - # Create a ray-caster sensor - ray_caster_cfg = MultiMeshRayCasterCfg( - prim_path="{ENV_REGEX_NS}/ball", - mesh_prim_paths=mesh_targets, - pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=(1.6, 1.0)), - ray_alignment="yaw", - debug_vis=not args_cli.headless, - ) - ray_caster = MultiMeshRayCaster(cfg=ray_caster_cfg) - # Create a view over all the balls - balls_cfg = RigidObjectCfg( - prim_path="{ENV_REGEX_NS}/ball", - spawn=None, - init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 5.0)), - ) - balls = RigidObject(cfg=balls_cfg) - - # Play simulator - sim.reset() - - # Initialize the views - # -- balls - print(balls) - # Print the sensor information - print(ray_caster) - - # Get the initial positions of the balls - ball_initial_poses = balls.data.root_pose_w.torch.clone() - ball_initial_velocities = balls.data.root_vel_w.torch.clone() - - # Create a counter for resetting the scene - step_count = 0 - # Simulate physics - while simulation_app.is_running(): - # If simulation is stopped, then exit. - if sim.is_stopped(): - break - # If simulation is paused, then skip. - if not sim.is_playing(): - sim.step(render=False) - continue - # Reset the scene - if step_count % 500 == 0: - # sample random indices to reset - reset_indices = torch.randint(0, num_envs, (num_envs // 2,), device=sim.device) - # reset the balls - balls.write_root_pose_to_sim(ball_initial_poses[reset_indices], env_ids=reset_indices) - balls.write_root_velocity_to_sim(ball_initial_velocities[reset_indices], env_ids=reset_indices) - balls.reset(reset_indices) - # reset the sensor - ray_caster.reset(reset_indices) - # reset the counter - step_count = 0 - # Step simulation - sim.step() - # Update the ray-caster - with Timer( - f"Ray-caster update with {num_envs} x {ray_caster.num_rays} rays", synchronize="both", device=sim.device - ): - ray_caster.update(dt=sim.get_physics_dt(), force_recompute=True) - # Update counter - step_count += 1 - - -if __name__ == "__main__": - # run the main function - main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab/test/sensors/check_ray_caster.py b/source/isaaclab/test/sensors/check_ray_caster.py deleted file mode 100644 index bf517054d6cf..000000000000 --- a/source/isaaclab/test/sensors/check_ray_caster.py +++ /dev/null @@ -1,190 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -This script shows how to use the ray caster from the Isaac Lab framework. - -.. code-block:: bash - - # Usage - uv run python source/isaaclab/test/sensors/check_ray_caster.py -""" - -"""Launch Isaac Sim Simulator first.""" - -import argparse - -from isaaclab.app import AppLauncher - -# add argparse arguments -parser = argparse.ArgumentParser(description="Ray Caster Test Script") -parser.add_argument("--num_envs", type=int, default=128, help="Number of environments to clone.") -parser.add_argument( - "--terrain_type", - type=str, - default="generator", - help="Type of terrain to import. Can be 'generator' or 'usd' or 'plane'.", -) -# 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 isaaclab.sim as sim_utils -import isaaclab.terrains as terrain_gen -from isaaclab import cloner as lab_cloner -from isaaclab.assets import RigidObject, RigidObjectCfg -from isaaclab.sensors.ray_caster import RayCaster, RayCasterCfg, patterns -from isaaclab.sim import SimulationCfg, SimulationContext -from isaaclab.terrains.config.rough import ROUGH_TERRAINS_CFG -from isaaclab.terrains.terrain_importer import TerrainImporter -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR -from isaaclab.utils.timer import Timer - - -def design_scene(sim: SimulationContext, num_envs: int = 2048): - """Design the scene.""" - # Create interface to clone the scene - # Create environment clones using Lab's cloner utilities - env_fmt = "/World/envs/env_{}" - env_ids = np.arange(num_envs, dtype=np.int64) - env_origins, _ = lab_cloner.grid_transforms(num_envs, spacing=2.0) - # Everything under the namespace "/World/envs/env_0" will be cloned - sim.stage.DefinePrim("/World/envs/env_0", "Xform") - # Define the scene - # -- Light - cfg = sim_utils.DistantLightCfg(intensity=2000) - cfg.func("/World/light", cfg) - # -- Balls - cfg = sim_utils.SphereCfg( - radius=0.25, - rigid_props=sim_utils.UsdPhysicsRigidBodyCfg(), - mass_props=sim_utils.MassCfg(mass=0.5), - collision_props=sim_utils.UsdPhysicsCollisionCfg(), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0)), - ) - cfg.func("/World/envs/env_0/ball", cfg, translation=(0.0, 0.0, 5.0)) - # Clone the scene - envs_prim_paths = [f"/World/envs/env_{i}" for i in range(num_envs)] - lab_cloner.usd_replicate(sim.stage, [env_fmt.format(0)], [env_fmt], env_ids, positions=env_origins) - # PhysX-only optimization: filter collisions across env clones. Skip on Newton — - # PhysxSceneAPI isn't applied there and the cloner helper is PhysX-specific. - physics_scene_path = None - for prim in sim.stage.Traverse(): - if "PhysxSceneAPI" in prim.GetAppliedSchemas(): - physics_scene_path = prim.GetPrimPath().pathString - break - if physics_scene_path is not None: - lab_cloner.filter_collisions( - sim.stage, - physics_scene_path, - "/World/collisions", - prim_paths=envs_prim_paths, - global_paths=["/World/ground"], - ) - - -def main(): - """Main function.""" - - sim = SimulationContext(SimulationCfg()) - # Set main camera - sim.set_camera_view([0.0, 30.0, 25.0], [0.0, 0.0, -2.5]) - - # Parameters - num_envs = args_cli.num_envs - # Design the scene - design_scene(sim=sim, num_envs=num_envs) - # Handler for terrains importing - terrain_importer_cfg = terrain_gen.TerrainImporterCfg( - prim_path="/World/ground", - terrain_type=args_cli.terrain_type, - terrain_generator=ROUGH_TERRAINS_CFG, - usd_path=f"{ISAAC_NUCLEUS_DIR}/Environments/Terrains/rough_plane.usd", - max_init_terrain_level=None, - num_envs=1, - env_spacing=10.0, - ) - _ = TerrainImporter(terrain_importer_cfg) - - # Create a ray-caster sensor - ray_caster_cfg = RayCasterCfg( - prim_path="{ENV_REGEX_NS}/ball", - mesh_prim_paths=["/World/ground"], - pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=(1.6, 1.0)), - ray_alignment="yaw", - debug_vis=not args_cli.headless, - ) - ray_caster = RayCaster(cfg=ray_caster_cfg) - # Create a view over all the balls - balls_cfg = RigidObjectCfg( - prim_path="{ENV_REGEX_NS}/ball", - spawn=None, - init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 5.0)), - ) - balls = RigidObject(cfg=balls_cfg) - - # Play simulator - sim.reset() - - # Initialize the views - # -- balls - print(balls) - # Print the sensor information - print(ray_caster) - - # Get the initial positions of the balls - ball_initial_poses = balls.data.root_pose_w.torch.clone() - ball_initial_velocities = balls.data.root_vel_w.torch.clone() - - # Create a counter for resetting the scene - step_count = 0 - # Simulate physics - while simulation_app.is_running(): - # If simulation is stopped, then exit. - if sim.is_stopped(): - break - # If simulation is paused, then skip. - if not sim.is_playing(): - sim.step(render=False) - continue - # Reset the scene - if step_count % 500 == 0: - # sample random indices to reset - reset_indices = torch.randint(0, num_envs, (num_envs // 2,), device=sim.device) - # reset the balls - balls.write_root_pose_to_sim(ball_initial_poses[reset_indices], env_ids=reset_indices) - balls.write_root_velocity_to_sim(ball_initial_velocities[reset_indices], env_ids=reset_indices) - balls.reset(reset_indices) - # reset the sensor - ray_caster.reset(reset_indices) - # reset the counter - step_count = 0 - # Step simulation - sim.step() - # Update the ray-caster - with Timer( - f"Ray-caster update with {num_envs} x {ray_caster.num_rays} rays", synchronize="both", device=sim.device - ): - ray_caster.update(dt=sim.get_physics_dt(), force_recompute=True) - # Update counter - step_count += 1 - - -if __name__ == "__main__": - # run the main function - main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab/test/sim/test_mjcf_converter.py b/source/isaaclab/test/sim/test_mjcf_converter.py index 9c3ace3fedc4..7a0d05435d41 100644 --- a/source/isaaclab/test/sim/test_mjcf_converter.py +++ b/source/isaaclab/test/sim/test_mjcf_converter.py @@ -3,27 +3,31 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Run the converter from the standalone importer wheel when installed; otherwise launch Isaac Sim.""" +"""Run the converter from the standalone importer wheel when installed; otherwise launch the default runtime.""" -from isaaclab.app import AppLauncher +from isaaclab.test.utils import launch_test_simulation from isaaclab.utils.version import standalone_importers_available -# Prefer kit-less; fall back to Kit when the standalone importers are not usable. -_USE_KIT = not standalone_importers_available() and AppLauncher.is_available() -simulation_app = AppLauncher(headless=True).app if _USE_KIT else None +# The standalone importers need no runtime; without them the default runtime provides the importer. +_USE_RUNTIME = not standalone_importers_available() +if _USE_RUNTIME: + try: + launch_test_simulation() + except SystemExit: + # the launcher exits when its runtime is not installed; without either importer there is nothing to test + import pytest -"""Rest everything follows.""" + pytest.skip( + "Needs the MJCF importer: install isaacsim-asset-isolated or the Isaac Sim runtime.", + allow_module_level=True, + ) import os import sys from types import SimpleNamespace -import pytest - -if _USE_KIT: - import omni.kit.app - import newton +import pytest import isaaclab.sim as sim_utils from isaaclab.sim import SimulationCfg, SimulationContext @@ -33,43 +37,27 @@ pytestmark = [pytest.mark.integration, pytest.mark.isaacsim_ci, pytest.mark.kitless] -_MJCF_IMPORTER_EXTENSION = "isaacsim.asset.importer.mjcf" +_MJCF_IMPORTER_MODULE = "isaacsim.asset.importer.mjcf" -# Portable MJCF for the kitless path (the Kit path uses the importer extension's bundled -# ``nv_ant.xml``). ``newton`` ships the same NVIDIA Ant model and is a base dependency of both -# environments. +# Portable NVIDIA Ant MJCF; ``newton`` is a base dependency of every environment. _PORTABLE_MJCF = os.path.join(os.path.dirname(newton.__file__), "examples", "assets", "nv_ant.xml") -def _get_extension_path_without_enabling(extension_name: str) -> str: - """Get the path for an extension without enabling it.""" - manager = omni.kit.app.get_app().get_extension_manager() - for extension in manager.get_extensions(): - if extension["name"] == extension_name: - return extension["path"] - raise RuntimeError(f"Extension not found: {extension_name}") - - @pytest.fixture(autouse=True) def test_setup_teardown(): """Setup and teardown for each test.""" # Setup: Create a new stage stage = sim_utils.create_new_stage() - if _USE_KIT: - # Kit path: create a simulation context and use the importer extension's asset. + if _USE_RUNTIME: sim = SimulationContext(SimulationCfg(dt=0.01)) - extension_path = _get_extension_path_without_enabling(_MJCF_IMPORTER_EXTENSION) - asset_path = f"{extension_path}/data/mjcf/nv_ant.xml" else: - # Kitless path: the converter loads the importer from the standalone wheel. Spawning and - # inspecting prims needs a USD stage but neither physics nor Kit, so the plain stage above - # stands in for the simulation context; use newton's bundled ``nv_ant.xml``. + # Spawning and inspecting prims needs a USD stage but no physics, so the plain stage + # stands in for the simulation context. sim = SimpleNamespace(stage=stage) - asset_path = _PORTABLE_MJCF # Setup: Create MJCF config config = MjcfConverterCfg( - asset_path=asset_path, + asset_path=_PORTABLE_MJCF, self_collision=False, ) @@ -77,39 +65,26 @@ def test_setup_teardown(): yield sim, config # Teardown: Cleanup simulation - if _USE_KIT: + if _USE_RUNTIME: sim._disable_app_control_on_stop_handle = True # prevent timeout sim.stop() sim.clear_instance() -def test_converter_enables_importer_extension(test_setup_teardown): - """Constructing the converter makes the importer API available on the active backend. - - Under Kit that means the owning extension is enabled; kitlessly the importer module is - resolved from the standalone wheel instead, with no extension manager involved. - """ +def test_converter_loads_importer(test_setup_teardown): + """Converting loads the importer module on the active runtime.""" _, mjcf_config = test_setup_teardown # the importer is loaded lazily during conversion, which is skipped when a matching USD # already exists, so force it to run mjcf_config.force_usd_conversion = True - if not _USE_KIT: - MjcfConverter(mjcf_config) - - # the importer must come from the installed wheel, not a Kit extension tree - module_path = sys.modules[_MJCF_IMPORTER_EXTENSION].__file__ - assert module_path is not None - assert "site-packages" in module_path, f"expected a wheel-provided importer, got {module_path}" - return - - manager = omni.kit.app.get_app().get_extension_manager() - if manager.is_extension_enabled(_MJCF_IMPORTER_EXTENSION): - pytest.skip("MJCF importer extension was already enabled before constructing MjcfConverter.") - MjcfConverter(mjcf_config) - assert manager.is_extension_enabled(_MJCF_IMPORTER_EXTENSION) + module_path = sys.modules[_MJCF_IMPORTER_MODULE].__file__ + assert module_path is not None + if not _USE_RUNTIME: + # without a runtime the importer must come from the installed wheel + assert "site-packages" in module_path, f"expected a wheel-provided importer, got {module_path}" def test_no_change(test_setup_teardown): diff --git a/source/isaaclab/test/sim/test_urdf_converter.py b/source/isaaclab/test/sim/test_urdf_converter.py index 14f6234038f8..1a49377c1f87 100644 --- a/source/isaaclab/test/sim/test_urdf_converter.py +++ b/source/isaaclab/test/sim/test_urdf_converter.py @@ -3,16 +3,24 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Run the converter from the standalone importer wheel when installed; otherwise launch Isaac Sim.""" +"""Run the converter from the standalone importer wheel when installed; otherwise launch the default runtime.""" -from isaaclab.app import AppLauncher +from isaaclab.test.utils import launch_test_simulation from isaaclab.utils.version import standalone_importers_available -# Prefer kit-less; fall back to Kit when the standalone importers are not usable. -_USE_KIT = not standalone_importers_available() and AppLauncher.is_available() -simulation_app = AppLauncher(headless=True).app if _USE_KIT else None - -"""Rest everything follows.""" +# The standalone importers need no runtime; without them the default runtime provides the importer. +_USE_RUNTIME = not standalone_importers_available() +if _USE_RUNTIME: + try: + launch_test_simulation() + except SystemExit: + # the launcher exits when its runtime is not installed; without either importer there is nothing to test + import pytest + + pytest.skip( + "Needs the URDF importer: install isaacsim-asset-isolated or the Isaac Sim runtime.", + allow_module_level=True, + ) import math import os @@ -21,9 +29,6 @@ import pytest -if _USE_KIT: - import omni.kit.app - import isaaclab import isaaclab.sim as sim_utils from isaaclab.sim import SimulationCfg, SimulationContext @@ -33,8 +38,7 @@ pytestmark = [pytest.mark.integration, pytest.mark.kitless] -# Portable Franka URDF for the kitless path (the Kit path uses the importer extension's bundled -# ``panda_arm_hand.urdf``). Both expose the same 7 revolute + 2 prismatic joint structure. +# Portable Franka URDF with 7 revolute + 2 prismatic joints, readable by either importer. _REPO_FRANKA_URDF = os.path.join( os.path.dirname(isaaclab.__file__), "controllers", "config", "data", "lula_franka_gen.urdf" ) @@ -52,25 +56,15 @@ @pytest.fixture def sim_config(): stage = sim_utils.create_new_stage() - if _USE_KIT: - # Kit path: enable the importer extension and use its bundled Franka asset. - manager = omni.kit.app.get_app().get_extension_manager() - if not manager.is_extension_enabled("isaacsim.asset.importer.urdf"): - manager.set_extension_enabled_immediate("isaacsim.asset.importer.urdf", True) - extension_id = manager.get_enabled_extension_id("isaacsim.asset.importer.urdf") - extension_path = manager.get_extension_path(extension_id) - asset_path = f"{extension_path}/data/urdf/robots/franka_description/robots/panda_arm_hand.urdf" - # Load kit helper + if _USE_RUNTIME: sim = SimulationContext(SimulationCfg(dt=0.01)) else: - # Kitless path: the converter loads the importer from the standalone wheel. Spawning and - # inspecting prims needs a USD stage but neither physics nor Kit, so the plain stage above + # Spawning and inspecting prims needs a USD stage but no physics, so the plain stage # stands in for the simulation context. - asset_path = _REPO_FRANKA_URDF sim = SimpleNamespace(stage=stage) # default configuration config = UrdfConverterCfg( - asset_path=asset_path, + asset_path=_REPO_FRANKA_URDF, fix_base=True, joint_drive=UrdfConverterCfg.JointDriveCfg( gains=UrdfConverterCfg.JointDriveCfg.PDGainsCfg(stiffness=None, damping=None) @@ -78,7 +72,7 @@ def sim_config(): ) yield sim, config # Teardown - if _USE_KIT: + if _USE_RUNTIME: sim._disable_app_control_on_stop_handle = True # prevent timeout sim.stop() sim.clear_instance() diff --git a/source/isaaclab/test/terrains/check_height_field_subterrains.py b/source/isaaclab/test/terrains/check_height_field_subterrains.py index de49524d0873..9261941c7775 100644 --- a/source/isaaclab/test/terrains/check_height_field_subterrains.py +++ b/source/isaaclab/test/terrains/check_height_field_subterrains.py @@ -3,22 +3,12 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Launch Isaac Sim Simulator first.""" - import argparse parser = argparse.ArgumentParser(description="Generate terrains using trimesh") parser.add_argument("--visualize", action="store_true", help="Open a window to display each output.") args_cli = parser.parse_args() -from isaaclab.app import AppLauncher - -# launch omniverse app -# note: we only need to do this because of `TerrainImporter` which uses Omniverse functions -simulation_app = AppLauncher(headless=True).app - -"""Rest everything follows.""" - import os import trimesh @@ -259,7 +249,5 @@ def main(): if __name__ == "__main__": - # run the main function + # note: terrain generation is pure Python (trimesh), so no simulation runtime is launched main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab/test/terrains/check_mesh_subterrains.py b/source/isaaclab/test/terrains/check_mesh_subterrains.py index 7e15b86701d3..bda92eb0fa68 100644 --- a/source/isaaclab/test/terrains/check_mesh_subterrains.py +++ b/source/isaaclab/test/terrains/check_mesh_subterrains.py @@ -3,23 +3,12 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Launch Isaac Sim Simulator first.""" - import argparse parser = argparse.ArgumentParser(description="Generate terrains using trimesh") parser.add_argument("--visualize", action="store_true", help="Open a window to display each output.") args_cli = parser.parse_args() -from isaaclab.app import AppLauncher - -# launch omniverse app -# note: we only need to do this because of `TerrainImporter` which uses Omniverse functions -simulation_app = AppLauncher(headless=True).app - -"""Rest everything follows.""" - -import argparse import os import trimesh @@ -423,7 +412,5 @@ def main(): if __name__ == "__main__": - # run the main function + # note: terrain generation is pure Python (trimesh), so no simulation runtime is launched main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab/test/terrains/check_terrain_importer.py b/source/isaaclab/test/terrains/check_terrain_importer.py deleted file mode 100644 index 97a70e266a1d..000000000000 --- a/source/isaaclab/test/terrains/check_terrain_importer.py +++ /dev/null @@ -1,215 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -This script shows how to use the terrain generator from the Isaac Lab framework. - -The terrains are generated using the :class:`TerrainGenerator` class and imported using the :class:`TerrainImporter` -class. The terrains can be imported from a file or generated procedurally. - -Example usage: - -.. code-block:: bash - - # generate terrain - # -- use physics sphere mesh - uv run python source/isaaclab/test/terrains/check_terrain_importer.py --terrain_type generator - # -- usd usd sphere geom - uv run python source/isaaclab/test/terrains/check_terrain_importer.py --terrain_type generator --geom_sphere - - # usd terrain - uv run python source/isaaclab/test/terrains/check_terrain_importer.py --terrain_type usd - - # plane terrain - uv run python source/isaaclab/test/terrains/check_terrain_importer.py --terrain_type plane -""" - -"""Launch Isaac Sim Simulator first.""" - -import argparse - -from isaaclab.app import AppLauncher - -# isaaclab -from isaaclab.utils import replace - -# add argparse arguments -parser = argparse.ArgumentParser(description="This script shows how to use the terrain importer.") -parser.add_argument("--geom_sphere", action="store_true", default=False, help="Whether to use sphere mesh or shape.") -parser.add_argument( - "--terrain_type", - type=str, - choices=["generator", "usd", "plane"], - default="generator", - help="Type of terrain to import. Can be 'generator' or 'usd' or 'plane'.", -) -parser.add_argument( - "--color_scheme", - type=str, - default="height", - choices=["height", "random", "none"], - help="The color scheme to use for the generated terrain.", -) -# 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 isaaclab.sim as sim_utils -import isaaclab.terrains as terrain_gen -from isaaclab import cloner as lab_cloner -from isaaclab.sim import SimulationCfg, SimulationContext -from isaaclab.terrains.config.rough import ROUGH_TERRAINS_CFG -from isaaclab.terrains.terrain_importer import TerrainImporter -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR - - -def main(): - """Generates a terrain from isaaclab.""" - - # Load kit helper - sim = SimulationContext(SimulationCfg()) - # Set main camera - sim.set_camera_view(eye=(0.0, 30.0, 25.0), target=(0.0, 0.0, -2.5)) - - # Parameters - num_balls = 2048 - - # Create interface to clone the scene - # Create environment clones using Lab's cloner utilities - env_fmt = "/World/envs/env_{}" - env_ids = np.arange(num_balls, dtype=np.int64) - env_origins, _ = lab_cloner.grid_transforms(num_balls, spacing=2.0) - # Everything under the namespace "/World/envs/env_0" will be cloned - sim_utils.define_prim("/World/envs/env_0") - - # Handler for terrains importing - terrain_importer_cfg = terrain_gen.TerrainImporterCfg( - num_envs=2048, - env_spacing=3.0, - prim_path="/World/ground", - max_init_terrain_level=None, - terrain_type=args_cli.terrain_type, - terrain_generator=replace(ROUGH_TERRAINS_CFG, curriculum=True, color_scheme=args_cli.color_scheme), - usd_path=f"{ISAAC_NUCLEUS_DIR}/Environments/Terrains/rough_plane.usd", - ) - terrain_importer = TerrainImporter(terrain_importer_cfg) - - # Define the scene - # -- Light - cfg = sim_utils.DistantLightCfg(intensity=1000.0) - cfg.func("/World/Light", cfg) - - # -- Ball with physics properties using Isaac Lab spawners - ball_prim_path = "/World/envs/env_0/ball" - - # Create physics material - physics_material_cfg = sim_utils.RigidBodyMaterialCfg( - static_friction=0.2, - dynamic_friction=1.0, - restitution=0.0, - ) - - # Create visual material - visual_material_cfg = sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0)) - - if args_cli.geom_sphere: - # Spawn a geom sphere with rigid body properties - sphere_cfg = sim_utils.SphereCfg( - radius=0.25, - rigid_props=sim_utils.UsdPhysicsRigidBodyCfg(), - mass_props=sim_utils.MassCfg(mass=0.5), - collision_props=sim_utils.UsdPhysicsCollisionCfg(), - visual_material=visual_material_cfg, - physics_material=physics_material_cfg, - ) - sphere_cfg.func(ball_prim_path, sphere_cfg, translation=(0.0, 0.0, 5.0)) - else: - # Spawn a mesh sphere with rigid body properties - mesh_sphere_cfg = sim_utils.MeshSphereCfg( - radius=0.25, - rigid_props=sim_utils.UsdPhysicsRigidBodyCfg(), - mass_props=sim_utils.MassCfg(mass=0.5), - collision_props=sim_utils.UsdPhysicsCollisionCfg(collision_enabled=True), - visual_material=visual_material_cfg, - physics_material=physics_material_cfg, - ) - mesh_sphere_cfg.func(ball_prim_path, mesh_sphere_cfg, translation=(0.0, 0.0, 0.5)) - - # Clone the scene - envs_prim_paths = [f"/World/envs/env_{i}" for i in range(num_balls)] - lab_cloner.usd_replicate(sim.stage, [env_fmt.format(0)], [env_fmt], env_ids, positions=env_origins) - physics_scene_path = sim.cfg.physics.physics_prim_path - lab_cloner.filter_collisions( - sim.stage, physics_scene_path, "/World/collisions", prim_paths=envs_prim_paths, global_paths=["/World/ground"] - ) - - # Set ball positions over terrain origins using FrameView (before simulation starts) - xform_view = sim_utils.FrameView("{ENV_REGEX_NS}/ball") - # cache initial state of the balls - ball_initial_positions = terrain_importer.env_origins.clone() - ball_initial_positions[:, 2] += 5.0 - # set initial poses (writes to USD before simulation) - with xform_view.xform_world_space_writer() as w: - w.set_poses(positions=ball_initial_positions) - - # Play simulator - sim.reset() - - # Create a PhysX rigid body view for physics simulation - physics_sim_view = sim.physics_manager.get_physics_sim_view() - ball_view = physics_sim_view.create_rigid_body_view("/World/envs/env_*/ball") - - # Cache initial velocities (all zeros) - ball_initial_velocities = ball_view.get_velocities() - - # Build initial transforms tensor for reset: (N, 7) = [pos(3), quat_xyzw(4)] - num_balls_actual = ball_initial_positions.shape[0] - ball_initial_transforms = torch.zeros(num_balls_actual, 7, device=ball_initial_positions.device) - ball_initial_transforms[:, :3] = ball_initial_positions - ball_initial_transforms[:, 6] = 1.0 # w=1 for identity quaternion (xyzw format) - - # Create indices for all balls (required by PhysX view API) - all_indices = torch.arange(num_balls_actual, dtype=torch.int32, device=ball_initial_positions.device) - - # Create a counter for resetting the scene - step_count = 0 - # Simulate physics - while simulation_app.is_running(): - # If simulation is stopped, then exit. - if sim.is_stopped(): - break - # If simulation is paused, then skip. - if not sim.is_playing(): - sim.step() - continue - # Reset the scene - if step_count % 500 == 0: - # reset the balls using PhysX tensor API - ball_view.set_transforms(ball_initial_transforms, all_indices) - ball_view.set_velocities(ball_initial_velocities, all_indices) - # reset the counter - step_count = 0 - # Step simulation - sim.step() - # Update counter - step_count += 1 - - -if __name__ == "__main__": - # run the main function - main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab/test/xr_visualization/check_scene_xr_visualization.py b/source/isaaclab/test/xr_visualization/check_scene_xr_visualization.py index c736d0099878..7dedae5464f3 100644 --- a/source/isaaclab/test/xr_visualization/check_scene_xr_visualization.py +++ b/source/isaaclab/test/xr_visualization/check_scene_xr_visualization.py @@ -13,37 +13,34 @@ """ -"""Launch Isaac Sim Simulator first.""" +from __future__ import annotations import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# add argparse arguments parser = argparse.ArgumentParser(description="Check XR visualization widgets in Isaac Lab.") 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 +add_launcher_args(parser) args_cli = parser.parse_args() # launch omniverse app with XR support args_cli.xr = True -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything follows.""" import time -from typing import Any - -from pxr import Gf +from typing import TYPE_CHECKING, Any import isaaclab.sim as sim_utils from isaaclab.assets import AssetBaseCfg -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg -from isaaclab.ui.xr_widgets import DataCollector, TriggerType, VisualizationManager, XRVisualization, update_instruction -from isaaclab.utils import configclass +from isaaclab.scene import InteractiveSceneCfg +from isaaclab.utils import configclass, instantiate + +if TYPE_CHECKING: + from isaaclab.scene import InteractiveScene + from isaaclab.ui.xr_widgets import DataCollector, VisualizationManager + +# Note: pxr and the XR widgets (Kit UI modules) are imported inside the functions below, +# since they must not be loaded before Kit starts in main(). ## # Pre-defined configs @@ -93,6 +90,10 @@ def get_camera_position(): def _sample_handle_ik_error(mgr: VisualizationManager, data_collector: DataCollector, params: Any = None) -> None: + from pxr import Gf + + from isaaclab.ui.xr_widgets import VisualizationManager + error_text_color = getattr(mgr, "_error_text_color", 0xFF0000FF) mgr.display_widget( "IK Error Detected", @@ -115,6 +116,10 @@ def _sample_update_error_text_color(mgr: VisualizationManager, data_collector: D def _sample_update_left_panel(mgr: VisualizationManager, data_collector: DataCollector) -> None: + from pxr import Gf + + from isaaclab.ui.xr_widgets import VisualizationManager, update_instruction + left_panel_id = getattr(mgr, "left_panel_id", None) if left_panel_id is None: @@ -143,6 +148,10 @@ def _sample_update_left_panel(mgr: VisualizationManager, data_collector: DataCol def _sample_update_right_panel(mgr: VisualizationManager, data_collector: DataCollector) -> None: + from pxr import Gf + + from isaaclab.ui.xr_widgets import VisualizationManager, update_instruction + right_panel_id = getattr(mgr, "right_panel_id", None) if right_panel_id is None: @@ -179,6 +188,10 @@ def _sample_update_right_panel(mgr: VisualizationManager, data_collector: DataCo def apply_sample_visualization(): + from pxr import Gf + + from isaaclab.ui.xr_widgets import TriggerType, XRVisualization + # Error Message XRVisualization.register_callback(TriggerType.TRIGGER_ON_EVENT, {"event_name": "ik_error"}, _sample_handle_ik_error) @@ -220,6 +233,7 @@ def run_simulator( scene: InteractiveScene, ): """Run the simulator.""" + from isaaclab.ui.xr_widgets import XRVisualization # Define simulation stepping sim_dt = sim.get_physics_dt() @@ -227,7 +241,7 @@ def run_simulator( apply_sample_visualization() # Simulate - while simulation_app.is_running(): + while sim.is_headless_or_exist_active_visualizer(): if int(time.time()) % 10 < 1: XRVisualization.push_event("ik_error") @@ -240,23 +254,18 @@ def run_simulator( def main(): """Main function.""" - # Initialize the simulation context - sim_cfg = sim_utils.SimulationCfg(dt=0.005) - sim = sim_utils.SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view(eye=(8, 0, 4), target=(0.0, 0.0, 0.0)) - # design scene - scene = InteractiveScene(SimpleSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)) - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run the simulator - run_simulator(sim, scene) + sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device) + with launch_simulation(sim_cfg, args_cli): + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view(eye=(8, 0, 4), target=(0.0, 0.0, 0.0)) + # design scene + scene_cfg = SimpleSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0) + scene = instantiate(scene_cfg) + sim.reset() + print("[INFO]: Setup complete...") + run_simulator(sim, scene) if __name__ == "__main__": - # run the main function main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab_physx/benchmark/sensors/benchmark_contact_sensor.py b/source/isaaclab_physx/benchmark/sensors/benchmark_contact_sensor.py index b09ec2d29937..8b9a7acfd11b 100644 --- a/source/isaaclab_physx/benchmark/sensors/benchmark_contact_sensor.py +++ b/source/isaaclab_physx/benchmark/sensors/benchmark_contact_sensor.py @@ -23,16 +23,16 @@ from __future__ import annotations import argparse -import traceback from functools import partial -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.benchmark._cli import parse_non_negative_int, parse_positive_int from isaaclab.benchmark.sensor_suites import ( add_sensor_benchmark_args, create_contact_sensor_scene_cfg, run_contact_sensor_workload, ) +from isaaclab.utils import instantiate parser = argparse.ArgumentParser(description="Benchmark the PhysX contact sensor update.") add_sensor_benchmark_args( @@ -47,73 +47,60 @@ ) parser.add_argument("--disable_graph", action="store_true", help="Disable CUDA graph capture of the sensor update.") -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) args_cli = parser.parse_args() -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything follows.""" - import warp as wp import isaaclab.sim as sim_utils -from isaaclab.scene import InteractiveScene def main(): sim_dt = 1.0 / 120.0 sim_cfg = sim_utils.SimulationCfg(dt=sim_dt, device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) - - scene_cfg = create_contact_sensor_scene_cfg( - history_length=args_cli.history_length, - num_envs=args_cli.num_envs, - ) - scene = InteractiveScene(scene_cfg) - sim.reset() - scene.reset() - - sensor = scene["contact_sensor"] - if args_cli.disable_graph: - sensor._use_graph = False - - synchronize_device = partial(wp.synchronize_device, sim.device) - mode = "eager" if args_cli.disable_graph else "graph" - run_contact_sensor_workload( - benchmark_name="physx_contact_sensor", - formatter_type=args_cli.benchmark_formatter, - output_path=args_cli.output_path, - metadata={ - "physics_variant": args_cli.physics_variant, - "label": args_cli.label, - "mode": mode, - "device": str(sim.device), - "num_envs": args_cli.num_envs, - "num_steps": args_cli.num_steps, - "warmup_steps": args_cli.warmup_steps, - "decimation": args_cli.decimation, - "history_length": args_cli.history_length, - }, - num_steps=args_cli.num_steps, - warmup_steps=args_cli.warmup_steps, - decimation=args_cli.decimation, - expected_contacts=args_cli.num_envs, - step=lambda: sim.step(render=False), - update=lambda: sensor.update(sim_dt), - read=lambda: getattr(sensor, "data"), - count_contacts=lambda: int((sensor.data.net_normal_forces_w.torch.norm(dim=-1) > 0.1).sum().item()), - synchronize=synchronize_device, - ) + with launch_simulation(sim_cfg, args_cli): + sim = sim_utils.SimulationContext(sim_cfg) + + scene_cfg = create_contact_sensor_scene_cfg( + history_length=args_cli.history_length, + num_envs=args_cli.num_envs, + ) + scene = instantiate(scene_cfg) + sim.reset() + scene.reset() + + sensor = scene["contact_sensor"] + if args_cli.disable_graph: + sensor._use_graph = False + + synchronize_device = partial(wp.synchronize_device, sim.device) + mode = "eager" if args_cli.disable_graph else "graph" + run_contact_sensor_workload( + benchmark_name="physx_contact_sensor", + formatter_type=args_cli.benchmark_formatter, + output_path=args_cli.output_path, + metadata={ + "physics_variant": args_cli.physics_variant, + "label": args_cli.label, + "mode": mode, + "device": str(sim.device), + "num_envs": args_cli.num_envs, + "num_steps": args_cli.num_steps, + "warmup_steps": args_cli.warmup_steps, + "decimation": args_cli.decimation, + "history_length": args_cli.history_length, + }, + num_steps=args_cli.num_steps, + warmup_steps=args_cli.warmup_steps, + decimation=args_cli.decimation, + expected_contacts=args_cli.num_envs, + step=lambda: sim.step(render=False), + update=lambda: sensor.update(sim_dt), + read=lambda: getattr(sensor, "data"), + count_contacts=lambda: int((sensor.data.net_normal_forces_w.torch.norm(dim=-1) > 0.1).sum().item()), + synchronize=synchronize_device, + ) if __name__ == "__main__": - try: - main() - except BaseException: - if simulation_app.config.get("fast_shutdown", False): - traceback.print_exc() - simulation_app.close(exit_code=1) - raise - else: - simulation_app.close() + main() diff --git a/source/isaaclab_physx/benchmark/sensors/benchmark_frame_transformer.py b/source/isaaclab_physx/benchmark/sensors/benchmark_frame_transformer.py index 332b559c3087..42bc5578449e 100644 --- a/source/isaaclab_physx/benchmark/sensors/benchmark_frame_transformer.py +++ b/source/isaaclab_physx/benchmark/sensors/benchmark_frame_transformer.py @@ -16,10 +16,9 @@ from __future__ import annotations import argparse -import traceback from functools import partial -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.benchmark._cli import parse_positive_int from isaaclab.benchmark.sensor_suites import add_sensor_benchmark_args @@ -38,14 +37,9 @@ action="store_true", help="Use cached PhysX views with ordinary eager Warp launches.", ) -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) args_cli = parser.parse_args() -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Everything below follows application launch.""" - import torch import warp as wp from isaaclab_physx.sim.schemas import PhysxRigidBodyCfg @@ -54,9 +48,9 @@ from isaaclab.assets import RigidObjectCfg from isaaclab.benchmark import LatencyBenchmarkRunner, SingleMeasurement from isaaclab.benchmark.sensor_suites import add_sensor_latency_measurements, collect_sensor_latency_samples -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors import FrameTransformerCfg, OffsetCfg -from isaaclab.utils import configclass +from isaaclab.utils import configclass, instantiate @configclass @@ -92,87 +86,80 @@ def main() -> None: """Run the benchmark and print latency statistics.""" sim_dt = 1.0 / 120.0 sim_cfg = sim_utils.SimulationCfg(dt=sim_dt, device=args_cli.device, gravity=(0.0, 0.0, 0.0)) - sim = sim_utils.SimulationContext(sim_cfg) - - scene_cfg = FrameTransformerBenchmarkSceneCfg( - num_envs=args_cli.num_envs, - env_spacing=1.0, - lazy_sensor_update=True, - ) - scene_cfg.frame_transformer = FrameTransformerCfg( - prim_path="{ENV_REGEX_NS}/Source", - target_frames=[ - FrameTransformerCfg.FrameCfg( - name=f"target_{index}", - prim_path="{ENV_REGEX_NS}/Target", - offset=OffsetCfg(pos=(0.0, 0.01 * index, 0.0)), - ) - for index in range(args_cli.num_target_frames) - ], - ) - scene = InteractiveScene(scene_cfg) - sim.reset() - scene.reset() - - sensor = scene["frame_transformer"] - - if args_cli.disable_recorded_launch: - sensor._use_recorded_launch = False - for _ in range(args_cli.warmup_steps): - sim.step(render=False) - sensor.update(sim_dt, force_recompute=True) - wp.synchronize_device(sim.device) - - synchronize_device = partial(wp.synchronize_device, sim.device) - samples = collect_sensor_latency_samples( - num_steps=args_cli.num_steps, - step=lambda: sim.step(render=False), - update=lambda: sensor.update(sim_dt, force_recompute=True), - synchronize=synchronize_device, - ) - - target_positions = sensor.data.target_pos_w.torch - finite_frames = int(torch.isfinite(target_positions).all(dim=-1).sum().item()) - expected_frames = args_cli.num_envs * args_cli.num_target_frames - - if finite_frames != expected_frames: - raise RuntimeError(f"Expected {expected_frames} finite target frames, received {finite_frames}.") - - benchmark = LatencyBenchmarkRunner( - benchmark_name="physx_frame_transformer_sensor", - formatter_type=args_cli.benchmark_formatter, - output_path=args_cli.output_path, - metadata={ - "physics_variant": args_cli.physics_variant, - "label": args_cli.label, - "device": str(sim.device), - "num_envs": args_cli.num_envs, - "target_frames_per_env": args_cli.num_target_frames, - "num_steps": args_cli.num_steps, - "warmup_steps": args_cli.warmup_steps, - }, - ) - add_sensor_latency_measurements( - benchmark, - samples=samples, - validation=[ - SingleMeasurement(name="Finite Target Frames", value=finite_frames, unit="count"), - SingleMeasurement(name="Expected Target Frames", value=expected_frames, unit="count"), - ], - update_phase="sensor_update", - observer_phase="observer", - validation_phase="validation", - ) - benchmark.finalize() + with launch_simulation(sim_cfg, args_cli): + sim = sim_utils.SimulationContext(sim_cfg) + + scene_cfg = FrameTransformerBenchmarkSceneCfg( + num_envs=args_cli.num_envs, + env_spacing=1.0, + lazy_sensor_update=True, + ) + scene_cfg.frame_transformer = FrameTransformerCfg( + prim_path="{ENV_REGEX_NS}/Source", + target_frames=[ + FrameTransformerCfg.FrameCfg( + name=f"target_{index}", + prim_path="{ENV_REGEX_NS}/Target", + offset=OffsetCfg(pos=(0.0, 0.01 * index, 0.0)), + ) + for index in range(args_cli.num_target_frames) + ], + ) + scene = instantiate(scene_cfg) + sim.reset() + scene.reset() + + sensor = scene["frame_transformer"] + + if args_cli.disable_recorded_launch: + sensor._use_recorded_launch = False + for _ in range(args_cli.warmup_steps): + sim.step(render=False) + sensor.update(sim_dt, force_recompute=True) + wp.synchronize_device(sim.device) + + synchronize_device = partial(wp.synchronize_device, sim.device) + samples = collect_sensor_latency_samples( + num_steps=args_cli.num_steps, + step=lambda: sim.step(render=False), + update=lambda: sensor.update(sim_dt, force_recompute=True), + synchronize=synchronize_device, + ) + + target_positions = sensor.data.target_pos_w.torch + finite_frames = int(torch.isfinite(target_positions).all(dim=-1).sum().item()) + expected_frames = args_cli.num_envs * args_cli.num_target_frames + + if finite_frames != expected_frames: + raise RuntimeError(f"Expected {expected_frames} finite target frames, received {finite_frames}.") + + benchmark = LatencyBenchmarkRunner( + benchmark_name="physx_frame_transformer_sensor", + formatter_type=args_cli.benchmark_formatter, + output_path=args_cli.output_path, + metadata={ + "physics_variant": args_cli.physics_variant, + "label": args_cli.label, + "device": str(sim.device), + "num_envs": args_cli.num_envs, + "target_frames_per_env": args_cli.num_target_frames, + "num_steps": args_cli.num_steps, + "warmup_steps": args_cli.warmup_steps, + }, + ) + add_sensor_latency_measurements( + benchmark, + samples=samples, + validation=[ + SingleMeasurement(name="Finite Target Frames", value=finite_frames, unit="count"), + SingleMeasurement(name="Expected Target Frames", value=expected_frames, unit="count"), + ], + update_phase="sensor_update", + observer_phase="observer", + validation_phase="validation", + ) + benchmark.finalize() if __name__ == "__main__": - try: - main() - except BaseException: - if simulation_app.config.get("fast_shutdown", False): - traceback.print_exc() - simulation_app.close(exit_code=1) - raise - else: - simulation_app.close() + main() diff --git a/source/isaaclab_physx/benchmark/sensors/benchmark_imu_pva.py b/source/isaaclab_physx/benchmark/sensors/benchmark_imu_pva.py index 034504316d2c..7f25d89c778c 100644 --- a/source/isaaclab_physx/benchmark/sensors/benchmark_imu_pva.py +++ b/source/isaaclab_physx/benchmark/sensors/benchmark_imu_pva.py @@ -15,10 +15,9 @@ from __future__ import annotations import argparse -import traceback from functools import partial -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.benchmark.sensor_suites import add_sensor_benchmark_args parser = argparse.ArgumentParser(description="Benchmark a PhysX IMU or PVA sensor update path.") @@ -34,14 +33,9 @@ action="store_true", help="Use cached PhysX views with ordinary eager Warp launches.", ) -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) args_cli = parser.parse_args() -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Everything below follows application launch.""" - import torch import warp as wp from isaaclab_physx.sim.schemas import PhysxRigidBodyCfg @@ -50,9 +44,9 @@ from isaaclab.assets import RigidObjectCfg from isaaclab.benchmark import LatencyBenchmarkRunner, SingleMeasurement from isaaclab.benchmark.sensor_suites import add_sensor_latency_measurements, collect_sensor_latency_samples -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors import ImuCfg, PvaCfg -from isaaclab.utils import configclass +from isaaclab.utils import configclass, instantiate @configclass @@ -78,81 +72,74 @@ def main() -> None: """Run the selected sensor benchmark and print latency statistics.""" sim_dt = 1.0 / 120.0 sim_cfg = sim_utils.SimulationCfg(dt=sim_dt, device=args_cli.device, gravity=(0.0, 0.0, -9.81)) - sim = sim_utils.SimulationContext(sim_cfg) - - scene_cfg = ImuPvaBenchmarkSceneCfg( - num_envs=args_cli.num_envs, - env_spacing=1.0, - lazy_sensor_update=True, - ) - if args_cli.sensor == "imu": - scene_cfg.imu = ImuCfg(prim_path="{ENV_REGEX_NS}/Body") - else: - scene_cfg.pva = PvaCfg(prim_path="{ENV_REGEX_NS}/Body") - - scene = InteractiveScene(scene_cfg) - sim.reset() - scene.reset() - sensor = scene[args_cli.sensor] - - if args_cli.disable_recorded_launch: - sensor._use_recorded_launch = False - for _ in range(args_cli.warmup_steps): - sim.step(render=False) - sensor.update(sim_dt, force_recompute=True) - wp.synchronize_device(sim.device) - - synchronize_device = partial(wp.synchronize_device, sim.device) - samples = collect_sensor_latency_samples( - num_steps=args_cli.num_steps, - step=lambda: sim.step(render=False), - update=lambda: sensor.update(sim_dt, force_recompute=True), - synchronize=synchronize_device, - ) - - lin_acc_b = sensor.data.lin_acc_b.torch - ang_vel_b = sensor.data.ang_vel_b.torch - finite_instances = int((torch.isfinite(lin_acc_b).all(dim=-1) & torch.isfinite(ang_vel_b).all(dim=-1)).sum()) - - if finite_instances != args_cli.num_envs: - raise RuntimeError(f"Expected {args_cli.num_envs} finite sensor outputs, received {finite_instances}.") - - benchmark_name = f"physx_{args_cli.sensor}_sensor" - benchmark = LatencyBenchmarkRunner( - benchmark_name=benchmark_name, - formatter_type=args_cli.benchmark_formatter, - output_path=args_cli.output_path, - metadata={ - "physics_variant": args_cli.physics_variant, - "label": args_cli.label, - "sensor": args_cli.sensor, - "device": str(sim.device), - "num_envs": args_cli.num_envs, - "num_steps": args_cli.num_steps, - "warmup_steps": args_cli.warmup_steps, - }, - ) - add_sensor_latency_measurements( - benchmark, - samples=samples, - validation=[ - SingleMeasurement(name="Finite Sensor Outputs", value=finite_instances, unit="count"), - SingleMeasurement(name="Expected Sensor Outputs", value=args_cli.num_envs, unit="count"), - ], - update_phase="sensor_update", - observer_phase="observer", - validation_phase="validation", - ) - benchmark.finalize() + with launch_simulation(sim_cfg, args_cli): + sim = sim_utils.SimulationContext(sim_cfg) + + scene_cfg = ImuPvaBenchmarkSceneCfg( + num_envs=args_cli.num_envs, + env_spacing=1.0, + lazy_sensor_update=True, + ) + if args_cli.sensor == "imu": + scene_cfg.imu = ImuCfg(prim_path="{ENV_REGEX_NS}/Body") + else: + scene_cfg.pva = PvaCfg(prim_path="{ENV_REGEX_NS}/Body") + + scene = instantiate(scene_cfg) + sim.reset() + scene.reset() + sensor = scene[args_cli.sensor] + + if args_cli.disable_recorded_launch: + sensor._use_recorded_launch = False + for _ in range(args_cli.warmup_steps): + sim.step(render=False) + sensor.update(sim_dt, force_recompute=True) + wp.synchronize_device(sim.device) + + synchronize_device = partial(wp.synchronize_device, sim.device) + samples = collect_sensor_latency_samples( + num_steps=args_cli.num_steps, + step=lambda: sim.step(render=False), + update=lambda: sensor.update(sim_dt, force_recompute=True), + synchronize=synchronize_device, + ) + + lin_acc_b = sensor.data.lin_acc_b.torch + ang_vel_b = sensor.data.ang_vel_b.torch + finite_instances = int((torch.isfinite(lin_acc_b).all(dim=-1) & torch.isfinite(ang_vel_b).all(dim=-1)).sum()) + + if finite_instances != args_cli.num_envs: + raise RuntimeError(f"Expected {args_cli.num_envs} finite sensor outputs, received {finite_instances}.") + + benchmark_name = f"physx_{args_cli.sensor}_sensor" + benchmark = LatencyBenchmarkRunner( + benchmark_name=benchmark_name, + formatter_type=args_cli.benchmark_formatter, + output_path=args_cli.output_path, + metadata={ + "physics_variant": args_cli.physics_variant, + "label": args_cli.label, + "sensor": args_cli.sensor, + "device": str(sim.device), + "num_envs": args_cli.num_envs, + "num_steps": args_cli.num_steps, + "warmup_steps": args_cli.warmup_steps, + }, + ) + add_sensor_latency_measurements( + benchmark, + samples=samples, + validation=[ + SingleMeasurement(name="Finite Sensor Outputs", value=finite_instances, unit="count"), + SingleMeasurement(name="Expected Sensor Outputs", value=args_cli.num_envs, unit="count"), + ], + update_phase="sensor_update", + observer_phase="observer", + validation_phase="validation", + ) + benchmark.finalize() if __name__ == "__main__": - try: - main() - except BaseException: - if simulation_app.config.get("fast_shutdown", False): - traceback.print_exc() - simulation_app.close(exit_code=1) - raise - else: - simulation_app.close() + main() diff --git a/source/isaaclab_physx/benchmark/sensors/benchmark_joint_wrench.py b/source/isaaclab_physx/benchmark/sensors/benchmark_joint_wrench.py index 6ce14537a427..f0d7e7c87b02 100644 --- a/source/isaaclab_physx/benchmark/sensors/benchmark_joint_wrench.py +++ b/source/isaaclab_physx/benchmark/sensors/benchmark_joint_wrench.py @@ -16,10 +16,9 @@ from __future__ import annotations import argparse -import traceback from functools import partial -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.benchmark.sensor_suites import add_sensor_benchmark_args parser = argparse.ArgumentParser(description="Benchmark the PhysX JointWrench sensor update path.") @@ -34,14 +33,9 @@ action="store_true", help="Use the cached PhysX view with ordinary eager Warp launches.", ) -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) args_cli = parser.parse_args() -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Everything below follows application launch.""" - import torch import warp as wp from isaaclab_physx.physics import PhysxCfg @@ -49,9 +43,9 @@ import isaaclab.sim as sim_utils from isaaclab.benchmark import LatencyBenchmarkRunner, SingleMeasurement from isaaclab.benchmark.sensor_suites import add_sensor_latency_measurements, collect_sensor_latency_samples -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors import JointWrenchSensorCfg -from isaaclab.utils import configclass, replace +from isaaclab.utils import configclass, instantiate, replace from isaaclab_assets import CARTPOLE_CFG @@ -68,80 +62,73 @@ def main() -> None: """Run the benchmark and print latency and output sanity statistics.""" sim_dt = 1.0 / 120.0 sim_cfg = sim_utils.SimulationCfg(dt=sim_dt, device=args_cli.device, physics=PhysxCfg()) - sim = sim_utils.SimulationContext(sim_cfg) - - scene_cfg = JointWrenchBenchmarkSceneCfg( - num_envs=args_cli.num_envs, - env_spacing=4.0, - lazy_sensor_update=True, - ) - scene = InteractiveScene(scene_cfg) - sim.reset() - scene.reset() - - sensor = scene["joint_wrench"] - if args_cli.disable_recorded_launch: - sensor._use_recorded_launch = False - - for _ in range(args_cli.warmup_steps): - sim.step(render=False) - sensor.update(sim_dt, force_recompute=True) - wp.synchronize_device(sim.device) - - synchronize_device = partial(wp.synchronize_device, sim.device) - samples = collect_sensor_latency_samples( - num_steps=args_cli.num_steps, - step=lambda: sim.step(render=False), - update=lambda: sensor.update(sim_dt, force_recompute=True), - synchronize=synchronize_device, - ) - - force = sensor.data.force.torch - torque = sensor.data.torque.torch - finite_wrenches = int((torch.isfinite(force).all(dim=-1) & torch.isfinite(torque).all(dim=-1)).sum().item()) - nonzero_wrenches = int(((force != 0.0).any(dim=-1) | (torque != 0.0).any(dim=-1)).sum().item()) - expected_wrenches = args_cli.num_envs * len(sensor.body_names) - if finite_wrenches != expected_wrenches: - raise RuntimeError(f"Expected {expected_wrenches} finite wrenches, received {finite_wrenches}.") - if nonzero_wrenches == 0: - raise RuntimeError("Expected at least one nonzero wrench.") - - benchmark = LatencyBenchmarkRunner( - benchmark_name="physx_joint_wrench_sensor", - formatter_type=args_cli.benchmark_formatter, - output_path=args_cli.output_path, - metadata={ - "physics_variant": args_cli.physics_variant, - "label": args_cli.label, - "device": str(sim.device), - "num_envs": args_cli.num_envs, - "bodies_per_env": len(sensor.body_names), - "num_steps": args_cli.num_steps, - "warmup_steps": args_cli.warmup_steps, - }, - ) - add_sensor_latency_measurements( - benchmark, - samples=samples, - validation=[ - SingleMeasurement(name="Finite Wrenches", value=finite_wrenches, unit="count"), - SingleMeasurement(name="Nonzero Wrenches", value=nonzero_wrenches, unit="count"), - SingleMeasurement(name="Expected Wrenches", value=expected_wrenches, unit="count"), - ], - update_phase="sensor_update", - observer_phase="observer", - validation_phase="validation", - ) - benchmark.finalize() + with launch_simulation(sim_cfg, args_cli): + sim = sim_utils.SimulationContext(sim_cfg) + + scene_cfg = JointWrenchBenchmarkSceneCfg( + num_envs=args_cli.num_envs, + env_spacing=4.0, + lazy_sensor_update=True, + ) + scene = instantiate(scene_cfg) + sim.reset() + scene.reset() + + sensor = scene["joint_wrench"] + if args_cli.disable_recorded_launch: + sensor._use_recorded_launch = False + + for _ in range(args_cli.warmup_steps): + sim.step(render=False) + sensor.update(sim_dt, force_recompute=True) + wp.synchronize_device(sim.device) + + synchronize_device = partial(wp.synchronize_device, sim.device) + samples = collect_sensor_latency_samples( + num_steps=args_cli.num_steps, + step=lambda: sim.step(render=False), + update=lambda: sensor.update(sim_dt, force_recompute=True), + synchronize=synchronize_device, + ) + + force = sensor.data.force.torch + torque = sensor.data.torque.torch + finite_wrenches = int((torch.isfinite(force).all(dim=-1) & torch.isfinite(torque).all(dim=-1)).sum().item()) + nonzero_wrenches = int(((force != 0.0).any(dim=-1) | (torque != 0.0).any(dim=-1)).sum().item()) + expected_wrenches = args_cli.num_envs * len(sensor.body_names) + if finite_wrenches != expected_wrenches: + raise RuntimeError(f"Expected {expected_wrenches} finite wrenches, received {finite_wrenches}.") + if nonzero_wrenches == 0: + raise RuntimeError("Expected at least one nonzero wrench.") + + benchmark = LatencyBenchmarkRunner( + benchmark_name="physx_joint_wrench_sensor", + formatter_type=args_cli.benchmark_formatter, + output_path=args_cli.output_path, + metadata={ + "physics_variant": args_cli.physics_variant, + "label": args_cli.label, + "device": str(sim.device), + "num_envs": args_cli.num_envs, + "bodies_per_env": len(sensor.body_names), + "num_steps": args_cli.num_steps, + "warmup_steps": args_cli.warmup_steps, + }, + ) + add_sensor_latency_measurements( + benchmark, + samples=samples, + validation=[ + SingleMeasurement(name="Finite Wrenches", value=finite_wrenches, unit="count"), + SingleMeasurement(name="Nonzero Wrenches", value=nonzero_wrenches, unit="count"), + SingleMeasurement(name="Expected Wrenches", value=expected_wrenches, unit="count"), + ], + update_phase="sensor_update", + observer_phase="observer", + validation_phase="validation", + ) + benchmark.finalize() if __name__ == "__main__": - try: - main() - except BaseException: - if simulation_app.config.get("fast_shutdown", False): - traceback.print_exc() - simulation_app.close(exit_code=1) - raise - else: - simulation_app.close() + main() diff --git a/source/isaaclab_physx/benchmark/sensors/benchmark_ray_caster.py b/source/isaaclab_physx/benchmark/sensors/benchmark_ray_caster.py index 46f67c881be9..df2bc6a57973 100644 --- a/source/isaaclab_physx/benchmark/sensors/benchmark_ray_caster.py +++ b/source/isaaclab_physx/benchmark/sensors/benchmark_ray_caster.py @@ -16,10 +16,9 @@ from __future__ import annotations import argparse -import traceback from functools import partial -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation from isaaclab.benchmark.sensor_suites import add_sensor_benchmark_args, rough_terrain_size parser = argparse.ArgumentParser(description="Benchmark the standard PhysX RayCaster update path.") @@ -42,18 +41,13 @@ action="store_true", help="Use the cached PhysX view with ordinary eager Warp launches.", ) -AppLauncher.add_app_launcher_args(parser) +add_launcher_args(parser) args_cli = parser.parse_args() if args_cli.grid_size <= 0: parser.error("--grid_size must be greater than zero") if args_cli.grid_resolution <= 0: parser.error("--grid_resolution must be greater than zero") -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Everything below follows application launch.""" - import torch import warp as wp from isaaclab_physx.sim.schemas import PhysxRigidBodyCfg @@ -62,10 +56,10 @@ from isaaclab.assets import AssetBaseCfg, RigidObjectCfg from isaaclab.benchmark import LatencyBenchmarkRunner, SingleMeasurement from isaaclab.benchmark.sensor_suites import add_sensor_latency_measurements, collect_sensor_latency_samples -from isaaclab.scene import InteractiveScene, InteractiveSceneCfg +from isaaclab.scene import InteractiveSceneCfg from isaaclab.sensors import RayCasterCfg, patterns from isaaclab.terrains import HfRandomUniformTerrainCfg, TerrainGeneratorCfg, TerrainImporterCfg -from isaaclab.utils import configclass +from isaaclab.utils import configclass, instantiate from isaaclab.utils.seed import configure_seed _ROUGH_TERRAIN_SEED = 0 @@ -133,125 +127,122 @@ def main() -> None: configure_seed(_ROUGH_TERRAIN_SEED) sim_dt = 1.0 / 120.0 sim_cfg = sim_utils.SimulationCfg(dt=sim_dt, device=args_cli.device, gravity=(0.0, 0.0, 0.0)) - sim = sim_utils.SimulationContext(sim_cfg) + with launch_simulation(sim_cfg, args_cli): + sim = sim_utils.SimulationContext(sim_cfg) - scene_cfg = RayCasterBenchmarkSceneCfg( - num_envs=args_cli.num_envs, - env_spacing=_ENV_SPACING, - lazy_sensor_update=True, - ) - workloads = _WORKLOADS if args_cli.terrain == "all" else (args_cli.terrain,) - pattern_cfg = patterns.GridPatternCfg( - resolution=args_cli.grid_resolution, - size=(args_cli.grid_size, args_cli.grid_size), - direction=(0.0, 0.0, -1.0), - ) - for workload in workloads: - setattr( - scene_cfg, - f"{workload}_ray_caster", - RayCasterCfg( - prim_path=f"{{ENV_REGEX_NS}}/{workload.title()}SensorBody", - mesh_prim_paths=[f"/World/{workload}_ground"], - ray_alignment="world", - pattern_cfg=pattern_cfg, - global_world_only=True, - ), + scene_cfg = RayCasterBenchmarkSceneCfg( + num_envs=args_cli.num_envs, + env_spacing=_ENV_SPACING, + lazy_sensor_update=True, ) - - scene = InteractiveScene(scene_cfg) - sim.reset() - scene.reset() - - sensors = {workload: scene[f"{workload}_ray_caster"] for workload in workloads} - rays_per_env = next(iter(sensors.values())).num_rays - if any(sensor.num_rays != rays_per_env for sensor in sensors.values()): - raise RuntimeError("Plane and rough workloads must cast the same number of rays.") - if args_cli.disable_graph: - for sensor in sensors.values(): - sensor._use_graph = False - - synchronize_device = partial(wp.synchronize_device, sim.device) - samples_by_workload = {} - validation_by_workload = {} - for workload, sensor in sensors.items(): - for _ in range(args_cli.warmup_steps): - sim.step(render=False) - sensor.update(sim_dt, force_recompute=True) - # Intentionally drain warm-up work before any timed boundary. - wp.synchronize_device(sim.device) - - samples_by_workload[workload] = collect_sensor_latency_samples( - num_steps=args_cli.num_steps, - step=lambda: sim.step(render=False), - update=lambda sensor=sensor: sensor.update(sim_dt, force_recompute=True), - synchronize=synchronize_device, + workloads = _WORKLOADS if args_cli.terrain == "all" else (args_cli.terrain,) + pattern_cfg = patterns.GridPatternCfg( + resolution=args_cli.grid_resolution, + size=(args_cli.grid_size, args_cli.grid_size), + direction=(0.0, 0.0, -1.0), ) + for workload in workloads: + setattr( + scene_cfg, + f"{workload}_ray_caster", + RayCasterCfg( + prim_path=f"{{ENV_REGEX_NS}}/{workload.title()}SensorBody", + mesh_prim_paths=[f"/World/{workload}_ground"], + ray_alignment="world", + pattern_cfg=pattern_cfg, + global_world_only=True, + ), + ) - ray_hits = sensor.data.ray_hits_w.torch - finite_hits = int(torch.isfinite(ray_hits).all(dim=-1).sum().item()) - expected_hits = args_cli.num_envs * sensor.num_rays - if finite_hits != expected_hits: - raise RuntimeError(f"Expected {expected_hits} finite {workload} ray hits, received {finite_hits}.") - validation = [ - SingleMeasurement(name="Finite Ray Hits", value=finite_hits, unit="count"), - SingleMeasurement(name="Expected Ray Hits", value=expected_hits, unit="count"), - ] - if workload == "plane": - max_hit_height = float(torch.abs(ray_hits[..., 2]).max().item()) - hit_height_tolerance = 1.0e-4 - if max_hit_height > hit_height_tolerance: - raise RuntimeError( - f"Expected plane ray hits within {hit_height_tolerance} m of z=0, " - f"received maximum |z| {max_hit_height} m." - ) - validation.append(SingleMeasurement(name="Maximum Absolute Hit Height", value=max_hit_height, unit="m")) - else: - validation.extend( - [ - SingleMeasurement(name="Minimum Hit Height", value=float(ray_hits[..., 2].min().item()), unit="m"), - SingleMeasurement(name="Maximum Hit Height", value=float(ray_hits[..., 2].max().item()), unit="m"), - ] + scene = instantiate(scene_cfg) + sim.reset() + scene.reset() + + sensors = {workload: scene[f"{workload}_ray_caster"] for workload in workloads} + rays_per_env = next(iter(sensors.values())).num_rays + if any(sensor.num_rays != rays_per_env for sensor in sensors.values()): + raise RuntimeError("Plane and rough workloads must cast the same number of rays.") + if args_cli.disable_graph: + for sensor in sensors.values(): + sensor._use_graph = False + + synchronize_device = partial(wp.synchronize_device, sim.device) + samples_by_workload = {} + validation_by_workload = {} + for workload, sensor in sensors.items(): + for _ in range(args_cli.warmup_steps): + sim.step(render=False) + sensor.update(sim_dt, force_recompute=True) + # Intentionally drain warm-up work before any timed boundary. + wp.synchronize_device(sim.device) + + samples_by_workload[workload] = collect_sensor_latency_samples( + num_steps=args_cli.num_steps, + step=lambda: sim.step(render=False), + update=lambda sensor=sensor: sensor.update(sim_dt, force_recompute=True), + synchronize=synchronize_device, ) - validation_by_workload[workload] = validation - benchmark = LatencyBenchmarkRunner( - benchmark_name="physx_ray_caster_sensor", - formatter_type=args_cli.benchmark_formatter, - output_path=args_cli.output_path, - metadata={ - "physics_variant": args_cli.physics_variant, - "label": args_cli.label, - "device": str(sim.device), - "num_envs": args_cli.num_envs, - "rays_per_env": rays_per_env, - "grid_size": args_cli.grid_size, - "grid_resolution": args_cli.grid_resolution, - "num_steps": args_cli.num_steps, - "warmup_steps": args_cli.warmup_steps, - "terrain": args_cli.terrain, - "rough_terrain_seed": _ROUGH_TERRAIN_SEED, - }, - ) - for workload in workloads: - add_sensor_latency_measurements( - benchmark, - samples=samples_by_workload[workload], - validation=validation_by_workload[workload], - update_phase=f"{workload}_sensor_update", - observer_phase=f"{workload}_observer", - validation_phase=f"{workload}_validation", + ray_hits = sensor.data.ray_hits_w.torch + finite_hits = int(torch.isfinite(ray_hits).all(dim=-1).sum().item()) + expected_hits = args_cli.num_envs * sensor.num_rays + if finite_hits != expected_hits: + raise RuntimeError(f"Expected {expected_hits} finite {workload} ray hits, received {finite_hits}.") + validation = [ + SingleMeasurement(name="Finite Ray Hits", value=finite_hits, unit="count"), + SingleMeasurement(name="Expected Ray Hits", value=expected_hits, unit="count"), + ] + if workload == "plane": + max_hit_height = float(torch.abs(ray_hits[..., 2]).max().item()) + hit_height_tolerance = 1.0e-4 + if max_hit_height > hit_height_tolerance: + raise RuntimeError( + f"Expected plane ray hits within {hit_height_tolerance} m of z=0, " + f"received maximum |z| {max_hit_height} m." + ) + validation.append(SingleMeasurement(name="Maximum Absolute Hit Height", value=max_hit_height, unit="m")) + else: + validation.extend( + [ + SingleMeasurement( + name="Minimum Hit Height", value=float(ray_hits[..., 2].min().item()), unit="m" + ), + SingleMeasurement( + name="Maximum Hit Height", value=float(ray_hits[..., 2].max().item()), unit="m" + ), + ] + ) + validation_by_workload[workload] = validation + + benchmark = LatencyBenchmarkRunner( + benchmark_name="physx_ray_caster_sensor", + formatter_type=args_cli.benchmark_formatter, + output_path=args_cli.output_path, + metadata={ + "physics_variant": args_cli.physics_variant, + "label": args_cli.label, + "device": str(sim.device), + "num_envs": args_cli.num_envs, + "rays_per_env": rays_per_env, + "grid_size": args_cli.grid_size, + "grid_resolution": args_cli.grid_resolution, + "num_steps": args_cli.num_steps, + "warmup_steps": args_cli.warmup_steps, + "terrain": args_cli.terrain, + "rough_terrain_seed": _ROUGH_TERRAIN_SEED, + }, ) - benchmark.finalize() + for workload in workloads: + add_sensor_latency_measurements( + benchmark, + samples=samples_by_workload[workload], + validation=validation_by_workload[workload], + update_phase=f"{workload}_sensor_update", + observer_phase=f"{workload}_observer", + validation_phase=f"{workload}_validation", + ) + benchmark.finalize() if __name__ == "__main__": - try: - main() - except BaseException: - if simulation_app.config.get("fast_shutdown", False): - traceback.print_exc() - simulation_app.close(exit_code=1) - raise - else: - simulation_app.close() + main() diff --git a/source/isaaclab_physx/changelog.d/launch-simulation-scripts.rst b/source/isaaclab_physx/changelog.d/launch-simulation-scripts.rst new file mode 100644 index 000000000000..301c20d6dbb6 --- /dev/null +++ b/source/isaaclab_physx/changelog.d/launch-simulation-scripts.rst @@ -0,0 +1,11 @@ +Added +^^^^^ + +* Added :func:`~isaaclab_physx.renderers.isaac_rtx_renderer_utils.wait_for_stage_load` to run Kit app updates + until the USD stage has no assets left to load. + +Removed +^^^^^^^ + +* Removed ``isaaclab_physx.app.show_stage_in_viewport``. Preview a USD file by spawning it into a scene and + rendering it with the visualizer selected with ``--viz``, as the ``convert_*`` tools do. diff --git a/source/isaaclab_physx/isaaclab_physx/app/__init__.pyi b/source/isaaclab_physx/isaaclab_physx/app/__init__.pyi index e5a7cec02763..930342ea937f 100644 --- a/source/isaaclab_physx/isaaclab_physx/app/__init__.pyi +++ b/source/isaaclab_physx/isaaclab_physx/app/__init__.pyi @@ -7,8 +7,7 @@ __all__ = [ "KitLauncher", "KitStageBackend", "KitStageBackendCfg", - "show_stage_in_viewport", ] from .kit_launcher import KitLauncher -from .kit_stage import KitStageBackend, KitStageBackendCfg, show_stage_in_viewport +from .kit_stage import KitStageBackend, KitStageBackendCfg diff --git a/source/isaaclab_physx/isaaclab_physx/app/kit_stage.py b/source/isaaclab_physx/isaaclab_physx/app/kit_stage.py index 6b8387a50a77..550d748052c6 100644 --- a/source/isaaclab_physx/isaaclab_physx/app/kit_stage.py +++ b/source/isaaclab_physx/isaaclab_physx/app/kit_stage.py @@ -7,10 +7,8 @@ from __future__ import annotations -import contextlib from dataclasses import field -import omni.kit.app import omni.usd from pxr import Usd, UsdUtils @@ -45,29 +43,3 @@ class KitStageBackendCfg(BackendCfg): class_type: type = KitStageBackend stage: Usd.Stage = field(kw_only=True, metadata={"copy": False}) - - -def show_stage_in_viewport(usd_path: str) -> None: - """Open a USD file in the Kit viewport and keep the app running until the user closes it. - - Opens the stage through the Kit USD context so it appears in the viewport (or the - livestream client), then spins the Kit update loop until the window is closed or the - loop is interrupted. Must only be called inside a running Kit process with a GUI. - - Args: - usd_path: Path of the USD file to display. - - Raises: - RuntimeError: If the stage cannot be opened. - """ - # A failed open leaves the previously loaded stage in the viewport, which would look like a - # successful preview of the wrong asset, so surface the failure instead of blocking on it. - result = omni.usd.get_context().open_stage(usd_path) - opened = result[0] if isinstance(result, tuple) else result - if opened is False: - raise RuntimeError(f"Failed to open the USD stage in the Kit viewport: {usd_path}") - - app = omni.kit.app.get_app_interface() - with contextlib.suppress(KeyboardInterrupt): - while app.is_running(): - app.update() diff --git a/source/isaaclab_physx/isaaclab_physx/renderers/isaac_rtx_renderer_utils.py b/source/isaaclab_physx/isaaclab_physx/renderers/isaac_rtx_renderer_utils.py index d73e21cd1fd6..2fd70912165f 100644 --- a/source/isaaclab_physx/isaaclab_physx/renderers/isaac_rtx_renderer_utils.py +++ b/source/isaaclab_physx/isaaclab_physx/renderers/isaac_rtx_renderer_utils.py @@ -9,6 +9,7 @@ import logging import time +from collections.abc import Callable from typing import Any import omni.usd @@ -134,6 +135,16 @@ def _get_stage_streaming_busy() -> bool: return usd_context.get_stage_streaming_status() +def _pump_app_while(busy: Callable[[], bool], timeout_s: float) -> float: + """Pump ``app.update()`` while ``busy()`` holds, up to ``timeout_s`` seconds; return the elapsed time [s].""" + import omni.kit.app + + start = time.monotonic() + while busy() and (time.monotonic() - start) < timeout_s: + omni.kit.app.get_app().update() + return time.monotonic() - start + + def _wait_for_streaming_complete() -> None: """Pump ``app.update()`` until RTX streaming reports idle or timeout. @@ -142,11 +153,7 @@ def _wait_for_streaming_complete() -> None: """ import omni.kit.app - start = time.monotonic() - while _get_stage_streaming_busy() and (time.monotonic() - start) < _STREAMING_WAIT_TIMEOUT_S: - omni.kit.app.get_app().update() - - elapsed = time.monotonic() - start + elapsed = _pump_app_while(_get_stage_streaming_busy, _STREAMING_WAIT_TIMEOUT_S) if _get_stage_streaming_busy(): logger.warning( "RTX streaming did not complete within %.1f s – proceeding anyway.", @@ -158,6 +165,37 @@ def _wait_for_streaming_complete() -> None: omni.kit.app.get_app().update() +def _get_stage_loading_count() -> int: + """Return the number of USD stage assets still loading.""" + usd_context = omni.usd.get_context() + if usd_context is None: + return 0 + # get_stage_loading_status -> (message, count_loaded, count_loading) + return usd_context.get_stage_loading_status()[2] + + +def wait_for_stage_load(timeout_s: float, settle_frames: int = 0) -> None: + """Pump ``app.update()`` until the USD stage has no assets pending, then ``settle_frames`` more. + + The extra frames let the renderer finish shader compilation and material warm-up after + every referenced asset has been resolved. + + Args: + timeout_s: Upper bound on the wait for pending assets [s]; a warning is logged when reached. + settle_frames: Number of additional app updates pumped after loading completes. + """ + import omni.kit.app + + elapsed = _pump_app_while(lambda: _get_stage_loading_count() > 0, timeout_s) + pending = _get_stage_loading_count() + if pending: + logger.warning("Stage still reports %d assets pending after %.1f s; proceeding anyway.", pending, elapsed) + else: + logger.info("Stage load completed in %.2f s.", elapsed) + for _ in range(settle_frames): + omni.kit.app.get_app().update() + + def ensure_rtx_hydra_engine_attached() -> None: """Attach the RTX Hydra engine to the USD context if not already attached. diff --git a/source/isaaclab_physx/test/app/test_app_launcher_shim.py b/source/isaaclab_physx/test/app/test_app_launcher_shim.py new file mode 100644 index 000000000000..5a1ecab75cdf --- /dev/null +++ b/source/isaaclab_physx/test/app/test_app_launcher_shim.py @@ -0,0 +1,63 @@ +# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +"""Verify that a script written against the deprecated ``AppLauncher`` still runs.""" + +import subprocess +import sys +import textwrap + +import pytest + +_READY_MARKER = "OLD_STYLE_SCRIPT_RAN" + +_OLD_STYLE_SCRIPT = textwrap.dedent(f""" + import argparse + + from isaaclab.app import AppLauncher + + parser = argparse.ArgumentParser() + AppLauncher.add_app_launcher_args(parser) + args_cli = parser.parse_args(["--device", "cpu"]) + simulation_app = AppLauncher(args_cli).app + + import isaaclab.sim as sim_utils + + sim = sim_utils.SimulationContext(sim_utils.SimulationCfg(device=args_cli.device)) + sim.reset() + for _ in range(3): + sim.step() + assert simulation_app.is_running() and not simulation_app.is_exiting() + print("{_READY_MARKER}", flush=True) + simulation_app.close() +""") + + +# ``AppLauncher(headless=True).app`` in a local that goes out of scope: Kit must keep running until exit +_DROPPED_LAUNCHER_SCRIPT = textwrap.dedent(f""" + import gc + + from isaaclab.app import AppLauncher + + AppLauncher(headless=True, device="cpu") + gc.collect() + + import isaaclab.sim as sim_utils + + sim = sim_utils.SimulationContext(sim_utils.SimulationCfg(device="cpu")) + sim.reset() + sim.step() + print("{_READY_MARKER}", flush=True) +""") + + +@pytest.mark.integration +@pytest.mark.parametrize("script", [_OLD_STYLE_SCRIPT, _DROPPED_LAUNCHER_SCRIPT], ids=["kept-app", "dropped-launcher"]) +def test_old_style_script_starts_kit_steps_and_closes(script): + """The shim starts Kit, the script steps a simulation, and ``close()`` ends the process with 0.""" + result = subprocess.run([sys.executable, "-c", script], capture_output=True, text=True, timeout=900) + output = f"--- stdout ---\n{result.stdout}\n--- stderr ---\n{result.stderr}" + assert _READY_MARKER in result.stdout, output + assert result.returncode == 0, output diff --git a/source/isaaclab_physx/test/renderers/test_isaac_rtx_renderer_utils.py b/source/isaaclab_physx/test/renderers/test_isaac_rtx_renderer_utils.py index 2761242d1f83..8ca6eab13249 100644 --- a/source/isaaclab_physx/test/renderers/test_isaac_rtx_renderer_utils.py +++ b/source/isaaclab_physx/test/renderers/test_isaac_rtx_renderer_utils.py @@ -133,6 +133,19 @@ def test_respects_timeout(self, monkeypatch, mock_omni_kit_app, caplog): assert "RTX streaming did not complete within" in caplog.text +def test_wait_for_stage_load_pumps_until_loaded_then_settles(mock_omni_usd, mock_omni_kit_app): + """Pumps while assets are pending, then pumps the requested settle frames.""" + mock_app = MagicMock() + mock_omni_kit_app.get_app.return_value = mock_app + pending = [2, 1, 0, 0] + mock_omni_usd.get_context.return_value.get_stage_loading_status.side_effect = lambda: ("", 0, pending[0]) + mock_app.update.side_effect = lambda: pending.pop(0) if len(pending) > 1 else None + + rtx_utils.wait_for_stage_load(timeout_s=10.0, settle_frames=3) + + assert mock_app.update.call_count == 2 + 3 + + # --------------------------------------------------------------------------- # ensure_isaac_rtx_render_update # --------------------------------------------------------------------------- diff --git a/source/isaaclab_physx/test/sensors/check_contact_sensor.py b/source/isaaclab_physx/test/sensors/check_contact_sensor.py deleted file mode 100644 index 2e842b2db54b..000000000000 --- a/source/isaaclab_physx/test/sensors/check_contact_sensor.py +++ /dev/null @@ -1,187 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -This script demonstrates how to use the contact sensor sensor in Isaac Lab. - -.. code-block:: bash - - uv run python source/isaaclab/test/sensors/test_contact_sensor.py --num_robots 2 -""" - -"""Launch Isaac Sim Simulator first.""" - - -import argparse - -from isaaclab.app import AppLauncher -from isaaclab.utils import replace - -# add argparse arguments -parser = argparse.ArgumentParser(description="Contact Sensor Test Script") -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(args_cli) -simulation_app = app_launcher.app - - -"""Rest everything follows.""" - -import numpy as np -import torch - -import isaaclab.sim as sim_utils -from isaaclab import cloner as lab_cloner -from isaaclab.assets import Articulation -from isaaclab.sensors.contact_sensor import ContactSensor, ContactSensorCfg -from isaaclab.sim import SimulationCfg, SimulationContext -from isaaclab.utils.timer import Timer - -## -# Pre-defined configs -## -from isaaclab_assets.robots.anymal import ANYMAL_C_CFG # isort:skip - - -""" -Helpers -""" - - -def design_scene(): - """Add prims to the scene.""" - # Ground-plane - cfg = sim_utils.GroundPlaneCfg() - cfg.func("/World/defaultGroundPlane", cfg) - # Lights - cfg = sim_utils.DomeLightCfg(intensity=2000) - cfg.func("/World/Light/DomeLight", cfg, translation=(-4.5, 3.5, 10.0)) - - -""" -Main -""" - - -def main(): - """Spawns the ANYmal robot and clones it using Isaac Sim Cloner API.""" - - # Load kit helper - sim = SimulationContext(SimulationCfg(dt=0.005)) - # Set main camera - sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0]) - - # Enable hydra scene-graph instancing - # this is needed to visualize the scene when flatcache is enabled - sim.set_setting("/persistent/omnihydra/useSceneGraphInstancing", True) - - # Create environment clones using Lab's cloner utilities - num_envs = args_cli.num_robots - env_fmt = "/World/envs/env_{}" - env_ids = np.arange(num_envs, dtype=np.int64) - env_origins, _ = lab_cloner.grid_transforms(num_envs, spacing=2.0) - # Everything under the namespace "/World/envs/env_0" will be cloned - sim.stage.DefinePrim("/World/envs/env_0", "Xform") - # Clone the scene - envs_prim_paths = [f"/World/envs/env_{i}" for i in range(num_envs)] - lab_cloner.usd_replicate(sim.stage, [env_fmt.format(0)], [env_fmt], env_ids, positions=env_origins) - # Design props - design_scene() - # Spawn things into the scene - robot_cfg = replace(ANYMAL_C_CFG, prim_path="{ENV_REGEX_NS}/Robot") - robot_cfg.spawn.activate_contact_sensors = True - robot = Articulation(cfg=robot_cfg) - # Contact sensor - contact_sensor_cfg = ContactSensorCfg( - prim_path="{ENV_REGEX_NS}/Robot/[^/]*_FOOT", - track_air_time=True, - track_contact_points=True, - track_friction_forces=True, - debug_vis=False, # not args_cli.headless, - filter_prim_paths_expr=["/World/defaultGroundPlane/GroundPlane/CollisionPlane"], - ) - contact_sensor = ContactSensor(cfg=contact_sensor_cfg) - # filter collisions within each environment instance - physics_scene_path = sim.cfg.physics_prim_path - lab_cloner.filter_collisions( - sim.stage, physics_scene_path, "/World/collisions", envs_prim_paths, global_paths=["/World/defaultGroundPlane"] - ) - - # Play the simulator - sim.reset() - # print info - print(contact_sensor) - - # Now we are ready! - print("[INFO]: Setup complete...") - - # Define simulation stepping - decimation = 4 - physics_dt = sim.get_physics_dt() - sim_dt = decimation * physics_dt - sim_time = 0.0 - count = 0 - dt = [] - # Simulate physics - while simulation_app.is_running(): - # If simulation is stopped, then exit. - if sim.is_stopped(): - break - # If simulation is paused, then skip. - if not sim.is_playing(): - sim.step(render=False) - continue - # reset - if count % 1000 == 0 and count != 0: - # reset counters - sim_time = 0.0 - count = 0 - print("=" * 80) - print("avg dt real-time", sum(dt) / len(dt)) - print("=" * 80) - - # 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() - dt = [] - - # perform 4 steps - for _ in range(decimation): - # apply actions - robot.set_joint_position_target(robot.data.default_joint_pos) - # write commands to sim - robot.write_data_to_sim() - # perform step - sim.step() - # fetch data - robot.update(physics_dt) - # update sim-time - sim_time += sim_dt - count += 1 - # update the buffers - if sim.is_playing(): - with Timer(synchronize="both", device=sim.device) as timer: - contact_sensor.update(sim_dt, force_recompute=True) - dt.append(timer.total_run_time) - - contact_sensor.update(sim_dt, force_recompute=True) - if count % 100 == 0: - print("Sim-time: ", sim_time) - print("Number of contacts: ", torch.count_nonzero(contact_sensor.data.current_air_time == 0.0).item()) - print("-" * 80) - - -if __name__ == "__main__": - # run the main function - main() - # close sim app - simulation_app.close() diff --git a/source/isaaclab_physx/test/sensors/check_pva_sensor.py b/source/isaaclab_physx/test/sensors/check_pva_sensor.py deleted file mode 100644 index 55752642c076..000000000000 --- a/source/isaaclab_physx/test/sensors/check_pva_sensor.py +++ /dev/null @@ -1,192 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -""" -Visual test script for the pva sensor from the Orbit framework. -""" - -from __future__ import annotations - -"""Launch Isaac Sim Simulator first.""" - -import argparse - -from isaacsim import SimulationApp - -# add argparse arguments -parser = argparse.ArgumentParser(description="Pva Test Script") -parser.add_argument("--visualize", action="store_true", help="Open a window to display sensor output.") -parser.add_argument("--num_envs", type=int, default=128, help="Number of environments to clone.") -parser.add_argument( - "--terrain_type", - type=str, - default="generator", - choices=["generator", "usd", "plane"], - help="Type of terrain to import. Can be 'generator' or 'usd' or 'plane'.", -) -args_cli = parser.parse_args() - -# launch omniverse app -config = {"headless": not args_cli.visualize} -simulation_app = SimulationApp(config) - - -"""Rest everything follows.""" - -import logging -import traceback - -import numpy as np -import torch -from isaaclab_physx.renderers.kit_viewport_utils import _set_kit_camera_view - -import isaaclab.sim as sim_utils -import isaaclab.terrains as terrain_gen -from isaaclab import cloner as lab_cloner -from isaaclab.assets import RigidObject, RigidObjectCfg -from isaaclab.sensors.pva import Pva, PvaCfg -from isaaclab.sim import SimulationCfg, SimulationContext -from isaaclab.terrains.config.rough import ROUGH_TERRAINS_CFG -from isaaclab.terrains.terrain_importer import TerrainImporter -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR -from isaaclab.utils.timer import Timer - -# import logger -logger = logging.getLogger(__name__) - - -def design_scene(sim: SimulationContext, num_envs: int = 2048) -> RigidObject: - """Design the scene.""" - # Handler for terrains importing - terrain_importer_cfg = terrain_gen.TerrainImporterCfg( - prim_path="/World/ground", - terrain_type="generator", - terrain_generator=ROUGH_TERRAINS_CFG, - usd_path=f"{ISAAC_NUCLEUS_DIR}/Environments/Terrains/rough_plane.usd", - max_init_terrain_level=None, - num_envs=1, - ) - _ = TerrainImporter(terrain_importer_cfg) - # obtain the current stage - stage = sim_utils.get_current_stage() - # Create interface to clone the scene - # Create environment clones using Lab's cloner utilities - env_fmt = "/World/envs/env_{}" - env_ids = np.arange(num_envs, dtype=np.int64) - env_origins, _ = lab_cloner.grid_transforms(num_envs, spacing=2.0) - envs_prim_paths = [f"/World/envs/env_{i}" for i in range(num_envs)] - # create source prim - stage.DefinePrim(envs_prim_paths[0], "Xform") - # clone the env xform - lab_cloner.usd_replicate(stage, [env_fmt.format(0)], [env_fmt], env_ids, positions=env_origins) - # Define the scene - # -- Light - cfg = sim_utils.DistantLightCfg(intensity=2000) - cfg.func("/World/light", cfg) - # -- Balls - cfg = RigidObjectCfg( - spawn=sim_utils.SphereCfg( - radius=0.25, - rigid_props=sim_utils.UsdPhysicsRigidBodyCfg(), - mass_props=sim_utils.MassCfg(mass=0.5), - collision_props=sim_utils.UsdPhysicsCollisionCfg(), - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.0, 0.0, 1.0)), - ), - prim_path="{ENV_REGEX_NS}/ball", - init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, 0.0, 5.0)), - ) - balls = RigidObject(cfg) - # Clone the scene - # obtain the current physics scene - physics_scene_prim_path = None - for prim in stage.Traverse(): - if "PhysxSceneAPI" in prim.GetAppliedSchemas(): - physics_scene_prim_path = prim.GetPrimPath() - logging.info(f"Physics scene prim path: {physics_scene_prim_path}") - break - # filter collisions within each environment instance - lab_cloner.filter_collisions( - stage, - physics_scene_prim_path, - "/World/collisions", - envs_prim_paths, - ) - return balls - - -def main(): - """Main function.""" - - # Load kit helper - sim = SimulationContext(SimulationCfg()) - # Set main camera - _set_kit_camera_view([0.0, 30.0, 25.0], [0.0, 0.0, -2.5], "/OmniverseKit_Persp") - - # Parameters - num_envs = args_cli.num_envs - # Design the scene - balls = design_scene(sim=sim, num_envs=num_envs) - - # Create a pva sensor - pva_cfg = PvaCfg( - prim_path="{ENV_REGEX_NS}/ball", - debug_vis=args_cli.visualize, - ) - # increase scale of the arrows for better visualization - pva_cfg.visualizer_cfg.markers["arrow"].scale = (1.0, 0.2, 0.2) - pva = Pva(cfg=pva_cfg) - - # Play simulator and init the Pva - sim.reset() - - # Print the sensor information - print(pva) - - # Get the ball initial positions - sim.step(render=args_cli.visualize) - balls.update(sim.get_physics_dt()) - ball_initial_positions = balls.data.root_pos_w.torch.clone() - ball_initial_orientations = balls.data.root_quat_w.torch.clone() - - # Create a counter for resetting the scene - step_count = 0 - # Simulate physics - while simulation_app.is_running(): - # If simulation is stopped, then exit. - if sim.is_stopped(): - break - # If simulation is paused, then skip. - if not sim.is_playing(): - sim.step(render=args_cli.visualize) - continue - # Reset the scene - if step_count % 500 == 0: - # reset ball positions - balls.write_root_pose_to_sim(torch.cat([ball_initial_positions, ball_initial_orientations], dim=-1)) - balls.reset() - # reset the sensor - pva.reset() - # reset the counter - step_count = 0 - # Step simulation - sim.step() - # Update the pva sensor - with Timer(f"Pva sensor update with {num_envs}", synchronize="both", device=sim.device): - pva.update(dt=sim.get_physics_dt(), force_recompute=True) - # Update counter - step_count += 1 - - -if __name__ == "__main__": - try: - # Run the main function - main() - except Exception as err: - logger.error(err) - logger.error(traceback.format_exc()) - raise - finally: - # close sim app - simulation_app.close() diff --git a/tools/docs/media/capture_renderer_gallery.py b/tools/docs/media/capture_renderer_gallery.py index a566a133f8f6..1f4407f96523 100644 --- a/tools/docs/media/capture_renderer_gallery.py +++ b/tools/docs/media/capture_renderer_gallery.py @@ -186,7 +186,7 @@ def resolve_gallery_scene(scene: str) -> str: def add_gallery_arguments(parser: argparse.ArgumentParser) -> None: - """Add renderer-gallery arguments without colliding with AppLauncher options.""" + """Add renderer-gallery arguments without colliding with the simulation launcher options.""" script_dir = Path(__file__).resolve().parent parser.add_argument("--renderer-backend", choices=tuple(_RENDERER_SLUGS), required=True) parser.add_argument("--capture-group", choices=("standard", *_SIMPLE_SHADING_MODES), default="standard") @@ -206,11 +206,11 @@ def add_gallery_arguments(parser: argparse.ArgumentParser) -> None: def _parse_args() -> argparse.Namespace: """Parse capture and Isaac Lab launcher arguments.""" - from isaaclab.app import AppLauncher + from isaaclab.app import add_launcher_args parser = argparse.ArgumentParser(description=__doc__) add_gallery_arguments(parser) - AppLauncher.add_app_launcher_args(parser) + add_launcher_args(parser) parser.set_defaults(enable_cameras=True, headless=True) args = parser.parse_args() args.output_dir = args.output_dir.expanduser().resolve() @@ -418,18 +418,16 @@ def tensor_to_image(data: torch.Tensor, output_name: str) -> Image.Image: def main() -> None: """Launch Isaac Sim and capture the selected renderer gallery group.""" - from isaaclab.app import AppLauncher + from isaaclab.app import launch_simulation args = _parse_args() if not renderer_requires_kit(args.renderer_backend): _capture(args) else: - app_launcher = AppLauncher(args) - simulation_app = app_launcher.app - try: + # the capture builds its simulation config after launch, so request Kit explicitly + args.require_kit = True + with launch_simulation(None, args): _capture(args) - finally: - simulation_app.close() if __name__ == "__main__": diff --git a/tools/docs/media/visualizers/capture_visualizer.py b/tools/docs/media/visualizers/capture_visualizer.py index 63e603041d50..4fd1feaf9aec 100644 --- a/tools/docs/media/visualizers/capture_visualizer.py +++ b/tools/docs/media/visualizers/capture_visualizer.py @@ -731,7 +731,7 @@ def configure_playback_hero() -> list[str]: def _hero_configure_capture(env_cfg: AnymalDTileCaptureCfg) -> None: """Attach the requested visualizer and video recorder to the AnymalD flat config.""" # Read the visualizer selection from HERO_VISUALIZER, not --viz off sys.argv: by the time - # __post_init__ runs, rsl_rl has already rewritten sys.argv and AppLauncher's parser has + # __post_init__ runs, rsl_rl has already rewritten sys.argv and the launcher's parser has # consumed --viz. visualizer = os.environ.get("HERO_VISUALIZER") if visualizer != "combined" and visualizer not in _HERO_VISUALIZER_BUILDERS: From 90ee05819720740755b5b655bbdf9dff46ac8864 Mon Sep 17 00:00:00 2001 From: Octi Zhang Date: Sun, 27 Sep 2026 21:30:39 -0700 Subject: [PATCH 2/2] Address review: is_running, exit status, camera data, video docs - Rename SimulationContext.is_headless_or_exist_active_visualizer to is_running(), which also ends once every started visualizer has closed instead of treating the emptied visualizer list as a headless run. - launch_simulation keeps the status of sys.exit(n) raised in its block, and the AppLauncher shim's app.close(exit_code=n) ends the process with n. - run_usd_camera --save also writes depth and normals as .npy files. - The wrap_rl_env guide records video with VideoRecorderCfg instead of the deprecated rgb_array render mode, so it no longer passes enable_cameras. --- docs/source/concepts/sensors/ray_caster.rst | 2 +- docs/source/how-to/create_empty.rst | 2 +- docs/source/how-to/save_camera_output.rst | 10 ++--- docs/source/how-to/wrap_rl_env.rst | 39 ++++++++----------- examples/arl_robot_1.py | 4 +- examples/bin_packing.py | 2 +- examples/cables.py | 2 +- examples/deformables.py | 2 +- examples/demos/h1_locomotion.py | 4 +- .../demos/newton_viewer_block_and_tackle.py | 2 +- examples/demos/pick_and_place.py | 4 +- examples/demos/snowball_smash.py | 2 +- examples/demos/teapot_fill.py | 2 +- examples/demos/zoo.py | 2 +- examples/haply_teleoperation.py | 2 +- examples/heterogeneous_scene.py | 4 +- examples/markers.py | 4 +- examples/mpm/newton_mpm_granular.py | 2 +- examples/mpm/newton_mpm_twoway_coupling.py | 2 +- examples/multi_asset.py | 2 +- examples/newton_viewer_dominoes.py | 2 +- examples/procedural_terrain.py | 2 +- examples/sensors/cameras.py | 2 +- examples/sensors/contact_sensor.py | 2 +- examples/sensors/frame_transformer_sensor.py | 2 +- examples/sensors/imu_sensor.py | 2 +- examples/sensors/multi_mesh_raycaster.py | 2 +- .../sensors/multi_mesh_raycaster_camera.py | 2 +- examples/sensors/newton_raycast.py | 2 +- examples/sensors/ppisp_camera.py | 2 +- examples/sensors/pva_sensor.py | 2 +- examples/sensors/raycaster_sensor.py | 2 +- examples/sensors/tacsl_sensor.py | 2 +- examples/visual_color_randomization.py | 4 +- .../state_machine/lift_cube_sm.py | 2 +- .../state_machine/open_cabinet_sm.py | 2 +- .../teleoperation/teleop_replay_agent.py | 9 ++--- .../teleoperation/teleop_se3_agent.py | 2 +- .../isaaclab_mimic/annotate_demos.py | 2 +- .../locomanipulation_sdg/generate_data.py | 2 +- .../gr00t/rollout_policy.py | 2 +- scripts/tools/convert_mesh.py | 2 +- scripts/tools/convert_mjcf.py | 2 +- scripts/tools/convert_urdf.py | 2 +- scripts/tools/record_demos.py | 2 +- scripts/tools/replay_demos.py | 2 +- scripts/tutorials/00_sim/create_empty.py | 2 +- scripts/tutorials/00_sim/launch_app.py | 2 +- scripts/tutorials/00_sim/log_time.py | 2 +- scripts/tutorials/00_sim/spawn_prims.py | 2 +- scripts/tutorials/01_assets/add_new_robot.py | 2 +- .../tutorials/01_assets/run_articulation.py | 2 +- .../01_assets/run_deformable_object.py | 2 +- .../tutorials/01_assets/run_rigid_object.py | 2 +- .../01_assets/run_surface_gripper.py | 2 +- scripts/tutorials/02_scene/create_scene.py | 2 +- .../03_envs/create_cartpole_base_env.py | 2 +- .../tutorials/03_envs/create_cube_base_env.py | 2 +- .../03_envs/create_quadruped_base_env.py | 2 +- .../03_envs/policy_inference_in_usd.py | 2 +- .../tutorials/03_envs/run_cartpole_rl_env.py | 2 +- .../04_sensors/add_sensors_on_robot.py | 2 +- .../04_sensors/run_frame_transformer.py | 2 +- .../tutorials/04_sensors/run_ray_caster.py | 2 +- .../04_sensors/run_ray_caster_camera.py | 2 +- .../tutorials/04_sensors/run_usd_camera.py | 10 +++-- .../tutorials/05_controllers/run_diff_ik.py | 2 +- scripts/tutorials/05_controllers/run_osc.py | 2 +- .../launch-simulation-scripts.major.rst | 10 ++++- source/isaaclab/isaaclab/app/app_launcher.py | 5 ++- source/isaaclab/isaaclab/app/sim_launcher.py | 4 ++ .../isaaclab/isaaclab/cli/commands/deploy.py | 2 +- .../isaaclab/sim/simulation_context.py | 12 ++++-- .../cli/test_replay_demos_loop_termination.py | 2 +- .../isaaclab/test/devices/check_keyboard.py | 2 +- .../test/markers/check_markers_visibility.py | 2 +- .../test_simulation_context_visualizers.py | 13 +++++++ .../check_scene_xr_visualization.py | 2 +- .../test/app/test_app_launcher_shim.py | 16 ++++++-- .../test/app/test_non_headless_launch.py | 2 +- .../launch-simulation-scripts.skip | 1 + .../isaaclab_rl/entrypoints/simple_agents.py | 2 +- source/isaaclab_rl/test/test_entrypoints.py | 4 +- 83 files changed, 155 insertions(+), 132 deletions(-) create mode 100644 source/isaaclab_rl/changelog.d/launch-simulation-scripts.skip diff --git a/docs/source/concepts/sensors/ray_caster.rst b/docs/source/concepts/sensors/ray_caster.rst index 20dbd1484c56..d004c674d50f 100644 --- a/docs/source/concepts/sensors/ray_caster.rst +++ b/docs/source/concepts/sensors/ray_caster.rst @@ -44,7 +44,7 @@ Querying the sensor for data can be done at simulation run time like any other s . . # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): . . . diff --git a/docs/source/how-to/create_empty.rst b/docs/source/how-to/create_empty.rst index 2e115ad65fb2..fb45d9b3b6bd 100644 --- a/docs/source/how-to/create_empty.rst +++ b/docs/source/how-to/create_empty.rst @@ -118,7 +118,7 @@ properly. 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. -The loop condition :meth:`sim.SimulationContext.is_headless_or_exist_active_visualizer` keeps it running +The loop condition :meth:`sim.SimulationContext.is_running` keeps it running forever when no visualizer is open, and until the last visualizer window is closed otherwise. 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. diff --git a/docs/source/how-to/save_camera_output.rst b/docs/source/how-to/save_camera_output.rst index 83c648bebc98..7506cd9b7557 100644 --- a/docs/source/how-to/save_camera_output.rst +++ b/docs/source/how-to/save_camera_output.rst @@ -32,14 +32,14 @@ the output folder once, before the simulation loop: :start-at: # Create the output directory :end-at: os.makedirs(output_dir, exist_ok=True) -While stepping the simulator, the 8-bit color outputs (the RGB image and the colorized segmentations) of the -camera at ``camera_index`` are saved as one PNG file per data type and frame. Floating-point outputs, such as -depth and normals, are not saved. +While stepping the simulator, the outputs of the camera at ``camera_index`` are saved once per data type and +frame: 8-bit color outputs (the RGB image and the colorized segmentations) as PNG files, and floating-point +outputs, such as depth and normals, as NumPy ``.npy`` files. .. literalinclude:: ../../../scripts/tutorials/04_sensors/run_usd_camera.py :language: python - :start-at: # Save the 8-bit color images - :end-at: save_images_to_file(data.torch[camera_index + :start-at: # Save the camera outputs at camera_index + :end-at: np.save( Projection into 3D Space diff --git a/docs/source/how-to/wrap_rl_env.rst b/docs/source/how-to/wrap_rl_env.rst index 2174ddab4b75..5033d83a8e6f 100644 --- a/docs/source/how-to/wrap_rl_env.rst +++ b/docs/source/how-to/wrap_rl_env.rst @@ -77,35 +77,30 @@ The camera's pose and image resolution can be configured through the :pyobject: ViewerCfg -After adjusting the parameters, you can record videos by wrapping the environment with the -:class:`gymnasium.wrappers.RecordVideo` wrapper and enabling the off-screen rendering -flag. Additionally, you need to specify the render mode of the environment as ``"rgb_array"``. +To record videos, add a :class:`~envs.utils.video_recorder_cfg.VideoRecorderCfg` to the environment +configuration. It records from a visualizer (or a camera sensor) into ``mp4`` clips while the environment steps, +so no wrapper or ``render_mode`` is needed, and the runtime the visualizer needs starts automatically. -As an example, the following code records a video of the ``Isaac-Reach-Franka`` environment -for 200 steps, and saves it in the ``videos`` folder at a step interval of 1500 steps. +As an example, the following code records 200-step clips of the ``Isaac-Reach-Franka`` environment from a Kit +visualizer every 1500 steps into the ``videos/train`` folder. See :ref:`how_to_record_video` for the other +sources and clip options. .. code:: python import gymnasium as gym - from isaaclab.app import launch_simulation + from isaaclab_visualizers.kit import KitVisualizerCfg - # adjust camera resolution and pose - env_cfg.viewer.resolution = (640, 480) - env_cfg.viewer.eye = (1.0, 1.0, 1.0) - env_cfg.viewer.lookat = (0.0, 0.0, 0.0) - # start the runtime with off-screen rendering enabled for the video frames - with launch_simulation(env_cfg, {"enable_cameras": True}): - # create isaac-env instance - # set render mode to rgb_array to obtain images on render calls - env = gym.make(task_name, cfg=env_cfg, render_mode="rgb_array") - # wrap for video recording - video_kwargs = { - "video_folder": "videos/train", - "step_trigger": lambda step: step % 1500 == 0, - "video_length": 200, - } - env = gym.wrappers.RecordVideo(env, **video_kwargs) + from isaaclab.app import launch_simulation + from isaaclab.envs.utils.video_recorder_cfg import VideoRecorderCfg + + # record from a Kit visualizer with this camera pose + env_cfg.sim.visualizer_cfgs = KitVisualizerCfg(eye=(1.0, 1.0, 1.0), lookat=(0.0, 0.0, 0.0)) + env_cfg.video_recorders = [ + VideoRecorderCfg(source="visualizer:kit", output_dir="videos/train", video_length=200, video_interval=1500) + ] + with launch_simulation(env_cfg): + env = gym.make(task_name, cfg=env_cfg) Wrapper for learning frameworks diff --git a/examples/arl_robot_1.py b/examples/arl_robot_1.py index 43787ccf9706..614f4f41b5fb 100644 --- a/examples/arl_robot_1.py +++ b/examples/arl_robot_1.py @@ -96,9 +96,7 @@ def main(): step_count = 0 # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. - while sim.is_headless_or_exist_active_visualizer() and ( - args_cli.max_steps < 0 or step_count < args_cli.max_steps - ): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): # Compute wrench from position controller wrench = controller.compute(pos_command) # Shape: (1, 6) diff --git a/examples/bin_packing.py b/examples/bin_packing.py index e560f904ba9c..4ebffec25958 100644 --- a/examples/bin_packing.py +++ b/examples/bin_packing.py @@ -337,7 +337,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene) -> step_count = 0 # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): if count % 250 == 0: # reset counter count = 0 diff --git a/examples/cables.py b/examples/cables.py index 5fea98cab218..ad27feb58610 100644 --- a/examples/cables.py +++ b/examples/cables.py @@ -121,7 +121,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene, max reset_steps = max(1, int(2.0 / sim_dt)) count = 0 - while (max_steps < 0 or count < max_steps) and sim.is_headless_or_exist_active_visualizer(): + while (max_steps < 0 or count < max_steps) and sim.is_running(): if count > 0 and count % reset_steps == 0: reset_cables(scene.cable_objects) print("[INFO]: Resetting cable state...") diff --git a/examples/deformables.py b/examples/deformables.py index 9336fb661834..b2799d60b528 100644 --- a/examples/deformables.py +++ b/examples/deformables.py @@ -207,7 +207,7 @@ def run_simulator(sim: "sim_utils.SimulationContext", entities: dict[str, "Defor step_count = 0 # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): # reset if count % int(3.0 / sim_dt) == 0: # reset counters diff --git a/examples/demos/h1_locomotion.py b/examples/demos/h1_locomotion.py index 495e0886eca1..b70c048b4f38 100644 --- a/examples/demos/h1_locomotion.py +++ b/examples/demos/h1_locomotion.py @@ -208,9 +208,7 @@ def main() -> None: sim = demo_h1.env.unwrapped.sim step_count = 0 try: - while sim.is_headless_or_exist_active_visualizer() and ( - args_cli.max_steps < 0 or step_count < args_cli.max_steps - ): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): demo_h1.update_controls() with torch.inference_mode(): obs = demo_h1.apply_commands() diff --git a/examples/demos/newton_viewer_block_and_tackle.py b/examples/demos/newton_viewer_block_and_tackle.py index 817fc8c5a32a..1ed0a261fae4 100644 --- a/examples/demos/newton_viewer_block_and_tackle.py +++ b/examples/demos/newton_viewer_block_and_tackle.py @@ -298,7 +298,7 @@ def _initialize_wrapped_cable(cable_body_start: int, wrapped_xforms: list[wp.tra def run_simulator(sim: sim_utils.SimulationContext) -> None: """Run until the viewer closes or the optional step limit is reached.""" step_count = 0 - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): sim.step() step_count += 1 diff --git a/examples/demos/pick_and_place.py b/examples/demos/pick_and_place.py index 4da27b871ae5..1aef0aca9770 100644 --- a/examples/demos/pick_and_place.py +++ b/examples/demos/pick_and_place.py @@ -334,9 +334,7 @@ def main() -> None: print("\tN/M make the grippers track the cube/target position.") step_count = 0 try: - while pick_and_place.sim.is_headless_or_exist_active_visualizer() and ( - args_cli.max_steps < 0 or step_count < args_cli.max_steps - ): + while pick_and_place.sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): if teleop is not None: cmd = teleop.advance() actions[:, :2] = cmd[:2] diff --git a/examples/demos/snowball_smash.py b/examples/demos/snowball_smash.py index 600978876128..07750cf45dfc 100644 --- a/examples/demos/snowball_smash.py +++ b/examples/demos/snowball_smash.py @@ -332,7 +332,7 @@ def keep_running(sim, count: int) -> bool: """Return whether the demo loop should continue.""" if args_cli.max_steps >= 0 and count >= args_cli.max_steps: return False - return sim.is_headless_or_exist_active_visualizer() + return sim.is_running() def run_simulator(sim, scene) -> None: diff --git a/examples/demos/teapot_fill.py b/examples/demos/teapot_fill.py index fea8a0d9caaf..b9a41150e9ca 100644 --- a/examples/demos/teapot_fill.py +++ b/examples/demos/teapot_fill.py @@ -781,7 +781,7 @@ def keep_running(sim, count: int) -> bool: """Return whether the demo loop should continue this frame.""" if args_cli.max_steps >= 0 and count >= args_cli.max_steps: return False - return sim.is_headless_or_exist_active_visualizer() + return sim.is_running() def write_container_state(container, sim_time: float) -> None: diff --git a/examples/demos/zoo.py b/examples/demos/zoo.py index b63b58d12c16..f6b4bb36315f 100644 --- a/examples/demos/zoo.py +++ b/examples/demos/zoo.py @@ -180,7 +180,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene) -> sim_dt = sim.get_physics_dt() sim_time = 0.0 step_count = 0 - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): if step_count % 800 == 0: _reset_scene(scene) sim_time = 0.0 diff --git a/examples/haply_teleoperation.py b/examples/haply_teleoperation.py index 1cf1f3fefbb1..129d9fa3e674 100644 --- a/examples/haply_teleoperation.py +++ b/examples/haply_teleoperation.py @@ -235,7 +235,7 @@ def run_simulator( print(" Move handler: Control pose of the end-effector") print(" Button A: Open | Button B: Close | Button C: Rotate EE (60°)\n") - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): if reset_count % 10000 == 0: reset_count = 1 root_pose = robot.data.default_root_pose.torch.clone() diff --git a/examples/heterogeneous_scene.py b/examples/heterogeneous_scene.py index ddcf408ee4c3..b7ec74d2f509 100644 --- a/examples/heterogeneous_scene.py +++ b/examples/heterogeneous_scene.py @@ -154,9 +154,7 @@ def is_global_asset(a: AssetBaseCfg) -> bool: sim_dt = sim.get_physics_dt() step_count = 0 - while sim.is_headless_or_exist_active_visualizer() and ( - args_cli.max_steps < 0 or step_count < args_cli.max_steps - ): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): if not sim.is_playing(): sim.step() continue diff --git a/examples/markers.py b/examples/markers.py index 402b1cbc3ca0..8b380ae5f046 100644 --- a/examples/markers.py +++ b/examples/markers.py @@ -134,9 +134,7 @@ def main(): yaw = torch.zeros_like(marker_locations[:, 0]) step_count = 0 # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. - while sim.is_headless_or_exist_active_visualizer() and ( - args_cli.max_steps < 0 or step_count < args_cli.max_steps - ): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): # rotate the markers around the z-axis for visualization marker_orientations = quat_from_angle_axis(yaw, torch.tensor([0.0, 0.0, 1.0])) # visualize diff --git a/examples/mpm/newton_mpm_granular.py b/examples/mpm/newton_mpm_granular.py index d94e33af9119..95aabd473e39 100644 --- a/examples/mpm/newton_mpm_granular.py +++ b/examples/mpm/newton_mpm_granular.py @@ -190,7 +190,7 @@ def keep_running(sim, count: int) -> bool: """Return whether the example loop should continue this frame.""" if args_cli.max_steps >= 0 and count >= args_cli.max_steps: return False - return sim.is_headless_or_exist_active_visualizer() + return sim.is_running() def run_simulator(sim, scene) -> None: diff --git a/examples/mpm/newton_mpm_twoway_coupling.py b/examples/mpm/newton_mpm_twoway_coupling.py index c392949b7f41..2a14f7f09586 100644 --- a/examples/mpm/newton_mpm_twoway_coupling.py +++ b/examples/mpm/newton_mpm_twoway_coupling.py @@ -295,7 +295,7 @@ def run_simulator(sim, scene) -> None: """Run until the viewer closes or the optional step limit is reached.""" sim_dt = sim.get_physics_dt() step_count = 0 - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): sim.step(render=False) scene.update(sim_dt) if sim.is_rendering: diff --git a/examples/multi_asset.py b/examples/multi_asset.py index 4b18df7688a7..d9a33ebb2638 100644 --- a/examples/multi_asset.py +++ b/examples/multi_asset.py @@ -178,7 +178,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): count = 0 step_count = 0 # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): # Reset if count % 250 == 0: # reset counter diff --git a/examples/newton_viewer_dominoes.py b/examples/newton_viewer_dominoes.py index abfa5b995441..9ac81de58e3b 100644 --- a/examples/newton_viewer_dominoes.py +++ b/examples/newton_viewer_dominoes.py @@ -129,7 +129,7 @@ def _apply_display_colors() -> None: def run_simulator(sim: sim_utils.SimulationContext) -> None: """Run until the viewer closes or the optional step limit is reached.""" step_count = 0 - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): sim.step() step_count += 1 diff --git a/examples/procedural_terrain.py b/examples/procedural_terrain.py index 278ad110b534..8b9a77206fab 100644 --- a/examples/procedural_terrain.py +++ b/examples/procedural_terrain.py @@ -151,7 +151,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, AssetBas """Runs the simulation loop.""" step_count = 0 # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): # perform step sim.step() step_count += 1 diff --git a/examples/sensors/cameras.py b/examples/sensors/cameras.py index 9a0efda16d9f..335d86e641a1 100644 --- a/examples/sensors/cameras.py +++ b/examples/sensors/cameras.py @@ -169,7 +169,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") - if args_cli.save: args_cli.output_dir.mkdir(parents=True, exist_ok=True) - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or count < args_cli.max_steps): # Reset if count % 500 == 0: # reset the scene entities diff --git a/examples/sensors/contact_sensor.py b/examples/sensors/contact_sensor.py index 2f659e5e9520..5b6873b0de39 100644 --- a/examples/sensors/contact_sensor.py +++ b/examples/sensors/contact_sensor.py @@ -105,7 +105,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") - sim_dt = sim.get_physics_dt() count = 0 - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or count < args_cli.max_steps): if count % 500 == 0: root_pose = scene["robot"].data.default_root_pose.torch.clone() root_pose[:, :3] += scene.env_origins diff --git a/examples/sensors/frame_transformer_sensor.py b/examples/sensors/frame_transformer_sensor.py index 6a653adb50ea..213010117a5b 100644 --- a/examples/sensors/frame_transformer_sensor.py +++ b/examples/sensors/frame_transformer_sensor.py @@ -99,7 +99,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") - sim_dt = sim.get_physics_dt() count = 0 - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or count < args_cli.max_steps): if count % 500 == 0: root_pose = scene["robot"].data.default_root_pose.torch.clone() root_pose[:, :3] += scene.env_origins diff --git a/examples/sensors/imu_sensor.py b/examples/sensors/imu_sensor.py index a952b0eaa602..0d47ef3f9747 100644 --- a/examples/sensors/imu_sensor.py +++ b/examples/sensors/imu_sensor.py @@ -67,7 +67,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") - sim_dt = sim.get_physics_dt() count = 0 - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or count < args_cli.max_steps): if count % 500 == 0: root_pose = scene["robot"].data.default_root_pose.torch.clone() root_pose[:, :3] += scene.env_origins diff --git a/examples/sensors/multi_mesh_raycaster.py b/examples/sensors/multi_mesh_raycaster.py index e2d190ff9ccc..1f87a495caf7 100644 --- a/examples/sensors/multi_mesh_raycaster.py +++ b/examples/sensors/multi_mesh_raycaster.py @@ -267,7 +267,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene): # Simulate physics step_count = 0 - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or step_count < args_cli.max_steps): if count % 500 == 0: # reset counter count = 0 diff --git a/examples/sensors/multi_mesh_raycaster_camera.py b/examples/sensors/multi_mesh_raycaster_camera.py index b7c248cd132c..6684fdd63b6b 100644 --- a/examples/sensors/multi_mesh_raycaster_camera.py +++ b/examples/sensors/multi_mesh_raycaster_camera.py @@ -238,7 +238,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") - sim_dt = sim.get_physics_dt() count = 0 - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or count < args_cli.max_steps): if count % 500 == 0: root_pose = scene["asset"].data.default_root_pose.torch.clone() root_pose[:, :3] += scene.env_origins diff --git a/examples/sensors/newton_raycast.py b/examples/sensors/newton_raycast.py index 35b358da8ebf..bf6de40a159a 100644 --- a/examples/sensors/newton_raycast.py +++ b/examples/sensors/newton_raycast.py @@ -246,7 +246,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene, sce bar = scene["bar"] step = 0 - while sim.is_headless_or_exist_active_visualizer() and (max_steps < 0 or step < max_steps): + while sim.is_running() and (max_steps < 0 or step < max_steps): if bar is None: _animate_heightfield(body, step * sim_dt, zero_velocity) else: diff --git a/examples/sensors/ppisp_camera.py b/examples/sensors/ppisp_camera.py index 43f406df87ae..a158e7c5420b 100644 --- a/examples/sensors/ppisp_camera.py +++ b/examples/sensors/ppisp_camera.py @@ -458,7 +458,7 @@ def run_simulator(sim: sim_utils.SimulationContext, baseline_camera: Camera, ppi count = 0 reported_shape = False - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): sim.step() baseline_camera.update(sim_dt) ppisp_camera.update(sim_dt) diff --git a/examples/sensors/pva_sensor.py b/examples/sensors/pva_sensor.py index 34acf76ad14d..ff64353b7732 100644 --- a/examples/sensors/pva_sensor.py +++ b/examples/sensors/pva_sensor.py @@ -68,7 +68,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") - sim_dt = sim.get_physics_dt() count = 0 - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or count < args_cli.max_steps): if count % 500 == 0: # reset the scene entities # root state diff --git a/examples/sensors/raycaster_sensor.py b/examples/sensors/raycaster_sensor.py index 6680bfddbf82..13e15d842168 100644 --- a/examples/sensors/raycaster_sensor.py +++ b/examples/sensors/raycaster_sensor.py @@ -83,7 +83,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") - sim_dt = sim.get_physics_dt() count = 0 - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or count < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or count < args_cli.max_steps): if count % 500 == 0: # reset the scene entities # root state diff --git a/examples/sensors/tacsl_sensor.py b/examples/sensors/tacsl_sensor.py index 5d0c85a518a5..d9a2b80cd22b 100644 --- a/examples/sensors/tacsl_sensor.py +++ b/examples/sensors/tacsl_sensor.py @@ -339,7 +339,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene") - entity_list.append("contact_object") total_steps = 0 - while sim.is_headless_or_exist_active_visualizer() and (args_cli.max_steps < 0 or total_steps < args_cli.max_steps): + while sim.is_running() and (args_cli.max_steps < 0 or total_steps < args_cli.max_steps): if count == 122: # Reset robot and contact object positions count = 0 diff --git a/examples/visual_color_randomization.py b/examples/visual_color_randomization.py index c95381a8cd06..4c0b8815c62c 100644 --- a/examples/visual_color_randomization.py +++ b/examples/visual_color_randomization.py @@ -277,9 +277,7 @@ def main() -> None: count = 0 env.reset() print("[INFO]: Setup complete.") - while env.sim.is_headless_or_exist_active_visualizer() and ( - args_cli.max_steps < 0 or count < args_cli.max_steps - ): + while env.sim.is_running() and (args_cli.max_steps < 0 or count < args_cli.max_steps): if count > 0 and count % 50 == 0: num_reset = int(torch.randint(1, env.num_envs + 1, ()).item()) env_ids = torch.randperm(env.num_envs, dtype=torch.int32, device=env.device)[:num_reset] diff --git a/scripts/environments/state_machine/lift_cube_sm.py b/scripts/environments/state_machine/lift_cube_sm.py index 986b0827aaa8..d40c8d49bf40 100644 --- a/scripts/environments/state_machine/lift_cube_sm.py +++ b/scripts/environments/state_machine/lift_cube_sm.py @@ -265,7 +265,7 @@ def main(): env_cfg.sim.dt * env_cfg.decimation, env.unwrapped.num_envs, env.unwrapped.device, position_threshold=0.01 ) - while env.unwrapped.sim.is_headless_or_exist_active_visualizer(): + while env.unwrapped.sim.is_running(): # run everything in inference mode with torch.inference_mode(): # step environment diff --git a/scripts/environments/state_machine/open_cabinet_sm.py b/scripts/environments/state_machine/open_cabinet_sm.py index 474dd8d6363c..606f1caf2e21 100644 --- a/scripts/environments/state_machine/open_cabinet_sm.py +++ b/scripts/environments/state_machine/open_cabinet_sm.py @@ -282,7 +282,7 @@ def main(): # create state machine open_sm = OpenDrawerSm(env_cfg.sim.dt * env_cfg.decimation, env.unwrapped.num_envs, env.unwrapped.device) - while env.unwrapped.sim.is_headless_or_exist_active_visualizer(): + while env.unwrapped.sim.is_running(): # run everything in inference mode with torch.inference_mode(): # step environment diff --git a/scripts/environments/teleoperation/teleop_replay_agent.py b/scripts/environments/teleoperation/teleop_replay_agent.py index 90641a98eacc..ca3aebd31e56 100644 --- a/scripts/environments/teleoperation/teleop_replay_agent.py +++ b/scripts/environments/teleoperation/teleop_replay_agent.py @@ -1296,10 +1296,7 @@ def _patched_sim_render(): " before consuming MCAP frames." ) buffer_start_s = time.monotonic() - while ( - env.sim.is_headless_or_exist_active_visualizer() - and time.monotonic() - buffer_start_s < args_cli.replay_start_delay_s - ): + while env.sim.is_running() and time.monotonic() - buffer_start_s < args_cli.replay_start_delay_s: env.sim.render() print( @@ -1321,7 +1318,7 @@ def _patched_sim_render(): # subsequent renders are excluded. last_active_end_s: float | None = None - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): try: with torch.inference_mode(): # Wall-clock safety cap. Only hit when the recording @@ -1598,7 +1595,7 @@ def _run_replay_batch(env_cfg: ManagerBasedRLEnvCfg, success_term: object | None ) all_runs.append(run_stats) print(f"Replay {run_idx + 1}/{args_cli.num_replays} outcome: {run_stats.outcome}") - if not env.sim.is_headless_or_exist_active_visualizer(): + if not env.sim.is_running(): # The simulator was closed externally mid-batch; stop the # outer loop rather than spawning a fresh device against # a dead app. diff --git a/scripts/environments/teleoperation/teleop_se3_agent.py b/scripts/environments/teleoperation/teleop_se3_agent.py index e2f6efdb831b..625e89c16d9f 100644 --- a/scripts/environments/teleoperation/teleop_se3_agent.py +++ b/scripts/environments/teleoperation/teleop_se3_agent.py @@ -477,7 +477,7 @@ def run_loop(): print(f"{stack_name} teleoperation started. Press 'R' to reset the environment.") # simulate environment - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): try: # run everything in inference mode with torch.inference_mode(): diff --git a/scripts/imitation_learning/isaaclab_mimic/annotate_demos.py b/scripts/imitation_learning/isaaclab_mimic/annotate_demos.py index b59794c1edd5..1659510f7412 100644 --- a/scripts/imitation_learning/isaaclab_mimic/annotate_demos.py +++ b/scripts/imitation_learning/isaaclab_mimic/annotate_demos.py @@ -298,7 +298,7 @@ def annotate_dataset(env_cfg, dataset_file_handler: HDF5DatasetFileHandler, succ processed_episode_count = 0 successful_task_count = 0 # Counter for successful task completions with contextlib.suppress(KeyboardInterrupt) and torch.inference_mode(): - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): # Iterate over the episodes in the loaded dataset file for episode_index, episode_name in enumerate(dataset_file_handler.get_episode_names()): processed_episode_count += 1 diff --git a/scripts/imitation_learning/locomanipulation_sdg/generate_data.py b/scripts/imitation_learning/locomanipulation_sdg/generate_data.py index dd199c990c63..e4c22bdaf858 100644 --- a/scripts/imitation_learning/locomanipulation_sdg/generate_data.py +++ b/scripts/imitation_learning/locomanipulation_sdg/generate_data.py @@ -928,7 +928,7 @@ def replay( recording_step = 0 # Main simulation loop with state machine - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): if current_state != previous_state: print(f"State changed: {current_state.name}, Recording step: {recording_step}", flush=True) previous_state = current_state diff --git a/scripts/imitation_learning/locomanipulation_sdg/gr00t/rollout_policy.py b/scripts/imitation_learning/locomanipulation_sdg/gr00t/rollout_policy.py index 7a53fdc01293..07b740aac69b 100644 --- a/scripts/imitation_learning/locomanipulation_sdg/gr00t/rollout_policy.py +++ b/scripts/imitation_learning/locomanipulation_sdg/gr00t/rollout_policy.py @@ -341,7 +341,7 @@ def eval_policy( action_idx = 0 inference_interval = 16 - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): if step % inference_interval == 0: model_input, dummy_action = build_model_input(env, base_goal, policy_quat_format) action_dict = policy.policy.get_action(model_input) diff --git a/scripts/tools/convert_mesh.py b/scripts/tools/convert_mesh.py index e1b7bc0b8456..c023b07586c4 100644 --- a/scripts/tools/convert_mesh.py +++ b/scripts/tools/convert_mesh.py @@ -129,7 +129,7 @@ def preview(usd_path: str, physics_cfg: PhysicsCfg) -> None: _scene = instantiate(scene_cfg) sim.reset() - # Checked per visualizer rather than through ``SimulationContext.is_headless_or_exist_active_visualizer``: + # Checked per visualizer rather than through ``SimulationContext.is_running``: # that predicate also reports True for an empty visualizer list (headless stepping), and ``render`` # drops visualizers once they close, so the preview would never exit. while any(viz.is_running() and not viz.is_closed for viz in sim.visualizers): diff --git a/scripts/tools/convert_mjcf.py b/scripts/tools/convert_mjcf.py index 4e54e171f33d..ad0b66faa9e8 100644 --- a/scripts/tools/convert_mjcf.py +++ b/scripts/tools/convert_mjcf.py @@ -122,7 +122,7 @@ def preview(usd_path: str, physics_cfg: PhysicsCfg) -> None: _scene = instantiate(scene_cfg) sim.reset() - # Checked per visualizer rather than through ``SimulationContext.is_headless_or_exist_active_visualizer``: + # Checked per visualizer rather than through ``SimulationContext.is_running``: # that predicate also reports True for an empty visualizer list (headless stepping), and ``render`` # drops visualizers once they close, so the preview would never exit. while any(viz.is_running() and not viz.is_closed for viz in sim.visualizers): diff --git a/scripts/tools/convert_urdf.py b/scripts/tools/convert_urdf.py index dd3dde789151..c024d5e21745 100644 --- a/scripts/tools/convert_urdf.py +++ b/scripts/tools/convert_urdf.py @@ -126,7 +126,7 @@ def preview(usd_path: str, physics_cfg: PhysicsCfg) -> None: _scene = instantiate(scene_cfg) sim.reset() - # Checked per visualizer rather than through ``SimulationContext.is_headless_or_exist_active_visualizer``: + # Checked per visualizer rather than through ``SimulationContext.is_running``: # that predicate also reports True for an empty visualizer list (headless stepping), and ``render`` # drops visualizers once they close, so the preview would never exit. while any(viz.is_running() and not viz.is_closed for viz in sim.visualizers): diff --git a/scripts/tools/record_demos.py b/scripts/tools/record_demos.py index 88bb0bd871a5..28e3ea7bb0f2 100644 --- a/scripts/tools/record_demos.py +++ b/scripts/tools/record_demos.py @@ -705,7 +705,7 @@ def inner_loop(): from isaaclab_mimic.ui.instruction_display import show_subtask_instructions with contextlib.suppress(KeyboardInterrupt), torch.inference_mode(), camera_feed_session.bind(env): - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): # Get teleop command (may be None while waiting for session start) action = teleop_interface.advance() diff --git a/scripts/tools/replay_demos.py b/scripts/tools/replay_demos.py index 839a24b9a067..5a8ccd89d89d 100644 --- a/scripts/tools/replay_demos.py +++ b/scripts/tools/replay_demos.py @@ -147,7 +147,7 @@ def replay_episodes_loop( # noqa: C901 failed_demo_ids: list[int] = [] with contextlib.suppress(KeyboardInterrupt) and torch.inference_mode(): - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): env_episode_data_map = {index: EpisodeData() for index in range(num_envs)} first_loop = True has_next_action = True diff --git a/scripts/tutorials/00_sim/create_empty.py b/scripts/tutorials/00_sim/create_empty.py index 90adb1125343..782bae4cf1e5 100644 --- a/scripts/tutorials/00_sim/create_empty.py +++ b/scripts/tutorials/00_sim/create_empty.py @@ -49,7 +49,7 @@ def main(): print("[INFO]: Setup complete...") # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # perform step sim.step() diff --git a/scripts/tutorials/00_sim/launch_app.py b/scripts/tutorials/00_sim/launch_app.py index 514a4e530c74..6740466d92e5 100644 --- a/scripts/tutorials/00_sim/launch_app.py +++ b/scripts/tutorials/00_sim/launch_app.py @@ -85,7 +85,7 @@ def main(): print("[INFO]: Setup complete...") # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # perform step sim.step() diff --git a/scripts/tutorials/00_sim/log_time.py b/scripts/tutorials/00_sim/log_time.py index bc5309238ee6..05c2d52c0343 100644 --- a/scripts/tutorials/00_sim/log_time.py +++ b/scripts/tutorials/00_sim/log_time.py @@ -70,7 +70,7 @@ def main(): # Open logging file with open(os.path.join(log_dir_path, "log.txt"), "w") as log_file: # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): log_file.write(f"{sim_time}" + "\n") # perform step sim.step() diff --git a/scripts/tutorials/00_sim/spawn_prims.py b/scripts/tutorials/00_sim/spawn_prims.py index a2e1d0d147dc..5bb0d1a202a1 100644 --- a/scripts/tutorials/00_sim/spawn_prims.py +++ b/scripts/tutorials/00_sim/spawn_prims.py @@ -105,7 +105,7 @@ def main(): print("[INFO]: Setup complete...") # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # perform step sim.step() diff --git a/scripts/tutorials/01_assets/add_new_robot.py b/scripts/tutorials/01_assets/add_new_robot.py index 96d414971fa6..667fe6ac0f89 100644 --- a/scripts/tutorials/01_assets/add_new_robot.py +++ b/scripts/tutorials/01_assets/add_new_robot.py @@ -114,7 +114,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): straight_action = torch.tensor([[10.0, 10.0]], device=sim.device).repeat(scene.num_envs, 1) turn_action = torch.tensor([[5.0, -5.0]], device=sim.device).repeat(scene.num_envs, 1) - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # reset if count % 500 == 0: # reset counters diff --git a/scripts/tutorials/01_assets/run_articulation.py b/scripts/tutorials/01_assets/run_articulation.py index ce52f3d1e167..54aa7c4d84c0 100644 --- a/scripts/tutorials/01_assets/run_articulation.py +++ b/scripts/tutorials/01_assets/run_articulation.py @@ -78,7 +78,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articula sim_dt = sim.get_physics_dt() count = 0 # Simulation loop - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # Reset if count % 500 == 0: # reset counter diff --git a/scripts/tutorials/01_assets/run_deformable_object.py b/scripts/tutorials/01_assets/run_deformable_object.py index d1bcf25267d0..0e276f39aa01 100644 --- a/scripts/tutorials/01_assets/run_deformable_object.py +++ b/scripts/tutorials/01_assets/run_deformable_object.py @@ -126,7 +126,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): nodal_kinematic_target = cube_object.data.nodal_kinematic_target.torch.clone() # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # reset at start and after 3 seconds if count % int(3.0 / sim_dt) == 0: # reset counters diff --git a/scripts/tutorials/01_assets/run_rigid_object.py b/scripts/tutorials/01_assets/run_rigid_object.py index 4290065dbca0..72b54333220f 100644 --- a/scripts/tutorials/01_assets/run_rigid_object.py +++ b/scripts/tutorials/01_assets/run_rigid_object.py @@ -83,7 +83,7 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, RigidObj sim_time = 0.0 count = 0 # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # reset if count % 250 == 0: # reset counters diff --git a/scripts/tutorials/01_assets/run_surface_gripper.py b/scripts/tutorials/01_assets/run_surface_gripper.py index 0fcabf692299..29201e349414 100644 --- a/scripts/tutorials/01_assets/run_surface_gripper.py +++ b/scripts/tutorials/01_assets/run_surface_gripper.py @@ -98,7 +98,7 @@ def run_simulator( sim_dt = sim.get_physics_dt() count = 0 # Simulation loop - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # Reset if count % 500 == 0: # reset counter diff --git a/scripts/tutorials/02_scene/create_scene.py b/scripts/tutorials/02_scene/create_scene.py index 40c69509dca7..f0f3890eecd6 100644 --- a/scripts/tutorials/02_scene/create_scene.py +++ b/scripts/tutorials/02_scene/create_scene.py @@ -72,7 +72,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): sim_dt = sim.get_physics_dt() count = 0 # Simulation loop - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # Reset if count % 500 == 0: # reset counter diff --git a/scripts/tutorials/03_envs/create_cartpole_base_env.py b/scripts/tutorials/03_envs/create_cartpole_base_env.py index 82bd6ad649be..2eac3eb15094 100644 --- a/scripts/tutorials/03_envs/create_cartpole_base_env.py +++ b/scripts/tutorials/03_envs/create_cartpole_base_env.py @@ -145,7 +145,7 @@ def main(): # simulate physics count = 0 - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): with torch.inference_mode(): # reset if count % 300 == 0: diff --git a/scripts/tutorials/03_envs/create_cube_base_env.py b/scripts/tutorials/03_envs/create_cube_base_env.py index 38d8d3d213a0..6656263e6253 100644 --- a/scripts/tutorials/03_envs/create_cube_base_env.py +++ b/scripts/tutorials/03_envs/create_cube_base_env.py @@ -328,7 +328,7 @@ def main(): # simulate physics count = 0 obs, _ = env.reset() - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): with torch.inference_mode(): # reset if count % 300 == 0: diff --git a/scripts/tutorials/03_envs/create_quadruped_base_env.py b/scripts/tutorials/03_envs/create_quadruped_base_env.py index a1d721ed768c..90a1e8481068 100644 --- a/scripts/tutorials/03_envs/create_quadruped_base_env.py +++ b/scripts/tutorials/03_envs/create_quadruped_base_env.py @@ -221,7 +221,7 @@ def main(): # simulate physics count = 0 obs, _ = env.reset() - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): with torch.inference_mode(): # reset if count % 1000 == 0: diff --git a/scripts/tutorials/03_envs/policy_inference_in_usd.py b/scripts/tutorials/03_envs/policy_inference_in_usd.py index e2d284bbc39e..59eb77ed2143 100644 --- a/scripts/tutorials/03_envs/policy_inference_in_usd.py +++ b/scripts/tutorials/03_envs/policy_inference_in_usd.py @@ -67,7 +67,7 @@ def main(): # run inference with the policy obs, _ = env.reset() with torch.inference_mode(): - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): action = policy(obs["policy"]) obs, _, _, _, _ = env.step(action) env.close() diff --git a/scripts/tutorials/03_envs/run_cartpole_rl_env.py b/scripts/tutorials/03_envs/run_cartpole_rl_env.py index f796ff596b5a..6dce288fae72 100644 --- a/scripts/tutorials/03_envs/run_cartpole_rl_env.py +++ b/scripts/tutorials/03_envs/run_cartpole_rl_env.py @@ -53,7 +53,7 @@ def main(): # simulate physics count = 0 - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): with torch.inference_mode(): # reset if count % 300 == 0: diff --git a/scripts/tutorials/04_sensors/add_sensors_on_robot.py b/scripts/tutorials/04_sensors/add_sensors_on_robot.py index 623b7ae9780c..bf92807e73d6 100644 --- a/scripts/tutorials/04_sensors/add_sensors_on_robot.py +++ b/scripts/tutorials/04_sensors/add_sensors_on_robot.py @@ -104,7 +104,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): count = 0 # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # Reset if count % 500 == 0: # reset counter diff --git a/scripts/tutorials/04_sensors/run_frame_transformer.py b/scripts/tutorials/04_sensors/run_frame_transformer.py index 2a40e144f173..8fd566c3d52d 100644 --- a/scripts/tutorials/04_sensors/run_frame_transformer.py +++ b/scripts/tutorials/04_sensors/run_frame_transformer.py @@ -104,7 +104,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): frame_index = 0 # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # perform this loop at policy control freq (50 Hz) robot.set_joint_position_target_index(target=robot.data.default_joint_pos.torch.clone()) robot.write_data_to_sim() diff --git a/scripts/tutorials/04_sensors/run_ray_caster.py b/scripts/tutorials/04_sensors/run_ray_caster.py index 3cd003d28800..24a435998556 100644 --- a/scripts/tutorials/04_sensors/run_ray_caster.py +++ b/scripts/tutorials/04_sensors/run_ray_caster.py @@ -107,7 +107,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): # Create a counter for resetting the scene step_count = 0 # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # Reset the scene if step_count % 250 == 0: # reset the balls diff --git a/scripts/tutorials/04_sensors/run_ray_caster_camera.py b/scripts/tutorials/04_sensors/run_ray_caster_camera.py index 0222e47814c7..ebcaab4d1732 100644 --- a/scripts/tutorials/04_sensors/run_ray_caster_camera.py +++ b/scripts/tutorials/04_sensors/run_ray_caster_camera.py @@ -113,7 +113,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): # camera.set_world_poses(position, orientation, indices=[0], convention="ros") # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # Step simulation sim.step() # Update camera data diff --git a/scripts/tutorials/04_sensors/run_usd_camera.py b/scripts/tutorials/04_sensors/run_usd_camera.py index e27340379465..e066ef181ffd 100644 --- a/scripts/tutorials/04_sensors/run_usd_camera.py +++ b/scripts/tutorials/04_sensors/run_usd_camera.py @@ -198,7 +198,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): pc_markers = VisualizationMarkers(cfg) # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # Step simulation sim.step() # Update camera data @@ -222,11 +222,13 @@ def run_simulator(sim: sim_utils.SimulationContext, scene_entities: dict): # Extract camera data if args_cli.save: - # Save the 8-bit color images (rgb and colorized segmentations) from camera at camera_index + # Save the camera outputs at camera_index: 8-bit color images as PNG, other data (depth, normals) as NumPy for key, data in camera.data.output.items(): + file_stem = os.path.join(output_dir, f"{key}_{camera.frame[camera_index]}") if data.torch.dtype == torch.uint8: - file_path = os.path.join(output_dir, f"{key}_{camera.frame[camera_index]}.png") - save_images_to_file(data.torch[camera_index : camera_index + 1].float() / 255.0, file_path) + save_images_to_file(data.torch[camera_index : camera_index + 1].float() / 255.0, f"{file_stem}.png") + else: + np.save(f"{file_stem}.npy", data.torch[camera_index].cpu().numpy()) # Draw pointcloud if there is a GUI and --draw has been passed if ( diff --git a/scripts/tutorials/05_controllers/run_diff_ik.py b/scripts/tutorials/05_controllers/run_diff_ik.py index 00176c88569f..9f91b6596195 100644 --- a/scripts/tutorials/05_controllers/run_diff_ik.py +++ b/scripts/tutorials/05_controllers/run_diff_ik.py @@ -139,7 +139,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): sim_dt = sim.get_physics_dt() count = 0 # Simulation loop - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # reset if count % 150 == 0: # reset time diff --git a/scripts/tutorials/05_controllers/run_osc.py b/scripts/tutorials/05_controllers/run_osc.py index 90cf8d63a18c..f2dddf957364 100644 --- a/scripts/tutorials/05_controllers/run_osc.py +++ b/scripts/tutorials/05_controllers/run_osc.py @@ -209,7 +209,7 @@ def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"): count = 0 # Simulation loop - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # reset every 500 steps if count % 500 == 0: # reset joint state to default diff --git a/source/isaaclab/changelog.d/launch-simulation-scripts.major.rst b/source/isaaclab/changelog.d/launch-simulation-scripts.major.rst index cbb7705e984e..c031dcbfbe9f 100644 --- a/source/isaaclab/changelog.d/launch-simulation-scripts.major.rst +++ b/source/isaaclab/changelog.d/launch-simulation-scripts.major.rst @@ -9,13 +9,21 @@ Changed * Changed the ``run_usd_camera`` and ``run_ray_caster_camera`` tutorials to save images as PNG files with :func:`~isaaclab.utils.save_images_to_file` instead of Replicator writers. * Changed ``pick_and_place.py`` to use :class:`~isaaclab.devices.Se3Keyboard`. +* Changed :func:`~isaaclab.app.launch_simulation` to end the process with the status of a ``sys.exit(n)`` raised in + its block; Kit previously exited with 0. +* Changed the ``run_usd_camera`` tutorial to also save floating-point outputs, such as depth and normals, as NumPy + ``.npy`` files when ``--save`` is passed. Removed ^^^^^^^ * **Breaking:** Reduced the deprecated ``isaaclab.app.AppLauncher`` to a shim for scripts that still use it. - ``AppLauncher(args).app`` still starts Isaac Sim / Kit through :func:`~isaaclab.app.launch_simulation`, and + ``AppLauncher(args).app`` still starts Isaac Sim / Kit through :func:`~isaaclab.app.launch_simulation` and + ``app.close(exit_code=n)`` ends the process with ``n``, and ``AppLauncher.add_app_launcher_args`` still adds the launcher arguments. ``is_available``, ``has_gui``, ``has_window``, and ``device`` were removed: use ``isaaclab_physx.app.KitLauncher.is_available()``, :attr:`~isaaclab.sim.SimulationContext.has_gui`, and the simulation config's ``device``. * Removed the ``check_*`` scripts under the core and PhysX test folders, which duplicated pytest coverage. +* **Breaking:** Renamed ``SimulationContext.is_headless_or_exist_active_visualizer`` to + :meth:`~isaaclab.sim.SimulationContext.is_running`, which also returns False once every started visualizer is + closed instead of treating the emptied visualizer list as a headless run. Replace the calls. diff --git a/source/isaaclab/isaaclab/app/app_launcher.py b/source/isaaclab/isaaclab/app/app_launcher.py index 84fa4fced585..e4cb77aaa3c2 100644 --- a/source/isaaclab/isaaclab/app/app_launcher.py +++ b/source/isaaclab/isaaclab/app/app_launcher.py @@ -27,8 +27,9 @@ class _LaunchedApp: def __init__(self, app: Any): self._app = app - def close(self, *args, **kwargs) -> None: - _RUNTIME.close() + def close(self, *args, exit_code: int = 0, **kwargs) -> None: + # unwind the launch as a ``sys.exit(exit_code)`` so the runtime exits with the requested status + _RUNTIME.__exit__(SystemExit, SystemExit(exit_code), None) def is_exiting(self) -> bool: return not self._app.is_running() diff --git a/source/isaaclab/isaaclab/app/sim_launcher.py b/source/isaaclab/isaaclab/app/sim_launcher.py index bbf47eda8b3a..d4c2aa1d94a9 100644 --- a/source/isaaclab/isaaclab/app/sim_launcher.py +++ b/source/isaaclab/isaaclab/app/sim_launcher.py @@ -679,6 +679,10 @@ def launch_simulation( configure_storage_profile() yield physics_cfg + except SystemExit as exc: + # keep the status of ``sys.exit(n)`` in the block; Kit would otherwise exit with 0 + exit_code = exc.code if isinstance(exc.code, int) else int(exc.code is not None) + raise except Exception: exit_code = 1 import traceback diff --git a/source/isaaclab/isaaclab/cli/commands/deploy.py b/source/isaaclab/isaaclab/cli/commands/deploy.py index 117e189f0fe1..e2d44f59d23b 100644 --- a/source/isaaclab/isaaclab/cli/commands/deploy.py +++ b/source/isaaclab/isaaclab/cli/commands/deploy.py @@ -74,7 +74,7 @@ def command_deploy_leapp(argv: list[str] | None = None) -> int: env.reset() try: with torch.inference_mode(): - while env.sim.is_headless_or_exist_active_visualizer(): + while env.sim.is_running(): env.step() finally: env.close() diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index 582ed776ba8a..a839f37b5768 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -218,6 +218,7 @@ def __init__(self, cfg: SimulationCfg | None = None): self._scene_data_provider = SceneDataProvider(self.physics_manager.get_scene_data_backend()) self._visualizers: list[BaseVisualizer] = [] self._pending_visualizers: list[BaseVisualizer] = [] + self._visualizers_started = False self._reset_requested: bool = False # Set by the visualizers and renderers in use; read by the scene data provider. self.requires_usd_stage = False @@ -344,9 +345,13 @@ def has_active_visualizers(self) -> bool: self.get_setting("/isaaclab/video/auto_start_kit") ) - def is_headless_or_exist_active_visualizer(self) -> bool: - """Return whether the simulation should keep stepping without visualizers or with an active visualizer.""" - return not self._visualizers or any(viz.is_running() and not viz.is_closed for viz in self._visualizers) + def is_running(self) -> bool: + """Return whether the simulation should keep running. + + Without visualizers it keeps running until the caller stops. Once visualizers were started, it keeps + running while one of them is still open, so closing the last one ends the loop. + """ + return not self._visualizers_started or any(viz.is_running() and not viz.is_closed for viz in self._visualizers) def require_visual_shapes(self) -> None: """Record that something in this simulation draws the physics model's visual-only shapes. @@ -669,6 +674,7 @@ def _initialize_visualizers(self, config_filter: Callable[[Any], bool] | None = visualizer.initialize(self._scene_data_provider) self._pending_visualizers.remove(visualizer) self._visualizers.append(visualizer) + self._visualizers_started = True if self._pending_camera_view is not None: visualizer.set_camera_view(*self._pending_camera_view) if not self._pending_visualizers: diff --git a/source/isaaclab/test/cli/test_replay_demos_loop_termination.py b/source/isaaclab/test/cli/test_replay_demos_loop_termination.py index 3cc4366afe37..0692374b59ea 100644 --- a/source/isaaclab/test/cli/test_replay_demos_loop_termination.py +++ b/source/isaaclab/test/cli/test_replay_demos_loop_termination.py @@ -45,7 +45,7 @@ def _load_replay_episodes_loop(): class _SimStub: - def is_headless_or_exist_active_visualizer(self): + def is_running(self): return True def reset(self): diff --git a/source/isaaclab/test/devices/check_keyboard.py b/source/isaaclab/test/devices/check_keyboard.py index a4e9bf0fca4b..98f883240441 100644 --- a/source/isaaclab/test/devices/check_keyboard.py +++ b/source/isaaclab/test/devices/check_keyboard.py @@ -64,7 +64,7 @@ def main(): sim.reset() # Simulate - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # If simulation is stopped, then exit. if sim.is_stopped(): break diff --git a/source/isaaclab/test/markers/check_markers_visibility.py b/source/isaaclab/test/markers/check_markers_visibility.py index 96d588140838..d4a54e7da856 100644 --- a/source/isaaclab/test/markers/check_markers_visibility.py +++ b/source/isaaclab/test/markers/check_markers_visibility.py @@ -84,7 +84,7 @@ def run_simulator( count = 0 # Simulate physics - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): # Reset if count % 500 == 0: # reset counter diff --git a/source/isaaclab/test/sim/test_simulation_context_visualizers.py b/source/isaaclab/test/sim/test_simulation_context_visualizers.py index 598867f55ec6..0f3af5f9dbbb 100644 --- a/source/isaaclab/test/sim/test_simulation_context_visualizers.py +++ b/source/isaaclab/test/sim/test_simulation_context_visualizers.py @@ -142,6 +142,7 @@ def flush_startup_messages(self): def _make_context(visualizers, provider=None): ctx = object.__new__(SimulationContext) ctx._visualizers = list(visualizers) + ctx._visualizers_started = bool(visualizers) ctx._scene_data_provider = provider ctx.physics_manager = _FakePhysicsManager() ctx.vis_marker_registry = VisMarkerRegistry() @@ -193,6 +194,18 @@ def test_update_visualizers_removes_closed_nonrunning_and_failed(caplog): assert any("Error stepping visualizer" in r.message for r in caplog.records) +def test_is_running_until_the_last_visualizer_closes(): + """Headless runs keep going; once the last visualizer closes and is dropped, the run ends.""" + assert _make_context([]).is_running() + + stopped_viz = _FakeVisualizer(running=False) + ctx = _make_context([stopped_viz], provider=_FakeProvider()) + ctx.update_visualizers(0.1) + + assert ctx._visualizers == [] + assert not ctx.is_running() + + def test_update_visualizers_skips_zero_dt_for_paused_app_pumping_visualizer(): provider = _FakeProvider() paused_app_pumping_viz = _FakeVisualizer(rendering_paused=True, pumps_app_update=True) diff --git a/source/isaaclab/test/xr_visualization/check_scene_xr_visualization.py b/source/isaaclab/test/xr_visualization/check_scene_xr_visualization.py index 7dedae5464f3..b3a83ee733d0 100644 --- a/source/isaaclab/test/xr_visualization/check_scene_xr_visualization.py +++ b/source/isaaclab/test/xr_visualization/check_scene_xr_visualization.py @@ -241,7 +241,7 @@ def run_simulator( apply_sample_visualization() # Simulate - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): if int(time.time()) % 10 < 1: XRVisualization.push_event("ik_error") diff --git a/source/isaaclab_physx/test/app/test_app_launcher_shim.py b/source/isaaclab_physx/test/app/test_app_launcher_shim.py index 5a1ecab75cdf..eb02907a375d 100644 --- a/source/isaaclab_physx/test/app/test_app_launcher_shim.py +++ b/source/isaaclab_physx/test/app/test_app_launcher_shim.py @@ -54,10 +54,18 @@ @pytest.mark.integration -@pytest.mark.parametrize("script", [_OLD_STYLE_SCRIPT, _DROPPED_LAUNCHER_SCRIPT], ids=["kept-app", "dropped-launcher"]) -def test_old_style_script_starts_kit_steps_and_closes(script): - """The shim starts Kit, the script steps a simulation, and ``close()`` ends the process with 0.""" +@pytest.mark.parametrize( + ("script", "exit_code"), + [ + (_OLD_STYLE_SCRIPT, 0), + (_OLD_STYLE_SCRIPT.replace("simulation_app.close()", "simulation_app.close(exit_code=3)"), 3), + (_DROPPED_LAUNCHER_SCRIPT, 0), + ], + ids=["kept-app", "close-with-status", "dropped-launcher"], +) +def test_old_style_script_starts_kit_steps_and_closes(script, exit_code): + """The shim starts Kit, the script steps a simulation, and the process ends with the status the script chose.""" result = subprocess.run([sys.executable, "-c", script], capture_output=True, text=True, timeout=900) output = f"--- stdout ---\n{result.stdout}\n--- stderr ---\n{result.stderr}" assert _READY_MARKER in result.stdout, output - assert result.returncode == 0, output + assert result.returncode == exit_code, output diff --git a/source/isaaclab_physx/test/app/test_non_headless_launch.py b/source/isaaclab_physx/test/app/test_non_headless_launch.py index a408fe6ced40..20c3f9dc4a30 100644 --- a/source/isaaclab_physx/test/app/test_non_headless_launch.py +++ b/source/isaaclab_physx/test/app/test_non_headless_launch.py @@ -37,7 +37,7 @@ def run_simulator( count = 0 # Simulate physics - while sim.is_headless_or_exist_active_visualizer() and count < 100: + while sim.is_running() and count < 100: # perform step sim.step() count += 1 diff --git a/source/isaaclab_rl/changelog.d/launch-simulation-scripts.skip b/source/isaaclab_rl/changelog.d/launch-simulation-scripts.skip new file mode 100644 index 000000000000..c5bdf63b8eb8 --- /dev/null +++ b/source/isaaclab_rl/changelog.d/launch-simulation-scripts.skip @@ -0,0 +1 @@ +Renamed a SimulationContext.is_running call. diff --git a/source/isaaclab_rl/isaaclab_rl/entrypoints/simple_agents.py b/source/isaaclab_rl/isaaclab_rl/entrypoints/simple_agents.py index 9b7d6ba0fbbc..a18e3ccb4d04 100644 --- a/source/isaaclab_rl/isaaclab_rl/entrypoints/simple_agents.py +++ b/source/isaaclab_rl/isaaclab_rl/entrypoints/simple_agents.py @@ -100,7 +100,7 @@ def run(argv: list[str] | None = None, *, policy: PolicyName) -> None: # keep running while any visualizer is open and the step budget is not exhausted sim = env.unwrapped.sim step = 0 - while sim.is_headless_or_exist_active_visualizer(): + while sim.is_running(): if max_steps is not None and step >= max_steps: break step += 1 diff --git a/source/isaaclab_rl/test/test_entrypoints.py b/source/isaaclab_rl/test/test_entrypoints.py index 8358112ffe66..77857d22a98b 100644 --- a/source/isaaclab_rl/test/test_entrypoints.py +++ b/source/isaaclab_rl/test/test_entrypoints.py @@ -320,7 +320,7 @@ def test_random_agent_closes_environment_after_keyboard_interrupt( observation_space="observations", action_space=SimpleNamespace(shape=(1, 1)), unwrapped=SimpleNamespace( - sim=SimpleNamespace(is_headless_or_exist_active_visualizer=lambda: True), + sim=SimpleNamespace(is_running=lambda: True), device="cpu", ), reset=lambda: None, @@ -370,7 +370,7 @@ def test_simple_agent_video_step_budget( observation_space="observations", action_space=SimpleNamespace(shape=(1, 1)), unwrapped=SimpleNamespace( - sim=SimpleNamespace(is_headless_or_exist_active_visualizer=lambda: True), + sim=SimpleNamespace(is_running=lambda: True), device="cpu", ), reset=lambda: None,