From 05b1434ca6f496e753137fcf42529a08d682ed9b Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Mon, 25 May 2026 23:38:28 +0800 Subject: [PATCH 01/11] Support Demo Scripts With Multiple Backends Signed-off-by: Yize Wang --- scripts/demos/demo_helper.py | 72 +++++++++++++++++++++++ scripts/demos/quadrupeds.py | 107 ++++++++++++++++++++--------------- 2 files changed, 132 insertions(+), 47 deletions(-) create mode 100644 scripts/demos/demo_helper.py diff --git a/scripts/demos/demo_helper.py b/scripts/demos/demo_helper.py new file mode 100644 index 000000000000..f69ae8a864d7 --- /dev/null +++ b/scripts/demos/demo_helper.py @@ -0,0 +1,72 @@ +# 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 resolves the physics + visualizer cfgs from ``--physics`` / ``--visualizer``. +""" + +from contextlib import contextmanager + + +@contextmanager +def resolve_backend_and_visualizer(args, newton_cfg=None): + """Resolve physics + visualizer cfgs from ``--physics`` / ``--visualizer``. + + Yields ``(physics_cfg, visualizer_cfg)``. Kit is closed automatically on exit. + """ + + # Resolve physics cfg + if args.physics == "physx": + from isaaclab_physx.physics import PhysxCfg + physics_cfg = PhysxCfg() + elif args.physics == "newton_mjwarp": + from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg + DEFAULT_NEWTON_CFG = NewtonCfg( + solver_cfg=MJWarpSolverCfg( + njmax=70, + nconmax=70, + ls_iterations=40, + cone="elliptic", + impratio=100, + ls_parallel=False, + integrator="implicitfast", + ), + num_substeps=2, + ) + physics_cfg = newton_cfg or DEFAULT_NEWTON_CFG + else: + raise ValueError(f"Unsupported --physics value: {args.physics}") + + # Resolve visualizer cfg + DEFAULT_VISUALIZER = "kit" + viz_type = args.visualizer or DEFAULT_VISUALIZER + if viz_type == "kit": + from isaaclab_visualizers.kit import KitVisualizerCfg + visualizer_cfg = KitVisualizerCfg() + elif viz_type == "newton": + from isaaclab_visualizers.newton import NewtonVisualizerCfg + visualizer_cfg = NewtonVisualizerCfg() + else: + raise ValueError(f"Unsupported --visualizer value: {viz_type}") + + # Kit needs to be closed by explicitly calling AppLauncher.close() + needs_kit = (viz_type == "kit") + close_fn = None + if needs_kit: + from isaaclab.app import AppLauncher + + args.visualizer = [viz_type] + close_fn = AppLauncher(args).app.close + + try: + yield physics_cfg, visualizer_cfg + finally: + # No-op for the newton visualizer + if close_fn is not None: + close_fn() + + +def has_no_alive_visualizer_window(sim) -> bool: + return bool(sim.visualizers and not any(v.is_running() and not v.is_closed for v in sim.visualizers)) diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index 59551a3738a2..798a8034ee02 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -8,44 +8,39 @@ .. code-block:: bash - # Usage + # Usage with the default PhysX backend (launches Isaac Sim Kit by default). ./isaaclab.sh -p scripts/demos/quadrupeds.py + # Usage with the kit-less Newton (MJWarp) backend (launches Isaac Sim Kit by default). + ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics newton_mjwarp + + # Usage with the Newton (MJWarp) backend without Kit (launches Newton visualizer). + ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics newton_mjwarp --visualizer newton + + # Usage with the PhysX backend without Kit (launches Newton visualizer). + ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics physx --visualizer newton + """ -"""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 # add argparse arguments -parser = argparse.ArgumentParser(description="This script demonstrates different legged robots.") +parser = argparse.ArgumentParser(description="This script demonstrates different legged robots.", conflict_handler="resolve") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) -# demos should open Kit visualizer by default -parser.set_defaults(visualizer=["kit"]) +parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") +parser.add_argument("--visualizer", default="kit", choices=["kit", "newton"], help="Visualizer backend.") # 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.robots.anymal import ANYMAL_B_CFG, ANYMAL_C_CFG, ANYMAL_D_CFG # isort:skip -from isaaclab_assets.robots.spot import SPOT_CFG # isort:skip -from isaaclab_assets.robots.unitree import UNITREE_A1_CFG, UNITREE_GO1_CFG, UNITREE_GO2_CFG # isort:skip +from demo_helper import has_no_alive_visualizer_window, resolve_backend_and_visualizer def define_origins(num_origins: int, spacing: float) -> list[list[float]]: @@ -65,6 +60,12 @@ def define_origins(num_origins: int, spacing: float) -> list[list[float]]: def design_scene() -> tuple[dict, list[list[float]]]: """Designs the scene.""" + import isaaclab.sim as sim_utils + + from isaaclab_assets.robots.anymal import ANYMAL_B_CFG, ANYMAL_C_CFG, ANYMAL_D_CFG # isort:skip + from isaaclab_assets.robots.spot import SPOT_CFG # isort:skip + from isaaclab_assets.robots.unitree import UNITREE_A1_CFG, UNITREE_GO1_CFG, UNITREE_GO2_CFG # isort:skip + # Ground-plane cfg = sim_utils.GroundPlaneCfg() cfg.func("/World/defaultGroundPlane", cfg) @@ -79,37 +80,43 @@ def design_scene() -> tuple[dict, list[list[float]]]: # Origin 1 with Anymal B sim_utils.create_prim("/World/Origin1", "Xform", translation=origins[0]) # -- Robot - anymal_b = Articulation(ANYMAL_B_CFG.replace(prim_path="/World/Origin1/Robot")) - + anymal_b_cfg = ANYMAL_B_CFG.replace(prim_path="/World/Origin1/Robot") + anymal_b = anymal_b_cfg.class_type(anymal_b_cfg) # Origin 2 with Anymal C sim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1]) # -- Robot - anymal_c = Articulation(ANYMAL_C_CFG.replace(prim_path="/World/Origin2/Robot")) + anymal_c_cfg = ANYMAL_C_CFG.replace(prim_path="/World/Origin2/Robot") + anymal_c = anymal_c_cfg.class_type(anymal_c_cfg) # Origin 3 with Anymal D sim_utils.create_prim("/World/Origin3", "Xform", translation=origins[2]) # -- Robot - anymal_d = Articulation(ANYMAL_D_CFG.replace(prim_path="/World/Origin3/Robot")) + anymal_d_cfg = ANYMAL_D_CFG.replace(prim_path="/World/Origin3/Robot") + anymal_d = anymal_d_cfg.class_type(anymal_d_cfg) # Origin 4 with Unitree A1 sim_utils.create_prim("/World/Origin4", "Xform", translation=origins[3]) # -- Robot - unitree_a1 = Articulation(UNITREE_A1_CFG.replace(prim_path="/World/Origin4/Robot")) + unitree_a1_cfg = UNITREE_A1_CFG.replace(prim_path="/World/Origin4/Robot") + unitree_a1 = unitree_a1_cfg.class_type(unitree_a1_cfg) # Origin 5 with Unitree Go1 sim_utils.create_prim("/World/Origin5", "Xform", translation=origins[4]) # -- Robot - unitree_go1 = Articulation(UNITREE_GO1_CFG.replace(prim_path="/World/Origin5/Robot")) + unitree_go1_cfg = UNITREE_GO1_CFG.replace(prim_path="/World/Origin5/Robot") + unitree_go1 = unitree_go1_cfg.class_type(unitree_go1_cfg) # Origin 6 with Unitree Go2 sim_utils.create_prim("/World/Origin6", "Xform", translation=origins[5]) # -- Robot - unitree_go2 = Articulation(UNITREE_GO2_CFG.replace(prim_path="/World/Origin6/Robot")) + unitree_go2_cfg = UNITREE_GO2_CFG.replace(prim_path="/World/Origin6/Robot") + unitree_go2 = unitree_go2_cfg.class_type(unitree_go2_cfg) # Origin 7 with Boston Dynamics Spot sim_utils.create_prim("/World/Origin7", "Xform", translation=origins[6]) # -- Robot - spot = Articulation(SPOT_CFG.replace(prim_path="/World/Origin7/Robot")) + spot_cfg = SPOT_CFG.replace(prim_path="/World/Origin7/Robot") + spot = spot_cfg.class_type(spot_cfg) # return the scene information scene_entities = { @@ -124,14 +131,17 @@ def design_scene() -> tuple[dict, list[list[float]]]: return scene_entities, origins -def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articulation], origins: torch.Tensor): +def run_simulator(sim, entities: dict, origins): """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(): + while True: + # Exit when every visualizer window has been closed (works for kit and newton) + if has_no_alive_visualizer_window(sim): + break # reset if count % 200 == 0: # reset counters @@ -175,24 +185,27 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articula 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) + with resolve_backend_and_visualizer(args_cli) as (physics_cfg, visualizer_cfg): + import isaaclab.sim as sim_utils + + # define simulation time step + dt = 1 / 200 + # define simulation configuration + sim_cfg = sim_utils.SimulationCfg(dt=dt, device=args_cli.device, physics=physics_cfg, visualizer_cfgs=[visualizer_cfg]) + # 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_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() From c73177b1f612c5d8ff4e88cf1fc4e9d22e727ed2 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Tue, 26 May 2026 14:32:16 +0800 Subject: [PATCH 02/11] Minor Changes Signed-off-by: Yize Wang --- scripts/demos/demo_helper.py | 8 ++++---- scripts/demos/quadrupeds.py | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/demos/demo_helper.py b/scripts/demos/demo_helper.py index f69ae8a864d7..3c4e5295a294 100644 --- a/scripts/demos/demo_helper.py +++ b/scripts/demos/demo_helper.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: BSD-3-Clause """ -This script resolves the physics + visualizer cfgs from ``--physics`` / ``--visualizer``. +This script contains helper functions for the demos. """ from contextlib import contextmanager @@ -52,9 +52,8 @@ def resolve_backend_and_visualizer(args, newton_cfg=None): raise ValueError(f"Unsupported --visualizer value: {viz_type}") # Kit needs to be closed by explicitly calling AppLauncher.close() - needs_kit = (viz_type == "kit") close_fn = None - if needs_kit: + if viz_type == "kit": from isaaclab.app import AppLauncher args.visualizer = [viz_type] @@ -63,10 +62,11 @@ def resolve_backend_and_visualizer(args, newton_cfg=None): try: yield physics_cfg, visualizer_cfg finally: - # No-op for the newton visualizer + # No-op for the newton visualizer; close Kit automatically upon exit if close_fn is not None: close_fn() def has_no_alive_visualizer_window(sim) -> bool: + """Check if there are no alive visualizer windows.""" return bool(sim.visualizers and not any(v.is_running() and not v.is_closed for v in sim.visualizers)) diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index 798a8034ee02..fa1af18bf3ba 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -18,6 +18,7 @@ ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics newton_mjwarp --visualizer newton # Usage with the PhysX backend without Kit (launches Newton visualizer). + # TODO(yizew@nvidia.com): Not supported yet. Investigation needed. ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics physx --visualizer newton """ From 1beee90f9279d7c6c20a0f4b7c1d8507bef17963 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Wed, 27 May 2026 16:33:21 +0800 Subject: [PATCH 03/11] Resolve Comments Signed-off-by: Yize Wang --- scripts/demos/demo_helper.py | 3 ++- scripts/demos/quadrupeds.py | 35 +++++++++++++---------------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/scripts/demos/demo_helper.py b/scripts/demos/demo_helper.py index 3c4e5295a294..ea489b130245 100644 --- a/scripts/demos/demo_helper.py +++ b/scripts/demos/demo_helper.py @@ -69,4 +69,5 @@ def resolve_backend_and_visualizer(args, newton_cfg=None): def has_no_alive_visualizer_window(sim) -> bool: """Check if there are no alive visualizer windows.""" - return bool(sim.visualizers and not any(v.is_running() and not v.is_closed for v in sim.visualizers)) + visualizers = sim.visualizers or () + return not any(v.is_running() and not v.is_closed for v in visualizers) \ No newline at end of file diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index fa1af18bf3ba..308f8cbd1765 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -42,6 +42,9 @@ import torch from demo_helper import has_no_alive_visualizer_window, resolve_backend_and_visualizer +from isaaclab_assets.robots.anymal import ANYMAL_B_CFG, ANYMAL_C_CFG, ANYMAL_D_CFG # isort:skip +from isaaclab_assets.robots.spot import SPOT_CFG # isort:skip +from isaaclab_assets.robots.unitree import UNITREE_A1_CFG, UNITREE_GO1_CFG, UNITREE_GO2_CFG # isort:skip def define_origins(num_origins: int, spacing: float) -> list[list[float]]: @@ -62,10 +65,7 @@ def define_origins(num_origins: int, spacing: float) -> list[list[float]]: def design_scene() -> tuple[dict, list[list[float]]]: """Designs the scene.""" import isaaclab.sim as sim_utils - - from isaaclab_assets.robots.anymal import ANYMAL_B_CFG, ANYMAL_C_CFG, ANYMAL_D_CFG # isort:skip - from isaaclab_assets.robots.spot import SPOT_CFG # isort:skip - from isaaclab_assets.robots.unitree import UNITREE_A1_CFG, UNITREE_GO1_CFG, UNITREE_GO2_CFG # isort:skip + from isaaclab.assets import Articulation # Ground-plane cfg = sim_utils.GroundPlaneCfg() @@ -81,43 +81,36 @@ def design_scene() -> tuple[dict, list[list[float]]]: # Origin 1 with Anymal B sim_utils.create_prim("/World/Origin1", "Xform", translation=origins[0]) # -- Robot - anymal_b_cfg = ANYMAL_B_CFG.replace(prim_path="/World/Origin1/Robot") - anymal_b = anymal_b_cfg.class_type(anymal_b_cfg) + anymal_b = Articulation(ANYMAL_B_CFG.replace(prim_path="/World/Origin1/Robot")) # Origin 2 with Anymal C sim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1]) # -- Robot - anymal_c_cfg = ANYMAL_C_CFG.replace(prim_path="/World/Origin2/Robot") - anymal_c = anymal_c_cfg.class_type(anymal_c_cfg) + anymal_c = Articulation(ANYMAL_C_CFG.replace(prim_path="/World/Origin2/Robot")) # Origin 3 with Anymal D sim_utils.create_prim("/World/Origin3", "Xform", translation=origins[2]) # -- Robot - anymal_d_cfg = ANYMAL_D_CFG.replace(prim_path="/World/Origin3/Robot") - anymal_d = anymal_d_cfg.class_type(anymal_d_cfg) + anymal_d = Articulation(ANYMAL_D_CFG.replace(prim_path="/World/Origin3/Robot")) # Origin 4 with Unitree A1 sim_utils.create_prim("/World/Origin4", "Xform", translation=origins[3]) # -- Robot - unitree_a1_cfg = UNITREE_A1_CFG.replace(prim_path="/World/Origin4/Robot") - unitree_a1 = unitree_a1_cfg.class_type(unitree_a1_cfg) + unitree_a1 = Articulation(UNITREE_A1_CFG.replace(prim_path="/World/Origin4/Robot")) # Origin 5 with Unitree Go1 sim_utils.create_prim("/World/Origin5", "Xform", translation=origins[4]) # -- Robot - unitree_go1_cfg = UNITREE_GO1_CFG.replace(prim_path="/World/Origin5/Robot") - unitree_go1 = unitree_go1_cfg.class_type(unitree_go1_cfg) + unitree_go1 = Articulation(UNITREE_GO1_CFG.replace(prim_path="/World/Origin5/Robot")) # Origin 6 with Unitree Go2 sim_utils.create_prim("/World/Origin6", "Xform", translation=origins[5]) # -- Robot - unitree_go2_cfg = UNITREE_GO2_CFG.replace(prim_path="/World/Origin6/Robot") - unitree_go2 = unitree_go2_cfg.class_type(unitree_go2_cfg) + unitree_go2 = Articulation(UNITREE_GO2_CFG.replace(prim_path="/World/Origin6/Robot")) # Origin 7 with Boston Dynamics Spot sim_utils.create_prim("/World/Origin7", "Xform", translation=origins[6]) # -- Robot - spot_cfg = SPOT_CFG.replace(prim_path="/World/Origin7/Robot") - spot = spot_cfg.class_type(spot_cfg) + spot = Articulation(SPOT_CFG.replace(prim_path="/World/Origin7/Robot")) # return the scene information scene_entities = { @@ -139,10 +132,8 @@ def run_simulator(sim, entities: dict, origins): sim_time = 0.0 count = 0 # Simulate physics - while True: - # Exit when every visualizer window has been closed (works for kit and newton) - if has_no_alive_visualizer_window(sim): - break + # Exit when every visualizer window has been closed (works for kit and newton) + while not has_no_alive_visualizer_window(sim): # reset if count % 200 == 0: # reset counters From e85e64a871c302cf9b9ad7b403a707fbfb8cdfbf Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Wed, 27 May 2026 16:39:04 +0800 Subject: [PATCH 04/11] Fold dt Into Function Call Signed-off-by: Yize Wang --- scripts/demos/quadrupeds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index 308f8cbd1765..d1a45a6aa173 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -82,6 +82,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: sim_utils.create_prim("/World/Origin1", "Xform", translation=origins[0]) # -- Robot anymal_b = Articulation(ANYMAL_B_CFG.replace(prim_path="/World/Origin1/Robot")) + # Origin 2 with Anymal C sim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1]) # -- Robot @@ -181,9 +182,8 @@ def main(): import isaaclab.sim as sim_utils # define simulation time step - dt = 1 / 200 # define simulation configuration - sim_cfg = sim_utils.SimulationCfg(dt=dt, device=args_cli.device, physics=physics_cfg, visualizer_cfgs=[visualizer_cfg]) + sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device, physics=physics_cfg, visualizer_cfgs=[visualizer_cfg]) # Initialize the simulation context sim = sim_utils.SimulationContext(sim_cfg) # Set main camera From 35b051d03892d7e677bcf3c2ee13126cc904950a Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Wed, 27 May 2026 19:20:06 +0800 Subject: [PATCH 05/11] Recover 3-Line Comments Signed-off-by: Yize Wang --- scripts/demos/quadrupeds.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index d1a45a6aa173..5d5544c61a4f 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -42,6 +42,10 @@ import torch from demo_helper import has_no_alive_visualizer_window, resolve_backend_and_visualizer + +## +# Pre-defined configs +## from isaaclab_assets.robots.anymal import ANYMAL_B_CFG, ANYMAL_C_CFG, ANYMAL_D_CFG # isort:skip from isaaclab_assets.robots.spot import SPOT_CFG # isort:skip from isaaclab_assets.robots.unitree import UNITREE_A1_CFG, UNITREE_GO1_CFG, UNITREE_GO2_CFG # isort:skip From 34a07b1f917aff5b8403d0c57c19ff6e0063ae06 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Wed, 27 May 2026 19:33:38 +0800 Subject: [PATCH 06/11] Revert Articulation Import Location Change Signed-off-by: Yize Wang --- scripts/demos/demo_helper.py | 6 +++++- scripts/demos/quadrupeds.py | 12 ++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/demos/demo_helper.py b/scripts/demos/demo_helper.py index ea489b130245..8404bc8709e7 100644 --- a/scripts/demos/demo_helper.py +++ b/scripts/demos/demo_helper.py @@ -20,9 +20,11 @@ def resolve_backend_and_visualizer(args, newton_cfg=None): # Resolve physics cfg if args.physics == "physx": from isaaclab_physx.physics import PhysxCfg + physics_cfg = PhysxCfg() elif args.physics == "newton_mjwarp": from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg + DEFAULT_NEWTON_CFG = NewtonCfg( solver_cfg=MJWarpSolverCfg( njmax=70, @@ -44,9 +46,11 @@ def resolve_backend_and_visualizer(args, newton_cfg=None): viz_type = args.visualizer or DEFAULT_VISUALIZER if viz_type == "kit": from isaaclab_visualizers.kit import KitVisualizerCfg + visualizer_cfg = KitVisualizerCfg() elif viz_type == "newton": from isaaclab_visualizers.newton import NewtonVisualizerCfg + visualizer_cfg = NewtonVisualizerCfg() else: raise ValueError(f"Unsupported --visualizer value: {viz_type}") @@ -70,4 +74,4 @@ def resolve_backend_and_visualizer(args, newton_cfg=None): def has_no_alive_visualizer_window(sim) -> bool: """Check if there are no alive visualizer windows.""" visualizers = sim.visualizers or () - return not any(v.is_running() and not v.is_closed for v in visualizers) \ No newline at end of file + return not any(v.is_running() and not v.is_closed for v in visualizers) diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index 5d5544c61a4f..ae8be09e99bb 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -43,13 +43,6 @@ from demo_helper import has_no_alive_visualizer_window, resolve_backend_and_visualizer -## -# Pre-defined configs -## -from isaaclab_assets.robots.anymal import ANYMAL_B_CFG, ANYMAL_C_CFG, ANYMAL_D_CFG # isort:skip -from isaaclab_assets.robots.spot import SPOT_CFG # isort:skip -from isaaclab_assets.robots.unitree import UNITREE_A1_CFG, UNITREE_GO1_CFG, UNITREE_GO2_CFG # isort:skip - def define_origins(num_origins: int, spacing: float) -> list[list[float]]: """Defines the origins of the scene.""" @@ -70,6 +63,9 @@ def design_scene() -> tuple[dict, list[list[float]]]: """Designs the scene.""" import isaaclab.sim as sim_utils from isaaclab.assets import Articulation + from isaaclab_assets.robots.anymal import ANYMAL_B_CFG, ANYMAL_C_CFG, ANYMAL_D_CFG # isort:skip + from isaaclab_assets.robots.spot import SPOT_CFG # isort:skip + from isaaclab_assets.robots.unitree import UNITREE_A1_CFG, UNITREE_GO1_CFG, UNITREE_GO2_CFG # isort:skip # Ground-plane cfg = sim_utils.GroundPlaneCfg() @@ -185,7 +181,6 @@ def main(): with resolve_backend_and_visualizer(args_cli) as (physics_cfg, visualizer_cfg): import isaaclab.sim as sim_utils - # define simulation time step # define simulation configuration sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device, physics=physics_cfg, visualizer_cfgs=[visualizer_cfg]) # Initialize the simulation context @@ -194,6 +189,7 @@ def main(): 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() + # convert origins to tensor scene_origins = torch.tensor(scene_origins, device=sim.device) # Play the simulator sim.reset() From 018a1e663af53e9dfea9dfb3b23e8c3c80292747 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Wed, 27 May 2026 20:01:28 +0800 Subject: [PATCH 07/11] Add Instructions Signed-off-by: Yize Wang --- scripts/demos/quadrupeds.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index ae8be09e99bb..7b464d49130f 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -10,9 +10,13 @@ # Usage with the default PhysX backend (launches Isaac Sim Kit by default). ./isaaclab.sh -p scripts/demos/quadrupeds.py + ./isaaclab.sh -p scripts/demos/quadrupeds.py --visualizer kit + ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics physx + ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics physx --visualizer kit # Usage with the kit-less Newton (MJWarp) backend (launches Isaac Sim Kit by default). ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics newton_mjwarp + ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics newton_mjwarp --visualizer kit # Usage with the Newton (MJWarp) backend without Kit (launches Newton visualizer). ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics newton_mjwarp --visualizer newton From 9181225aa3c02fc6af2b1daa3334bd7355382566 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Thu, 28 May 2026 10:07:06 +0800 Subject: [PATCH 08/11] Support Physx+NewtonViz Signed-off-by: Yize Wang --- scripts/demos/demo_helper.py | 13 ++++++++----- scripts/demos/quadrupeds.py | 10 +++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/scripts/demos/demo_helper.py b/scripts/demos/demo_helper.py index 8404bc8709e7..8cd43026df1d 100644 --- a/scripts/demos/demo_helper.py +++ b/scripts/demos/demo_helper.py @@ -14,7 +14,8 @@ def resolve_backend_and_visualizer(args, newton_cfg=None): """Resolve physics + visualizer cfgs from ``--physics`` / ``--visualizer``. - Yields ``(physics_cfg, visualizer_cfg)``. Kit is closed automatically on exit. + Yields ``(physics_cfg, visualizer_cfg)``. Kit is launched when required by + either the visualizer or the physics backend, and closed automatically on exit. """ # Resolve physics cfg @@ -42,8 +43,9 @@ def resolve_backend_and_visualizer(args, newton_cfg=None): raise ValueError(f"Unsupported --physics value: {args.physics}") # Resolve visualizer cfg - DEFAULT_VISUALIZER = "kit" - viz_type = args.visualizer or DEFAULT_VISUALIZER + if not isinstance(args.visualizer, list) or len(args.visualizer) != 1: + raise ValueError("Demos support exactly one --visualizer value: kit or newton.") + viz_type = args.visualizer[0] if viz_type == "kit": from isaaclab_visualizers.kit import KitVisualizerCfg @@ -55,9 +57,10 @@ def resolve_backend_and_visualizer(args, newton_cfg=None): else: raise ValueError(f"Unsupported --visualizer value: {viz_type}") - # Kit needs to be closed by explicitly calling AppLauncher.close() + # PhysX requires Isaac Sim Kit extensions even when rendering through a + # standalone visualizer such as Newton. close_fn = None - if viz_type == "kit": + if viz_type == "kit" or args.physics == "physx": from isaaclab.app import AppLauncher args.visualizer = [viz_type] diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index 7b464d49130f..221b57e79591 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -21,8 +21,8 @@ # Usage with the Newton (MJWarp) backend without Kit (launches Newton visualizer). ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics newton_mjwarp --visualizer newton - # Usage with the PhysX backend without Kit (launches Newton visualizer). - # TODO(yizew@nvidia.com): Not supported yet. Investigation needed. + # Usage with the PhysX backend and Newton visualizer. + # PhysX still launches Isaac Sim Kit headless because it depends on Kit extensions. ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics physx --visualizer newton """ @@ -34,11 +34,11 @@ from isaaclab.app import AppLauncher # add argparse arguments -parser = argparse.ArgumentParser(description="This script demonstrates different legged robots.", conflict_handler="resolve") +parser = argparse.ArgumentParser(description="This script demonstrates different legged robots.") +parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) -parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") -parser.add_argument("--visualizer", default="kit", choices=["kit", "newton"], help="Visualizer backend.") +parser.set_defaults(visualizer=["kit"]) # parse the arguments args_cli = parser.parse_args() From a4272837ec61684981f316c974e0787331dbc7b4 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Thu, 28 May 2026 10:22:06 +0800 Subject: [PATCH 09/11] Append Usage Examples to Help Menu Signed-off-by: Yize Wang --- scripts/demos/demo_helper.py | 11 +++++++++++ scripts/demos/quadrupeds.py | 14 ++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/scripts/demos/demo_helper.py b/scripts/demos/demo_helper.py index 8cd43026df1d..0f3f3c2c7fb9 100644 --- a/scripts/demos/demo_helper.py +++ b/scripts/demos/demo_helper.py @@ -7,6 +7,7 @@ This script contains helper functions for the demos. """ +import textwrap from contextlib import contextmanager @@ -78,3 +79,13 @@ def has_no_alive_visualizer_window(sim) -> bool: """Check if there are no alive visualizer windows.""" visualizers = sim.visualizers or () return not any(v.is_running() and not v.is_closed for v in visualizers) + + +def get_usage_examples(doc: str) -> str: + """Return usage examples from the module docstring.""" + marker = ".. code-block:: bash" + if marker not in doc: + return "" + + examples = textwrap.dedent(doc.split(marker, maxsplit=1)[1]).strip() + return "Examples:\n" + textwrap.indent(examples, " ") diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index 221b57e79591..500099b27c8f 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -31,10 +31,16 @@ import argparse +from demo_helper import get_usage_examples, has_no_alive_visualizer_window, resolve_backend_and_visualizer + from isaaclab.app import AppLauncher # add argparse arguments -parser = argparse.ArgumentParser(description="This script demonstrates different legged robots.") +parser = argparse.ArgumentParser( + description="This script demonstrates different legged robots.", + epilog=get_usage_examples(str(__doc__)), + formatter_class=argparse.RawDescriptionHelpFormatter, +) parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) @@ -45,8 +51,6 @@ import numpy as np import torch -from demo_helper import has_no_alive_visualizer_window, resolve_backend_and_visualizer - def define_origins(num_origins: int, spacing: float) -> list[list[float]]: """Defines the origins of the scene.""" @@ -186,7 +190,9 @@ def main(): import isaaclab.sim as sim_utils # define simulation configuration - sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device, physics=physics_cfg, visualizer_cfgs=[visualizer_cfg]) + sim_cfg = sim_utils.SimulationCfg( + dt=0.005, device=args_cli.device, physics=physics_cfg, visualizer_cfgs=[visualizer_cfg] + ) # Initialize the simulation context sim = sim_utils.SimulationContext(sim_cfg) # Set main camera From b20cdaba81c3c5b8126392786e027662a7e614bc Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Thu, 28 May 2026 10:40:06 +0800 Subject: [PATCH 10/11] Recover 2 Comments Signed-off-by: Yize Wang --- scripts/demos/quadrupeds.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index 500099b27c8f..9d40bdf24841 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -44,6 +44,7 @@ parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") # append AppLauncher cli args AppLauncher.add_app_launcher_args(parser) +# demos should open Kit visualizer by default parser.set_defaults(visualizer=["kit"]) # parse the arguments args_cli = parser.parse_args() @@ -210,4 +211,5 @@ def main(): if __name__ == "__main__": + # run the main function main() From 5e5798252b35065308555905ac1aabf1e1390cf9 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Thu, 28 May 2026 14:07:53 +0800 Subject: [PATCH 11/11] Enable PhysxCfg Customization Signed-off-by: Yize Wang --- scripts/demos/demo_helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/demos/demo_helper.py b/scripts/demos/demo_helper.py index 0f3f3c2c7fb9..fcc694c59ce7 100644 --- a/scripts/demos/demo_helper.py +++ b/scripts/demos/demo_helper.py @@ -12,7 +12,7 @@ @contextmanager -def resolve_backend_and_visualizer(args, newton_cfg=None): +def resolve_backend_and_visualizer(args, physx_cfg=None, newton_cfg=None): """Resolve physics + visualizer cfgs from ``--physics`` / ``--visualizer``. Yields ``(physics_cfg, visualizer_cfg)``. Kit is launched when required by @@ -23,7 +23,7 @@ def resolve_backend_and_visualizer(args, newton_cfg=None): if args.physics == "physx": from isaaclab_physx.physics import PhysxCfg - physics_cfg = PhysxCfg() + physics_cfg = physx_cfg or PhysxCfg() elif args.physics == "newton_mjwarp": from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg