From 367804dfaf62840c432cb96680a069319242c534 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Tue, 2 Jun 2026 18:28:53 +0800 Subject: [PATCH 01/12] Support Multi-backend for Demo Scripts Signed-off-by: Yize Wang --- scripts/demos/arl_robot_1.py | 172 +++++++++-------- scripts/demos/arms.py | 112 ++++++----- scripts/demos/bin_packing.py | 95 +++++---- scripts/demos/bipeds.py | 105 +++++----- scripts/demos/hands.py | 92 +++++---- scripts/demos/quadcopter.py | 182 +++++++++--------- scripts/demos/quadrupeds.py | 57 +++--- .../isaaclab/sim/simulation_context.py | 4 + 8 files changed, 439 insertions(+), 380 deletions(-) diff --git a/scripts/demos/arl_robot_1.py b/scripts/demos/arl_robot_1.py index 987e6be6b2ec..38bb87ef8378 100644 --- a/scripts/demos/arl_robot_1.py +++ b/scripts/demos/arl_robot_1.py @@ -6,34 +6,46 @@ """ Script to view ARL Robot 1. -Launch Isaac Sim Simulator first. +.. code-block:: bash + + # Usage with default PhysX physics and default kit visualizer. + ./isaaclab.sh -p scripts/demos/arl_robot_1.py + + # Usage with Newton visualizer and default PhysX physics. + ./isaaclab.sh -p scripts/demos/arl_robot_1.py --visualizer newton + + # Usage with Newton (MJWarp) physics and default kit visualizer. + ./isaaclab.sh -p scripts/demos/arl_robot_1.py --physics newton_mjwarp + + # Usage with Newton visualizer and Newton (MJWarp) physics. + ./isaaclab.sh -p scripts/demos/arl_robot_1.py --visualizer newton --physics newton_mjwarp + """ -# Create argparser +"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" + import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -parser = argparse.ArgumentParser(description="View ARL Robot 1 with Lee Position Controller.") -# append AppLauncher cli args -AppLauncher.add_app_launcher_args(parser) +parser = argparse.ArgumentParser( + description="View ARL Robot 1 with Lee Position Controller.", + conflict_handler="resolve", +) +parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") +add_launcher_args(parser) +parser.set_defaults(visualizer=["kit"]) args_cli = parser.parse_args() -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything follows.""" - import torch -import omni.usd -from pxr import Gf, UsdLux - import isaaclab.sim as sim_utils -from isaaclab.sim import SimulationContext -from isaaclab_contrib.assets import Multirotor +## +# Pre-defined configs +## +from isaaclab.physics import PhysicsCfg + from isaaclab_contrib.controllers.lee_position_control import LeePosController from isaaclab_contrib.controllers.lee_position_control_cfg import LeePosControllerCfg @@ -43,71 +55,67 @@ def main(): """Main function to spawn arl_robot_1.""" - # Create simulation context - sim_cfg = sim_utils.SimulationCfg(dt=0.01) - sim = SimulationContext(sim_cfg) - - # Create a dome light with light blue color - stage = omni.usd.get_context().get_stage() - dome_light = UsdLux.DomeLight.Define(stage, "/World/DomeLight") - dome_light.CreateColorAttr(Gf.Vec3f(0.53, 0.81, 0.92)) # Light blue - dome_light.CreateIntensityAttr(1000.0) - - # Spawn ground plane - cfg = sim_utils.GroundPlaneCfg() - cfg.func("/World/defaultGroundPlane", cfg) - - # Spawn robot - robot_cfg = ARL_ROBOT_1_CFG.replace(prim_path="/World/Robot") - robot_cfg.actuators["thrusters"].dt = sim_cfg.dt - robot = Multirotor(robot_cfg) - - # Play the simulator - sim.reset() - - # Create Lee position controller - controller_cfg = LeePosControllerCfg( - K_pos_range=((2.5, 2.5, 1.5), (3.5, 3.5, 2.0)), - K_vel_range=((2.5, 2.5, 1.5), (3.5, 3.5, 2.0)), - K_rot_range=((1.6, 1.6, 0.25), (1.85, 1.85, 0.4)), - K_angvel_range=((0.4, 0.4, 0.075), (0.5, 0.5, 0.09)), - max_inclination_angle_rad=1.0471975511965976, - max_yaw_rate=1.0471975511965976, - ) - controller = LeePosController(controller_cfg, robot, num_envs=1, device=str(sim.device)) - - # Get allocation matrix and compute pseudoinverse - allocation_matrix = torch.tensor(robot_cfg.allocation_matrix, device=sim.device, dtype=torch.float32) - # allocation_matrix is (6, num_thrusters), we need pseudoinverse for wrench -> thrust - alloc_pinv = torch.linalg.pinv(allocation_matrix) # Shape: (num_thrusters, 6) - - # Position command: hover in place (zero position, zero yaw) - pos_command = torch.zeros((1, 4), device=sim.device) # [x, y, z, yaw] - pos_command[0, 2] = 1.0 # Hover at 1 meter height - - # Simulation loop - print("[INFO] Starting demo with Lee Position Controller. Press Ctrl+C to stop.") - - while simulation_app.is_running(): - # Compute wrench from velocity controller - wrench = controller.compute(pos_command) # Shape: (1, 6) - - # Allocate wrench to thrusters: thrust = pinv(A) @ wrench - thrust_cmd = torch.matmul(wrench, alloc_pinv.T) # Shape: (1, num_thrusters) - thrust_cmd = thrust_cmd.clamp(min=0.0) # Ensure non-negative thrust - - # Apply thrust - robot.set_thrust_target(thrust_cmd) - - # Step simulation - robot.write_data_to_sim() - sim.step() - - # Update robot - robot.update(sim_cfg.dt) - - # Cleanup - simulation_app.close() + with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: + # Create simulation context + sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device, physics=physics_cfg) + sim = sim_utils.SimulationContext(sim_cfg) + + # Create a dome light with light blue color + dome_light_cfg = sim_utils.DomeLightCfg(intensity=1000.0, color=(0.53, 0.81, 0.92)) + dome_light_cfg.func("/World/DomeLight", dome_light_cfg) + + # Spawn ground plane + ground_cfg = sim_utils.GroundPlaneCfg() + ground_cfg.func("/World/defaultGroundPlane", ground_cfg) + + # Spawn robot + robot_cfg = ARL_ROBOT_1_CFG.replace(prim_path="/World/Robot") + robot_cfg.actuators["thrusters"].dt = sim_cfg.dt + robot = robot_cfg.class_type(robot_cfg) + + # Play the simulator + sim.reset() + + # Create Lee position controller + controller_cfg = LeePosControllerCfg( + K_pos_range=((2.5, 2.5, 1.5), (3.5, 3.5, 2.0)), + K_vel_range=((2.5, 2.5, 1.5), (3.5, 3.5, 2.0)), + K_rot_range=((1.6, 1.6, 0.25), (1.85, 1.85, 0.4)), + K_angvel_range=((0.4, 0.4, 0.075), (0.5, 0.5, 0.09)), + max_inclination_angle_rad=1.0471975511965976, + max_yaw_rate=1.0471975511965976, + ) + controller = LeePosController(controller_cfg, robot, num_envs=1, device=str(sim.device)) + + # Get allocation matrix and compute pseudoinverse + allocation_matrix = torch.tensor(robot_cfg.allocation_matrix, device=sim.device, dtype=torch.float32) + # allocation_matrix is (6, num_thrusters), we need pseudoinverse for wrench -> thrust + alloc_pinv = torch.linalg.pinv(allocation_matrix) # Shape: (num_thrusters, 6) + + # Position command: hover in place (zero position, zero yaw) + pos_command = torch.zeros((1, 4), device=sim.device) # [x, y, z, yaw] + pos_command[0, 2] = 1.0 # Hover at 1 meter height + + # Simulation loop + print("[INFO] Starting demo with Lee Position Controller. Press Ctrl+C to stop.") + + while sim.is_headless_or_exist_active_visualizer(): + # Compute wrench from velocity controller + wrench = controller.compute(pos_command) # Shape: (1, 6) + + # Allocate wrench to thrusters: thrust = pinv(A) @ wrench + thrust_cmd = torch.matmul(wrench, alloc_pinv.T) # Shape: (1, num_thrusters) + thrust_cmd = thrust_cmd.clamp(min=0.0) # Ensure non-negative thrust + + # Apply thrust + robot.set_thrust_target(thrust_cmd) + + # Step simulation + robot.write_data_to_sim() + sim.step() + + # Update robot + robot.update(sim_cfg.dt) if __name__ == "__main__": diff --git a/scripts/demos/arms.py b/scripts/demos/arms.py index b08686a8e52c..6511b63bfda0 100644 --- a/scripts/demos/arms.py +++ b/scripts/demos/arms.py @@ -8,53 +8,51 @@ .. code-block:: bash - # Usage + # Usage with default PhysX physics and default kit visualizer. ./isaaclab.sh -p scripts/demos/arms.py + # Usage with Newton visualizer and default PhysX physics. + ./isaaclab.sh -p scripts/demos/arms.py --visualizer newton + + # Usage with Newton (MJWarp) physics and default kit visualizer. + ./isaaclab.sh -p scripts/demos/arms.py --physics newton_mjwarp + + # Usage with Newton visualizer and Newton (MJWarp) physics. + ./isaaclab.sh -p scripts/demos/arms.py --visualizer newton --physics newton_mjwarp + """ -"""Launch Isaac Sim Simulator first.""" +"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# 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 = argparse.ArgumentParser( + description="This script demonstrates different single-arm manipulators.", + conflict_handler="resolve", +) +parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") +add_launcher_args(parser) 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 +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg import isaaclab.sim as sim_utils -from isaaclab.assets import Articulation -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR ## # Pre-defined configs ## -# isort: off -from isaaclab_assets import ( - FRANKA_PANDA_CFG, - UR10_CFG, - KINOVA_JACO2_N7S300_CFG, - KINOVA_JACO2_N6S300_CFG, - KINOVA_GEN3_N7_CFG, - SAWYER_CFG, -) +from isaaclab.physics import PhysicsCfg +from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR -# isort: on +from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG # isort:skip +from isaaclab_assets.robots.kinova import KINOVA_GEN3_N7_CFG, KINOVA_JACO2_N6S300_CFG, KINOVA_JACO2_N7S300_CFG # isort:skip +from isaaclab_assets.robots.sawyer import SAWYER_CFG # isort:skip +from isaaclab_assets.robots.universal_robots import UR10_CFG # isort:skip def define_origins(num_origins: int, spacing: float) -> list[list[float]]: @@ -93,7 +91,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: # -- Robot franka_arm_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) + franka_panda = franka_arm_cfg.class_type(franka_arm_cfg) # Origin 2 with UR10 sim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1]) @@ -105,7 +103,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: # -- Robot ur10_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) + ur10 = ur10_cfg.class_type(ur10_cfg) # Origin 3 with Kinova JACO2 (7-Dof) arm sim_utils.create_prim("/World/Origin3", "Xform", translation=origins[2]) @@ -115,7 +113,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: # -- Robot kinova_arm_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) + kinova_j2n7s300 = kinova_arm_cfg.class_type(kinova_arm_cfg) # Origin 4 with Kinova JACO2 (6-Dof) arm sim_utils.create_prim("/World/Origin4", "Xform", translation=origins[3]) @@ -125,7 +123,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: # -- Robot kinova_arm_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) + kinova_j2n6s300 = kinova_arm_cfg.class_type(kinova_arm_cfg) # Origin 5 with Sawyer sim_utils.create_prim("/World/Origin5", "Xform", translation=origins[4]) @@ -135,7 +133,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: # -- Robot kinova_arm_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) + kinova_gen3n7 = kinova_arm_cfg.class_type(kinova_arm_cfg) # Origin 6 with Kinova Gen3 (7-Dof) arm sim_utils.create_prim("/World/Origin6", "Xform", translation=origins[5]) @@ -147,7 +145,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: # -- Robot sawyer_arm_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) + sawyer = sawyer_arm_cfg.class_type(sawyer_arm_cfg) # return the scene information scene_entities = { @@ -161,14 +159,14 @@ 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: "sim_utils.SimulationContext", entities: dict[str, "Articulation"], origins: torch.Tensor): """Runs the simulation loop.""" # Define simulation stepping sim_dt = sim.get_physics_dt() sim_time = 0.0 count = 0 - # Simulate physics - while simulation_app.is_running(): + # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. + while sim.is_headless_or_exist_active_visualizer(): # reset if count % 200 == 0: # reset counters @@ -214,24 +212,34 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articula def main(): """Main function.""" - # Initialize the simulation context - sim_cfg = sim_utils.SimulationCfg(device=args_cli.device) - sim = sim_utils.SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view([3.5, 0.0, 3.2], [0.0, 0.0, 0.5]) - # 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 launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: + # The default newton mjwarp solver configuration needs to be tuned for these arms. + if isinstance(physics_cfg, NewtonCfg) and isinstance(physics_cfg.solver_cfg, MJWarpSolverCfg): + physics_cfg.solver_cfg.njmax = 70 + physics_cfg.solver_cfg.nconmax = 70 + physics_cfg.solver_cfg.ls_iterations = 40 + physics_cfg.solver_cfg.cone = "elliptic" + physics_cfg.solver_cfg.impratio = 100 + physics_cfg.solver_cfg.ls_parallel = False + physics_cfg.solver_cfg.integrator = "implicitfast" + physics_cfg.num_substeps = 2 + + # Initialize the simulation context + sim_cfg = sim_utils.SimulationCfg(device=args_cli.device, physics=physics_cfg) + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view([3.5, 0.0, 3.2], [0.0, 0.0, 0.5]) + # design scene + scene_entities, scene_origins = design_scene() + scene_origins = torch.tensor(scene_origins, device=sim.device) + # Play the simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + # Run the simulator + run_simulator(sim, scene_entities, scene_origins) if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/demos/bin_packing.py b/scripts/demos/bin_packing.py index 989c71554285..4a2bd21bdd64 100644 --- a/scripts/demos/bin_packing.py +++ b/scripts/demos/bin_packing.py @@ -13,45 +13,51 @@ .. code-block:: bash - # Usage - ./isaaclab.sh -p scripts/demos/bin_packing.py --num_envs 32 + # Usage with default PhysX physics and default kit visualizer. + ./isaaclab.sh -p scripts/demos/bin_packing.py + + # Usage with Newton visualizer and default PhysX physics. + ./isaaclab.sh -p scripts/demos/bin_packing.py --visualizer newton + + # Usage with Newton (MJWarp) physics and default kit visualizer. + ./isaaclab.sh -p scripts/demos/bin_packing.py --physics newton_mjwarp + + # Usage with Newton visualizer and Newton (MJWarp) physics. + ./isaaclab.sh -p scripts/demos/bin_packing.py --visualizer newton --physics newton_mjwarp """ from __future__ import annotations -"""Launch Isaac Sim Simulator first.""" - +"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# add argparse arguments -parser = argparse.ArgumentParser(description="Demo usage of RigidObjectCollection through bin packing example") +parser = argparse.ArgumentParser( + description="Demo usage of RigidObjectCollection through bin packing example", + conflict_handler="resolve", +) 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.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") +add_launcher_args(parser) 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 math import torch import isaaclab.sim as sim_utils import isaaclab.utils.math as math_utils -from isaaclab.assets import AssetBaseCfg, RigidObjectCfg, RigidObjectCollection, RigidObjectCollectionCfg + +## +# Pre-defined configs +##from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg +from isaaclab.assets import AssetBaseCfg, RigidObjectCfg, RigidObjectCollectionCfg +from isaaclab.physics import PhysicsCfg from isaaclab.scene import InteractiveScene, InteractiveSceneCfg -from isaaclab.sim import SimulationContext from isaaclab.utils import Timer from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR from isaaclab.utils.configclass import configclass @@ -131,7 +137,7 @@ class MultiObjectSceneCfg(InteractiveSceneCfg): # Instantiate four grocery variants per layer and replicate across all layers in each environment. rigid_objects={ f"Object_{label}_Layer{layer}": RigidObjectCfg( - prim_path=f"/World/envs/env_.*/Object_{label}_Layer{layer}", + prim_path=f"/World/envs/env_.*/Groceries/Object_{label}_Layer{layer}", init_state=RigidObjectCfg.InitialStateCfg(pos=(x, y, 0.2 + (layer) * 0.2)), spawn=GROCERIES.get(f"OBJECT_{label}"), ) @@ -263,7 +269,7 @@ def build_grocery_defaults( ## -def run_simulator(sim: SimulationContext, scene: InteractiveScene) -> None: +def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene) -> None: """Runs the simulation loop that coordinates spawn randomization and stepping. Returns: @@ -295,8 +301,8 @@ def run_simulator(sim: SimulationContext, scene: InteractiveScene) -> None: # Precompute a helper mask to toggle objects between active and cached sets. # Precompute XY bounds [[x_min,y_min],[x_max,y_max]] bounds_xy = torch.as_tensor(BIN_XY_BOUND, device=device, dtype=spawn_poses_w.dtype) - # Simulation loop - while simulation_app.is_running(): + # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. + while sim.is_headless_or_exist_active_visualizer(): # Reset if count % 250 == 0: # reset counter @@ -343,27 +349,32 @@ def main() -> None: Returns: None: The function drives the simulation for its side-effects. """ - # Load kit helper - sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device) - sim = SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view((2.5, 0.0, 4.0), (0.0, 0.0, 2.0)) - - # Design scene - scene_cfg = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=1.0, replicate_physics=True) - with Timer("[INFO] Time to create scene: "): - scene = InteractiveScene(scene_cfg) - - # Play the simulator - sim.reset() - # Now we are ready! - print("[INFO]: Setup complete...") - # Run the simulator - run_simulator(sim, scene) + with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: + # The default newton mjwarp solver configuration needs to be tuned for this demo. + if isinstance(physics_cfg, NewtonCfg) and isinstance(physics_cfg.solver_cfg, MJWarpSolverCfg): + physics_cfg.solver_cfg.nconmax = 128 + physics_cfg.solver_cfg.naconmax = 2048 + physics_cfg.solver_cfg.njmax = 512 + + # Load kit helper + sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device, physics=physics_cfg) + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + 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=1.0, replicate_physics=True) + with Timer("[INFO] Time to create scene: "): + scene = InteractiveScene(scene_cfg) + + # Play the simulator + sim.reset() + # Now we are ready! + print("[INFO]: Setup complete...") + # Run the simulator + run_simulator(sim, scene) if __name__ == "__main__": # run the main execution main() - # close sim app - simulation_app.close() diff --git a/scripts/demos/bipeds.py b/scripts/demos/bipeds.py index 68f79db14fcb..39af14c4b5d8 100644 --- a/scripts/demos/bipeds.py +++ b/scripts/demos/bipeds.py @@ -8,47 +8,50 @@ .. code-block:: bash - # Usage + # Usage with default PhysX physics and default kit visualizer. ./isaaclab.sh -p scripts/demos/bipeds.py + # Usage with Newton visualizer and default PhysX physics. + ./isaaclab.sh -p scripts/demos/bipeds.py --visualizer newton + + # Usage with Newton (MJWarp) physics and default kit visualizer. + ./isaaclab.sh -p scripts/demos/bipeds.py --physics newton_mjwarp + + # Usage with Newton visualizer and Newton (MJWarp) physics. + ./isaaclab.sh -p scripts/demos/bipeds.py --visualizer newton --physics newton_mjwarp + """ -"""Launch Isaac Sim Simulator first.""" +"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# 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 = argparse.ArgumentParser( + description="This script demonstrates how to simulate bipedal robots.", + conflict_handler="resolve", +) +parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") +add_launcher_args(parser) parser.set_defaults(visualizer=["kit"]) -# parse the arguments args_cli = parser.parse_args() -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything follows.""" - import torch +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg import isaaclab.sim as sim_utils -from isaaclab.assets import Articulation -from isaaclab.sim import SimulationContext ## # Pre-defined configs ## +from isaaclab.physics import PhysicsCfg + from isaaclab_assets.robots.cassie import CASSIE_CFG # isort:skip -from isaaclab_assets import H1_CFG # isort:skip -from isaaclab_assets import G1_CFG # isort:skip +from isaaclab_assets.robots.unitree import G1_CFG, H1_CFG # isort:skip -def design_scene(sim: sim_utils.SimulationContext) -> tuple[list, torch.Tensor]: +def design_scene(sim: "sim_utils.SimulationContext") -> tuple[list, torch.Tensor]: """Designs the scene.""" # Ground-plane cfg = sim_utils.GroundPlaneCfg() @@ -67,22 +70,25 @@ def design_scene(sim: sim_utils.SimulationContext) -> tuple[list, torch.Tensor]: ).to(device=sim.device) # Robots - cassie = Articulation(CASSIE_CFG.replace(prim_path="/World/Cassie")) - h1 = Articulation(H1_CFG.replace(prim_path="/World/H1")) - g1 = Articulation(G1_CFG.replace(prim_path="/World/G1")) + cassie_cfg = CASSIE_CFG.replace(prim_path="/World/Cassie") + cassie = cassie_cfg.class_type(cassie_cfg) + h1_cfg = H1_CFG.replace(prim_path="/World/H1") + h1 = h1_cfg.class_type(h1_cfg) + g1_cfg = G1_CFG.replace(prim_path="/World/G1") + g1 = g1_cfg.class_type(g1_cfg) robots = [cassie, h1, g1] return robots, origins -def run_simulator(sim: sim_utils.SimulationContext, robots: list[Articulation], origins: torch.Tensor): +def run_simulator(sim: "sim_utils.SimulationContext", robots: list["Articulation"], origins: torch.Tensor): """Runs the simulation loop.""" # Define simulation stepping sim_dt = sim.get_physics_dt() sim_time = 0.0 count = 0 - # Simulate physics - while simulation_app.is_running(): + # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. + while sim.is_headless_or_exist_active_visualizer(): # reset if count % 200 == 0: # reset counters @@ -120,27 +126,36 @@ def run_simulator(sim: sim_utils.SimulationContext, robots: list[Articulation], def main(): """Main function.""" - # Load kit helper - sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device) - sim = SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view(eye=[3.0, 0.0, 2.25], target=[0.0, 0.0, 1.0]) - - # design scene - robots, origins = design_scene(sim) - - # Play the simulator - sim.reset() - - # Now we are ready! - print("[INFO]: Setup complete...") - - # Run the simulator - run_simulator(sim, robots, origins) + with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: + # The default newton mjwarp solver configuration needs to be tuned for these bipeds. + if isinstance(physics_cfg, NewtonCfg) and isinstance(physics_cfg.solver_cfg, MJWarpSolverCfg): + physics_cfg.solver_cfg.njmax = 70 + physics_cfg.solver_cfg.nconmax = 70 + physics_cfg.solver_cfg.ls_iterations = 40 + physics_cfg.solver_cfg.cone = "elliptic" + physics_cfg.solver_cfg.impratio = 100 + physics_cfg.solver_cfg.ls_parallel = False + physics_cfg.solver_cfg.integrator = "implicitfast" + physics_cfg.num_substeps = 2 + # Load kit helper + sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device, physics=physics_cfg) + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view(eye=[3.0, 0.0, 2.25], target=[0.0, 0.0, 1.0]) + + # design scene + robots, origins = design_scene(sim) + + # Play the simulator + sim.reset() + + # Now we are ready! + print("[INFO]: Setup complete...") + + # Run the simulator + run_simulator(sim, robots, origins) if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/demos/hands.py b/scripts/demos/hands.py index 4b9a12d7b433..7c30f7d87657 100644 --- a/scripts/demos/hands.py +++ b/scripts/demos/hands.py @@ -8,41 +8,47 @@ .. code-block:: bash - # Usage + # Usage with default PhysX physics and default kit visualizer. ./isaaclab.sh -p scripts/demos/hands.py + # Usage with Newton visualizer and default PhysX physics. + ./isaaclab.sh -p scripts/demos/hands.py --visualizer newton + + # Usage with Newton (MJWarp) physics and default kit visualizer. + ./isaaclab.sh -p scripts/demos/hands.py --physics newton_mjwarp + + # Usage with Newton visualizer and Newton (MJWarp) physics. + ./isaaclab.sh -p scripts/demos/hands.py --visualizer newton --physics newton_mjwarp + """ -"""Launch Isaac Sim Simulator first.""" +"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# 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 = argparse.ArgumentParser( + description="This script demonstrates different dexterous hands.", + conflict_handler="resolve", +) +parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") +add_launcher_args(parser) 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 +from isaaclab_newton.physics import NewtonCfg +from isaaclab_newton.sim.schemas import MJWarpSolverCfg import isaaclab.sim as sim_utils -from isaaclab.assets import Articulation ## # Pre-defined configs ## +from isaaclab.physics import PhysicsCfg + from isaaclab_assets.robots.allegro import ALLEGRO_HAND_CFG # isort:skip from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_CFG # isort:skip @@ -78,12 +84,14 @@ def design_scene() -> tuple[dict, list[list[float]]]: # Origin 1 with Allegro Hand sim_utils.create_prim("/World/Origin1", "Xform", translation=origins[0]) # -- Robot - allegro = Articulation(ALLEGRO_HAND_CFG.replace(prim_path="/World/Origin1/Robot")) + allegro_cfg = ALLEGRO_HAND_CFG.replace(prim_path="/World/Origin1/Robot") + allegro = allegro_cfg.class_type(allegro_cfg) # Origin 2 with Shadow Hand sim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1]) # -- Robot - shadow_hand = Articulation(SHADOW_HAND_CFG.replace(prim_path="/World/Origin2/Robot")) + shadow_hand_cfg = SHADOW_HAND_CFG.replace(prim_path="/World/Origin2/Robot") + shadow_hand = shadow_hand_cfg.class_type(shadow_hand_cfg) # return the scene information scene_entities = { @@ -93,7 +101,7 @@ 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: "sim_utils.SimulationContext", entities: dict[str, "Articulation"], origins: torch.Tensor): """Runs the simulation loop.""" # Define simulation stepping sim_dt = sim.get_physics_dt() @@ -101,8 +109,8 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articula count = 0 # Start with hand open grasp_mode = 0 - # Simulate physics - while simulation_app.is_running(): + # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. + while sim.is_headless_or_exist_active_visualizer(): # reset if count % 1000 == 0: # reset counters @@ -149,24 +157,34 @@ def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, Articula 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) - # Set main camera - sim.set_camera_view(eye=[0.0, -0.5, 1.5], target=[0.0, -0.2, 0.5]) - # 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 launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: + # The default newton mjwarp solver configuration needs to be tuned for these hands. + if isinstance(physics_cfg, NewtonCfg) and isinstance(physics_cfg.solver_cfg, MJWarpSolverCfg): + physics_cfg.solver_cfg.njmax = 70 + physics_cfg.solver_cfg.nconmax = 70 + physics_cfg.solver_cfg.ls_iterations = 40 + physics_cfg.solver_cfg.cone = "elliptic" + physics_cfg.solver_cfg.impratio = 100 + physics_cfg.solver_cfg.ls_parallel = False + physics_cfg.solver_cfg.integrator = "implicitfast" + physics_cfg.num_substeps = 2 + + # Initialize the simulation context + sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device, physics=physics_cfg) + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view(eye=[0.0, -0.5, 1.5], target=[0.0, -0.2, 0.5]) + # 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 execution main() - # close sim app - simulation_app.close() diff --git a/scripts/demos/quadcopter.py b/scripts/demos/quadcopter.py index f3e15ff899d2..073587f149e0 100644 --- a/scripts/demos/quadcopter.py +++ b/scripts/demos/quadcopter.py @@ -8,121 +8,123 @@ .. code-block:: bash - # Usage + # Usage with default PhysX physics and default kit visualizer. ./isaaclab.sh -p scripts/demos/quadcopter.py + # Usage with Newton visualizer and default PhysX physics. + ./isaaclab.sh -p scripts/demos/quadcopter.py --visualizer newton + + # Usage with Newton (MJWarp) physics and default kit visualizer. + ./isaaclab.sh -p scripts/demos/quadcopter.py --physics newton_mjwarp + + # Usage with Newton visualizer and Newton (MJWarp) physics. + ./isaaclab.sh -p scripts/demos/quadcopter.py --visualizer newton --physics newton_mjwarp + """ -"""Launch Isaac Sim Simulator first.""" +"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" import argparse -from isaaclab.app import AppLauncher +from isaaclab.app import add_launcher_args, launch_simulation -# 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 = argparse.ArgumentParser( + description="This script demonstrates how to simulate a quadcopter.", + conflict_handler="resolve", +) +parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") +add_launcher_args(parser) parser.set_defaults(visualizer=["kit"]) -# parse the arguments args_cli = parser.parse_args() -# launch omniverse app -app_launcher = AppLauncher(args_cli) -simulation_app = app_launcher.app - -"""Rest everything follows.""" - import torch import isaaclab.sim as sim_utils -from isaaclab.assets import Articulation -from isaaclab.sim import SimulationContext ## # Pre-defined configs ## +from isaaclab.physics import PhysicsCfg + from isaaclab_assets import CRAZYFLIE_CFG # isort:skip def main(): """Main function.""" - # Load kit helper - sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device) - sim = SimulationContext(sim_cfg) - # Set main camera - sim.set_camera_view(eye=[0.5, 0.5, 1.0], target=[0.0, 0.0, 0.5]) - - # Spawn things into stage - # Ground-plane - cfg = sim_utils.GroundPlaneCfg() - cfg.func("/World/defaultGroundPlane", cfg) - # Lights - cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75)) - cfg.func("/World/Light", cfg) - - # Robots - robot_cfg = CRAZYFLIE_CFG.replace(prim_path="/World/Crazyflie") - robot_cfg.spawn.func("/World/Crazyflie", robot_cfg.spawn, translation=robot_cfg.init_state.pos) - - # create handles for the robots - robot = Articulation(robot_cfg) - - # Play the simulator - sim.reset() - - # Fetch relevant parameters to make the quadcopter hover in place - prop_body_ids = robot.find_bodies("m.*_prop")[0] - robot_mass = robot.data.body_mass.torch[0].sum() - gravity = torch.tensor(sim.cfg.gravity, device=sim.device).norm() - - # Now we are ready! - print("[INFO]: Setup complete...") - - # Define simulation stepping - sim_dt = sim.get_physics_dt() - sim_time = 0.0 - count = 0 - # Simulate physics - while simulation_app.is_running(): - # reset - if count % 2000 == 0: - # reset counters - sim_time = 0.0 - count = 0 - # reset dof state - joint_pos, joint_vel = robot.data.default_joint_pos.torch, robot.data.default_joint_vel.torch - robot.write_joint_position_to_sim_index(position=joint_pos) - robot.write_joint_velocity_to_sim_index(velocity=joint_vel) - default_root_pose = robot.data.default_root_pose.torch - robot.write_root_pose_to_sim_index(root_pose=default_root_pose) - default_root_vel = robot.data.default_root_vel.torch - robot.write_root_velocity_to_sim_index(root_velocity=default_root_vel) - robot.reset() - # reset command - print(">>>>>>>> Reset!") - # apply action to the robot (make the robot float in place) - forces = torch.zeros(robot.num_instances, 4, 3, device=sim.device) - torques = torch.zeros_like(forces) - forces[..., 2] = robot_mass * gravity / 4.0 - robot.permanent_wrench_composer.set_forces_and_torques( - forces=forces, - torques=torques, - body_ids=prop_body_ids, - ) - robot.write_data_to_sim() - # perform step - sim.step() - # update sim-time - sim_time += sim_dt - count += 1 - # update buffers - robot.update(sim_dt) + with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: + # Load kit helper + sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device, physics=physics_cfg) + sim = sim_utils.SimulationContext(sim_cfg) + # Set main camera + sim.set_camera_view(eye=[0.5, 0.5, 1.0], target=[0.0, 0.0, 0.5]) + + # Spawn things into stage + # Ground-plane + cfg = sim_utils.GroundPlaneCfg() + cfg.func("/World/defaultGroundPlane", cfg) + # Lights + cfg = sim_utils.DistantLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75)) + cfg.func("/World/Light", cfg) + + # Robots + robot_cfg = CRAZYFLIE_CFG.replace(prim_path="/World/Crazyflie") + robot_cfg.spawn.func("/World/Crazyflie", robot_cfg.spawn, translation=robot_cfg.init_state.pos) + + # create handles for the robots + robot = robot_cfg.class_type(robot_cfg) + + # Play the simulator + sim.reset() + + # Fetch relevant parameters to make the quadcopter hover in place + prop_body_ids = robot.find_bodies("m.*_prop")[0] + robot_mass = robot.data.body_mass.torch[0].sum() + gravity = torch.tensor(sim.cfg.gravity, device=sim.device).norm() + + # Now we are ready! + print("[INFO]: Setup complete...") + + # Define simulation stepping + sim_dt = sim.get_physics_dt() + sim_time = 0.0 + count = 0 + # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. + while sim.is_headless_or_exist_active_visualizer(): + # reset + if count % 2000 == 0: + # reset counters + sim_time = 0.0 + count = 0 + # reset dof state + joint_pos, joint_vel = robot.data.default_joint_pos.torch, robot.data.default_joint_vel.torch + robot.write_joint_position_to_sim_index(position=joint_pos) + robot.write_joint_velocity_to_sim_index(velocity=joint_vel) + default_root_pose = robot.data.default_root_pose.torch + robot.write_root_pose_to_sim_index(root_pose=default_root_pose) + default_root_vel = robot.data.default_root_vel.torch + robot.write_root_velocity_to_sim_index(root_velocity=default_root_vel) + robot.reset() + # reset command + print(">>>>>>>> Reset!") + # apply action to the robot (make the robot float in place) + forces = torch.zeros(robot.num_instances, 4, 3, device=sim.device) + torques = torch.zeros_like(forces) + forces[..., 2] = robot_mass * gravity / 4.0 + robot.permanent_wrench_composer.set_forces_and_torques( + forces=forces, + torques=torques, + body_ids=prop_body_ids, + ) + robot.write_data_to_sim() + # perform step + sim.step() + # update sim-time + sim_time += sim_dt + count += 1 + # update buffers + robot.update(sim_dt) if __name__ == "__main__": # run the main function main() - # close sim app - simulation_app.close() diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index ec3718ce830f..3ab3093c2891 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -8,28 +8,33 @@ .. code-block:: bash - # Usage with the default PhysX backend (launches Isaac Sim Kit). + # Usage with default PhysX physics and default kit visualizer. ./isaaclab.sh -p scripts/demos/quadrupeds.py - # Usage with the kit-less Newton (MJWarp) backend. + # Usage with Newton visualizer and default PhysX physics. + ./isaaclab.sh -p scripts/demos/quadrupeds.py --visualizer newton + + # Usage with Newton (MJWarp) physics and default kit visualizer. ./isaaclab.sh -p scripts/demos/quadrupeds.py --physics newton_mjwarp + # Usage with Newton visualizer and Newton (MJWarp) physics. + ./isaaclab.sh -p scripts/demos/quadrupeds.py --visualizer newton --physics newton_mjwarp + """ """Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" import argparse -from typing import TYPE_CHECKING + +from isaaclab.app import add_launcher_args, launch_simulation parser = argparse.ArgumentParser( description="This script demonstrates different legged robots.", conflict_handler="resolve", ) -from isaaclab.app import add_launcher_args, launch_simulation - parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") add_launcher_args(parser) -# parse the arguments +parser.set_defaults(visualizer=["kit"]) args_cli = parser.parse_args() import numpy as np @@ -46,11 +51,8 @@ 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 -if TYPE_CHECKING: - from isaaclab.assets import Articulation - -def define_origins(num_origins: int, spacing: float) -> list[list[float]]: +def define_origins(num_origins: int, spacing: float) -> torch.Tensor: """Defines the origins of the scene.""" # create tensor based on number of environments env_origins = torch.zeros(num_origins, 3) @@ -62,10 +64,10 @@ def define_origins(num_origins: int, spacing: float) -> list[list[float]]: env_origins[:, 1] = spacing * yy.flatten()[:num_origins] - spacing * (num_cols - 1) / 2 env_origins[:, 2] = 0.0 # return the origins - return env_origins.tolist() + return env_origins -def design_scene() -> tuple[dict, list[list[float]]]: +def design_scene() -> tuple[dict, torch.Tensor]: """Designs the scene.""" # Ground-plane cfg = sim_utils.GroundPlaneCfg() @@ -83,6 +85,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: # -- 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 @@ -132,18 +135,16 @@ def design_scene() -> tuple[dict, list[list[float]]]: return scene_entities, origins -def run_simulator(sim, entities: dict[str, "Articulation"], origins: torch.Tensor): +def run_simulator(sim: "sim_utils.SimulationContext", entities: dict[str, "Articulation"], origins: torch.Tensor): """Runs the simulation loop.""" # Define simulation stepping sim_dt = sim.get_physics_dt() - sim_time = 0.0 count = 0 # Step while a visualizer window is still open (or none exist, e.g. headless); works for kit and newton. - while not sim.visualizers or any(v.is_running() and not v.is_closed for v in sim.visualizers): - # reset + while sim.is_headless_or_exist_active_visualizer(): + # Reset robots every 200 steps. if count % 200 == 0: # reset counters - sim_time = 0.0 count = 0 # reset robots for index, robot in enumerate(entities.values()): @@ -154,16 +155,14 @@ def run_simulator(sim, entities: dict[str, "Articulation"], origins: torch.Tenso root_vel = robot.data.default_root_vel.torch.clone() robot.write_root_velocity_to_sim_index(root_velocity=root_vel) # joint state - joint_pos, joint_vel = ( - robot.data.default_joint_pos.torch.clone(), - robot.data.default_joint_vel.torch.clone(), - ) + joint_pos = robot.data.default_joint_pos.torch.clone() robot.write_joint_position_to_sim_index(position=joint_pos) + joint_vel = robot.data.default_joint_vel.torch.clone() robot.write_joint_velocity_to_sim_index(velocity=joint_vel) # reset the internal state robot.reset() - print("[INFO]: Resetting robots state...") - # apply default actions to the quadrupedal robots + print("[INFO]: Reset robots' state...") + # Apply default actions to the quadrupedal robots. for robot in entities.values(): # generate random joint positions joint_pos_target = robot.data.default_joint_pos.torch + torch.randn_like(robot.data.joint_pos.torch) * 0.1 @@ -173,8 +172,7 @@ def run_simulator(sim, entities: dict[str, "Articulation"], origins: torch.Tenso robot.write_data_to_sim() # perform step sim.step() - # update sim-time - sim_time += sim_dt + # update counter count += 1 # update buffers for robot in entities.values(): @@ -185,13 +183,8 @@ def main(): """Main function.""" with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: dt = 1 / 200 - sim = sim_utils.SimulationContext( - sim_utils.SimulationCfg( - dt=dt, - device=args_cli.device, - physics=physics_cfg, - ) - ) + sim_cfg: sim_utils.SimulationCfg = sim_utils.SimulationCfg(dt=dt, device=args_cli.device, physics=physics_cfg) + sim = sim_utils.SimulationContext(sim_cfg) sim.set_camera_view(eye=[2.5, 2.5, 2.5], target=[0.0, 0.0, 0.0]) scene_entities, scene_origins = design_scene() scene_origins = torch.tensor(scene_origins, device=sim.device) diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index c068469ec599..8b1400b6d8ed 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -391,6 +391,10 @@ def has_active_visualizers(self) -> bool: self.get_setting("/isaaclab/video/auto_start_kit") ) + def is_headless_or_exist_active_visualizer(self) -> bool: + """Return whether the simulation should keep stepping without visualizers or with an active visualizer.""" + return not self._visualizers or any(viz.is_running() and not viz.is_closed for viz in self._visualizers) + def can_render_rgb_array(self) -> bool: """Return whether rgb-array rendering is currently available.""" return self.has_gui or self.has_offscreen_render or self.has_active_visualizers() From 20100ebab1b34ef78c3596fd5bc73101950ba5d9 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Wed, 3 Jun 2026 18:35:03 +0800 Subject: [PATCH 02/12] Unify Code Format Signed-off-by: Yize Wang --- scripts/demos/arms.py | 2 +- scripts/demos/bin_packing.py | 6 ++++-- scripts/demos/bipeds.py | 2 +- scripts/demos/hands.py | 5 ++--- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/scripts/demos/arms.py b/scripts/demos/arms.py index 6511b63bfda0..403134fee56c 100644 --- a/scripts/demos/arms.py +++ b/scripts/demos/arms.py @@ -39,7 +39,6 @@ import numpy as np import torch -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg import isaaclab.sim as sim_utils @@ -48,6 +47,7 @@ ## from isaaclab.physics import PhysicsCfg from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG # isort:skip from isaaclab_assets.robots.kinova import KINOVA_GEN3_N7_CFG, KINOVA_JACO2_N6S300_CFG, KINOVA_JACO2_N7S300_CFG # isort:skip diff --git a/scripts/demos/bin_packing.py b/scripts/demos/bin_packing.py index 4a2bd21bdd64..35204ca8fd8a 100644 --- a/scripts/demos/bin_packing.py +++ b/scripts/demos/bin_packing.py @@ -51,10 +51,12 @@ import isaaclab.sim as sim_utils import isaaclab.utils.math as math_utils +from isaaclab.utils.string import string_to_callable ## # Pre-defined configs -##from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg +## +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg from isaaclab.assets import AssetBaseCfg, RigidObjectCfg, RigidObjectCollectionCfg from isaaclab.physics import PhysicsCfg from isaaclab.scene import InteractiveScene, InteractiveSceneCfg @@ -365,7 +367,7 @@ def main() -> None: # Design scene scene_cfg = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=1.0, replicate_physics=True) with Timer("[INFO] Time to create scene: "): - scene = InteractiveScene(scene_cfg) + scene = string_to_callable("isaaclab.scene:InteractiveScene")(scene_cfg) # Play the simulator sim.reset() diff --git a/scripts/demos/bipeds.py b/scripts/demos/bipeds.py index 39af14c4b5d8..8e01d2983862 100644 --- a/scripts/demos/bipeds.py +++ b/scripts/demos/bipeds.py @@ -38,7 +38,6 @@ args_cli = parser.parse_args() import torch -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg import isaaclab.sim as sim_utils @@ -46,6 +45,7 @@ # Pre-defined configs ## from isaaclab.physics import PhysicsCfg +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg from isaaclab_assets.robots.cassie import CASSIE_CFG # isort:skip from isaaclab_assets.robots.unitree import G1_CFG, H1_CFG # isort:skip diff --git a/scripts/demos/hands.py b/scripts/demos/hands.py index 7c30f7d87657..3b60ee75cc31 100644 --- a/scripts/demos/hands.py +++ b/scripts/demos/hands.py @@ -39,8 +39,6 @@ import numpy as np import torch -from isaaclab_newton.physics import NewtonCfg -from isaaclab_newton.sim.schemas import MJWarpSolverCfg import isaaclab.sim as sim_utils @@ -48,6 +46,7 @@ # Pre-defined configs ## from isaaclab.physics import PhysicsCfg +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg from isaaclab_assets.robots.allegro import ALLEGRO_HAND_CFG # isort:skip from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_CFG # isort:skip @@ -160,7 +159,7 @@ def main(): with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: # The default newton mjwarp solver configuration needs to be tuned for these hands. if isinstance(physics_cfg, NewtonCfg) and isinstance(physics_cfg.solver_cfg, MJWarpSolverCfg): - physics_cfg.solver_cfg.njmax = 70 + physics_cfg.solver_cfg.njmax = 128 physics_cfg.solver_cfg.nconmax = 70 physics_cfg.solver_cfg.ls_iterations = 40 physics_cfg.solver_cfg.cone = "elliptic" From 49c5927a43fa5d14f83d6744d09a6bce49c57663 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Wed, 3 Jun 2026 20:25:35 +0800 Subject: [PATCH 03/12] Improve Viz Signed-off-by: Yize Wang --- scripts/demos/hands.py | 2 +- scripts/demos/quadcopter.py | 2 +- .../newton/newton_visualizer.py | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/demos/hands.py b/scripts/demos/hands.py index 3b60ee75cc31..1fc724547192 100644 --- a/scripts/demos/hands.py +++ b/scripts/demos/hands.py @@ -172,7 +172,7 @@ def main(): sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device, physics=physics_cfg) sim = sim_utils.SimulationContext(sim_cfg) # Set main camera - sim.set_camera_view(eye=[0.0, -0.5, 1.5], target=[0.0, -0.2, 0.5]) + sim.set_camera_view(eye=[0.0, -0.35, 1.1], target=[0.0, -0.05, 0.45]) # design scene scene_entities, scene_origins = design_scene() scene_origins = torch.tensor(scene_origins, device=sim.device) diff --git a/scripts/demos/quadcopter.py b/scripts/demos/quadcopter.py index 073587f149e0..aa3739eb0261 100644 --- a/scripts/demos/quadcopter.py +++ b/scripts/demos/quadcopter.py @@ -56,7 +56,7 @@ def main(): sim_cfg = sim_utils.SimulationCfg(dt=0.005, device=args_cli.device, physics=physics_cfg) sim = sim_utils.SimulationContext(sim_cfg) # Set main camera - sim.set_camera_view(eye=[0.5, 0.5, 1.0], target=[0.0, 0.0, 0.5]) + sim.set_camera_view(eye=[0.25, -0.25, 0.7], target=[0.0, 0.0, 0.5]) # Spawn things into stage # Ground-plane diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py b/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py index 3dbecdec794f..7a8ecfc2bb2b 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py @@ -742,6 +742,20 @@ def _apply_camera_focal_length(self) -> None: return self._viewer.camera.fov = self._focal_length_to_vertical_fov_degrees() + def set_camera_view( + self, eye: tuple[float, float, float] | list[float], target: tuple[float, float, float] | list[float] + ) -> None: + """Set active viewer camera eye/target. + + Args: + eye: Camera eye position. + target: Camera look-at target. + """ + if not self._is_initialized: + logger.debug("[NewtonVisualizer] set_camera_view() ignored because visualizer is not initialized.") + return + self._apply_camera_pose((tuple(eye), tuple(target))) + def supports_markers(self) -> bool: """Newton OpenGL viewer supports Isaac Lab markers through viewer-side meshes and lines.""" return bool(self.cfg.enable_markers) From 06791430cfe75c7f89a6fff5b573edadc2e87f58 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Wed, 3 Jun 2026 21:09:40 +0800 Subject: [PATCH 04/12] Lint --- scripts/demos/arl_robot_1.py | 172 +++++++++++++++++------------------ scripts/demos/arms.py | 6 +- scripts/demos/bin_packing.py | 12 ++- scripts/demos/bipeds.py | 6 +- scripts/demos/hands.py | 6 +- scripts/demos/quadrupeds.py | 4 + 6 files changed, 109 insertions(+), 97 deletions(-) diff --git a/scripts/demos/arl_robot_1.py b/scripts/demos/arl_robot_1.py index 38bb87ef8378..987e6be6b2ec 100644 --- a/scripts/demos/arl_robot_1.py +++ b/scripts/demos/arl_robot_1.py @@ -6,46 +6,34 @@ """ Script to view ARL Robot 1. -.. code-block:: bash - - # Usage with default PhysX physics and default kit visualizer. - ./isaaclab.sh -p scripts/demos/arl_robot_1.py - - # Usage with Newton visualizer and default PhysX physics. - ./isaaclab.sh -p scripts/demos/arl_robot_1.py --visualizer newton - - # Usage with Newton (MJWarp) physics and default kit visualizer. - ./isaaclab.sh -p scripts/demos/arl_robot_1.py --physics newton_mjwarp - - # Usage with Newton visualizer and Newton (MJWarp) physics. - ./isaaclab.sh -p scripts/demos/arl_robot_1.py --visualizer newton --physics newton_mjwarp - +Launch Isaac Sim Simulator first. """ -"""Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" - +# Create argparser import argparse -from isaaclab.app import add_launcher_args, launch_simulation +from isaaclab.app import AppLauncher -parser = argparse.ArgumentParser( - description="View ARL Robot 1 with Lee Position Controller.", - conflict_handler="resolve", -) -parser.add_argument("--physics", default="physx", choices=["physx", "newton_mjwarp"], help="Physics backend.") -add_launcher_args(parser) -parser.set_defaults(visualizer=["kit"]) +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() +# launch omniverse app +app_launcher = AppLauncher(args_cli) +simulation_app = app_launcher.app + +"""Rest everything follows.""" + import torch -import isaaclab.sim as sim_utils +import omni.usd +from pxr import Gf, UsdLux -## -# Pre-defined configs -## -from isaaclab.physics import PhysicsCfg +import isaaclab.sim as sim_utils +from isaaclab.sim import SimulationContext +from isaaclab_contrib.assets import Multirotor from isaaclab_contrib.controllers.lee_position_control import LeePosController from isaaclab_contrib.controllers.lee_position_control_cfg import LeePosControllerCfg @@ -55,67 +43,71 @@ def main(): """Main function to spawn arl_robot_1.""" - with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: - # Create simulation context - sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device, physics=physics_cfg) - sim = sim_utils.SimulationContext(sim_cfg) - - # Create a dome light with light blue color - dome_light_cfg = sim_utils.DomeLightCfg(intensity=1000.0, color=(0.53, 0.81, 0.92)) - dome_light_cfg.func("/World/DomeLight", dome_light_cfg) - - # Spawn ground plane - ground_cfg = sim_utils.GroundPlaneCfg() - ground_cfg.func("/World/defaultGroundPlane", ground_cfg) - - # Spawn robot - robot_cfg = ARL_ROBOT_1_CFG.replace(prim_path="/World/Robot") - robot_cfg.actuators["thrusters"].dt = sim_cfg.dt - robot = robot_cfg.class_type(robot_cfg) - - # Play the simulator - sim.reset() - - # Create Lee position controller - controller_cfg = LeePosControllerCfg( - K_pos_range=((2.5, 2.5, 1.5), (3.5, 3.5, 2.0)), - K_vel_range=((2.5, 2.5, 1.5), (3.5, 3.5, 2.0)), - K_rot_range=((1.6, 1.6, 0.25), (1.85, 1.85, 0.4)), - K_angvel_range=((0.4, 0.4, 0.075), (0.5, 0.5, 0.09)), - max_inclination_angle_rad=1.0471975511965976, - max_yaw_rate=1.0471975511965976, - ) - controller = LeePosController(controller_cfg, robot, num_envs=1, device=str(sim.device)) - - # Get allocation matrix and compute pseudoinverse - allocation_matrix = torch.tensor(robot_cfg.allocation_matrix, device=sim.device, dtype=torch.float32) - # allocation_matrix is (6, num_thrusters), we need pseudoinverse for wrench -> thrust - alloc_pinv = torch.linalg.pinv(allocation_matrix) # Shape: (num_thrusters, 6) - - # Position command: hover in place (zero position, zero yaw) - pos_command = torch.zeros((1, 4), device=sim.device) # [x, y, z, yaw] - pos_command[0, 2] = 1.0 # Hover at 1 meter height - - # Simulation loop - print("[INFO] Starting demo with Lee Position Controller. Press Ctrl+C to stop.") - - while sim.is_headless_or_exist_active_visualizer(): - # Compute wrench from velocity controller - wrench = controller.compute(pos_command) # Shape: (1, 6) - - # Allocate wrench to thrusters: thrust = pinv(A) @ wrench - thrust_cmd = torch.matmul(wrench, alloc_pinv.T) # Shape: (1, num_thrusters) - thrust_cmd = thrust_cmd.clamp(min=0.0) # Ensure non-negative thrust - - # Apply thrust - robot.set_thrust_target(thrust_cmd) - - # Step simulation - robot.write_data_to_sim() - sim.step() - - # Update robot - robot.update(sim_cfg.dt) + # Create simulation context + sim_cfg = sim_utils.SimulationCfg(dt=0.01) + sim = SimulationContext(sim_cfg) + + # Create a dome light with light blue color + stage = omni.usd.get_context().get_stage() + dome_light = UsdLux.DomeLight.Define(stage, "/World/DomeLight") + dome_light.CreateColorAttr(Gf.Vec3f(0.53, 0.81, 0.92)) # Light blue + dome_light.CreateIntensityAttr(1000.0) + + # Spawn ground plane + cfg = sim_utils.GroundPlaneCfg() + cfg.func("/World/defaultGroundPlane", cfg) + + # Spawn robot + robot_cfg = ARL_ROBOT_1_CFG.replace(prim_path="/World/Robot") + robot_cfg.actuators["thrusters"].dt = sim_cfg.dt + robot = Multirotor(robot_cfg) + + # Play the simulator + sim.reset() + + # Create Lee position controller + controller_cfg = LeePosControllerCfg( + K_pos_range=((2.5, 2.5, 1.5), (3.5, 3.5, 2.0)), + K_vel_range=((2.5, 2.5, 1.5), (3.5, 3.5, 2.0)), + K_rot_range=((1.6, 1.6, 0.25), (1.85, 1.85, 0.4)), + K_angvel_range=((0.4, 0.4, 0.075), (0.5, 0.5, 0.09)), + max_inclination_angle_rad=1.0471975511965976, + max_yaw_rate=1.0471975511965976, + ) + controller = LeePosController(controller_cfg, robot, num_envs=1, device=str(sim.device)) + + # Get allocation matrix and compute pseudoinverse + allocation_matrix = torch.tensor(robot_cfg.allocation_matrix, device=sim.device, dtype=torch.float32) + # allocation_matrix is (6, num_thrusters), we need pseudoinverse for wrench -> thrust + alloc_pinv = torch.linalg.pinv(allocation_matrix) # Shape: (num_thrusters, 6) + + # Position command: hover in place (zero position, zero yaw) + pos_command = torch.zeros((1, 4), device=sim.device) # [x, y, z, yaw] + pos_command[0, 2] = 1.0 # Hover at 1 meter height + + # Simulation loop + print("[INFO] Starting demo with Lee Position Controller. Press Ctrl+C to stop.") + + while simulation_app.is_running(): + # Compute wrench from velocity controller + wrench = controller.compute(pos_command) # Shape: (1, 6) + + # Allocate wrench to thrusters: thrust = pinv(A) @ wrench + thrust_cmd = torch.matmul(wrench, alloc_pinv.T) # Shape: (1, num_thrusters) + thrust_cmd = thrust_cmd.clamp(min=0.0) # Ensure non-negative thrust + + # Apply thrust + robot.set_thrust_target(thrust_cmd) + + # Step simulation + robot.write_data_to_sim() + sim.step() + + # Update robot + robot.update(sim_cfg.dt) + + # Cleanup + simulation_app.close() if __name__ == "__main__": diff --git a/scripts/demos/arms.py b/scripts/demos/arms.py index 403134fee56c..f26cc8b35135 100644 --- a/scripts/demos/arms.py +++ b/scripts/demos/arms.py @@ -25,6 +25,7 @@ """Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" import argparse +from typing import TYPE_CHECKING from isaaclab.app import add_launcher_args, launch_simulation @@ -39,6 +40,7 @@ import numpy as np import torch +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg import isaaclab.sim as sim_utils @@ -47,13 +49,15 @@ ## from isaaclab.physics import PhysicsCfg from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG # isort:skip from isaaclab_assets.robots.kinova import KINOVA_GEN3_N7_CFG, KINOVA_JACO2_N6S300_CFG, KINOVA_JACO2_N7S300_CFG # isort:skip from isaaclab_assets.robots.sawyer import SAWYER_CFG # isort:skip from isaaclab_assets.robots.universal_robots import UR10_CFG # isort:skip +if TYPE_CHECKING: + from isaaclab.assets import Articulation + def define_origins(num_origins: int, spacing: float) -> list[list[float]]: """Defines the origins of the the scene.""" diff --git a/scripts/demos/bin_packing.py b/scripts/demos/bin_packing.py index 35204ca8fd8a..d9642010c5d2 100644 --- a/scripts/demos/bin_packing.py +++ b/scripts/demos/bin_packing.py @@ -32,6 +32,7 @@ """Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" import argparse +from typing import TYPE_CHECKING from isaaclab.app import add_launcher_args, launch_simulation @@ -49,20 +50,23 @@ import torch -import isaaclab.sim as sim_utils -import isaaclab.utils.math as math_utils -from isaaclab.utils.string import string_to_callable - ## # Pre-defined configs ## from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg + +import isaaclab.sim as sim_utils +import isaaclab.utils.math as math_utils from isaaclab.assets import AssetBaseCfg, RigidObjectCfg, RigidObjectCollectionCfg from isaaclab.physics import PhysicsCfg from isaaclab.scene import InteractiveScene, InteractiveSceneCfg from isaaclab.utils import Timer from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR from isaaclab.utils.configclass import configclass +from isaaclab.utils.string import string_to_callable + +if TYPE_CHECKING: + from isaaclab.assets import RigidObjectCollection ## # Scene Configuration diff --git a/scripts/demos/bipeds.py b/scripts/demos/bipeds.py index 8e01d2983862..18664925f430 100644 --- a/scripts/demos/bipeds.py +++ b/scripts/demos/bipeds.py @@ -25,6 +25,7 @@ """Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" import argparse +from typing import TYPE_CHECKING from isaaclab.app import add_launcher_args, launch_simulation @@ -38,6 +39,7 @@ args_cli = parser.parse_args() import torch +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg import isaaclab.sim as sim_utils @@ -45,11 +47,13 @@ # Pre-defined configs ## from isaaclab.physics import PhysicsCfg -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg from isaaclab_assets.robots.cassie import CASSIE_CFG # isort:skip from isaaclab_assets.robots.unitree import G1_CFG, H1_CFG # isort:skip +if TYPE_CHECKING: + from isaaclab.assets import Articulation + def design_scene(sim: "sim_utils.SimulationContext") -> tuple[list, torch.Tensor]: """Designs the scene.""" diff --git a/scripts/demos/hands.py b/scripts/demos/hands.py index 1fc724547192..04e3ecaf6a09 100644 --- a/scripts/demos/hands.py +++ b/scripts/demos/hands.py @@ -25,6 +25,7 @@ """Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" import argparse +from typing import TYPE_CHECKING from isaaclab.app import add_launcher_args, launch_simulation @@ -39,6 +40,7 @@ import numpy as np import torch +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg import isaaclab.sim as sim_utils @@ -46,11 +48,13 @@ # Pre-defined configs ## from isaaclab.physics import PhysicsCfg -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg from isaaclab_assets.robots.allegro import ALLEGRO_HAND_CFG # isort:skip from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_CFG # isort:skip +if TYPE_CHECKING: + from isaaclab.assets import Articulation + def define_origins(num_origins: int, spacing: float) -> list[list[float]]: """Defines the origins of the the scene.""" diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index 3ab3093c2891..15aa25d7c227 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -25,6 +25,7 @@ """Parse CLI first so we can decide whether to launch Isaac Sim Kit.""" import argparse +from typing import TYPE_CHECKING from isaaclab.app import add_launcher_args, launch_simulation @@ -51,6 +52,9 @@ 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 +if TYPE_CHECKING: + from isaaclab.assets import Articulation + def define_origins(num_origins: int, spacing: float) -> torch.Tensor: """Defines the origins of the scene.""" From 0b7e5eb9ff524c391bd96306e56e6b2b7acd50f5 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Wed, 3 Jun 2026 21:20:26 +0800 Subject: [PATCH 05/12] Reorder Imports --- scripts/demos/arms.py | 2 +- scripts/demos/bipeds.py | 2 +- scripts/demos/hands.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/demos/arms.py b/scripts/demos/arms.py index f26cc8b35135..7575ddb81a52 100644 --- a/scripts/demos/arms.py +++ b/scripts/demos/arms.py @@ -40,7 +40,6 @@ import numpy as np import torch -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg import isaaclab.sim as sim_utils @@ -50,6 +49,7 @@ from isaaclab.physics import PhysicsCfg from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg # isort:skip from isaaclab_assets.robots.franka import FRANKA_PANDA_CFG # isort:skip from isaaclab_assets.robots.kinova import KINOVA_GEN3_N7_CFG, KINOVA_JACO2_N6S300_CFG, KINOVA_JACO2_N7S300_CFG # isort:skip from isaaclab_assets.robots.sawyer import SAWYER_CFG # isort:skip diff --git a/scripts/demos/bipeds.py b/scripts/demos/bipeds.py index 18664925f430..36a51e7def2e 100644 --- a/scripts/demos/bipeds.py +++ b/scripts/demos/bipeds.py @@ -39,7 +39,6 @@ args_cli = parser.parse_args() import torch -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg import isaaclab.sim as sim_utils @@ -48,6 +47,7 @@ ## from isaaclab.physics import PhysicsCfg +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg # isort:skip from isaaclab_assets.robots.cassie import CASSIE_CFG # isort:skip from isaaclab_assets.robots.unitree import G1_CFG, H1_CFG # isort:skip diff --git a/scripts/demos/hands.py b/scripts/demos/hands.py index 04e3ecaf6a09..f75b8b4a6bb8 100644 --- a/scripts/demos/hands.py +++ b/scripts/demos/hands.py @@ -40,7 +40,6 @@ import numpy as np import torch -from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg import isaaclab.sim as sim_utils @@ -49,6 +48,7 @@ ## from isaaclab.physics import PhysicsCfg +from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg # isort:skip from isaaclab_assets.robots.allegro import ALLEGRO_HAND_CFG # isort:skip from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_CFG # isort:skip From cc2b00b06b2694eb3839ac04031793e9683277f0 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Thu, 4 Jun 2026 15:17:24 +0800 Subject: [PATCH 06/12] Update Newton Config for hands.py Signed-off-by: Yize Wang --- scripts/demos/hands.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/demos/hands.py b/scripts/demos/hands.py index f75b8b4a6bb8..090350c1237d 100644 --- a/scripts/demos/hands.py +++ b/scripts/demos/hands.py @@ -163,14 +163,14 @@ def main(): with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg: # The default newton mjwarp solver configuration needs to be tuned for these hands. if isinstance(physics_cfg, NewtonCfg) and isinstance(physics_cfg.solver_cfg, MJWarpSolverCfg): - physics_cfg.solver_cfg.njmax = 128 + physics_cfg.solver_cfg.njmax = 200 physics_cfg.solver_cfg.nconmax = 70 - physics_cfg.solver_cfg.ls_iterations = 40 + physics_cfg.solver_cfg.impratio = 10.0 physics_cfg.solver_cfg.cone = "elliptic" - physics_cfg.solver_cfg.impratio = 100 - physics_cfg.solver_cfg.ls_parallel = False - physics_cfg.solver_cfg.integrator = "implicitfast" + physics_cfg.solver_cfg.update_data_interval = 2 + physics_cfg.solver_cfg.ccd_iterations = 50 physics_cfg.num_substeps = 2 + physics_cfg.debug_mode = False # Initialize the simulation context sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device, physics=physics_cfg) From 02f996c60600ca1944e9d6afe9cfeb5a4e032b6f Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Thu, 4 Jun 2026 15:44:12 +0800 Subject: [PATCH 07/12] Fix Shadow Hand Tendons Signed-off-by: Yize Wang --- scripts/demos/hands.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/scripts/demos/hands.py b/scripts/demos/hands.py index 090350c1237d..81f411128ee3 100644 --- a/scripts/demos/hands.py +++ b/scripts/demos/hands.py @@ -46,15 +46,31 @@ ## # Pre-defined configs ## +from isaaclab.actuators import ImplicitActuatorCfg from isaaclab.physics import PhysicsCfg from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg # isort:skip from isaaclab_assets.robots.allegro import ALLEGRO_HAND_CFG # isort:skip from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_CFG # isort:skip +from isaaclab_tasks.core.shadow_hand.shadow_hand_env_cfg import ShadowHandRobotCfg # isort:skip if TYPE_CHECKING: from isaaclab.assets import Articulation +_SHADOW_HAND_NEWTON_MJWARP_CFG = ShadowHandRobotCfg().newton_mjwarp +SHADOW_HAND_NEWTON_MJWARP_CFG = _SHADOW_HAND_NEWTON_MJWARP_CFG.replace( + actuators={ + "fingers": _SHADOW_HAND_NEWTON_MJWARP_CFG.actuators["fingers"].replace(stiffness=20.0, damping=2.0), + "distal_passive": ImplicitActuatorCfg( + joint_names_expr=["robot0_(FF|MF|RF)J4", "robot0_LFJ5"], + stiffness=10.0, + damping=0.1, + friction=1e-2, + armature=2e-3, + ), + }, +) + def define_origins(num_origins: int, spacing: float) -> list[list[float]]: """Defines the origins of the the scene.""" @@ -93,7 +109,8 @@ def design_scene() -> tuple[dict, list[list[float]]]: # Origin 2 with Shadow Hand sim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1]) # -- Robot - shadow_hand_cfg = SHADOW_HAND_CFG.replace(prim_path="/World/Origin2/Robot") + shadow_hand_cfg = SHADOW_HAND_NEWTON_MJWARP_CFG if args_cli.physics == "newton_mjwarp" else SHADOW_HAND_CFG + shadow_hand_cfg = shadow_hand_cfg.replace(prim_path="/World/Origin2/Robot") shadow_hand = shadow_hand_cfg.class_type(shadow_hand_cfg) # return the scene information @@ -176,7 +193,7 @@ def main(): sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device, physics=physics_cfg) sim = sim_utils.SimulationContext(sim_cfg) # Set main camera - sim.set_camera_view(eye=[0.0, -0.35, 1.1], target=[0.0, -0.05, 0.45]) + sim.set_camera_view(eye=[0.0, -0.5, 1.5], target=[0.0, -0.05, 0.45]) # design scene scene_entities, scene_origins = design_scene() scene_origins = torch.tensor(scene_origins, device=sim.device) From 28fb8415a37d6d9bae37bbffda5cdabc56c6d58a Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Thu, 4 Jun 2026 16:24:31 +0800 Subject: [PATCH 08/12] Use New API for quadcopter.py Signed-off-by: Yize Wang --- scripts/demos/quadcopter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/demos/quadcopter.py b/scripts/demos/quadcopter.py index aa3739eb0261..2c8215cbc152 100644 --- a/scripts/demos/quadcopter.py +++ b/scripts/demos/quadcopter.py @@ -110,7 +110,7 @@ def main(): forces = torch.zeros(robot.num_instances, 4, 3, device=sim.device) torques = torch.zeros_like(forces) forces[..., 2] = robot_mass * gravity / 4.0 - robot.permanent_wrench_composer.set_forces_and_torques( + robot.permanent_wrench_composer.set_forces_and_torques_index( forces=forces, torques=torques, body_ids=prop_body_ids, From e822c26edea53182b36ffdea842704793fb50208 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Fri, 5 Jun 2026 22:08:16 +0800 Subject: [PATCH 09/12] Remove Config Override For Shadow Hand --- scripts/demos/hands.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/scripts/demos/hands.py b/scripts/demos/hands.py index 81f411128ee3..9523d5fcde26 100644 --- a/scripts/demos/hands.py +++ b/scripts/demos/hands.py @@ -46,7 +46,6 @@ ## # Pre-defined configs ## -from isaaclab.actuators import ImplicitActuatorCfg from isaaclab.physics import PhysicsCfg from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg # isort:skip @@ -57,20 +56,6 @@ if TYPE_CHECKING: from isaaclab.assets import Articulation -_SHADOW_HAND_NEWTON_MJWARP_CFG = ShadowHandRobotCfg().newton_mjwarp -SHADOW_HAND_NEWTON_MJWARP_CFG = _SHADOW_HAND_NEWTON_MJWARP_CFG.replace( - actuators={ - "fingers": _SHADOW_HAND_NEWTON_MJWARP_CFG.actuators["fingers"].replace(stiffness=20.0, damping=2.0), - "distal_passive": ImplicitActuatorCfg( - joint_names_expr=["robot0_(FF|MF|RF)J4", "robot0_LFJ5"], - stiffness=10.0, - damping=0.1, - friction=1e-2, - armature=2e-3, - ), - }, -) - def define_origins(num_origins: int, spacing: float) -> list[list[float]]: """Defines the origins of the the scene.""" @@ -109,7 +94,7 @@ def design_scene() -> tuple[dict, list[list[float]]]: # Origin 2 with Shadow Hand sim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1]) # -- Robot - shadow_hand_cfg = SHADOW_HAND_NEWTON_MJWARP_CFG if args_cli.physics == "newton_mjwarp" else SHADOW_HAND_CFG + shadow_hand_cfg = ShadowHandRobotCfg().newton_mjwarp if args_cli.physics == "newton_mjwarp" else SHADOW_HAND_CFG shadow_hand_cfg = shadow_hand_cfg.replace(prim_path="/World/Origin2/Robot") shadow_hand = shadow_hand_cfg.class_type(shadow_hand_cfg) From a93be33f3f3ffaf838004fd75e94a876d79e3c6c Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Fri, 5 Jun 2026 22:18:42 +0800 Subject: [PATCH 10/12] Simplify quadrupeds.py --- scripts/demos/quadrupeds.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/demos/quadrupeds.py b/scripts/demos/quadrupeds.py index 15aa25d7c227..901c91e0b425 100644 --- a/scripts/demos/quadrupeds.py +++ b/scripts/demos/quadrupeds.py @@ -191,7 +191,7 @@ def main(): sim = sim_utils.SimulationContext(sim_cfg) sim.set_camera_view(eye=[2.5, 2.5, 2.5], target=[0.0, 0.0, 0.0]) scene_entities, scene_origins = design_scene() - scene_origins = torch.tensor(scene_origins, device=sim.device) + scene_origins = scene_origins.to(sim.device) sim.reset() print("[INFO]: Setup complete...") run_simulator(sim, scene_entities, scene_origins) From 13ec17561f6e7187e3cf4cf56e1687fa7bb7a445 Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Sat, 6 Jun 2026 11:20:58 +0800 Subject: [PATCH 11/12] Remove string_to_callable Signed-off-by: Yize Wang --- scripts/demos/bin_packing.py | 3 +-- .../isaaclab/isaaclab/scene/interactive_scene_cfg.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/demos/bin_packing.py b/scripts/demos/bin_packing.py index d9642010c5d2..273f633fecd4 100644 --- a/scripts/demos/bin_packing.py +++ b/scripts/demos/bin_packing.py @@ -63,7 +63,6 @@ from isaaclab.utils import Timer from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR from isaaclab.utils.configclass import configclass -from isaaclab.utils.string import string_to_callable if TYPE_CHECKING: from isaaclab.assets import RigidObjectCollection @@ -371,7 +370,7 @@ def main() -> None: # Design scene scene_cfg = MultiObjectSceneCfg(num_envs=args_cli.num_envs, env_spacing=1.0, replicate_physics=True) with Timer("[INFO] Time to create scene: "): - scene = string_to_callable("isaaclab.scene:InteractiveScene")(scene_cfg) + scene = scene_cfg.class_type(scene_cfg) # Play the simulator sim.reset() diff --git a/source/isaaclab/isaaclab/scene/interactive_scene_cfg.py b/source/isaaclab/isaaclab/scene/interactive_scene_cfg.py index 034307017219..be5fa82fd7d0 100644 --- a/source/isaaclab/isaaclab/scene/interactive_scene_cfg.py +++ b/source/isaaclab/isaaclab/scene/interactive_scene_cfg.py @@ -3,10 +3,16 @@ # # SPDX-License-Identifier: BSD-3-Clause +from __future__ import annotations + from dataclasses import MISSING +from typing import TYPE_CHECKING from isaaclab.utils.configclass import configclass +if TYPE_CHECKING: + from .interactive_scene import InteractiveScene + @configclass class InteractiveSceneCfg: @@ -67,6 +73,12 @@ class MySceneCfg(InteractiveSceneCfg): """ + class_type: type[InteractiveScene] | str = "{DIR}.interactive_scene:InteractiveScene" + """The class to use for the interactive scene. + + Defaults to :class:`isaaclab.scene.InteractiveScene`. + """ + num_envs: int = MISSING """Number of environment instances handled by the scene.""" From b0847bcaade7461fca7ff1e70b2c394e42d6f85d Mon Sep 17 00:00:00 2001 From: Yize Wang Date: Sat, 6 Jun 2026 22:53:06 +0800 Subject: [PATCH 12/12] Add Change Logs Signed-off-by: Yize Wang --- .../changelog.d/yizew-support-multi-backends.rst | 14 ++++++++++++++ .../changelog.d/yizew-support-multi-backends.rst | 6 ++++++ 2 files changed, 20 insertions(+) create mode 100644 source/isaaclab/changelog.d/yizew-support-multi-backends.rst create mode 100644 source/isaaclab_visualizers/changelog.d/yizew-support-multi-backends.rst diff --git a/source/isaaclab/changelog.d/yizew-support-multi-backends.rst b/source/isaaclab/changelog.d/yizew-support-multi-backends.rst new file mode 100644 index 000000000000..3b07fde871f0 --- /dev/null +++ b/source/isaaclab/changelog.d/yizew-support-multi-backends.rst @@ -0,0 +1,14 @@ +Added +^^^^^ + +* Added :attr:`~isaaclab.scene.InteractiveSceneCfg.class_type` so scene configs + can instantiate custom scene classes. +* Added :meth:`~isaaclab.sim.SimulationContext.is_headless_or_exist_active_visualizer` + to let kitless and external-visualizer demos share a visualizer-aware stepping + condition. + +Changed +^^^^^^^ + +* Updated demo scripts to support selectable PhysX or Newton MJWarp physics + backends and Kit or Newton visualizers. diff --git a/source/isaaclab_visualizers/changelog.d/yizew-support-multi-backends.rst b/source/isaaclab_visualizers/changelog.d/yizew-support-multi-backends.rst new file mode 100644 index 000000000000..7a1cd353d7ec --- /dev/null +++ b/source/isaaclab_visualizers/changelog.d/yizew-support-multi-backends.rst @@ -0,0 +1,6 @@ +Added +^^^^^ + +* Added :meth:`~isaaclab_visualizers.newton.NewtonVisualizer.set_camera_view` so + the Newton visualizer follows :meth:`~isaaclab.sim.SimulationContext.set_camera_view` + camera updates.