From d82bcc2b5529a5e04147f1dd7a8bc3da679dca7b Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Thu, 14 May 2026 23:40:27 +0800 Subject: [PATCH 1/6] Fix Scripts Under scripts/demos Using Default PhysX Backend Description to be updated to be compliant with IsaacLab conventions. Signed-off-by: Yize Wang --- scripts/demos/bin_packing.py | 51 ++++++++++++++++------ scripts/demos/h1_locomotion.py | 4 +- scripts/demos/quadcopter.py | 3 +- source/isaaclab/isaaclab/terrains/utils.py | 6 +-- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/scripts/demos/bin_packing.py b/scripts/demos/bin_packing.py index fd0f02a4a4a..0d52fb7bc8e 100644 --- a/scripts/demos/bin_packing.py +++ b/scripts/demos/bin_packing.py @@ -46,6 +46,7 @@ import math import torch +import warp as wp import isaaclab.sim as sim_utils import isaaclab.utils.math as math_utils @@ -141,6 +142,21 @@ class MultiObjectSceneCfg(InteractiveSceneCfg): ) +def torch_indices_to_warp(indices: torch.Tensor, device: str | None = None) -> wp.array: + """Convert Torch indices to a Warp int32 array.""" + indices = indices.to(dtype=torch.int32).contiguous() + return wp.clone(wp.from_torch(indices, dtype=wp.int32), device=device or str(indices.device)) + + +def reshape_data_to_view_tensor( + rigid_object_collection: RigidObjectCollection, data: torch.Tensor, data_dim: int, device: str +) -> torch.Tensor: + """Reshape Torch data from collection order to PhysX view order.""" + data = data.contiguous() + data_wp = wp.from_torch(data, dtype=wp.float32) + return wp.to_torch(rigid_object_collection.reshape_data_to_view_3d(data_wp, data_dim, device=device)) + + def reset_object_collections( scene: InteractiveScene, asset_name: str, view_states: torch.Tensor, view_ids: torch.Tensor, noise: bool = False ) -> None: @@ -189,8 +205,11 @@ def reset_object_collections( view_states[view_ids, :7] = torch.concat((positions, orientations), dim=-1) view_states[view_ids, 7:] = new_velocities - rigid_object_collection.root_view.set_transforms(view_states[:, :7], indices=view_ids) - rigid_object_collection.root_view.set_velocities(view_states[:, 7:], indices=view_ids) + view_ids_wp = torch_indices_to_warp(view_ids, device=scene.device) + transforms_wp = wp.from_torch(view_states[:, :7].contiguous(), dtype=wp.float32) + velocities_wp = wp.from_torch(view_states[:, 7:].contiguous(), dtype=wp.float32) + rigid_object_collection.root_view.set_transforms(transforms_wp, indices=view_ids_wp) + rigid_object_collection.root_view.set_velocities(velocities_wp, indices=view_ids_wp) def build_grocery_defaults( @@ -280,14 +299,16 @@ def run_simulator(sim: SimulationContext, scene: InteractiveScene) -> None: # Offset poses into each environment's world frame. active_spawn_poses[..., :3] += scene.env_origins.view(-1, 1, 3) cached_spawn_poses[..., :3] += scene.env_origins.view(-1, 1, 3) - active_spawn_poses = groceries.reshape_data_to_view(active_spawn_poses) - cached_spawn_poses = groceries.reshape_data_to_view(cached_spawn_poses) - spawn_w = groceries.reshape_data_to_view(default_state_w).clone() + active_spawn_poses = reshape_data_to_view_tensor(groceries, active_spawn_poses, 7, device) + cached_spawn_poses = reshape_data_to_view_tensor(groceries, cached_spawn_poses, 7, device) + spawn_w = reshape_data_to_view_tensor(groceries, default_state_w, 13, device).clone() - groceries_mask_helper = torch.arange(num_objects * num_envs, device=device) % num_objects - # Precompute a helper mask to toggle objects between active and cached sets. + # Precompute helpers in PhysX view order: body_0/env_0, body_0/env_1, ..., body_1/env_0, ... + view_env_ids = torch.arange(num_envs, device=device).repeat(num_objects) + view_object_ids = torch.arange(num_objects, device=device).repeat_interleave(num_envs) # Precompute XY bounds [[x_min,y_min],[x_max,y_max]] bounds_xy = torch.as_tensor(BIN_XY_BOUND, device=device, dtype=spawn_w.dtype) + view_indices_cpu_wp = wp.array(list(range(num_envs * num_objects)), dtype=wp.int32, device="cpu") # Simulation loop while simulation_app.is_running(): # Reset @@ -295,17 +316,20 @@ def run_simulator(sim: SimulationContext, scene: InteractiveScene) -> None: # reset counter count = 0 # Randomly choose how many groceries stay active in each environment. - num_active_groceries = torch.randint(MIN_OBJECTS_PER_BIN, num_objects, (num_envs, 1), device=device) - groceries_mask = (groceries_mask_helper.view(num_envs, -1) < num_active_groceries).view(-1, 1) + num_active_groceries = torch.randint(MIN_OBJECTS_PER_BIN, num_objects, (num_envs,), device=device) + groceries_mask = (view_object_ids < num_active_groceries[view_env_ids]).unsqueeze(-1) spawn_w[:, :7] = cached_spawn_poses * (~groceries_mask) + active_spawn_poses * groceries_mask # Retrieve positions with Timer("[INFO] Time to reset scene: "): reset_object_collections(scene, "groceries", spawn_w, view_indices[~groceries_mask.view(-1)]) reset_object_collections(scene, "groceries", spawn_w, view_indices[groceries_mask.view(-1)], noise=True) # Vary the mass and gravity settings so cached objects stay parked. - random_masses = torch.rand(groceries.num_instances * num_objects, device=device) * 0.2 + 0.2 - groceries.root_view.set_masses(random_masses.cpu(), view_indices.cpu()) - groceries.root_view.set_disable_gravities((~groceries_mask).cpu(), indices=view_indices.cpu()) + random_masses = torch.rand((groceries.num_instances * num_objects, 1), device="cpu") * 0.2 + 0.2 + random_masses_wp = wp.from_torch(random_masses.contiguous(), dtype=wp.float32) + groceries.root_view.set_masses(random_masses_wp, indices=view_indices_cpu_wp) + disable_gravities = (~groceries_mask).cpu().to(torch.uint8).contiguous() + disable_gravities_wp = wp.from_torch(disable_gravities, dtype=wp.uint8) + groceries.root_view.set_disable_gravities(disable_gravities_wp, indices=view_indices_cpu_wp) scene.reset() # Write data to sim @@ -314,7 +338,8 @@ def run_simulator(sim: SimulationContext, scene: InteractiveScene) -> None: sim.step() # Bring out-of-bounds objects back to the bin in one pass. - xy = groceries.reshape_data_to_view(groceries.data.object_pos_w - scene.env_origins.unsqueeze(1))[:, :2] + body_pos_w = groceries.data.body_link_pos_w.torch - scene.env_origins.unsqueeze(1) + xy = reshape_data_to_view_tensor(groceries, body_pos_w, 3, device)[:, :2] out_bound = torch.nonzero(~((xy >= bounds_xy[0]) & (xy <= bounds_xy[1])).all(dim=1), as_tuple=False).flatten() if out_bound.numel(): # Teleport stray objects back into the active stack to keep the bin tidy. diff --git a/scripts/demos/h1_locomotion.py b/scripts/demos/h1_locomotion.py index 7e7429c35b0..e77b2b663cd 100644 --- a/scripts/demos/h1_locomotion.py +++ b/scripts/demos/h1_locomotion.py @@ -16,6 +16,7 @@ """Launch Isaac Sim Simulator first.""" import argparse +import importlib.metadata as metadata import os import sys @@ -57,7 +58,7 @@ from isaaclab.sim.utils.stage import get_current_stage from isaaclab.utils.math import quat_apply -from isaaclab_rl.rsl_rl import RslRlOnPolicyRunnerCfg, RslRlVecEnvWrapper +from isaaclab_rl.rsl_rl import RslRlOnPolicyRunnerCfg, RslRlVecEnvWrapper, handle_deprecated_rsl_rl_cfg from isaaclab_rl.utils.pretrained_checkpoint import get_published_pretrained_checkpoint from isaaclab_tasks.manager_based.locomotion.velocity.config.h1.rough_env_cfg import H1RoughEnvCfg_PLAY @@ -85,6 +86,7 @@ def __init__(self): """Initializes environment config designed for the interactive model and sets up the environment, loads pre-trained checkpoints, and registers keyboard events.""" agent_cfg: RslRlOnPolicyRunnerCfg = cli_args.parse_rsl_rl_cfg(TASK, args_cli) + agent_cfg = handle_deprecated_rsl_rl_cfg(agent_cfg, metadata.version("rsl-rl-lib")) # load the trained jit policy checkpoint = get_published_pretrained_checkpoint(RL_LIBRARY, TASK) # create envionrment diff --git a/scripts/demos/quadcopter.py b/scripts/demos/quadcopter.py index 4cc9e1a2709..5efa67beb67 100644 --- a/scripts/demos/quadcopter.py +++ b/scripts/demos/quadcopter.py @@ -35,6 +35,7 @@ """Rest everything follows.""" import torch +import warp as wp import isaaclab.sim as sim_utils from isaaclab.assets import Articulation @@ -74,7 +75,7 @@ def main(): # Fetch relevant parameters to make the quadcopter hover in place prop_body_ids = robot.find_bodies("m.*_prop")[0] - robot_mass = robot.root_view.get_masses().sum() + robot_mass = wp.to_torch(robot.root_view.get_masses())[0].sum() gravity = torch.tensor(sim.cfg.gravity, device=sim.device).norm() # Now we are ready! diff --git a/source/isaaclab/isaaclab/terrains/utils.py b/source/isaaclab/isaaclab/terrains/utils.py index 0feee6ca51f..837d4fca454 100644 --- a/source/isaaclab/isaaclab/terrains/utils.py +++ b/source/isaaclab/isaaclab/terrains/utils.py @@ -17,7 +17,7 @@ def color_meshes_by_height(meshes: list[trimesh.Trimesh], **kwargs) -> trimesh.Trimesh: """ Color the vertices of a trimesh object based on the z-coordinate (height) of each vertex, - using the Turbo colormap. If the z-coordinates are all the same, the vertices will be colored + using the Viridis colormap. If the z-coordinates are all the same, the vertices will be colored with a single color. Args: @@ -27,7 +27,7 @@ def color_meshes_by_height(meshes: list[trimesh.Trimesh], **kwargs) -> trimesh.T color: A list of 3 integers in the range [0,255] representing the RGB color of the mesh. Used when the z-coordinates of all vertices are the same. Defaults to [172, 216, 230]. - color_map: The name of the color map to be used. Defaults to "turbo". + color_map: The name of the color map to be used. Defaults to "viridis". Returns: A trimesh object with the vertices colored based on the z-coordinate (height) of each vertex. @@ -49,7 +49,7 @@ def color_meshes_by_height(meshes: list[trimesh.Trimesh], **kwargs) -> trimesh.T # clip lower and upper bounds to have better color mapping heights_normalized = np.clip(heights_normalized, 0.1, 0.9) # Get the color for each vertex based on the height - color_map = kwargs.pop("color_map", "turbo") + color_map = kwargs.pop("color_map", "viridis") # options: magma, inferno, plasma, viridis colors = trimesh.visual.color.interpolate(heights_normalized, color_map=color_map) # Set the vertex colors mesh.visual.vertex_colors = colors From c9ecb788944fcb5b96bcf4c547d2df4596d94581 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Wed, 20 May 2026 17:47:31 +0800 Subject: [PATCH 2/6] Support Demo Scripts with Newton Backend Signed-off-by: Yize Wang --- scripts/demos/arl_robot_1.py | 20 +- scripts/demos/arms.py | 35 +- scripts/demos/bin_packing.py | 14 +- scripts/demos/bipeds.py | 16 +- scripts/demos/deformables.py | 14 +- scripts/demos/h1_locomotion.py | 12 +- scripts/demos/hands.py | 16 +- scripts/demos/haply_teleoperation.py | 13 +- scripts/demos/markers.py | 16 +- scripts/demos/multi_asset.py | 18 +- scripts/demos/pick_and_place.py | 21 +- scripts/demos/procedural_terrain.py | 16 +- scripts/demos/quadcopter.py | 19 +- scripts/demos/quadrupeds.py | 16 +- .../isaaclab_tasks/utils/demo_launcher.py | 304 ++++++++++++++++++ 15 files changed, 424 insertions(+), 126 deletions(-) create mode 100644 source/isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py diff --git a/scripts/demos/arl_robot_1.py b/scripts/demos/arl_robot_1.py index 987e6be6b2e..6d812a4911a 100644 --- a/scripts/demos/arl_robot_1.py +++ b/scripts/demos/arl_robot_1.py @@ -6,22 +6,26 @@ """ Script to view ARL Robot 1. -Launch Isaac Sim Simulator first. +.. code-block:: bash + + # Usage + ./isaaclab.sh -p scripts/demos/arl_robot_1.py + + # Run with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/arl_robot_1.py physics=newton_mjwarp --visualizer newton + """ # Create argparser import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher parser = argparse.ArgumentParser(description="View ARL Robot 1 with Lee Position Controller.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) -args_cli = parser.parse_args() +args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +simulation_app = DemoAppLauncher(args_cli, kit_required=True) """Rest everything follows.""" @@ -45,7 +49,7 @@ def main(): # Create simulation context sim_cfg = sim_utils.SimulationCfg(dt=0.01) - sim = SimulationContext(sim_cfg) + sim = simulation_app.create_context(sim_cfg, SimulationContext) # Create a dome light with light blue color stage = omni.usd.get_context().get_stage() diff --git a/scripts/demos/arms.py b/scripts/demos/arms.py index b08686a8e52..b5d3b7ef1b2 100644 --- a/scripts/demos/arms.py +++ b/scripts/demos/arms.py @@ -11,26 +11,23 @@ # Usage ./isaaclab.sh -p scripts/demos/arms.py -""" + # Run with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/arms.py physics=newton_mjwarp --visualizer newton -"""Launch Isaac Sim Simulator first.""" +""" import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates different single-arm manipulators.") -# 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() +args_cli = DemoAppLauncher.parse_args(parser) +simulation_app = DemoAppLauncher(args_cli) -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app """Rest everything follows.""" @@ -91,7 +88,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: cfg = sim_utils.UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Mounts/SeattleLabTable/table_instanceable.usd") cfg.func("/World/Origin1/Table", cfg, translation=(0.55, 0.0, 1.05)) # -- Robot - franka_arm_cfg = FRANKA_PANDA_CFG.replace(prim_path="/World/Origin1/Robot") + franka_arm_cfg = simulation_app.tune_articulation_cfg(FRANKA_PANDA_CFG.replace(prim_path="/World/Origin1/Robot")) franka_arm_cfg.init_state.pos = (0.0, 0.0, 1.05) franka_panda = Articulation(cfg=franka_arm_cfg) @@ -103,7 +100,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: ) cfg.func("/World/Origin2/Table", cfg, translation=(0.0, 0.0, 1.03)) # -- Robot - ur10_cfg = UR10_CFG.replace(prim_path="/World/Origin2/Robot") + ur10_cfg = simulation_app.tune_articulation_cfg(UR10_CFG.replace(prim_path="/World/Origin2/Robot")) ur10_cfg.init_state.pos = (0.0, 0.0, 1.03) ur10 = Articulation(cfg=ur10_cfg) @@ -113,7 +110,9 @@ def design_scene() -> tuple[dict, list[list[float]]]: cfg = sim_utils.UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Mounts/ThorlabsTable/table_instanceable.usd") cfg.func("/World/Origin3/Table", cfg, translation=(0.0, 0.0, 0.8)) # -- Robot - kinova_arm_cfg = KINOVA_JACO2_N7S300_CFG.replace(prim_path="/World/Origin3/Robot") + kinova_arm_cfg = simulation_app.tune_articulation_cfg( + KINOVA_JACO2_N7S300_CFG.replace(prim_path="/World/Origin3/Robot") + ) kinova_arm_cfg.init_state.pos = (0.0, 0.0, 0.8) kinova_j2n7s300 = Articulation(cfg=kinova_arm_cfg) @@ -123,7 +122,9 @@ def design_scene() -> tuple[dict, list[list[float]]]: cfg = sim_utils.UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Mounts/ThorlabsTable/table_instanceable.usd") cfg.func("/World/Origin4/Table", cfg, translation=(0.0, 0.0, 0.8)) # -- Robot - kinova_arm_cfg = KINOVA_JACO2_N6S300_CFG.replace(prim_path="/World/Origin4/Robot") + kinova_arm_cfg = simulation_app.tune_articulation_cfg( + KINOVA_JACO2_N6S300_CFG.replace(prim_path="/World/Origin4/Robot") + ) kinova_arm_cfg.init_state.pos = (0.0, 0.0, 0.8) kinova_j2n6s300 = Articulation(cfg=kinova_arm_cfg) @@ -133,7 +134,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: cfg = sim_utils.UsdFileCfg(usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Mounts/SeattleLabTable/table_instanceable.usd") cfg.func("/World/Origin5/Table", cfg, translation=(0.55, 0.0, 1.05)) # -- Robot - kinova_arm_cfg = KINOVA_GEN3_N7_CFG.replace(prim_path="/World/Origin5/Robot") + kinova_arm_cfg = simulation_app.tune_articulation_cfg(KINOVA_GEN3_N7_CFG.replace(prim_path="/World/Origin5/Robot")) kinova_arm_cfg.init_state.pos = (0.0, 0.0, 1.05) kinova_gen3n7 = Articulation(cfg=kinova_arm_cfg) @@ -145,7 +146,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: ) cfg.func("/World/Origin6/Table", cfg, translation=(0.0, 0.0, 1.03)) # -- Robot - sawyer_arm_cfg = SAWYER_CFG.replace(prim_path="/World/Origin6/Robot") + sawyer_arm_cfg = simulation_app.tune_articulation_cfg(SAWYER_CFG.replace(prim_path="/World/Origin6/Robot")) sawyer_arm_cfg.init_state.pos = (0.0, 0.0, 1.03) sawyer = Articulation(cfg=sawyer_arm_cfg) @@ -216,7 +217,7 @@ def main(): """Main function.""" # Initialize the simulation context sim_cfg = sim_utils.SimulationCfg(device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) + sim = simulation_app.create_context(sim_cfg, sim_utils.SimulationContext) # Set main camera sim.set_camera_view([3.5, 0.0, 3.2], [0.0, 0.0, 0.5]) # design scene @@ -231,7 +232,5 @@ def main(): if __name__ == "__main__": - # run the main function main() - # close sim app simulation_app.close() diff --git a/scripts/demos/bin_packing.py b/scripts/demos/bin_packing.py index 0d52fb7bc8e..9f81b85c760 100644 --- a/scripts/demos/bin_packing.py +++ b/scripts/demos/bin_packing.py @@ -16,6 +16,9 @@ # Usage ./isaaclab.sh -p scripts/demos/bin_packing.py --num_envs 32 + # Run with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/bin_packing.py physics=newton_mjwarp --visualizer newton + """ from __future__ import annotations @@ -25,21 +28,18 @@ import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # add argparse arguments parser = argparse.ArgumentParser(description="Demo usage of RigidObjectCollection through bin packing example") 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) # demos should open Kit visualizer by default parser.set_defaults(visualizer=["kit"]) # parse the arguments -args_cli = parser.parse_args() +args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +simulation_app = DemoAppLauncher(args_cli) """Rest everything follows.""" @@ -358,7 +358,7 @@ def main() -> None: """ # Load kit helper sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device) - sim = SimulationContext(sim_cfg) + sim = simulation_app.create_context(sim_cfg, SimulationContext) # Set main camera sim.set_camera_view((2.5, 0.0, 4.0), (0.0, 0.0, 2.0)) diff --git a/scripts/demos/bipeds.py b/scripts/demos/bipeds.py index 68f79db14fc..a55ae538dab 100644 --- a/scripts/demos/bipeds.py +++ b/scripts/demos/bipeds.py @@ -11,26 +11,24 @@ # Usage ./isaaclab.sh -p scripts/demos/bipeds.py -""" + # Run with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/bipeds.py physics=newton_mjwarp --visualizer newton -"""Launch Isaac Sim Simulator first.""" +""" import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates how to simulate bipedal robots.") -# 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() +args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +simulation_app = DemoAppLauncher(args_cli) """Rest everything follows.""" @@ -122,7 +120,7 @@ def main(): """Main function.""" # Load kit helper sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device) - sim = SimulationContext(sim_cfg) + sim = simulation_app.create_context(sim_cfg, SimulationContext) # Set main camera sim.set_camera_view(eye=[3.0, 0.0, 2.25], target=[0.0, 0.0, 1.0]) diff --git a/scripts/demos/deformables.py b/scripts/demos/deformables.py index 71f40b5f9c4..4768897d0db 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -12,24 +12,20 @@ """ -"""Launch Isaac Sim Simulator first.""" - +# TODO: Non-rigid bodies are not supported by the newton backend yet. import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # create argparser parser = argparse.ArgumentParser(description="This script demonstrates how to spawn deformable prims into the scene.") -# 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() +args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +simulation_app = DemoAppLauncher(args_cli, kit_required=True) """Rest everything follows.""" @@ -225,7 +221,7 @@ def main(): """Main function.""" # Initialize the simulation context sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) + sim = simulation_app.create_context(sim_cfg, sim_utils.SimulationContext) # Set main camera sim.set_camera_view([4.0, 4.0, 3.0], [0.5, 0.5, 0.0]) diff --git a/scripts/demos/h1_locomotion.py b/scripts/demos/h1_locomotion.py index e77b2b663cd..a979a941159 100644 --- a/scripts/demos/h1_locomotion.py +++ b/scripts/demos/h1_locomotion.py @@ -15,6 +15,8 @@ """Launch Isaac Sim Simulator first.""" +# TODO: Known issues in the newton backend: robots instantly fall upon spawn + import argparse import importlib.metadata as metadata import os @@ -24,7 +26,7 @@ import scripts.reinforcement_learning.rsl_rl.cli_args as cli_args # isort: skip -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # add argparse arguments parser = argparse.ArgumentParser( @@ -32,16 +34,13 @@ ) # append RSL-RL cli arguments cli_args.add_rsl_rl_args(parser) -# 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() +args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +simulation_app = DemoAppLauncher(args_cli, kit_required=True) """Rest everything follows.""" @@ -218,6 +217,7 @@ def main(): """Main function.""" demo_h1 = H1RoughDemo() obs, _ = demo_h1.env.reset() + print("[INFO]: Setup complete...") while simulation_app.is_running(): # check for selected robots demo_h1.update_selected_object() diff --git a/scripts/demos/hands.py b/scripts/demos/hands.py index 4b9a12d7b43..b9436ccc196 100644 --- a/scripts/demos/hands.py +++ b/scripts/demos/hands.py @@ -11,26 +11,24 @@ # Usage ./isaaclab.sh -p scripts/demos/hands.py -""" + # Run with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/hands.py physics=newton_mjwarp --visualizer newton -"""Launch Isaac Sim Simulator first.""" +""" import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates different dexterous hands.") -# 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() +args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +simulation_app = DemoAppLauncher(args_cli) """Rest everything follows.""" @@ -151,7 +149,7 @@ def main(): """Main function.""" # Initialize the simulation context sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) + sim = simulation_app.create_context(sim_cfg, sim_utils.SimulationContext) # Set main camera sim.set_camera_view(eye=[0.0, -0.5, 1.5], target=[0.0, -0.2, 0.5]) # design scene diff --git a/scripts/demos/haply_teleoperation.py b/scripts/demos/haply_teleoperation.py index 9880cae46f2..63abe26a079 100644 --- a/scripts/demos/haply_teleoperation.py +++ b/scripts/demos/haply_teleoperation.py @@ -23,6 +23,9 @@ # With sensitivity adjustment ./isaaclab.sh -p scripts/demos/haply_teleoperation.py --pos_sensitivity 2.0 + # Run with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/haply_teleoperation.py physics=newton_mjwarp --visualizer newton + Prerequisites: 1. Install websockets package: pip install websockets 2. Have Haply SDK running and accessible via WebSocket @@ -33,7 +36,7 @@ import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # add argparse arguments parser = argparse.ArgumentParser(description="Demonstration of Haply device teleoperation with Isaac Lab.") @@ -51,11 +54,9 @@ help="Position sensitivity scaling factor.", ) -AppLauncher.add_app_launcher_args(parser) parser.set_defaults(visualizer=["kit"]) -args_cli = parser.parse_args() -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +args_cli = DemoAppLauncher.parse_args(parser) +simulation_app = DemoAppLauncher(args_cli) import numpy as np import torch @@ -344,7 +345,7 @@ def run_simulator( def main(): """Main function to set up and run the Haply teleoperation demo.""" sim_cfg = sim_utils.SimulationCfg(device=args_cli.device, dt=1 / 200) - sim = sim_utils.SimulationContext(sim_cfg) + sim = simulation_app.create_context(sim_cfg, sim_utils.SimulationContext) # set the simulation view sim.set_camera_view([1.6, 1.0, 1.70], [0.4, 0.0, 1.0]) diff --git a/scripts/demos/markers.py b/scripts/demos/markers.py index bd44a887d3e..44804c72c21 100644 --- a/scripts/demos/markers.py +++ b/scripts/demos/markers.py @@ -10,26 +10,24 @@ # Usage ./isaaclab.sh -p scripts/demos/markers.py -""" + # Run with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/markers.py physics=newton_mjwarp --visualizer newton -"""Launch Isaac Sim Simulator first.""" +""" import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates different types of markers.") -# 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() +args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +simulation_app = DemoAppLauncher(args_cli) """Rest everything follows.""" @@ -97,7 +95,7 @@ def main(): """Main function.""" # Load kit helper sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device) - sim = SimulationContext(sim_cfg) + sim = simulation_app.create_context(sim_cfg, SimulationContext) # Set main camera sim.set_camera_view([0.0, 18.0, 12.0], [0.0, 3.0, 0.0]) diff --git a/scripts/demos/multi_asset.py b/scripts/demos/multi_asset.py index 907c993e954..0c0116f2cb6 100644 --- a/scripts/demos/multi_asset.py +++ b/scripts/demos/multi_asset.py @@ -10,30 +10,27 @@ # Usage ./isaaclab.sh -p scripts/demos/multi_asset.py --num_envs 2048 + # Run with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/multi_asset.py --num_envs 2048 physics=newton_mjwarp --visualizer newton + """ from __future__ import annotations -"""Launch Isaac Sim Simulator first.""" - - import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # add argparse arguments parser = argparse.ArgumentParser(description="Demo on spawning different objects in multiple environments.") parser.add_argument("--num_envs", type=int, default=512, help="Number of environments to spawn.") -# 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() +args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +simulation_app = DemoAppLauncher(args_cli) """Rest everything follows.""" @@ -286,12 +283,13 @@ def main(): """Main function.""" # Load kit helper sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device) - sim = SimulationContext(sim_cfg) + sim = simulation_app.create_context(sim_cfg, SimulationContext) # Set main camera sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0]) # Design scene scene_cfg = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0, replicate_physics=True) + scene_cfg = simulation_app.configure_scene_cfg(scene_cfg) with Timer("[INFO] Time to create scene: "): scene = InteractiveScene(scene_cfg) diff --git a/scripts/demos/pick_and_place.py b/scripts/demos/pick_and_place.py index d5e77aa4a12..1ee8533df7f 100644 --- a/scripts/demos/pick_and_place.py +++ b/scripts/demos/pick_and_place.py @@ -3,25 +3,34 @@ # # SPDX-License-Identifier: BSD-3-Clause +"""This script demonstrates a pick and place task with keyboard control. + +.. code-block:: bash + + # Usage + ./isaaclab.sh -p scripts/demos/pick_and_place.py + + # Run with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/pick_and_place.py physics=newton_mjwarp --visualizer newton + +""" + from __future__ import annotations import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # add argparse arguments 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.") -# 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() +args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +simulation_app = DemoAppLauncher(args_cli, kit_required=True) """Rest everything follows.""" diff --git a/scripts/demos/procedural_terrain.py b/scripts/demos/procedural_terrain.py index 98bcd027678..1175eada5ad 100644 --- a/scripts/demos/procedural_terrain.py +++ b/scripts/demos/procedural_terrain.py @@ -25,13 +25,14 @@ # Generate terrain with curriculum along with flat patches ./isaaclab.sh -p scripts/demos/procedural_terrain.py --use_curriculum --show_flat_patches -""" + # Generate terrain with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/procedural_terrain.py physics=newton_mjwarp --visualizer newton -"""Launch Isaac Sim Simulator first.""" +""" import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates procedural terrain generation.") @@ -54,16 +55,13 @@ default=False, help="Whether to show the flat patches computed during the terrain generation.", ) -# 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() +args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +simulation_app = DemoAppLauncher(args_cli) """Rest everything follows.""" @@ -157,7 +155,7 @@ def main(): """Main function.""" # Initialize the simulation context sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) + sim = simulation_app.create_context(sim_cfg, sim_utils.SimulationContext) # Set main camera sim.set_camera_view(eye=[5.0, 5.0, 5.0], target=[0.0, 0.0, 0.0]) # design scene diff --git a/scripts/demos/quadcopter.py b/scripts/demos/quadcopter.py index 5efa67beb67..0c78ce2a2b8 100644 --- a/scripts/demos/quadcopter.py +++ b/scripts/demos/quadcopter.py @@ -11,31 +11,28 @@ # Usage ./isaaclab.sh -p scripts/demos/quadcopter.py -""" + # Run with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/quadcopter.py physics=newton_mjwarp --visualizer newton -"""Launch Isaac Sim Simulator first.""" +""" import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates how to simulate a quadcopter.") -# 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() +args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +simulation_app = DemoAppLauncher(args_cli) """Rest everything follows.""" import torch -import warp as wp import isaaclab.sim as sim_utils from isaaclab.assets import Articulation @@ -51,7 +48,7 @@ def main(): """Main function.""" # Load kit helper sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device) - sim = SimulationContext(sim_cfg) + sim = simulation_app.create_context(sim_cfg, SimulationContext) # Set main camera sim.set_camera_view(eye=[0.5, 0.5, 1.0], target=[0.0, 0.0, 0.5]) @@ -75,7 +72,7 @@ def main(): # Fetch relevant parameters to make the quadcopter hover in place prop_body_ids = robot.find_bodies("m.*_prop")[0] - robot_mass = wp.to_torch(robot.root_view.get_masses())[0].sum() + robot_mass = robot.data.body_mass.torch[0].sum() gravity = torch.tensor(sim.cfg.gravity, device=sim.device).norm() # Now we are ready! diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index 59551a3738a..b3ae4a855f1 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -11,26 +11,24 @@ # Usage ./isaaclab.sh -p scripts/demos/quadrupeds.py -""" + # Run with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/quadrupeds.py physics=newton_mjwarp --visualizer newton -"""Launch Isaac Sim Simulator first.""" +""" import argparse -from isaaclab.app import AppLauncher +from isaaclab_tasks.utils.demo_launcher import DemoAppLauncher # add argparse arguments parser = argparse.ArgumentParser(description="This script demonstrates different legged robots.") -# 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() +args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app +simulation_app = DemoAppLauncher(args_cli) """Rest everything follows.""" @@ -177,7 +175,7 @@ def main(): """Main function.""" # Initialize the simulation context - sim = sim_utils.SimulationContext(sim_utils.SimulationCfg(dt=0.01)) + sim = simulation_app.create_context(sim_utils.SimulationCfg(dt=0.01), sim_utils.SimulationContext) # Set main camera sim.set_camera_view(eye=[2.5, 2.5, 2.5], target=[0.0, 0.0, 0.0]) # design scene diff --git a/source/isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py b/source/isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py new file mode 100644 index 00000000000..d868594e377 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py @@ -0,0 +1,304 @@ +# 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 + +"""Shared launcher utilities for demo scripts under scripts/demos.""" + +from __future__ import annotations + +import argparse +import copy +from contextlib import AbstractContextManager +from types import SimpleNamespace +from typing import TYPE_CHECKING, Any + +from .hydra import parse_overrides +from .preset_cli import fold_preset_tokens +from .sim_launcher import add_launcher_args, launch_simulation + +if TYPE_CHECKING: + from isaaclab_newton.physics import NewtonCfg + + from isaaclab.assets import ArticulationCfg + from isaaclab.physics.physics_manager_cfg import PhysicsCfg + from isaaclab.sim import SimulationCfg, SimulationContext + +MJWARP_ARMATURE = 0.02 +MJWARP_GRIPPER_ARMATURE = 0.005 +PHYSICS_PRESETS = ("physx", "newton_mjwarp") +_PHYSICS_PRESET_MAP = {"env": {"demo.physics": {preset: None for preset in PHYSICS_PRESETS}}, "agent": {}} + + +def create_demo_physics_cfg(physics: str, *, newton_cfg: NewtonCfg | None = None) -> PhysicsCfg: + """Create the selected demo physics backend configuration. + + Args: + physics: Name of the physics preset to create. + newton_cfg: Newton MJWarp configuration to use instead of the demo default. + The configuration is copied before it is returned. + + Returns: + Physics backend configuration for the selected preset. + """ + if physics == "physx": + from isaaclab_physx.physics import PhysxCfg + + return PhysxCfg() + + if physics != "newton_mjwarp": + raise ValueError(f"Unsupported physics preset: {physics}") + + if newton_cfg is not None: + return copy.deepcopy(newton_cfg) + + from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg + + return NewtonCfg( + solver_cfg=MJWarpSolverCfg( + njmax=512, + nconmax=256, + iterations=100, + ls_iterations=50, + solver="newton", + ls_parallel=False, + cone="elliptic", + impratio=10, + integrator="implicitfast", + ), + num_substeps=1, + debug_mode=False, + ) + + +def tune_mjwarp_articulation_cfg(cfg: ArticulationCfg) -> ArticulationCfg: + """Tune articulation actuator armature for the MJWarp solver.""" + for actuator_name, actuator_cfg in getattr(cfg, "actuators", {}).items(): + if any(token in actuator_name.lower() for token in ("finger", "gripper", "hand")): + actuator_cfg.armature = MJWARP_GRIPPER_ARMATURE + else: + actuator_cfg.armature = MJWARP_ARMATURE + return cfg + + +class DemoAppLauncher: + """Proxy layer shared by demo scripts. + + The proxy keeps demo scripts close to their original launcher shape while supporting + the Newton MJWarp backend and Kit-less visualizers through :func:`launch_simulation`. + """ + + def __init__( + self, + args_cli: argparse.Namespace, + *, + kit_required: bool = False, + newton_cfg: NewtonCfg | None = None, + ): + """Initialize the demo launcher proxy. + + Args: + args_cli: Parsed demo command line arguments. + kit_required: Whether the script imports or uses Kit APIs before a simulation config exists. + newton_cfg: Newton MJWarp configuration to use instead of the demo default when + ``physics=newton_mjwarp`` is selected. + """ + self.args_cli = args_cli + self.kit_required = kit_required + self._newton_cfg = newton_cfg + self._app: Any | None = None + self._launch_context: AbstractContextManager | None = None + self._sim: SimulationContext | None = None + self._saw_visualizers = False + + if self._needs_early_app_launch(): + self._launch_app() + + @staticmethod + def parse_args(parser: argparse.ArgumentParser) -> argparse.Namespace: + """Parse demo arguments.""" + parser_defaults = dict(parser._defaults) + add_launcher_args(parser) + if parser_defaults: + parser.set_defaults(**parser_defaults) + + if "physics" not in parser_defaults: + parser.set_defaults(physics="physx") + + args_cli, unknown_args = parser.parse_known_args() + folded_tokens = fold_preset_tokens(list(unknown_args)) + physics_presets, _, _, _ = parse_overrides(folded_tokens, _PHYSICS_PRESET_MAP) + + if physics_presets: + args_cli.physics = physics_presets[0] + else: + default_physics, _, _, _ = parse_overrides([f"presets={args_cli.physics}"], _PHYSICS_PRESET_MAP) + args_cli.physics = default_physics[0] + + return args_cli + + @property + def app(self): + """Return the underlying Kit app, if one was launched.""" + return self._app + + @property + def is_newton_mjwarp(self) -> bool: + """Whether the selected demo physics backend is Newton MJWarp.""" + return self.args_cli.physics == "newton_mjwarp" + + def configure_sim(self, sim_cfg: SimulationCfg, *, newton_cfg: NewtonCfg | None = None) -> SimulationCfg: + """Apply selected physics and launch the required simulation runtime. + + Args: + sim_cfg: Simulation configuration to update. + newton_cfg: Newton MJWarp configuration to use instead of the demo default. + + Returns: + Updated simulation configuration. + """ + self._configure_sim_physics(sim_cfg, newton_cfg=newton_cfg) + self._launch_runtime(SimpleNamespace(sim=sim_cfg)) + return sim_cfg + + def configure_env_cfg(self, env_cfg, *, newton_cfg: NewtonCfg | None = None): + """Apply selected physics to ``env_cfg.sim`` and launch the required runtime. + + Args: + env_cfg: Environment configuration to update. + newton_cfg: Newton MJWarp configuration to use instead of the demo default. + + Returns: + Updated environment configuration. + """ + if not self.is_newton_mjwarp: + self._launch_runtime(env_cfg) + return env_cfg + + from .hydra import resolve_presets + + env_cfg = resolve_presets(env_cfg, {self.args_cli.physics}) + sim_cfg = getattr(env_cfg, "sim", None) + if sim_cfg is not None: + self._configure_sim_physics(sim_cfg, newton_cfg=newton_cfg) + scene_cfg = getattr(env_cfg, "scene", None) + if scene_cfg is not None: + self.configure_scene_cfg(scene_cfg) + for asset_cfg in env_cfg.__dict__.values(): + self._configure_scene_asset_cfg(asset_cfg) + self._launch_runtime(env_cfg) + return env_cfg + + def configure_scene_cfg(self, scene_cfg): + """Apply Newton MJWarp demo adjustments to an interactive scene config.""" + if self.is_newton_mjwarp: + for asset_cfg in scene_cfg.__dict__.values(): + self._configure_scene_asset_cfg(asset_cfg) + return scene_cfg + + def create_context( + self, + sim_cfg: SimulationCfg, + context_cls: type[SimulationContext] | None = None, + *, + newton_cfg: NewtonCfg | None = None, + ) -> SimulationContext: + """Create and bind a :class:`~isaaclab.sim.SimulationContext` for the demo. + + Args: + sim_cfg: Simulation configuration used to create the context. + context_cls: Simulation context class to instantiate. + newton_cfg: Newton MJWarp configuration to use instead of the demo default. + + Returns: + Created simulation context. + """ + sim_cfg = self.configure_sim(sim_cfg, newton_cfg=newton_cfg) + if context_cls is None: + from isaaclab.sim import SimulationContext + + context_cls = SimulationContext + return self.bind_sim(context_cls(sim_cfg)) + + def bind_sim(self, sim: SimulationContext) -> SimulationContext: + """Bind an externally created simulation context to the launcher proxy.""" + self._sim = sim + self._saw_visualizers = self._saw_visualizers or bool(sim.visualizers) + return sim + + def tune_articulation_cfg(self, cfg: ArticulationCfg) -> ArticulationCfg: + """Apply MJWarp articulation tuning when the Newton backend is selected.""" + if self.is_newton_mjwarp: + return tune_mjwarp_articulation_cfg(cfg) + return cfg + + def is_running(self) -> bool: + """Return whether the demo should continue stepping.""" + if self._sim is not None: + self._saw_visualizers = self._saw_visualizers or bool(self._sim.visualizers) + if self._saw_visualizers: + return any(visualizer.is_running() for visualizer in self._sim.visualizers) + if self._app is not None: + return self._app.is_running() + return True + + def close(self) -> None: + """Close the simulation runtime owned by the proxy.""" + if self._launch_context is not None: + self._launch_context.__exit__(None, None, None) + self._launch_context = None + if self._app is None and self._sim is not None: + type(self._sim).clear_instance() + elif self._sim is not None and self._app is None: + type(self._sim).clear_instance() + + if self._app is not None: + self._app.close() + self._app = None + + def _needs_early_app_launch(self) -> bool: + """Return whether Kit must be launched before the rest of the script imports.""" + visualizers = getattr(self.args_cli, "visualizer", None) or [] + if isinstance(visualizers, str): + visualizers = [token.strip() for token in visualizers.split(",") if token.strip()] + visualizer_types = {str(visualizer).strip().lower() for visualizer in visualizers if str(visualizer).strip()} + return self.kit_required or not self.is_newton_mjwarp or "kit" in visualizer_types or "none" in visualizer_types + + def _launch_app(self) -> None: + """Launch Kit through :class:`~isaaclab.app.AppLauncher`.""" + from isaaclab.app import AppLauncher + + app_launcher = AppLauncher(self.args_cli) + self._app = app_launcher.app + if hasattr(app_launcher, "device"): + self.args_cli.device = app_launcher.device + + def _launch_runtime(self, cfg: Any) -> None: + """Launch the simulation runtime if this launcher has not done so yet.""" + if self._launch_context is not None: + return + + self._launch_context = launch_simulation(cfg, self.args_cli) + self._launch_context.__enter__() + + def _configure_scene_asset_cfg(self, asset_cfg: Any) -> None: + """Apply MJWarp compatibility adjustments to a scene asset config.""" + if asset_cfg is None: + return + + from isaaclab.assets import ArticulationCfg + + if isinstance(asset_cfg, ArticulationCfg): + tune_mjwarp_articulation_cfg(asset_cfg) + + def _configure_sim_physics(self, sim_cfg: SimulationCfg, *, newton_cfg: NewtonCfg | None = None) -> None: + """Apply selected demo physics to a simulation config.""" + if not self.is_newton_mjwarp: + return + + from isaaclab_newton.physics import NewtonCfg + + selected_newton_cfg = newton_cfg if newton_cfg is not None else self._newton_cfg + if selected_newton_cfg is None and isinstance(getattr(sim_cfg, "physics", None), NewtonCfg): + return + sim_cfg.physics = create_demo_physics_cfg("newton_mjwarp", newton_cfg=selected_newton_cfg) From 8d72e609dd35733b1bb4d0ba97b6db00636df541 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Wed, 20 May 2026 21:27:11 +0800 Subject: [PATCH 3/6] Fixed H1 Locomotion Signed-off-by: Yize Wang --- scripts/demos/h1_locomotion.py | 1 + .../isaaclab_tasks/utils/demo_launcher.py | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/scripts/demos/h1_locomotion.py b/scripts/demos/h1_locomotion.py index a979a941159..6cbd609df82 100644 --- a/scripts/demos/h1_locomotion.py +++ b/scripts/demos/h1_locomotion.py @@ -95,6 +95,7 @@ def __init__(self): env_cfg.curriculum = None env_cfg.commands.base_velocity.ranges.lin_vel_x = (0.0, 1.0) env_cfg.commands.base_velocity.ranges.heading = (-1.0, 1.0) + env_cfg = simulation_app.configure_env_cfg(env_cfg) # wrap around environment for rsl-rl self.env = RslRlVecEnvWrapper(ManagerBasedRLEnv(cfg=env_cfg)) self.device = self.env.unwrapped.device diff --git a/source/isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py b/source/isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py index d868594e377..441a6d4a5f7 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py +++ b/source/isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py @@ -270,9 +270,36 @@ def _launch_app(self) -> None: app_launcher = AppLauncher(self.args_cli) self._app = app_launcher.app + self._sync_sensor_cfg_modules() if hasattr(app_launcher, "device"): self.args_cli.device = app_launcher.device + @staticmethod + def _sync_sensor_cfg_modules() -> None: + """Reload sensor config modules whose base config class was loaded during Kit startup.""" + import importlib + import sys + + from isaaclab.sensors.sensor_base_cfg import SensorBaseCfg + + for module_name, module in list(sys.modules.items()): + if not module_name.startswith("isaaclab.sensors.") or not module_name.endswith("_cfg"): + continue + module_sensor_base = getattr(module, "SensorBaseCfg", None) + if module_sensor_base is None or module_sensor_base is SensorBaseCfg: + continue + + module = importlib.reload(module) + module_parts = module_name.split(".") + parent_packages = (".".join(module_parts[:i]) for i in range(1, len(module_parts))) + for attr_name, attr in vars(module).items(): + if not isinstance(attr, type) or attr.__module__ != module_name: + continue + for package_name in parent_packages: + package = sys.modules.get(package_name) + if package is not None and attr_name in vars(package): + setattr(package, attr_name, attr) + def _launch_runtime(self, cfg: Any) -> None: """Launch the simulation runtime if this launcher has not done so yet.""" if self._launch_context is not None: From de9b5aa352d246e64b9adfc33f445824d1c7a7ab Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Thu, 21 May 2026 17:31:49 +0800 Subject: [PATCH 4/6] Resolved Comments from IsaacLab Review Bot Signed-off-by: Yize Wang --- scripts/demos/deformables.py | 2 +- scripts/demos/h1_locomotion.py | 2 +- source/isaaclab/isaaclab/terrains/utils.py | 3 ++- .../isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py | 8 +++++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/scripts/demos/deformables.py b/scripts/demos/deformables.py index 4768897d0db..1efa9d4c238 100644 --- a/scripts/demos/deformables.py +++ b/scripts/demos/deformables.py @@ -12,7 +12,7 @@ """ -# TODO: Non-rigid bodies are not supported by the newton backend yet. +# TODO(yizew@nvidia.com): Non-rigid bodies are not supported by the Newton backend yet. import argparse diff --git a/scripts/demos/h1_locomotion.py b/scripts/demos/h1_locomotion.py index 6cbd609df82..692968b6efd 100644 --- a/scripts/demos/h1_locomotion.py +++ b/scripts/demos/h1_locomotion.py @@ -15,7 +15,7 @@ """Launch Isaac Sim Simulator first.""" -# TODO: Known issues in the newton backend: robots instantly fall upon spawn +# TODO(yizew@nvidia.com): Known Newton backend limitation: robots instantly fall upon spawn. import argparse import importlib.metadata as metadata diff --git a/source/isaaclab/isaaclab/terrains/utils.py b/source/isaaclab/isaaclab/terrains/utils.py index 837d4fca454..332df713d33 100644 --- a/source/isaaclab/isaaclab/terrains/utils.py +++ b/source/isaaclab/isaaclab/terrains/utils.py @@ -49,7 +49,8 @@ def color_meshes_by_height(meshes: list[trimesh.Trimesh], **kwargs) -> trimesh.T # clip lower and upper bounds to have better color mapping heights_normalized = np.clip(heights_normalized, 0.1, 0.9) # Get the color for each vertex based on the height - color_map = kwargs.pop("color_map", "viridis") # options: magma, inferno, plasma, viridis + # Available color maps: magma, inferno, plasma, viridis + color_map = kwargs.pop("color_map", "viridis") colors = trimesh.visual.color.interpolate(heights_normalized, color_map=color_map) # Set the vertex colors mesh.visual.vertex_colors = colors diff --git a/source/isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py b/source/isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py index 441a6d4a5f7..437a0e10365 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py +++ b/source/isaaclab_tasks/isaaclab_tasks/utils/demo_launcher.py @@ -17,6 +17,8 @@ from .preset_cli import fold_preset_tokens from .sim_launcher import add_launcher_args, launch_simulation +__all__ = ["DemoAppLauncher", "create_demo_physics_cfg", "tune_mjwarp_articulation_cfg"] + if TYPE_CHECKING: from isaaclab_newton.physics import NewtonCfg @@ -24,6 +26,7 @@ from isaaclab.physics.physics_manager_cfg import PhysicsCfg from isaaclab.sim import SimulationCfg, SimulationContext +# Conservative armature defaults used by demos to stabilize MJWarp articulation examples. MJWARP_ARMATURE = 0.02 MJWARP_GRIPPER_ARMATURE = 0.005 PHYSICS_PRESETS = ("physx", "newton_mjwarp") @@ -240,7 +243,7 @@ def is_running(self) -> bool: return any(visualizer.is_running() for visualizer in self._sim.visualizers) if self._app is not None: return self._app.is_running() - return True + return False def close(self) -> None: """Close the simulation runtime owned by the proxy.""" @@ -249,12 +252,15 @@ def close(self) -> None: self._launch_context = None if self._app is None and self._sim is not None: type(self._sim).clear_instance() + self._sim = None elif self._sim is not None and self._app is None: type(self._sim).clear_instance() + self._sim = None if self._app is not None: self._app.close() self._app = None + self._sim = None def _needs_early_app_launch(self) -> bool: """Return whether Kit must be launched before the rest of the script imports.""" From 0191f6ece994ac57bb9cfbcda4cd9dca1bdbc505 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Thu, 21 May 2026 20:36:43 +0800 Subject: [PATCH 5/6] Update CONTRIBUTORS.md Signed-off-by: Yize Wang --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7e66ec3dca6..1e109c1d641 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -193,6 +193,7 @@ Guidelines for modifications: * Yang Jin * Yanzi Zhu * Yijie Guo +* Yize Wang * Yohan Choi * Yujian Zhang * Yun Liu From 5fa0d4b6833698f60ce43ab398c699a3df62536a Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Thu, 21 May 2026 21:14:30 +0800 Subject: [PATCH 6/6] Fix H1 Locomotion Signed-off-by: Yize Wang --- scripts/demos/h1_locomotion.py | 41 +++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/scripts/demos/h1_locomotion.py b/scripts/demos/h1_locomotion.py index 692968b6efd..51e0e04e620 100644 --- a/scripts/demos/h1_locomotion.py +++ b/scripts/demos/h1_locomotion.py @@ -11,6 +11,9 @@ # Usage ./isaaclab.sh -p scripts/demos/h1_locomotion.py + # Run with Newton MJWarp physics and the Newton visualizer + ./isaaclab.sh -p scripts/demos/h1_locomotion.py physics=newton_mjwarp --visualizer newton + """ """Launch Isaac Sim Simulator first.""" @@ -40,21 +43,14 @@ args_cli = DemoAppLauncher.parse_args(parser) # launch omniverse app -simulation_app = DemoAppLauncher(args_cli, kit_required=True) +simulation_app = DemoAppLauncher(args_cli) """Rest everything follows.""" import torch from rsl_rl.runners import OnPolicyRunner -import carb -import omni -from omni.kit.viewport.utility import get_viewport_from_window_name -from omni.kit.viewport.utility.camera_state import ViewportCameraState -from pxr import Gf, Sdf - from isaaclab.envs import ManagerBasedRLEnv -from isaaclab.sim.utils.stage import get_current_stage from isaaclab.utils.math import quat_apply from isaaclab_rl.rsl_rl import RslRlOnPolicyRunnerCfg, RslRlVecEnvWrapper, handle_deprecated_rsl_rl_cfg @@ -62,6 +58,21 @@ from isaaclab_tasks.manager_based.locomotion.velocity.config.h1.rough_env_cfg import H1RoughEnvCfg_PLAY +try: + import carb + import omni + from omni.kit.viewport.utility import get_viewport_from_window_name + from omni.kit.viewport.utility.camera_state import ViewportCameraState + from pxr import Gf, Sdf + + from isaaclab.sim.utils.stage import get_current_stage + + KIT_VIEWPORT_AVAILABLE = True +except ModuleNotFoundError: + if simulation_app.app is not None: + raise + KIT_VIEWPORT_AVAILABLE = False + TASK = "Isaac-Velocity-Rough-H1-v0" RL_LIBRARY = "rsl_rl" @@ -98,6 +109,7 @@ def __init__(self): env_cfg = simulation_app.configure_env_cfg(env_cfg) # wrap around environment for rsl-rl self.env = RslRlVecEnvWrapper(ManagerBasedRLEnv(cfg=env_cfg)) + simulation_app.bind_sim(self.env.unwrapped.sim) self.device = self.env.unwrapped.device # load previously trained model ppo_runner = OnPolicyRunner(self.env, agent_cfg.to_dict(), log_dir=None, device=self.device) @@ -105,14 +117,19 @@ def __init__(self): # obtain the trained policy for inference self.policy = ppo_runner.get_inference_policy(device=self.device) - self.create_camera() self.commands = torch.zeros(env_cfg.scene.num_envs, 4, device=self.device) self.commands[:, 0:3] = self.env.unwrapped.command_manager.get_command("base_velocity") - self.set_up_keyboard() - self._prim_selection = omni.usd.get_context().get_selection() self._selected_id = None self._previous_selected_id = None self._camera_local_transform = torch.tensor([-2.5, 0.0, 0.8], device=self.device) + self._kit_interaction_enabled = KIT_VIEWPORT_AVAILABLE and simulation_app.app is not None + + if self._kit_interaction_enabled: + self.create_camera() + self.set_up_keyboard() + self._prim_selection = omni.usd.get_context().get_selection() + else: + self._prim_selection = None def create_camera(self): """Creates a camera to be used for third-person view.""" @@ -172,6 +189,8 @@ def update_selected_object(self): For valid robots, we enter the third-person view for that robot. When a new robot is selected, we reset the command of the previously selected to continue random commands.""" + if not self._kit_interaction_enabled: + return self._previous_selected_id = self._selected_id selected_prim_paths = self._prim_selection.get_selected_prim_paths()