Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions scripts/demos/demo_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# 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 contains helper functions for the demos.
"""

import textwrap
from contextlib import contextmanager


@contextmanager
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
either the visualizer or the physics backend, and closed automatically on exit.
"""

# Resolve physics cfg
if args.physics == "physx":
from isaaclab_physx.physics import PhysxCfg

physics_cfg = physx_cfg or PhysxCfg()
elif args.physics == "newton_mjwarp":
from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg

DEFAULT_NEWTON_CFG = NewtonCfg(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how generalizable is this cfg? do we expect this to work for most scripts? if not, it may be better to just keep this as a default MjWarpSolverCfg() object.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently works for most scripts while the default one barely works.

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
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]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we only parse the first element? is there a limitation to supporting multiple visualizers like the training scripts?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not that we expect multiple visualizer. Let me take a look at training scripts and get back to you.

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}")

# PhysX requires Isaac Sim Kit extensions even when rendering through a
# standalone visualizer such as Newton.
close_fn = None
if viz_type == "kit" or args.physics == "physx":
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; 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."""
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, " ")
91 changes: 54 additions & 37 deletions scripts/demos/quadrupeds.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,50 @@

.. code-block:: bash

# Usage
# 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

# 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
Comment on lines +11 to +26
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be simplified a bit to avoid duplicates

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Tried to exhaust all combinations. I can reduce to the minimal.


"""

"""Launch Isaac Sim Simulator first."""
"""Parse CLI first so we can decide whether to launch Isaac Sim Kit."""

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)
# demos 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 numpy as np
import torch

import isaaclab.sim as sim_utils
from isaaclab.assets import Articulation
Comment thread
hujc7 marked this conversation as resolved.

##
# 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."""
Expand All @@ -65,6 +70,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 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()
cfg.func("/World/defaultGroundPlane", cfg)
Expand Down Expand Up @@ -124,14 +135,15 @@ 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):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to remove the type hint here?

Copy link
Copy Markdown
Author

@YizeWang YizeWang May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reason as my last comment: https://github.com/isaac-sim/IsaacLab/pull/5771/changes#r3310474409. These types are not defined yet (these packages not imported). Let me know if my understanding in the previous comment is wrong.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use from __future__ import annotations and type checking for this. shouldn't be necessary to remove the type hints

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will update this.

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():
# Exit when every visualizer window has been closed (works for kit and newton)
while not has_no_alive_visualizer_window(sim):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the double negative can be a bit confusing. would it make sense to make the utility has_alive_visualizer_window instead?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

# reset
if count % 200 == 0:
# reset counters
Expand Down Expand Up @@ -175,24 +187,29 @@ 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 configuration
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
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()
# 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()