From e9e3834f59c6afffd493cd661210a7a71702d580 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Wed, 8 Jul 2026 11:46:31 +0000 Subject: [PATCH 1/7] Add OVPhysX presets to the dexterous tasks The Shadow Hand reorientation and handover environments gain OVPhysX physics presets (spawned without fixed-tendon overrides, which the OVPhysX runtime does not expose), and the Shadow Hand task frame is corrected on OVPhysX. Validated by full training runs on the OVPhysX backend. --- .../task-cleanup-dex-part07.minor.rst | 10 ++ .../core/handover/handover_env_cfg.py | 29 ++++-- .../config/shadow_hand/shadow_hand_env_cfg.py | 12 +++ .../core/test_dexterous_ovphysx_presets.py | 96 +++++++++++++++++++ .../test/core/test_handover_env_cfg.py | 51 ++++++++++ .../test/core/test_shadow_hand_env_cfg.py | 80 ++++++++++++++++ 6 files changed, 268 insertions(+), 10 deletions(-) create mode 100644 source/isaaclab_tasks/changelog.d/task-cleanup-dex-part07.minor.rst create mode 100644 source/isaaclab_tasks/test/core/test_dexterous_ovphysx_presets.py create mode 100644 source/isaaclab_tasks/test/core/test_handover_env_cfg.py create mode 100644 source/isaaclab_tasks/test/core/test_shadow_hand_env_cfg.py diff --git a/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part07.minor.rst b/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part07.minor.rst new file mode 100644 index 000000000000..ecbf7abb40d7 --- /dev/null +++ b/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part07.minor.rst @@ -0,0 +1,10 @@ +Added +^^^^^ + +* Added OVPhysX physics presets to the Shadow Hand reorientation and handover + environments. + +Fixed +^^^^^ + +* Fixed the Shadow Hand task frame on OVPhysX. diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py index a3d7126ce0bd..9fc81cf0af0d 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: BSD-3-Clause from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg +from isaaclab_ovphysx.physics import OvPhysxCfg from isaaclab_physx.physics import PhysxCfg import isaaclab.envs.mdp as mdp @@ -136,9 +137,9 @@ def _shadow_hand_cfg( init_pos: tuple[float, float, float], init_rot: tuple[float, float, float, float], ) -> PresetCfg: - """Per-hand Shadow Hand preset (PhysX and Newton MJWarp variants). + """Per-hand Shadow Hand preset (PhysX, Newton MJWarp, and OVPhysX variants). - Both variants are placed at *prim_path* with the same init pose; per-hand + All variants are placed at *prim_path* with the same init pose; per-hand differences (right vs left) come from the caller's *prim_path* / *init_pos* / *init_rot* — the gain tuning is identical on both hands. @@ -154,12 +155,13 @@ def _shadow_hand_cfg( authority. ``20.0`` / ``2.0`` is the smallest tested setting at which MAPPO learns the catch (mean reward at iter 200 / 2048 envs goes from ~27 at PhysX-mirrored gains to ~777). - * ``distal_passive`` on the four ``robot0_(FF|MF|RF|LF)J0`` joints with - ``stiffness=10.0`` / ``damping=0.1``. The Newton USD bakes - ``stiffness=286 / damping=57`` on these joints from the MJCF→USD - translation, which fights the ``MjcTendon`` coupling and bounces the - ball. ``stiffness=10`` (~1/3 of PhysX's ``limit_stiffness=30``) keeps - the joints near-passive while the tendon constraint dominates. + * ``distal_passive`` on the four ``robot0_(FF|MF|RF|LF)J1`` distal joints + (named ``J0`` before the current asset release) with ``stiffness=10.0`` / + ``damping=0.1``. The Newton USD bakes ``stiffness=286 / damping=57`` on + these joints from the MJCF→USD translation, which fights the + ``MjcTendon`` coupling and bounces the ball. ``stiffness=10`` (~1/3 of + PhysX's ``limit_stiffness=30``) keeps the joints near-passive while the + tendon constraint dominates. """ physx_cfg = SHADOW_HAND_CFG.replace(prim_path=prim_path).replace( init_state=ArticulationCfg.InitialStateCfg(pos=init_pos, rot=init_rot, joint_pos={".*": 0.0}) @@ -170,7 +172,7 @@ def _shadow_hand_cfg( actuators={ "fingers": _SHADOW_HAND_NEWTON_CFG.actuators["fingers"].replace(stiffness=20.0, damping=2.0), "distal_passive": ImplicitActuatorCfg( - joint_names_expr=["robot0_(FF|MF|RF|LF)J0"], + joint_names_expr=["robot0_(FF|MF|RF|LF)J1"], stiffness=10.0, damping=0.1, friction=1e-2, @@ -178,7 +180,13 @@ def _shadow_hand_cfg( ), }, ) - return preset(default=physx_cfg, physx=physx_cfg, newton_mjwarp=newton_cfg) + ovphysx_cfg = SHADOW_HAND_CFG.replace( + prim_path=prim_path, + # OVPhysX does not expose the fixed-tendon runtime API, so spawn without tendon overrides. + spawn=SHADOW_HAND_CFG.spawn.replace(fixed_tendons_props=None), + init_state=SHADOW_HAND_CFG.init_state.replace(pos=init_pos, rot=init_rot), + ) + return preset(default=physx_cfg, physx=physx_cfg, newton_mjwarp=newton_cfg, ovphysx=ovphysx_cfg) @configclass @@ -261,6 +269,7 @@ class PhysicsCfg(PresetCfg): num_substeps=2, debug_mode=False, ) + ovphysx = OvPhysxCfg() default = physx diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py index f6bdae2bd0d8..732f3805a3f5 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: BSD-3-Clause from isaaclab_newton.physics import KaminoSolverCfg, MJWarpSolverCfg, NewtonCfg +from isaaclab_ovphysx.physics import OvPhysxCfg from isaaclab_physx.physics import PhysxCfg import isaaclab.envs.mdp as mdp @@ -207,6 +208,16 @@ class ShadowHandRobotCfg(PresetCfg): }, soft_joint_pos_limit_factor=1.0, ) + ovphysx = SHADOW_HAND_CFG.replace( + prim_path="/World/envs/env_.*/Robot", + # OVPhysX does not expose the fixed-tendon runtime API, so spawn without tendon overrides. + spawn=SHADOW_HAND_CFG.spawn.replace(fixed_tendons_props=None), + init_state=ArticulationCfg.InitialStateCfg( + pos=(0.0, 0.0, 0.5), + rot=(0.0, 0.0, 0.0, 1.0), + joint_pos={".*": 0.0}, + ), + ) default = physx newton_kamino = newton_mjwarp @@ -290,6 +301,7 @@ class PhysicsCfg(PresetCfg): num_substeps=2, debug_mode=False, ) + ovphysx = OvPhysxCfg() default = physx newton_kamino = NewtonCfg(solver_cfg=KaminoSolverCfg(max_contacts_per_world=128)) diff --git a/source/isaaclab_tasks/test/core/test_dexterous_ovphysx_presets.py b/source/isaaclab_tasks/test/core/test_dexterous_ovphysx_presets.py new file mode 100644 index 000000000000..982a5813a011 --- /dev/null +++ b/source/isaaclab_tasks/test/core/test_dexterous_ovphysx_presets.py @@ -0,0 +1,96 @@ +# 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 + +"""Configuration tests for OVPhysX support in assigned dexterous tasks.""" + +import pytest +from isaaclab_newton.physics import NewtonCfg +from isaaclab_ovphysx.physics import OvPhysxCfg + +from isaaclab.assets import ArticulationCfg + +from isaaclab_tasks.core.handover.handover_env_cfg import HandoverEnvCfg +from isaaclab_tasks.core.handover.handover_manager_env_cfg import HandoverManagerEnvCfg +from isaaclab_tasks.core.reorient.config.allegro_hand.allegro_hand_direct_env_cfg import AllegroHandEnvCfg +from isaaclab_tasks.core.reorient.config.allegro_hand.allegro_hand_manager_env_cfg import ( + AllegroCubeEnvCfg, + AllegroCubeEnvCfg_PLAY, +) +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_camera_env_cfg import ( + ShadowHandCameraBenchmarkEnvCfg, + ShadowHandCameraEnvCfg, + ShadowHandCameraEnvPlayCfg, +) +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_camera_manager_env_cfg import ( + ShadowHandCameraManagerBenchmarkEnvCfg, + ShadowHandCameraManagerEnvCfg, + ShadowHandCameraManagerPlayEnvCfg, +) +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_env_cfg import ( + ShadowHandEnvCfg, + ShadowHandOpenAIEnvCfg, +) +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_manager_env_cfg import ( + ShadowHandManagerEnvCfg, + ShadowHandOpenAIManagerEnvCfg, +) +from isaaclab_tasks.utils.hydra import collect_presets, resolve_presets + + +@pytest.mark.parametrize( + "env_cfg_type", + [ + pytest.param(AllegroHandEnvCfg, id="allegro_direct"), + pytest.param(ShadowHandEnvCfg, id="shadow_direct"), + pytest.param(ShadowHandOpenAIEnvCfg, id="shadow_openai_ff"), + pytest.param(ShadowHandCameraEnvCfg, id="shadow_camera"), + pytest.param(ShadowHandCameraEnvPlayCfg, id="shadow_camera_play"), + pytest.param(ShadowHandCameraBenchmarkEnvCfg, id="shadow_camera_benchmark"), + pytest.param(ShadowHandCameraManagerEnvCfg, id="shadow_camera_manager"), + pytest.param(ShadowHandCameraManagerPlayEnvCfg, id="shadow_camera_manager_play"), + pytest.param(ShadowHandCameraManagerBenchmarkEnvCfg, id="shadow_camera_manager_benchmark"), + pytest.param(HandoverEnvCfg, id="handover"), + pytest.param(HandoverManagerEnvCfg, id="handover_manager"), + pytest.param(AllegroCubeEnvCfg, id="allegro_manager"), + pytest.param(AllegroCubeEnvCfg_PLAY, id="allegro_manager_play"), + pytest.param(ShadowHandManagerEnvCfg, id="shadow_manager"), + pytest.param(ShadowHandOpenAIManagerEnvCfg, id="shadow_openai_manager"), + ], +) +def test_ovphysx_physics_preset_resolves_for_assigned_dexterous_variants(env_cfg_type): + """Verify every assigned variant resolves its physics preset to OVPhysX.""" + env_cfg = env_cfg_type() + + assert "ovphysx" in collect_presets(env_cfg)["sim.physics"] + + resolved_cfg = resolve_presets(env_cfg, {"ovphysx"}) + + assert isinstance(resolved_cfg.sim.physics, OvPhysxCfg) + + +@pytest.mark.parametrize("env_cfg_type", [AllegroCubeEnvCfg, AllegroCubeEnvCfg_PLAY]) +def test_newton_physics_and_scene_presets_resolve_for_allegro_manager(env_cfg_type): + """Verify Allegro Manager uses Newton physics, an articulated cube, and non-Fabric cloning.""" + resolved_cfg = resolve_presets(env_cfg_type(), {"newton_mjwarp"}) + + assert isinstance(resolved_cfg.sim.physics, NewtonCfg) + assert isinstance(resolved_cfg.scene.object, ArticulationCfg) + assert not resolved_cfg.scene.clone_in_fabric + + +@pytest.mark.parametrize("env_cfg_type", [HandoverEnvCfg, HandoverManagerEnvCfg]) +def test_newton_physics_and_scene_presets_resolve_for_handover(env_cfg_type): + """Verify Direct and Manager Handover resolve Newton physics and cloning together.""" + resolved_cfg = resolve_presets(env_cfg_type(), {"newton_mjwarp"}) + + assert isinstance(resolved_cfg.sim.physics, NewtonCfg) + assert not resolved_cfg.scene.clone_in_fabric + + +def test_default_physics_scene_preset_resolves_play_env_count_for_allegro_manager(): + """Verify the default Allegro Manager play scene retains its reduced environment count.""" + resolved_cfg = resolve_presets(AllegroCubeEnvCfg_PLAY(), set()) + + assert resolved_cfg.scene.num_envs == 50 diff --git a/source/isaaclab_tasks/test/core/test_handover_env_cfg.py b/source/isaaclab_tasks/test/core/test_handover_env_cfg.py new file mode 100644 index 000000000000..487cb110111d --- /dev/null +++ b/source/isaaclab_tasks/test/core/test_handover_env_cfg.py @@ -0,0 +1,51 @@ +# 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 + +"""Tests for the Shadow Hand handover environment configuration.""" + +import pytest + +from isaaclab_tasks.core.handover.handover_env_cfg import HandoverEnvCfg +from isaaclab_tasks.core.handover.handover_manager_env_cfg import HandoverManagerEnvCfg +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_env_cfg import ShadowHandEnvCfg + + +def test_hands_use_standard_shadow_assets(): + """Verify both hands reuse the single-agent Shadow Hand assets per backend.""" + cfg = HandoverEnvCfg() + single_agent_robot_cfg = ShadowHandEnvCfg().robot_cfg + + for hand in (cfg.right_robot_cfg, cfg.left_robot_cfg): + assert hand.physx.spawn.usd_path == single_agent_robot_cfg.physx.spawn.usd_path + assert hand.newton_mjwarp.spawn.usd_path == single_agent_robot_cfg.newton_mjwarp.spawn.usd_path + assert hand.ovphysx.spawn.usd_path == single_agent_robot_cfg.ovphysx.spawn.usd_path + assert hand.newton_mjwarp.spawn.fixed_tendons_props.damping == 0.1 + + +def test_hands_place_each_backend_with_the_same_pose(): + """Verify each hand uses one init pose across backends and normalized rotations.""" + cfg = HandoverEnvCfg() + + for hand in (cfg.right_robot_cfg, cfg.left_robot_cfg): + assert hand.newton_mjwarp.init_state.pos == hand.physx.init_state.pos == hand.ovphysx.init_state.pos + assert hand.physx.init_state.rot == hand.ovphysx.init_state.rot + assert sum(value**2 for value in hand.newton_mjwarp.init_state.rot) == pytest.approx(1.0) + + assert cfg.right_robot_cfg.physx.init_state.rot == (0.0, 0.0, 0.0, 1.0) + assert cfg.left_robot_cfg.physx.init_state.rot == (0.0, 0.0, 1.0, 0.0) + + manager_cfg = HandoverManagerEnvCfg() + manager_scene = manager_cfg.scene.newton_mjwarp + assert manager_scene.right_hand.newton_mjwarp.init_state.rot == cfg.right_robot_cfg.newton_mjwarp.init_state.rot + assert manager_scene.left_hand.newton_mjwarp.init_state.rot == cfg.left_robot_cfg.newton_mjwarp.init_state.rot + + +def test_newton_hands_override_passive_distal_joints(): + """Verify Newton hands keep the near-passive distal-joint override.""" + cfg = HandoverEnvCfg() + + for hand in (cfg.right_robot_cfg, cfg.left_robot_cfg): + assert set(hand.newton_mjwarp.actuators) == {"fingers", "distal_passive"} + assert hand.newton_mjwarp.actuators["distal_passive"].stiffness == 10.0 diff --git a/source/isaaclab_tasks/test/core/test_shadow_hand_env_cfg.py b/source/isaaclab_tasks/test/core/test_shadow_hand_env_cfg.py new file mode 100644 index 000000000000..beb68d7a81f3 --- /dev/null +++ b/source/isaaclab_tasks/test/core/test_shadow_hand_env_cfg.py @@ -0,0 +1,80 @@ +# 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 + +"""Tests for the shared Shadow Hand environment configuration.""" + +import pytest + +from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR + +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_env_cfg import ( + NewtonEventCfg, + PhysxEventCfg, + ShadowHandEnvCfg, +) + +from isaaclab_assets import SHADOW_HAND_CFG + + +def test_events_randomize_fixed_tendons_on_both_backends(): + """Verify PhysX and Newton events preserve fixed-tendon randomization.""" + for event_cfg in (PhysxEventCfg(), NewtonEventCfg()): + event = event_cfg.robot_tendon_properties + + assert event.func.__name__ == "randomize_fixed_tendon_parameters" + assert event.params["asset_cfg"].fixed_tendon_names == ".*" + + +def test_shadow_variants_use_supported_asset_and_backend_physics_layers(): + """Verify each backend preset uses the production Shadow Hand asset it supports.""" + cfg = ShadowHandEnvCfg() + legacy_path = f"{ISAAC_NUCLEUS_DIR}/Robots/ShadowRobot/ShadowHand/shadow_hand_instanceable.usd" + + assert SHADOW_HAND_CFG.spawn.usd_path == legacy_path + assert SHADOW_HAND_CFG.spawn.fixed_tendons_props is not None + assert cfg.robot_cfg.physx.spawn.usd_path == legacy_path + assert cfg.robot_cfg.ovphysx.spawn.usd_path == legacy_path + assert cfg.robot_cfg.newton_mjwarp.spawn.usd_path == ( + f"{ISAAC_NUCLEUS_DIR}/Robots/ShadowRobot/ShadowHandNewton/shadow_hand_instanceable.usda" + ) + assert cfg.robot_cfg.physx.spawn.variants is None + assert cfg.robot_cfg.ovphysx.spawn.variants is None + assert cfg.robot_cfg.newton_mjwarp.spawn.variants is None + assert cfg.robot_cfg.ovphysx.spawn.fixed_tendons_props is None + assert cfg.robot_cfg.physx.init_state.rot == (0.0, 0.0, 0.0, 1.0) + assert cfg.robot_cfg.ovphysx.init_state.rot == (0.0, 0.0, 0.0, 1.0) + assert cfg.robot_cfg.newton_mjwarp.spawn.fixed_tendons_props.damping == 0.1 + assert cfg.robot_cfg.newton_mjwarp.init_state.rot == (0.0, 0.0, -0.70710678118, 0.70710678118) + assert sum(value**2 for value in cfg.robot_cfg.newton_mjwarp.init_state.rot) == pytest.approx(1.0) + assert cfg.sim.physics.newton_mjwarp.num_substeps == 2 + + +def test_shadow_joint_mapping_preserves_twenty_action_contract(): + """Verify the actuated-joint list preserves the legacy 20-action order.""" + cfg = ShadowHandEnvCfg() + + assert cfg.action_space == len(cfg.actuated_joint_names) == 20 + assert cfg.actuated_joint_names == [ + "robot0_WRJ1", + "robot0_WRJ0", + "robot0_FFJ3", + "robot0_FFJ2", + "robot0_FFJ1", + "robot0_MFJ3", + "robot0_MFJ2", + "robot0_MFJ1", + "robot0_RFJ3", + "robot0_RFJ2", + "robot0_RFJ1", + "robot0_LFJ4", + "robot0_LFJ3", + "robot0_LFJ2", + "robot0_LFJ1", + "robot0_THJ4", + "robot0_THJ3", + "robot0_THJ2", + "robot0_THJ1", + "robot0_THJ0", + ] From 770cb46fd835d1b7d520a2ace42200835e9c8fe9 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Wed, 8 Jul 2026 12:57:24 +0000 Subject: [PATCH 2/7] Restrict the acceptance tests to the Direct variants The earlier commit was meant to ship the reduced Direct-only test set but the reduction was accidentally committed on a detached HEAD and never reached the branch. The manager-importing test rows and the handover configuration test move to the final manager PR, which is where their imports resolve. --- .../core/test_dexterous_ovphysx_presets.py | 53 +------------------ .../test/core/test_handover_env_cfg.py | 51 ------------------ 2 files changed, 2 insertions(+), 102 deletions(-) delete mode 100644 source/isaaclab_tasks/test/core/test_handover_env_cfg.py diff --git a/source/isaaclab_tasks/test/core/test_dexterous_ovphysx_presets.py b/source/isaaclab_tasks/test/core/test_dexterous_ovphysx_presets.py index 982a5813a011..0c4bb125093e 100644 --- a/source/isaaclab_tasks/test/core/test_dexterous_ovphysx_presets.py +++ b/source/isaaclab_tasks/test/core/test_dexterous_ovphysx_presets.py @@ -9,33 +9,12 @@ from isaaclab_newton.physics import NewtonCfg from isaaclab_ovphysx.physics import OvPhysxCfg -from isaaclab.assets import ArticulationCfg - from isaaclab_tasks.core.handover.handover_env_cfg import HandoverEnvCfg -from isaaclab_tasks.core.handover.handover_manager_env_cfg import HandoverManagerEnvCfg from isaaclab_tasks.core.reorient.config.allegro_hand.allegro_hand_direct_env_cfg import AllegroHandEnvCfg -from isaaclab_tasks.core.reorient.config.allegro_hand.allegro_hand_manager_env_cfg import ( - AllegroCubeEnvCfg, - AllegroCubeEnvCfg_PLAY, -) -from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_camera_env_cfg import ( - ShadowHandCameraBenchmarkEnvCfg, - ShadowHandCameraEnvCfg, - ShadowHandCameraEnvPlayCfg, -) -from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_camera_manager_env_cfg import ( - ShadowHandCameraManagerBenchmarkEnvCfg, - ShadowHandCameraManagerEnvCfg, - ShadowHandCameraManagerPlayEnvCfg, -) from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_env_cfg import ( ShadowHandEnvCfg, ShadowHandOpenAIEnvCfg, ) -from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_manager_env_cfg import ( - ShadowHandManagerEnvCfg, - ShadowHandOpenAIManagerEnvCfg, -) from isaaclab_tasks.utils.hydra import collect_presets, resolve_presets @@ -45,18 +24,7 @@ pytest.param(AllegroHandEnvCfg, id="allegro_direct"), pytest.param(ShadowHandEnvCfg, id="shadow_direct"), pytest.param(ShadowHandOpenAIEnvCfg, id="shadow_openai_ff"), - pytest.param(ShadowHandCameraEnvCfg, id="shadow_camera"), - pytest.param(ShadowHandCameraEnvPlayCfg, id="shadow_camera_play"), - pytest.param(ShadowHandCameraBenchmarkEnvCfg, id="shadow_camera_benchmark"), - pytest.param(ShadowHandCameraManagerEnvCfg, id="shadow_camera_manager"), - pytest.param(ShadowHandCameraManagerPlayEnvCfg, id="shadow_camera_manager_play"), - pytest.param(ShadowHandCameraManagerBenchmarkEnvCfg, id="shadow_camera_manager_benchmark"), pytest.param(HandoverEnvCfg, id="handover"), - pytest.param(HandoverManagerEnvCfg, id="handover_manager"), - pytest.param(AllegroCubeEnvCfg, id="allegro_manager"), - pytest.param(AllegroCubeEnvCfg_PLAY, id="allegro_manager_play"), - pytest.param(ShadowHandManagerEnvCfg, id="shadow_manager"), - pytest.param(ShadowHandOpenAIManagerEnvCfg, id="shadow_openai_manager"), ], ) def test_ovphysx_physics_preset_resolves_for_assigned_dexterous_variants(env_cfg_type): @@ -70,27 +38,10 @@ def test_ovphysx_physics_preset_resolves_for_assigned_dexterous_variants(env_cfg assert isinstance(resolved_cfg.sim.physics, OvPhysxCfg) -@pytest.mark.parametrize("env_cfg_type", [AllegroCubeEnvCfg, AllegroCubeEnvCfg_PLAY]) -def test_newton_physics_and_scene_presets_resolve_for_allegro_manager(env_cfg_type): - """Verify Allegro Manager uses Newton physics, an articulated cube, and non-Fabric cloning.""" - resolved_cfg = resolve_presets(env_cfg_type(), {"newton_mjwarp"}) - - assert isinstance(resolved_cfg.sim.physics, NewtonCfg) - assert isinstance(resolved_cfg.scene.object, ArticulationCfg) - assert not resolved_cfg.scene.clone_in_fabric - - -@pytest.mark.parametrize("env_cfg_type", [HandoverEnvCfg, HandoverManagerEnvCfg]) +@pytest.mark.parametrize("env_cfg_type", [HandoverEnvCfg]) def test_newton_physics_and_scene_presets_resolve_for_handover(env_cfg_type): - """Verify Direct and Manager Handover resolve Newton physics and cloning together.""" + """Verify Direct Handover resolves Newton physics and cloning together.""" resolved_cfg = resolve_presets(env_cfg_type(), {"newton_mjwarp"}) assert isinstance(resolved_cfg.sim.physics, NewtonCfg) assert not resolved_cfg.scene.clone_in_fabric - - -def test_default_physics_scene_preset_resolves_play_env_count_for_allegro_manager(): - """Verify the default Allegro Manager play scene retains its reduced environment count.""" - resolved_cfg = resolve_presets(AllegroCubeEnvCfg_PLAY(), set()) - - assert resolved_cfg.scene.num_envs == 50 diff --git a/source/isaaclab_tasks/test/core/test_handover_env_cfg.py b/source/isaaclab_tasks/test/core/test_handover_env_cfg.py deleted file mode 100644 index 487cb110111d..000000000000 --- a/source/isaaclab_tasks/test/core/test_handover_env_cfg.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Tests for the Shadow Hand handover environment configuration.""" - -import pytest - -from isaaclab_tasks.core.handover.handover_env_cfg import HandoverEnvCfg -from isaaclab_tasks.core.handover.handover_manager_env_cfg import HandoverManagerEnvCfg -from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_env_cfg import ShadowHandEnvCfg - - -def test_hands_use_standard_shadow_assets(): - """Verify both hands reuse the single-agent Shadow Hand assets per backend.""" - cfg = HandoverEnvCfg() - single_agent_robot_cfg = ShadowHandEnvCfg().robot_cfg - - for hand in (cfg.right_robot_cfg, cfg.left_robot_cfg): - assert hand.physx.spawn.usd_path == single_agent_robot_cfg.physx.spawn.usd_path - assert hand.newton_mjwarp.spawn.usd_path == single_agent_robot_cfg.newton_mjwarp.spawn.usd_path - assert hand.ovphysx.spawn.usd_path == single_agent_robot_cfg.ovphysx.spawn.usd_path - assert hand.newton_mjwarp.spawn.fixed_tendons_props.damping == 0.1 - - -def test_hands_place_each_backend_with_the_same_pose(): - """Verify each hand uses one init pose across backends and normalized rotations.""" - cfg = HandoverEnvCfg() - - for hand in (cfg.right_robot_cfg, cfg.left_robot_cfg): - assert hand.newton_mjwarp.init_state.pos == hand.physx.init_state.pos == hand.ovphysx.init_state.pos - assert hand.physx.init_state.rot == hand.ovphysx.init_state.rot - assert sum(value**2 for value in hand.newton_mjwarp.init_state.rot) == pytest.approx(1.0) - - assert cfg.right_robot_cfg.physx.init_state.rot == (0.0, 0.0, 0.0, 1.0) - assert cfg.left_robot_cfg.physx.init_state.rot == (0.0, 0.0, 1.0, 0.0) - - manager_cfg = HandoverManagerEnvCfg() - manager_scene = manager_cfg.scene.newton_mjwarp - assert manager_scene.right_hand.newton_mjwarp.init_state.rot == cfg.right_robot_cfg.newton_mjwarp.init_state.rot - assert manager_scene.left_hand.newton_mjwarp.init_state.rot == cfg.left_robot_cfg.newton_mjwarp.init_state.rot - - -def test_newton_hands_override_passive_distal_joints(): - """Verify Newton hands keep the near-passive distal-joint override.""" - cfg = HandoverEnvCfg() - - for hand in (cfg.right_robot_cfg, cfg.left_robot_cfg): - assert set(hand.newton_mjwarp.actuators) == {"fingers", "distal_passive"} - assert hand.newton_mjwarp.actuators["distal_passive"].stiffness == 10.0 From 935284ed80ece8ed85a4aea5ab95c125b206ad08 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Thu, 9 Jul 2026 01:05:40 +0000 Subject: [PATCH 3/7] Carry the handover Newton fixes in the full backend preset Mirrors the per-backend actuated-joint mapping, composed hand rotations, and contact substeps shipped in the handover enablement PR; this file remains the superset with the OVPhysX preset. --- .../core/handover/handover_env_cfg.py | 135 ++++++++++++++---- 1 file changed, 110 insertions(+), 25 deletions(-) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py index 9fc81cf0af0d..9b72c38ebc4b 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py @@ -132,6 +132,71 @@ class EventCfg: _SHADOW_HAND_NEWTON_CFG = ShadowHandRobotCfg().newton_mjwarp +# Physical actuated set of the Shadow Hand (wrist + non-distal finger joints + +# full thumb). The PhysX and OVPhysX asset names the finger chains J0..J3 +# (LF: J0..J4) with the tendon-coupled distal pair unactuated; the production +# Newton asset renumbers the four finger chains by +1 (FF/MF/RF: J1..J4, +# LF: J1..J5, distal = J1), so the same physical joints carry shifted names. +_ACTUATED_JOINT_NAMES = [ + "robot0_WRJ1", + "robot0_WRJ0", + "robot0_FFJ3", + "robot0_FFJ2", + "robot0_FFJ1", + "robot0_MFJ3", + "robot0_MFJ2", + "robot0_MFJ1", + "robot0_RFJ3", + "robot0_RFJ2", + "robot0_RFJ1", + "robot0_LFJ4", + "robot0_LFJ3", + "robot0_LFJ2", + "robot0_LFJ1", + "robot0_THJ4", + "robot0_THJ3", + "robot0_THJ2", + "robot0_THJ1", + "robot0_THJ0", +] +_ACTUATED_JOINT_NAMES_NEWTON = [ + "robot0_WRJ1", + "robot0_WRJ0", + "robot0_FFJ4", + "robot0_FFJ3", + "robot0_FFJ2", + "robot0_MFJ4", + "robot0_MFJ3", + "robot0_MFJ2", + "robot0_RFJ4", + "robot0_RFJ3", + "robot0_RFJ2", + "robot0_LFJ5", + "robot0_LFJ4", + "robot0_LFJ3", + "robot0_LFJ2", + "robot0_THJ4", + "robot0_THJ3", + "robot0_THJ2", + "robot0_THJ1", + "robot0_THJ0", +] + + +def _quat_mul_xyzw( + q1: tuple[float, float, float, float], q2: tuple[float, float, float, float] +) -> tuple[float, float, float, float]: + """Compose two ``(x, y, z, w)`` quaternions (``q1`` applied on top of ``q2``).""" + x1, y1, z1, w1 = q1 + x2, y2, z2, w2 = q2 + return ( + w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2, + w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2, + w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2, + w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2, + ) + + def _shadow_hand_cfg( prim_path: str, init_pos: tuple[float, float, float], @@ -166,11 +231,44 @@ def _shadow_hand_cfg( physx_cfg = SHADOW_HAND_CFG.replace(prim_path=prim_path).replace( init_state=ArticulationCfg.InitialStateCfg(pos=init_pos, rot=init_rot, joint_pos={".*": 0.0}) ) + # Newton's importer bakes the asset's native root orientation into the + # root joint (see the note on _SHADOW_HAND_NEWTON_CFG.init_state), so the + # task rotation must compose with that base rotation rather than replace + # it — replacing left both palms heading 90 degrees off and the object + # never rested in the right hand. + newton_rot = _quat_mul_xyzw(init_rot, _SHADOW_HAND_NEWTON_CFG.init_state.rot) newton_cfg = _SHADOW_HAND_NEWTON_CFG.replace( prim_path=prim_path, - init_state=_SHADOW_HAND_NEWTON_CFG.init_state.replace(pos=init_pos, rot=init_rot), + init_state=_SHADOW_HAND_NEWTON_CFG.init_state.replace(pos=init_pos, rot=newton_rot), actuators={ - "fingers": _SHADOW_HAND_NEWTON_CFG.actuators["fingers"].replace(stiffness=20.0, damping=2.0), + # The inherited "fingers" expression predates the renamed Newton + # asset: on the renumbered chains it drives the tendon-coupled + # distal J1 joints and leaves the J4 knuckles (and the LFJ5 + # metacarpal) without a drive. Redeclare it against the renamed + # joints so the actuated set matches the PhysX hand physically. + "fingers": _SHADOW_HAND_NEWTON_CFG.actuators["fingers"].replace( + joint_names_expr=[ + "robot0_WR.*", + "robot0_(FF|MF|RF)J(4|3|2)", + "robot0_LFJ(5|4|3|2)", + "robot0_THJ[0-4]", + ], + effort_limit_sim={ + "robot0_WRJ1": 4.785, + "robot0_WRJ0": 2.175, + "robot0_(FF|MF|RF|LF)J2": 0.7245, + "robot0_FFJ(4|3)": 0.9, + "robot0_MFJ(4|3)": 0.9, + "robot0_RFJ(4|3)": 0.9, + "robot0_LFJ(5|4|3)": 0.9, + "robot0_THJ4": 2.3722, + "robot0_THJ3": 1.45, + "robot0_THJ(2|1)": 0.99, + "robot0_THJ0": 0.81, + }, + stiffness=20.0, + damping=2.0, + ), "distal_passive": ImplicitActuatorCfg( joint_names_expr=["robot0_(FF|MF|RF|LF)J1"], stiffness=10.0, @@ -266,7 +364,10 @@ class PhysicsCfg(PresetCfg): update_data_interval=2, ccd_iterations=50, # bumped from default 35 for multi-finger contact geometry ), - num_substeps=2, + # 4 substeps (vs the single-agent port's 2): sustained ball-palm contact + # against the near-passive distal joints explodes ~0.7% of 8192 envs to + # NaN at 2 substeps (zero-action probe, 300 steps); 4 substeps shows none. + num_substeps=4, debug_mode=False, ) ovphysx = OvPhysxCfg() @@ -304,28 +405,12 @@ class HandoverEnvCfg(DirectMARLEnvCfg): init_pos=(0.0, -1.0, 0.5), init_rot=(0.0, 0.0, 1.0, 0.0), ) - actuated_joint_names = [ - "robot0_WRJ1", - "robot0_WRJ0", - "robot0_FFJ3", - "robot0_FFJ2", - "robot0_FFJ1", - "robot0_MFJ3", - "robot0_MFJ2", - "robot0_MFJ1", - "robot0_RFJ3", - "robot0_RFJ2", - "robot0_RFJ1", - "robot0_LFJ4", - "robot0_LFJ3", - "robot0_LFJ2", - "robot0_LFJ1", - "robot0_THJ4", - "robot0_THJ3", - "robot0_THJ2", - "robot0_THJ1", - "robot0_THJ0", - ] + actuated_joint_names: PresetCfg = preset( + physx=_ACTUATED_JOINT_NAMES, + newton_mjwarp=_ACTUATED_JOINT_NAMES_NEWTON, + ovphysx=_ACTUATED_JOINT_NAMES, + default=_ACTUATED_JOINT_NAMES, + ) fingertip_body_names = [ "robot0_ffdistal", "robot0_mfdistal", From 701b9c1f7f4f2a395667641b12cd128f4056388b Mon Sep 17 00:00:00 2001 From: jichuanh Date: Thu, 9 Jul 2026 04:22:23 +0000 Subject: [PATCH 4/7] Apply the lumped-branch handover and constants updates The handover cfg carries the Newton fixes from Part 4 plus the OVPhysX presets; the Shadow cfg consumes the shared fingertip constants from Part 3. --- .../core/handover/handover_env_cfg.py | 95 ++++--------------- .../config/shadow_hand/shadow_hand_env_cfg.py | 9 +- 2 files changed, 23 insertions(+), 81 deletions(-) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py index 9b72c38ebc4b..8293b46c2895 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py @@ -20,6 +20,10 @@ from isaaclab.sim.spawners.materials.physics_materials_cfg import RigidBodyMaterialCfg from isaaclab.utils.configclass import configclass +from isaaclab_tasks.core.handover.handover_task_constants import ( + ACTUATED_JOINT_NAMES_PRESET, + FINGERTIP_BODY_NAMES, +) from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_env_cfg import ShadowHandRobotCfg from isaaclab_tasks.utils import PresetCfg, preset @@ -132,57 +136,6 @@ class EventCfg: _SHADOW_HAND_NEWTON_CFG = ShadowHandRobotCfg().newton_mjwarp -# Physical actuated set of the Shadow Hand (wrist + non-distal finger joints + -# full thumb). The PhysX and OVPhysX asset names the finger chains J0..J3 -# (LF: J0..J4) with the tendon-coupled distal pair unactuated; the production -# Newton asset renumbers the four finger chains by +1 (FF/MF/RF: J1..J4, -# LF: J1..J5, distal = J1), so the same physical joints carry shifted names. -_ACTUATED_JOINT_NAMES = [ - "robot0_WRJ1", - "robot0_WRJ0", - "robot0_FFJ3", - "robot0_FFJ2", - "robot0_FFJ1", - "robot0_MFJ3", - "robot0_MFJ2", - "robot0_MFJ1", - "robot0_RFJ3", - "robot0_RFJ2", - "robot0_RFJ1", - "robot0_LFJ4", - "robot0_LFJ3", - "robot0_LFJ2", - "robot0_LFJ1", - "robot0_THJ4", - "robot0_THJ3", - "robot0_THJ2", - "robot0_THJ1", - "robot0_THJ0", -] -_ACTUATED_JOINT_NAMES_NEWTON = [ - "robot0_WRJ1", - "robot0_WRJ0", - "robot0_FFJ4", - "robot0_FFJ3", - "robot0_FFJ2", - "robot0_MFJ4", - "robot0_MFJ3", - "robot0_MFJ2", - "robot0_RFJ4", - "robot0_RFJ3", - "robot0_RFJ2", - "robot0_LFJ5", - "robot0_LFJ4", - "robot0_LFJ3", - "robot0_LFJ2", - "robot0_THJ4", - "robot0_THJ3", - "robot0_THJ2", - "robot0_THJ1", - "robot0_THJ0", -] - - def _quat_mul_xyzw( q1: tuple[float, float, float, float], q2: tuple[float, float, float, float] ) -> tuple[float, float, float, float]: @@ -287,6 +240,19 @@ def _shadow_hand_cfg( return preset(default=physx_cfg, physx=physx_cfg, newton_mjwarp=newton_cfg, ovphysx=ovphysx_cfg) +# Per-hand presets shared by the Direct environment and the manager scene. +RIGHT_HAND_CFG = _shadow_hand_cfg( + prim_path="/World/envs/env_.*/RightRobot", + init_pos=(0.0, 0.0, 0.5), + init_rot=(0.0, 0.0, 0.0, 1.0), +) +LEFT_HAND_CFG = _shadow_hand_cfg( + prim_path="/World/envs/env_.*/LeftRobot", + init_pos=(0.0, -1.0, 0.5), + init_rot=(0.0, 0.0, 1.0, 0.0), +) + + @configclass class ObjectCfg(PresetCfg): """Hand-over object preset. @@ -395,29 +361,10 @@ class HandoverEnvCfg(DirectMARLEnvCfg): physics=PhysicsCfg(), ) # robot - right_robot_cfg: PresetCfg = _shadow_hand_cfg( - prim_path="/World/envs/env_.*/RightRobot", - init_pos=(0.0, 0.0, 0.5), - init_rot=(0.0, 0.0, 0.0, 1.0), - ) - left_robot_cfg: PresetCfg = _shadow_hand_cfg( - prim_path="/World/envs/env_.*/LeftRobot", - init_pos=(0.0, -1.0, 0.5), - init_rot=(0.0, 0.0, 1.0, 0.0), - ) - actuated_joint_names: PresetCfg = preset( - physx=_ACTUATED_JOINT_NAMES, - newton_mjwarp=_ACTUATED_JOINT_NAMES_NEWTON, - ovphysx=_ACTUATED_JOINT_NAMES, - default=_ACTUATED_JOINT_NAMES, - ) - fingertip_body_names = [ - "robot0_ffdistal", - "robot0_mfdistal", - "robot0_rfdistal", - "robot0_lfdistal", - "robot0_thdistal", - ] + right_robot_cfg: PresetCfg = RIGHT_HAND_CFG + left_robot_cfg: PresetCfg = LEFT_HAND_CFG + actuated_joint_names: PresetCfg = ACTUATED_JOINT_NAMES_PRESET + fingertip_body_names = FINGERTIP_BODY_NAMES # in-hand object object_cfg: ObjectCfg = ObjectCfg() diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py index 732f3805a3f5..2175945ac527 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py @@ -22,6 +22,7 @@ from isaaclab.utils.configclass import configclass from isaaclab.utils.noise import GaussianNoiseCfg, NoiseModelWithAdditiveBiasCfg +from isaaclab_tasks.core.reorient.reorient_task_constants import SHADOW_FINGERTIP_BODY_NAMES from isaaclab_tasks.utils import PresetCfg from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_CFG @@ -348,13 +349,7 @@ class ShadowHandEnvCfg(DirectRLEnvCfg): "robot0_THJ1", "robot0_THJ0", ] - fingertip_body_names = [ - "robot0_ffdistal", - "robot0_mfdistal", - "robot0_rfdistal", - "robot0_lfdistal", - "robot0_thdistal", - ] + fingertip_body_names = SHADOW_FINGERTIP_BODY_NAMES # in-hand object object_cfg: ObjectCfg = ObjectCfg() From 9c13b3e3aa3cffdefe8b1e77886f46b7977959e6 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Thu, 9 Jul 2026 08:22:29 +0000 Subject: [PATCH 5/7] Wire the Direct cfg scalars through the task constants The Shadow and OpenAI Direct cfgs default their scalar fields from the per-variant task constants and promote the shared scene and noise cfg objects to module scope, so the manager counterparts consume one source without instantiating a throwaway Direct cfg. The handover cfg carries the same treatment plus the float64 hand-rotation composition. --- .../core/handover/handover_env_cfg.py | 45 ++--- .../config/shadow_hand/shadow_hand_env_cfg.py | 188 ++++++++++-------- 2 files changed, 130 insertions(+), 103 deletions(-) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py index 8293b46c2895..1beefcfa67fb 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: BSD-3-Clause +import warp as wp from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg from isaaclab_ovphysx.physics import OvPhysxCfg from isaaclab_physx.physics import PhysxCfg @@ -22,7 +23,15 @@ from isaaclab_tasks.core.handover.handover_task_constants import ( ACTUATED_JOINT_NAMES_PRESET, + DECIMATION, + DIST_REWARD_SCALE, + EPISODE_LENGTH_S, + FALL_DISTANCE, FINGERTIP_BODY_NAMES, + RESET_DOF_POS_NOISE, + RESET_DOF_VEL_NOISE, + RESET_POSITION_NOISE, + SUCCESS_DISTANCE_THRESHOLD, ) from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_env_cfg import ShadowHandRobotCfg from isaaclab_tasks.utils import PresetCfg, preset @@ -132,24 +141,10 @@ class EventCfg: # Reuse the single-agent Shadow Hand Newton port (USD path, ``rot`` reapplication # workaround, effort limits, joint regex). The multi-agent variant only diverges # in actuator gains (stiffness/damping bumped for the catch task) and adds a -# ``distal_passive`` override for the J0 USD-baked values. +# ``distal_passive`` override for the J1 USD-baked values. _SHADOW_HAND_NEWTON_CFG = ShadowHandRobotCfg().newton_mjwarp -def _quat_mul_xyzw( - q1: tuple[float, float, float, float], q2: tuple[float, float, float, float] -) -> tuple[float, float, float, float]: - """Compose two ``(x, y, z, w)`` quaternions (``q1`` applied on top of ``q2``).""" - x1, y1, z1, w1 = q1 - x2, y2, z2, w2 = q2 - return ( - w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2, - w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2, - w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2, - w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2, - ) - - def _shadow_hand_cfg( prim_path: str, init_pos: tuple[float, float, float], @@ -189,7 +184,9 @@ def _shadow_hand_cfg( # task rotation must compose with that base rotation rather than replace # it — replacing left both palms heading 90 degrees off and the object # never rested in the right hand. - newton_rot = _quat_mul_xyzw(init_rot, _SHADOW_HAND_NEWTON_CFG.init_state.rot) + # Warp's quaternion type is (x, y, z, w); use the double variant so the + # composed cfg values keep float64 precision. + newton_rot = tuple(float(v) for v in wp.quatd(*init_rot) * wp.quatd(*_SHADOW_HAND_NEWTON_CFG.init_state.rot)) newton_cfg = _SHADOW_HAND_NEWTON_CFG.replace( prim_path=prim_path, init_state=_SHADOW_HAND_NEWTON_CFG.init_state.replace(pos=init_pos, rot=newton_rot), @@ -343,8 +340,8 @@ class PhysicsCfg(PresetCfg): @configclass class HandoverEnvCfg(DirectMARLEnvCfg): # env - decimation = 2 - episode_length_s = 7.5 + decimation = DECIMATION + episode_length_s = EPISODE_LENGTH_S possible_agents = ["right_hand", "left_hand"] action_spaces = {"right_hand": 20, "left_hand": 20} observation_spaces = {"right_hand": 157, "left_hand": 157} @@ -382,15 +379,15 @@ class HandoverEnvCfg(DirectMARLEnvCfg): scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=2048, env_spacing=1.5, replicate_physics=True) # reset - reset_position_noise = 0.01 # range of position at reset - reset_dof_pos_noise = 0.2 # range of dof pos at reset - reset_dof_vel_noise = 0.0 # range of dof vel at reset + reset_position_noise = RESET_POSITION_NOISE # range of position at reset + reset_dof_pos_noise = RESET_DOF_POS_NOISE # range of dof pos at reset + reset_dof_vel_noise = RESET_DOF_VEL_NOISE # range of dof vel at reset # scales and constants - fall_dist = 0.24 + fall_dist = FALL_DISTANCE vel_obs_scale = 0.2 act_moving_average = 1.0 # success criteria - success_distance_threshold: float = 0.1 + success_distance_threshold: float = SUCCESS_DISTANCE_THRESHOLD """Object-to-goal distance below which the handover is considered successful [m].""" # reward-related scales - dist_reward_scale = 20.0 + dist_reward_scale = DIST_REWARD_SCALE diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py index 2175945ac527..5dfe6713922f 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py @@ -22,7 +22,47 @@ from isaaclab.utils.configclass import configclass from isaaclab.utils.noise import GaussianNoiseCfg, NoiseModelWithAdditiveBiasCfg -from isaaclab_tasks.core.reorient.reorient_task_constants import SHADOW_FINGERTIP_BODY_NAMES +from isaaclab_tasks.core.reorient.reorient_task_constants import ( + OPENAI_ACT_MOVING_AVERAGE, + OPENAI_ACTION_PENALTY_SCALE, + OPENAI_AV_FACTOR, + OPENAI_DECIMATION, + OPENAI_DIST_REWARD_SCALE, + OPENAI_EPISODE_LENGTH_S, + OPENAI_FALL_PENALTY, + OPENAI_FORCE_TORQUE_OBS_SCALE, + OPENAI_MAX_CONSECUTIVE_SUCCESS, + OPENAI_REACH_GOAL_BONUS, + OPENAI_RESET_DOF_POS_NOISE, + OPENAI_RESET_DOF_VEL_NOISE, + OPENAI_RESET_POSITION_NOISE, + OPENAI_ROT_EPS, + OPENAI_ROT_REWARD_SCALE, + OPENAI_SIM_DT, + OPENAI_SUCCESS_TOLERANCE, + OPENAI_VEL_OBS_SCALE, + SHADOW_ACT_MOVING_AVERAGE, + SHADOW_ACTION_PENALTY_SCALE, + SHADOW_ACTUATED_JOINT_NAMES, + SHADOW_AV_FACTOR, + SHADOW_DECIMATION, + SHADOW_DIST_REWARD_SCALE, + SHADOW_EPISODE_LENGTH_S, + SHADOW_FALL_DIST, + SHADOW_FALL_PENALTY, + SHADOW_FINGERTIP_BODY_NAMES, + SHADOW_FORCE_TORQUE_OBS_SCALE, + SHADOW_REACH_GOAL_BONUS, + SHADOW_RESET_DOF_POS_NOISE, + SHADOW_RESET_DOF_VEL_NOISE, + SHADOW_RESET_POSITION_NOISE, + SHADOW_ROT_EPS, + SHADOW_ROT_REWARD_SCALE, + SHADOW_SIM_DT, + SHADOW_SUCCESS_COUNT_THRESHOLD, + SHADOW_SUCCESS_TOLERANCE, + SHADOW_VEL_OBS_SCALE, +) from isaaclab_tasks.utils import PresetCfg from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_CFG @@ -307,11 +347,25 @@ class PhysicsCfg(PresetCfg): newton_kamino = NewtonCfg(solver_cfg=KaminoSolverCfg(max_contacts_per_world=128)) +# Scene pieces shared verbatim by the manager-based variants. +ROBOT_CFG = ShadowHandRobotCfg() +OBJECT_CFG = ObjectCfg() +GOAL_OBJECT_CFG = VisualizationMarkersCfg( + prim_path="/Visuals/goal_marker", + markers={ + "goal": sim_utils.UsdFileCfg( + usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd", + scale=(1.0, 1.0, 1.0), + ) + }, +) + + @configclass class ShadowHandEnvCfg(DirectRLEnvCfg): # env - decimation = 2 - episode_length_s = 10.0 + decimation = SHADOW_DECIMATION + episode_length_s = SHADOW_EPISODE_LENGTH_S action_space = 20 observation_space = 157 # (full) state_space = 0 @@ -320,79 +374,61 @@ class ShadowHandEnvCfg(DirectRLEnvCfg): # simulation sim: SimulationCfg = SimulationCfg( - dt=1 / 120, + dt=SHADOW_SIM_DT, render_interval=decimation, physics_material=RigidBodyMaterialCfg(static_friction=1.0, dynamic_friction=1.0), physics=PhysicsCfg(), ) # robot - robot_cfg: ShadowHandRobotCfg = ShadowHandRobotCfg() - actuated_joint_names = [ - "robot0_WRJ1", - "robot0_WRJ0", - "robot0_FFJ3", - "robot0_FFJ2", - "robot0_FFJ1", - "robot0_MFJ3", - "robot0_MFJ2", - "robot0_MFJ1", - "robot0_RFJ3", - "robot0_RFJ2", - "robot0_RFJ1", - "robot0_LFJ4", - "robot0_LFJ3", - "robot0_LFJ2", - "robot0_LFJ1", - "robot0_THJ4", - "robot0_THJ3", - "robot0_THJ2", - "robot0_THJ1", - "robot0_THJ0", - ] + robot_cfg: ShadowHandRobotCfg = ROBOT_CFG + actuated_joint_names = SHADOW_ACTUATED_JOINT_NAMES fingertip_body_names = SHADOW_FINGERTIP_BODY_NAMES # in-hand object - object_cfg: ObjectCfg = ObjectCfg() + object_cfg: ObjectCfg = OBJECT_CFG # goal object - goal_object_cfg: VisualizationMarkersCfg = VisualizationMarkersCfg( - prim_path="/Visuals/goal_marker", - markers={ - "goal": sim_utils.UsdFileCfg( - usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd", - scale=(1.0, 1.0, 1.0), - ) - }, - ) + goal_object_cfg: VisualizationMarkersCfg = GOAL_OBJECT_CFG # scene — use ShadowHandSceneCfg so that presets=newton_mjwarp disables clone_in_fabric automatically scene: ShadowHandSceneCfg = ShadowHandSceneCfg() # reset - reset_position_noise = 0.01 # range of position at reset - reset_dof_pos_noise = 0.2 # range of dof pos at reset - reset_dof_vel_noise = 0.0 # range of dof vel at reset + reset_position_noise = SHADOW_RESET_POSITION_NOISE # range of position at reset + reset_dof_pos_noise = SHADOW_RESET_DOF_POS_NOISE # range of dof pos at reset + reset_dof_vel_noise = SHADOW_RESET_DOF_VEL_NOISE # range of dof vel at reset # reward scales - dist_reward_scale = -10.0 - rot_reward_scale = 1.0 - rot_eps = 0.1 - action_penalty_scale = -0.0002 - reach_goal_bonus = 250 - fall_penalty = 0 - fall_dist = 0.24 - vel_obs_scale = 0.2 - success_tolerance = 0.1 + dist_reward_scale = SHADOW_DIST_REWARD_SCALE + rot_reward_scale = SHADOW_ROT_REWARD_SCALE + rot_eps = SHADOW_ROT_EPS + action_penalty_scale = SHADOW_ACTION_PENALTY_SCALE + reach_goal_bonus = SHADOW_REACH_GOAL_BONUS + fall_penalty = SHADOW_FALL_PENALTY + fall_dist = SHADOW_FALL_DIST + vel_obs_scale = SHADOW_VEL_OBS_SCALE + success_tolerance = SHADOW_SUCCESS_TOLERANCE max_consecutive_success = 0 - success_count_threshold: int = 1 + success_count_threshold: int = SHADOW_SUCCESS_COUNT_THRESHOLD """Minimum number of goals reached in an episode to count it as a successful episode.""" - av_factor = 0.1 - act_moving_average = 1.0 - force_torque_obs_scale = 10.0 + av_factor = SHADOW_AV_FACTOR + act_moving_average = SHADOW_ACT_MOVING_AVERAGE + force_torque_obs_scale = SHADOW_FORCE_TORQUE_OBS_SCALE + + +# Per-step gaussian noise + reset-sampled bias, shared verbatim by the manager-based variant. +OPENAI_ACTION_NOISE_CFG = NoiseModelWithAdditiveBiasCfg( + noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.05, operation="add"), + bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.015, operation="abs"), +) +OPENAI_OBSERVATION_NOISE_CFG = NoiseModelWithAdditiveBiasCfg( + noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.002, operation="add"), + bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.0001, operation="abs"), +) @configclass class ShadowHandOpenAIEnvCfg(ShadowHandEnvCfg): # env - decimation = 3 - episode_length_s = 8.0 + decimation = OPENAI_DECIMATION + episode_length_s = OPENAI_EPISODE_LENGTH_S action_space = 20 observation_space = 42 state_space = 187 @@ -400,37 +436,31 @@ class ShadowHandOpenAIEnvCfg(ShadowHandEnvCfg): obs_type = "openai" # simulation sim: SimulationCfg = SimulationCfg( - dt=1 / 60, + dt=OPENAI_SIM_DT, render_interval=decimation, physics_material=RigidBodyMaterialCfg(static_friction=1.0, dynamic_friction=1.0), physics=PhysicsCfg(), ) # reset - reset_position_noise = 0.01 # range of position at reset - reset_dof_pos_noise = 0.2 # range of dof pos at reset - reset_dof_vel_noise = 0.0 # range of dof vel at reset + reset_position_noise = OPENAI_RESET_POSITION_NOISE # range of position at reset + reset_dof_pos_noise = OPENAI_RESET_DOF_POS_NOISE # range of dof pos at reset + reset_dof_vel_noise = OPENAI_RESET_DOF_VEL_NOISE # range of dof vel at reset # reward scales - dist_reward_scale = -10.0 - rot_reward_scale = 1.0 - rot_eps = 0.1 - action_penalty_scale = -0.0002 - reach_goal_bonus = 250 - fall_penalty = -50 - vel_obs_scale = 0.2 - success_tolerance = 0.4 - max_consecutive_success = 50 - av_factor = 0.1 - act_moving_average = 0.3 - force_torque_obs_scale = 10.0 + dist_reward_scale = OPENAI_DIST_REWARD_SCALE + rot_reward_scale = OPENAI_ROT_REWARD_SCALE + rot_eps = OPENAI_ROT_EPS + action_penalty_scale = OPENAI_ACTION_PENALTY_SCALE + reach_goal_bonus = OPENAI_REACH_GOAL_BONUS + fall_penalty = OPENAI_FALL_PENALTY + vel_obs_scale = OPENAI_VEL_OBS_SCALE + success_tolerance = OPENAI_SUCCESS_TOLERANCE + max_consecutive_success = OPENAI_MAX_CONSECUTIVE_SUCCESS + av_factor = OPENAI_AV_FACTOR + act_moving_average = OPENAI_ACT_MOVING_AVERAGE + force_torque_obs_scale = OPENAI_FORCE_TORQUE_OBS_SCALE # domain randomization config events: ShadowHandEventCfg = ShadowHandEventCfg() # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset - action_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( - noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.05, operation="add"), - bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.015, operation="abs"), - ) + action_noise_model: NoiseModelWithAdditiveBiasCfg = OPENAI_ACTION_NOISE_CFG # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset - observation_noise_model: NoiseModelWithAdditiveBiasCfg = NoiseModelWithAdditiveBiasCfg( - noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.002, operation="add"), - bias_noise_cfg=GaussianNoiseCfg(mean=0.0, std=0.0001, operation="abs"), - ) + observation_noise_model: NoiseModelWithAdditiveBiasCfg = OPENAI_OBSERVATION_NOISE_CFG From 398c541aab4d47ce5d6a6cda8dea1212d99f1123 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Sat, 11 Jul 2026 00:28:02 +0000 Subject: [PATCH 6/7] Sync the handover cfg constants and trim cfg tests The handover env cfg reads SIM_DT and VEL_OBS_SCALE from the shared constants module instead of literals. The cfg-content test files (Shadow cfg, OVPhysX preset acceptance) are dropped per the tests-assert-behavior principle; the consolidated kernel behavior suite ships with Part 11. --- .../core/handover/handover_env_cfg.py | 6 +- .../core/test_dexterous_ovphysx_presets.py | 47 ----------- .../test/core/test_shadow_hand_env_cfg.py | 80 ------------------- 3 files changed, 4 insertions(+), 129 deletions(-) delete mode 100644 source/isaaclab_tasks/test/core/test_dexterous_ovphysx_presets.py delete mode 100644 source/isaaclab_tasks/test/core/test_shadow_hand_env_cfg.py diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py index 1beefcfa67fb..189c7dd6995d 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py @@ -31,7 +31,9 @@ RESET_DOF_POS_NOISE, RESET_DOF_VEL_NOISE, RESET_POSITION_NOISE, + SIM_DT, SUCCESS_DISTANCE_THRESHOLD, + VEL_OBS_SCALE, ) from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_env_cfg import ShadowHandRobotCfg from isaaclab_tasks.utils import PresetCfg, preset @@ -349,7 +351,7 @@ class HandoverEnvCfg(DirectMARLEnvCfg): # simulation sim: SimulationCfg = SimulationCfg( - dt=1 / 120, + dt=SIM_DT, render_interval=decimation, physics_material=RigidBodyMaterialCfg( static_friction=1.0, @@ -384,7 +386,7 @@ class HandoverEnvCfg(DirectMARLEnvCfg): reset_dof_vel_noise = RESET_DOF_VEL_NOISE # range of dof vel at reset # scales and constants fall_dist = FALL_DISTANCE - vel_obs_scale = 0.2 + vel_obs_scale = VEL_OBS_SCALE act_moving_average = 1.0 # success criteria success_distance_threshold: float = SUCCESS_DISTANCE_THRESHOLD diff --git a/source/isaaclab_tasks/test/core/test_dexterous_ovphysx_presets.py b/source/isaaclab_tasks/test/core/test_dexterous_ovphysx_presets.py deleted file mode 100644 index 0c4bb125093e..000000000000 --- a/source/isaaclab_tasks/test/core/test_dexterous_ovphysx_presets.py +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Configuration tests for OVPhysX support in assigned dexterous tasks.""" - -import pytest -from isaaclab_newton.physics import NewtonCfg -from isaaclab_ovphysx.physics import OvPhysxCfg - -from isaaclab_tasks.core.handover.handover_env_cfg import HandoverEnvCfg -from isaaclab_tasks.core.reorient.config.allegro_hand.allegro_hand_direct_env_cfg import AllegroHandEnvCfg -from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_env_cfg import ( - ShadowHandEnvCfg, - ShadowHandOpenAIEnvCfg, -) -from isaaclab_tasks.utils.hydra import collect_presets, resolve_presets - - -@pytest.mark.parametrize( - "env_cfg_type", - [ - pytest.param(AllegroHandEnvCfg, id="allegro_direct"), - pytest.param(ShadowHandEnvCfg, id="shadow_direct"), - pytest.param(ShadowHandOpenAIEnvCfg, id="shadow_openai_ff"), - pytest.param(HandoverEnvCfg, id="handover"), - ], -) -def test_ovphysx_physics_preset_resolves_for_assigned_dexterous_variants(env_cfg_type): - """Verify every assigned variant resolves its physics preset to OVPhysX.""" - env_cfg = env_cfg_type() - - assert "ovphysx" in collect_presets(env_cfg)["sim.physics"] - - resolved_cfg = resolve_presets(env_cfg, {"ovphysx"}) - - assert isinstance(resolved_cfg.sim.physics, OvPhysxCfg) - - -@pytest.mark.parametrize("env_cfg_type", [HandoverEnvCfg]) -def test_newton_physics_and_scene_presets_resolve_for_handover(env_cfg_type): - """Verify Direct Handover resolves Newton physics and cloning together.""" - resolved_cfg = resolve_presets(env_cfg_type(), {"newton_mjwarp"}) - - assert isinstance(resolved_cfg.sim.physics, NewtonCfg) - assert not resolved_cfg.scene.clone_in_fabric diff --git a/source/isaaclab_tasks/test/core/test_shadow_hand_env_cfg.py b/source/isaaclab_tasks/test/core/test_shadow_hand_env_cfg.py deleted file mode 100644 index beb68d7a81f3..000000000000 --- a/source/isaaclab_tasks/test/core/test_shadow_hand_env_cfg.py +++ /dev/null @@ -1,80 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Tests for the shared Shadow Hand environment configuration.""" - -import pytest - -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR - -from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_env_cfg import ( - NewtonEventCfg, - PhysxEventCfg, - ShadowHandEnvCfg, -) - -from isaaclab_assets import SHADOW_HAND_CFG - - -def test_events_randomize_fixed_tendons_on_both_backends(): - """Verify PhysX and Newton events preserve fixed-tendon randomization.""" - for event_cfg in (PhysxEventCfg(), NewtonEventCfg()): - event = event_cfg.robot_tendon_properties - - assert event.func.__name__ == "randomize_fixed_tendon_parameters" - assert event.params["asset_cfg"].fixed_tendon_names == ".*" - - -def test_shadow_variants_use_supported_asset_and_backend_physics_layers(): - """Verify each backend preset uses the production Shadow Hand asset it supports.""" - cfg = ShadowHandEnvCfg() - legacy_path = f"{ISAAC_NUCLEUS_DIR}/Robots/ShadowRobot/ShadowHand/shadow_hand_instanceable.usd" - - assert SHADOW_HAND_CFG.spawn.usd_path == legacy_path - assert SHADOW_HAND_CFG.spawn.fixed_tendons_props is not None - assert cfg.robot_cfg.physx.spawn.usd_path == legacy_path - assert cfg.robot_cfg.ovphysx.spawn.usd_path == legacy_path - assert cfg.robot_cfg.newton_mjwarp.spawn.usd_path == ( - f"{ISAAC_NUCLEUS_DIR}/Robots/ShadowRobot/ShadowHandNewton/shadow_hand_instanceable.usda" - ) - assert cfg.robot_cfg.physx.spawn.variants is None - assert cfg.robot_cfg.ovphysx.spawn.variants is None - assert cfg.robot_cfg.newton_mjwarp.spawn.variants is None - assert cfg.robot_cfg.ovphysx.spawn.fixed_tendons_props is None - assert cfg.robot_cfg.physx.init_state.rot == (0.0, 0.0, 0.0, 1.0) - assert cfg.robot_cfg.ovphysx.init_state.rot == (0.0, 0.0, 0.0, 1.0) - assert cfg.robot_cfg.newton_mjwarp.spawn.fixed_tendons_props.damping == 0.1 - assert cfg.robot_cfg.newton_mjwarp.init_state.rot == (0.0, 0.0, -0.70710678118, 0.70710678118) - assert sum(value**2 for value in cfg.robot_cfg.newton_mjwarp.init_state.rot) == pytest.approx(1.0) - assert cfg.sim.physics.newton_mjwarp.num_substeps == 2 - - -def test_shadow_joint_mapping_preserves_twenty_action_contract(): - """Verify the actuated-joint list preserves the legacy 20-action order.""" - cfg = ShadowHandEnvCfg() - - assert cfg.action_space == len(cfg.actuated_joint_names) == 20 - assert cfg.actuated_joint_names == [ - "robot0_WRJ1", - "robot0_WRJ0", - "robot0_FFJ3", - "robot0_FFJ2", - "robot0_FFJ1", - "robot0_MFJ3", - "robot0_MFJ2", - "robot0_MFJ1", - "robot0_RFJ3", - "robot0_RFJ2", - "robot0_RFJ1", - "robot0_LFJ4", - "robot0_LFJ3", - "robot0_LFJ2", - "robot0_LFJ1", - "robot0_THJ4", - "robot0_THJ3", - "robot0_THJ2", - "robot0_THJ1", - "robot0_THJ0", - ] From 8fb580c4aefc502d56b30bf067c6dc9461ef2bac Mon Sep 17 00:00:00 2001 From: Jichuan Hu Date: Tue, 14 Jul 2026 02:07:23 -0700 Subject: [PATCH 7/7] Sync the Direct configurations to per-paradigm parameters The Shadow and handover Direct configurations define their scalar task parameters as flat literal fields (read live by the Direct environments), replacing the shared-constants indirection, and pick up the renamed *_task_base structural modules. --- .../core/handover/handover_env_cfg.py | 70 ++++---- .../config/shadow_hand/shadow_hand_env_cfg.py | 151 ++++++++---------- 2 files changed, 92 insertions(+), 129 deletions(-) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py index 189c7dd6995d..64b4a10758c0 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/handover/handover_env_cfg.py @@ -18,22 +18,14 @@ from isaaclab.markers import VisualizationMarkersCfg from isaaclab.scene import InteractiveSceneCfg from isaaclab.sim import SimulationCfg -from isaaclab.sim.spawners.materials.physics_materials_cfg import RigidBodyMaterialCfg +from isaaclab.sim.spawners.materials import RigidBodyMaterialBaseCfg from isaaclab.utils.configclass import configclass -from isaaclab_tasks.core.handover.handover_task_constants import ( +from isaaclab_tasks.core.handover.handover_task_base import ( ACTUATED_JOINT_NAMES_PRESET, - DECIMATION, - DIST_REWARD_SCALE, - EPISODE_LENGTH_S, - FALL_DISTANCE, FINGERTIP_BODY_NAMES, - RESET_DOF_POS_NOISE, - RESET_DOF_VEL_NOISE, - RESET_POSITION_NOISE, - SIM_DT, - SUCCESS_DISTANCE_THRESHOLD, - VEL_OBS_SCALE, + GOAL_MARKER_CFG, + OBJECT_RADIUS, ) from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_env_cfg import ShadowHandRobotCfg from isaaclab_tasks.utils import PresetCfg, preset @@ -267,7 +259,7 @@ class ObjectCfg(PresetCfg): physx = RigidObjectCfg( prim_path="/World/envs/env_.*/object", spawn=sim_utils.SphereCfg( - radius=0.0335, + radius=OBJECT_RADIUS, visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.8, 1.0, 0.0)), physics_material=sim_utils.RigidBodyMaterialCfg(static_friction=0.7), rigid_props=sim_utils.RigidBodyPropertiesCfg( @@ -288,7 +280,7 @@ class ObjectCfg(PresetCfg): newton_mjwarp = RigidObjectCfg( prim_path="/World/envs/env_.*/object", spawn=sim_utils.SphereCfg( - radius=0.0335, + radius=OBJECT_RADIUS, visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.8, 1.0, 0.0)), rigid_props=sim_utils.RigidBodyPropertiesCfg( kinematic_enabled=False, @@ -339,26 +331,30 @@ class PhysicsCfg(PresetCfg): default = physx +# Simulation settings shared by the Direct and manager variants (configclass +# deep-copies these defaults per cfg instance). The solver-common base material +# is sufficient: only friction values are set, so no PhysX-specific +# ``physxMaterial`` attributes are authored. +HANDOVER_SIM_CFG = SimulationCfg( + dt=1 / 120, + render_interval=2, + physics_material=RigidBodyMaterialBaseCfg(static_friction=1.0, dynamic_friction=1.0), + physics=PhysicsCfg(), +) + + @configclass class HandoverEnvCfg(DirectMARLEnvCfg): # env - decimation = DECIMATION - episode_length_s = EPISODE_LENGTH_S + decimation = 2 + episode_length_s = 7.5 possible_agents = ["right_hand", "left_hand"] action_spaces = {"right_hand": 20, "left_hand": 20} observation_spaces = {"right_hand": 157, "left_hand": 157} state_space = 290 # simulation - sim: SimulationCfg = SimulationCfg( - dt=SIM_DT, - render_interval=decimation, - physics_material=RigidBodyMaterialCfg( - static_friction=1.0, - dynamic_friction=1.0, - ), - physics=PhysicsCfg(), - ) + sim: SimulationCfg = HANDOVER_SIM_CFG # robot right_robot_cfg: PresetCfg = RIGHT_HAND_CFG left_robot_cfg: PresetCfg = LEFT_HAND_CFG @@ -368,28 +364,20 @@ class HandoverEnvCfg(DirectMARLEnvCfg): # in-hand object object_cfg: ObjectCfg = ObjectCfg() # goal object - goal_object_cfg: VisualizationMarkersCfg = VisualizationMarkersCfg( - prim_path="/Visuals/goal_marker", - markers={ - "goal": sim_utils.SphereCfg( - radius=0.0335, - visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.4, 0.3, 1.0)), - ), - }, - ) + goal_object_cfg: VisualizationMarkersCfg = GOAL_MARKER_CFG # scene scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=2048, env_spacing=1.5, replicate_physics=True) # reset - reset_position_noise = RESET_POSITION_NOISE # range of position at reset - reset_dof_pos_noise = RESET_DOF_POS_NOISE # range of dof pos at reset - reset_dof_vel_noise = RESET_DOF_VEL_NOISE # range of dof vel at reset + reset_position_noise = 0.01 # range of position at reset + reset_dof_pos_noise = 0.2 # range of dof pos at reset + reset_dof_vel_noise = 0.0 # range of dof vel at reset # scales and constants - fall_dist = FALL_DISTANCE - vel_obs_scale = VEL_OBS_SCALE + fall_dist = 0.24 + vel_obs_scale = 0.2 act_moving_average = 1.0 # success criteria - success_distance_threshold: float = SUCCESS_DISTANCE_THRESHOLD + success_distance_threshold: float = 0.1 """Object-to-goal distance below which the handover is considered successful [m].""" # reward-related scales - dist_reward_scale = DIST_REWARD_SCALE + dist_reward_scale = 20.0 diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py index 5dfe6713922f..c605f78cc6dd 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_env_cfg.py @@ -17,51 +17,14 @@ from isaaclab.markers import VisualizationMarkersCfg from isaaclab.scene import InteractiveSceneCfg from isaaclab.sim import SimulationCfg -from isaaclab.sim.spawners.materials.physics_materials_cfg import RigidBodyMaterialCfg +from isaaclab.sim.spawners.materials import RigidBodyMaterialBaseCfg from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR from isaaclab.utils.configclass import configclass from isaaclab.utils.noise import GaussianNoiseCfg, NoiseModelWithAdditiveBiasCfg -from isaaclab_tasks.core.reorient.reorient_task_constants import ( - OPENAI_ACT_MOVING_AVERAGE, - OPENAI_ACTION_PENALTY_SCALE, - OPENAI_AV_FACTOR, - OPENAI_DECIMATION, - OPENAI_DIST_REWARD_SCALE, - OPENAI_EPISODE_LENGTH_S, - OPENAI_FALL_PENALTY, - OPENAI_FORCE_TORQUE_OBS_SCALE, - OPENAI_MAX_CONSECUTIVE_SUCCESS, - OPENAI_REACH_GOAL_BONUS, - OPENAI_RESET_DOF_POS_NOISE, - OPENAI_RESET_DOF_VEL_NOISE, - OPENAI_RESET_POSITION_NOISE, - OPENAI_ROT_EPS, - OPENAI_ROT_REWARD_SCALE, - OPENAI_SIM_DT, - OPENAI_SUCCESS_TOLERANCE, - OPENAI_VEL_OBS_SCALE, - SHADOW_ACT_MOVING_AVERAGE, - SHADOW_ACTION_PENALTY_SCALE, +from isaaclab_tasks.core.reorient.reorient_task_base import ( SHADOW_ACTUATED_JOINT_NAMES, - SHADOW_AV_FACTOR, - SHADOW_DECIMATION, - SHADOW_DIST_REWARD_SCALE, - SHADOW_EPISODE_LENGTH_S, - SHADOW_FALL_DIST, - SHADOW_FALL_PENALTY, SHADOW_FINGERTIP_BODY_NAMES, - SHADOW_FORCE_TORQUE_OBS_SCALE, - SHADOW_REACH_GOAL_BONUS, - SHADOW_RESET_DOF_POS_NOISE, - SHADOW_RESET_DOF_VEL_NOISE, - SHADOW_RESET_POSITION_NOISE, - SHADOW_ROT_EPS, - SHADOW_ROT_REWARD_SCALE, - SHADOW_SIM_DT, - SHADOW_SUCCESS_COUNT_THRESHOLD, - SHADOW_SUCCESS_TOLERANCE, - SHADOW_VEL_OBS_SCALE, ) from isaaclab_tasks.utils import PresetCfg @@ -312,10 +275,16 @@ class ShadowHandSceneCfg(PresetCfg): """ physx: InteractiveSceneCfg = InteractiveSceneCfg( - num_envs=8192, env_spacing=0.75, replicate_physics=True, clone_in_fabric=True + num_envs=8192, + env_spacing=0.75, + replicate_physics=True, + clone_in_fabric=True, ) newton_mjwarp: InteractiveSceneCfg = InteractiveSceneCfg( - num_envs=8192, env_spacing=0.75, replicate_physics=True, clone_in_fabric=False + num_envs=8192, + env_spacing=0.75, + replicate_physics=True, + clone_in_fabric=False, ) default: InteractiveSceneCfg = physx newton_kamino = newton_mjwarp @@ -359,13 +328,29 @@ class PhysicsCfg(PresetCfg): ) }, ) +# Simulation settings shared by the Direct and manager variants (configclass +# deep-copies these defaults per cfg instance). The solver-common base material +# is sufficient: only friction values are set, so no PhysX-specific +# ``physxMaterial`` attributes are authored. +SHADOW_SIM_CFG = SimulationCfg( + dt=1 / 120, + render_interval=2, + physics_material=RigidBodyMaterialBaseCfg(static_friction=1.0, dynamic_friction=1.0), + physics=PhysicsCfg(), +) +OPENAI_SIM_CFG = SimulationCfg( + dt=1 / 60, + render_interval=3, + physics_material=RigidBodyMaterialBaseCfg(static_friction=1.0, dynamic_friction=1.0), + physics=PhysicsCfg(), +) @configclass class ShadowHandEnvCfg(DirectRLEnvCfg): # env - decimation = SHADOW_DECIMATION - episode_length_s = SHADOW_EPISODE_LENGTH_S + decimation = 2 + episode_length_s = 10.0 action_space = 20 observation_space = 157 # (full) state_space = 0 @@ -373,12 +358,7 @@ class ShadowHandEnvCfg(DirectRLEnvCfg): obs_type = "full" # simulation - sim: SimulationCfg = SimulationCfg( - dt=SHADOW_SIM_DT, - render_interval=decimation, - physics_material=RigidBodyMaterialCfg(static_friction=1.0, dynamic_friction=1.0), - physics=PhysicsCfg(), - ) + sim: SimulationCfg = SHADOW_SIM_CFG # robot robot_cfg: ShadowHandRobotCfg = ROBOT_CFG actuated_joint_names = SHADOW_ACTUATED_JOINT_NAMES @@ -392,25 +372,25 @@ class ShadowHandEnvCfg(DirectRLEnvCfg): scene: ShadowHandSceneCfg = ShadowHandSceneCfg() # reset - reset_position_noise = SHADOW_RESET_POSITION_NOISE # range of position at reset - reset_dof_pos_noise = SHADOW_RESET_DOF_POS_NOISE # range of dof pos at reset - reset_dof_vel_noise = SHADOW_RESET_DOF_VEL_NOISE # range of dof vel at reset + reset_position_noise = 0.01 # range of position at reset + reset_dof_pos_noise = 0.2 # range of dof pos at reset + reset_dof_vel_noise = 0.0 # range of dof vel at reset # reward scales - dist_reward_scale = SHADOW_DIST_REWARD_SCALE - rot_reward_scale = SHADOW_ROT_REWARD_SCALE - rot_eps = SHADOW_ROT_EPS - action_penalty_scale = SHADOW_ACTION_PENALTY_SCALE - reach_goal_bonus = SHADOW_REACH_GOAL_BONUS - fall_penalty = SHADOW_FALL_PENALTY - fall_dist = SHADOW_FALL_DIST - vel_obs_scale = SHADOW_VEL_OBS_SCALE - success_tolerance = SHADOW_SUCCESS_TOLERANCE + dist_reward_scale = -10.0 + rot_reward_scale = 1.0 + rot_eps = 0.1 + action_penalty_scale = -0.0002 + reach_goal_bonus = 250.0 + fall_penalty = 0.0 + fall_dist = 0.24 + vel_obs_scale = 0.2 + success_tolerance = 0.1 max_consecutive_success = 0 - success_count_threshold: int = SHADOW_SUCCESS_COUNT_THRESHOLD + success_count_threshold: int = 1 """Minimum number of goals reached in an episode to count it as a successful episode.""" - av_factor = SHADOW_AV_FACTOR - act_moving_average = SHADOW_ACT_MOVING_AVERAGE - force_torque_obs_scale = SHADOW_FORCE_TORQUE_OBS_SCALE + av_factor = 0.1 + act_moving_average = 1.0 + force_torque_obs_scale = 10.0 # Per-step gaussian noise + reset-sampled bias, shared verbatim by the manager-based variant. @@ -427,37 +407,32 @@ class ShadowHandEnvCfg(DirectRLEnvCfg): @configclass class ShadowHandOpenAIEnvCfg(ShadowHandEnvCfg): # env - decimation = OPENAI_DECIMATION - episode_length_s = OPENAI_EPISODE_LENGTH_S + decimation = 3 + episode_length_s = 8.0 action_space = 20 observation_space = 42 state_space = 187 asymmetric_obs = True obs_type = "openai" # simulation - sim: SimulationCfg = SimulationCfg( - dt=OPENAI_SIM_DT, - render_interval=decimation, - physics_material=RigidBodyMaterialCfg(static_friction=1.0, dynamic_friction=1.0), - physics=PhysicsCfg(), - ) + sim: SimulationCfg = OPENAI_SIM_CFG # reset - reset_position_noise = OPENAI_RESET_POSITION_NOISE # range of position at reset - reset_dof_pos_noise = OPENAI_RESET_DOF_POS_NOISE # range of dof pos at reset - reset_dof_vel_noise = OPENAI_RESET_DOF_VEL_NOISE # range of dof vel at reset + reset_position_noise = 0.01 # range of position at reset + reset_dof_pos_noise = 0.2 # range of dof pos at reset + reset_dof_vel_noise = 0.0 # range of dof vel at reset # reward scales - dist_reward_scale = OPENAI_DIST_REWARD_SCALE - rot_reward_scale = OPENAI_ROT_REWARD_SCALE - rot_eps = OPENAI_ROT_EPS - action_penalty_scale = OPENAI_ACTION_PENALTY_SCALE - reach_goal_bonus = OPENAI_REACH_GOAL_BONUS - fall_penalty = OPENAI_FALL_PENALTY - vel_obs_scale = OPENAI_VEL_OBS_SCALE - success_tolerance = OPENAI_SUCCESS_TOLERANCE - max_consecutive_success = OPENAI_MAX_CONSECUTIVE_SUCCESS - av_factor = OPENAI_AV_FACTOR - act_moving_average = OPENAI_ACT_MOVING_AVERAGE - force_torque_obs_scale = OPENAI_FORCE_TORQUE_OBS_SCALE + dist_reward_scale = -10.0 + rot_reward_scale = 1.0 + rot_eps = 0.1 + action_penalty_scale = -0.0002 + reach_goal_bonus = 250.0 + fall_penalty = -50.0 + vel_obs_scale = 0.2 + success_tolerance = 0.4 + max_consecutive_success = 50 + av_factor = 0.1 + act_moving_average = 0.3 + force_torque_obs_scale = 10.0 # domain randomization config events: ShadowHandEventCfg = ShadowHandEventCfg() # at every time-step add gaussian noise + bias. The bias is a gaussian sampled at reset