From a54cfbb0b2f662975c96e320cf6b0cffd4b05d64 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Mon, 3 Aug 2026 03:28:39 -0700 Subject: [PATCH 01/12] Initialize the manager reset buffer before the managers load Manager terms that run during the initial reset had no reset_buf to read: the attribute was only assigned in step(), which has not run yet at that point. Allocate it alongside episode_length_buf, before the managers load. --- source/isaaclab/changelog.d/task-cleanup-dex-part08.rst | 6 ++++++ source/isaaclab/isaaclab/envs/manager_based_rl_env.py | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 source/isaaclab/changelog.d/task-cleanup-dex-part08.rst diff --git a/source/isaaclab/changelog.d/task-cleanup-dex-part08.rst b/source/isaaclab/changelog.d/task-cleanup-dex-part08.rst new file mode 100644 index 000000000000..20f9533f8564 --- /dev/null +++ b/source/isaaclab/changelog.d/task-cleanup-dex-part08.rst @@ -0,0 +1,6 @@ +Fixed +^^^^^ + +* Fixed :attr:`~isaaclab.envs.ManagerBasedRLEnv.reset_buf` not existing until the first + call to :meth:`~isaaclab.envs.ManagerBasedRLEnv.step`, so manager terms that run during + the initial reset could not read it. diff --git a/source/isaaclab/isaaclab/envs/manager_based_rl_env.py b/source/isaaclab/isaaclab/envs/manager_based_rl_env.py index b17fe38bee0c..cb02f4b746cd 100644 --- a/source/isaaclab/isaaclab/envs/manager_based_rl_env.py +++ b/source/isaaclab/isaaclab/envs/manager_based_rl_env.py @@ -73,8 +73,11 @@ def __init__(self, cfg: ManagerBasedRLEnvCfg, render_mode: str | None = None, ** # -- counter for curriculum self.common_step_counter = 0 - # initialize the episode length buffer BEFORE loading the managers to use it in mdp functions. + # initialize the episode length and reset buffers BEFORE loading the managers to use them in + # mdp functions. The reset buffer is only assigned its computed value in :meth:`step`, so terms + # that run during the initial reset would otherwise not find it. self.episode_length_buf = torch.zeros(cfg.scene.num_envs, device=cfg.sim.device, dtype=torch.long) + self.reset_buf = torch.zeros(cfg.scene.num_envs, device=cfg.sim.device, dtype=torch.bool) # Forward render_mode and viewer camera to VideoRecorderCfg before super().__init__() # creates the VideoRecorder, so fallback cameras are only spawned when --video is active From 40096f485a163c89269d89b9148bc0f34bbaa407 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Mon, 3 Aug 2026 03:28:49 -0700 Subject: [PATCH 02/12] Add the reorientation manager MDP terms Provide the observation, action, event, command and termination terms the manager-based reorientation tasks need, alongside a parity test that pins their values against the Direct environment. Terminations evaluate success directly rather than reading the command's metrics, which the command manager only refreshes after the termination phase of step() has already run. --- .../core/reorient/mdp/__init__.pyi | 40 ++++- .../core/reorient/mdp/actions.py | 30 ++++ .../core/reorient/mdp/commands.py | 35 +++-- .../core/reorient/mdp/events.py | 71 +++++++++ .../core/reorient/mdp/noisy_actions.py | 56 +++++++ .../core/reorient/mdp/observations.py | 141 ++++++++++++++++-- .../core/reorient/mdp/terminations.py | 71 ++++++--- .../core/reorient/reorient_direct_env.py | 5 +- .../isaaclab_tasks/core/utils.py | 5 +- .../test/core/test_reorient_value_parity.py | 54 +++++++ 10 files changed, 455 insertions(+), 53 deletions(-) create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/actions.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/events.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/noisy_actions.py create mode 100644 source/isaaclab_tasks/test/core/test_reorient_value_parity.py diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/__init__.pyi index 3221965e78a6..9bd01bd20972 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/__init__.pyi @@ -4,8 +4,22 @@ # SPDX-License-Identifier: BSD-3-Clause __all__ = [ + "NoisyEMAJointPositionToLimitsAction", + "NoisyEMAJointPositionToLimitsActionCfg", "ReorientCommand", "ReorientCommandCfg", + "ReorientCommand", + "ReorientCommandCfg", + "reset_reorient_state", + "fingertip_pos", + "fingertip_quat", + "fingertip_vel", + "fingertip_wrench", + "reorient_last_action", + "openai_policy_observation", + "ShadowHandCameraFeatures", + "shadow_hand_camera_cached_features", + "shadow_hand_goal_keypoints", "goal_quat_diff", "success_bonus", "track_orientation_inv_l2", @@ -14,17 +28,35 @@ __all__ = [ "reorient_reward", "max_consecutive_success", "object_away_from_goal", - "object_away_from_robot", + "reorient_timeout", ] from .commands import ReorientCommand, ReorientCommandCfg -from .observations import goal_quat_diff +from .events import reset_reorient_state +from .noisy_actions import NoisyEMAJointPositionToLimitsAction +from .actions import NoisyEMAJointPositionToLimitsActionCfg +from .observations import ( + ShadowHandCameraFeatures, + shadow_hand_camera_cached_features, + shadow_hand_goal_keypoints, + fingertip_pos, + fingertip_quat, + fingertip_vel, + fingertip_wrench, + goal_quat_diff, + openai_policy_observation, + reorient_last_action, +) from .rewards import ( - reorient_reward, evaluate_reorient_success, + reorient_reward, success_bonus, track_orientation_inv_l2, track_pos_l2, ) -from .terminations import max_consecutive_success, object_away_from_goal, object_away_from_robot +from .terminations import ( + max_consecutive_success, + object_away_from_goal, + reorient_timeout, +) from isaaclab.envs.mdp import * diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/actions.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/actions.py new file mode 100644 index 000000000000..718c4cdfe9e5 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/actions.py @@ -0,0 +1,30 @@ +# 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 + +"""Action configurations for the reorientation task family.""" + +from __future__ import annotations + +from dataclasses import MISSING +from typing import TYPE_CHECKING + +from isaaclab.envs.mdp import EMAJointPositionToLimitsActionCfg +from isaaclab.utils.configclass import configclass +from isaaclab.utils.noise import NoiseModelCfg + +if TYPE_CHECKING: + from .noisy_actions import NoisyEMAJointPositionToLimitsAction + + +@configclass +class NoisyEMAJointPositionToLimitsActionCfg(EMAJointPositionToLimitsActionCfg): + """EMA joint action configuration with Direct-compatible stateful noise.""" + + class_type: type[NoisyEMAJointPositionToLimitsAction] | str = ( + "{DIR}.noisy_actions:NoisyEMAJointPositionToLimitsAction" + ) + + noise_model: NoiseModelCfg = MISSING + """Stateful noise applied to incoming normalized actions.""" diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/commands.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/commands.py index 77040f635282..bcd205832991 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/commands.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/commands.py @@ -80,6 +80,10 @@ def __init__(self, cfg: ReorientCommandCfg, env: ManagerBasedRLEnv): # -- per-attempt success accounting: each success-driven resample completes one attempt; # the trailing attempt at episode end counts as one unsuccessful attempt. self._completed_attempts = torch.zeros(self.num_envs, device=self.device) + # An auto-reset lands immediately before CommandManager.compute(); suppress success + # handling for those environments until one new physics step has run. + self._skip_success_update = torch.zeros(self.num_envs, dtype=torch.bool, device=self.device) + self._fixed_marker_pos_w: torch.Tensor | None = None # adds (optional) cmd kind and element names for leapp export # during export, semantic data about this command will be used to annotate the command input @@ -133,6 +137,7 @@ def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, float]: # card across tasks); pop it from the returned dict so CommandManager does not # additionally log it under ``Metrics//success_rate``. self._env.extras.setdefault("log", {})["Metrics/success_rate"] = extras.pop("success_rate") + self._skip_success_update[env_ids] = self._env.reset_buf[env_ids] return extras def _resample_command(self, env_ids: Sequence[int]): @@ -150,13 +155,12 @@ def _resample_command(self, env_ids: Sequence[int]): self.quat_command_w[env_ids] = math_utils.quat_unique(quat) if self.cfg.make_quat_unique else quat def _update_command(self): - # update the command if goal is reached if self.cfg.update_goal_on_success: - # compute the goal resets - goal_resets = self.metrics["orientation_error"] < self.cfg.orientation_success_threshold - goal_reset_ids = goal_resets.nonzero(as_tuple=False).squeeze(-1) - # resample the goals - self._resample(goal_reset_ids) + goal_resets = ( + self.metrics["orientation_error"] < self.cfg.orientation_success_threshold + ) & ~self._skip_success_update + self._resample(goal_resets.nonzero(as_tuple=False).squeeze(-1)) + self._skip_success_update[:] = False def _set_debug_vis_impl(self, debug_vis: bool): # set visibility of markers @@ -172,11 +176,17 @@ def _set_debug_vis_impl(self, debug_vis: bool): self.goal_pose_visualizer.set_visibility(False) def _debug_vis_callback(self, event): - # add an offset to the marker position to visualize the goal - marker_pos = self.pos_command_w + torch.tensor(self.cfg.marker_pos_offset, device=self.device) - marker_quat = self.quat_command_w - # visualize the goal marker - self.goal_pose_visualizer.visualize(translations=marker_pos, orientations=marker_quat) + if self.cfg.fixed_marker_pos is None: + marker_pos = self.pos_command_w + torch.tensor(self.cfg.marker_pos_offset, device=self.device) + else: + if self._fixed_marker_pos_w is None: + # constant per run; cached to avoid a host-to-device allocation every render frame + self._fixed_marker_pos_w = ( + torch.tensor(self.cfg.fixed_marker_pos, device=self.device).repeat(self.num_envs, 1) + + self._env.scene.env_origins + ) + marker_pos = self._fixed_marker_pos_w + self.goal_pose_visualizer.visualize(translations=marker_pos, orientations=self.quat_command_w) @configclass @@ -208,6 +218,9 @@ class ReorientCommandCfg(CommandTermCfg): If True, the quaternion is made unique by ensuring the real part is positive. """ + fixed_marker_pos: tuple[float, float, float] | None = None + """Fixed goal-marker position [m] in each environment, or ``None`` to follow the goal.""" + orientation_success_threshold: float = MISSING """Threshold for the orientation error to consider the goal orientation to be reached.""" diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/events.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/events.py new file mode 100644 index 000000000000..7f032faa3c2c --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/events.py @@ -0,0 +1,71 @@ +# 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 + +"""Reset events for state-based in-hand reorientation tasks.""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING + +import torch + +import isaaclab.utils.math as math_utils +from isaaclab.managers import SceneEntityCfg + +from isaaclab_tasks.core.utils import random_xy_rotation, sample_joint_positions_within_limits + +if TYPE_CHECKING: + from isaaclab.assets import Articulation, RigidObject + from isaaclab.envs import ManagerBasedRLEnv + + +def reset_reorient_state( + env: ManagerBasedRLEnv, + env_ids: Sequence[int], + position_noise: float, + joint_position_noise: float, + joint_velocity_noise: float, + action_name: str, + robot_cfg: SceneEntityCfg = SceneEntityCfg("robot"), + object_cfg: SceneEntityCfg = SceneEntityCfg("object"), +) -> None: + """Reset the object and hand with the Direct task's distributions. + + Args: + env: Environment containing the robot and object. + env_ids: Environment indices to reset. + position_noise: Object-position noise half-width [m]. + joint_position_noise: Scale applied to sampled joint-position deltas. + joint_velocity_noise: Joint-velocity noise half-width [rad/s]. + action_name: Action term whose terminal raw action is retained in the reset observation. + robot_cfg: Robot scene entity. + object_cfg: Object scene entity. + """ + raw_action = env.action_manager.get_term(action_name).raw_actions + if not hasattr(env, "_reorient_reset_action"): + env._reorient_reset_action = torch.zeros_like(raw_action) + env._reorient_reset_step = torch.full((env.num_envs,), -1, dtype=torch.long, device=raw_action.device) + env._reorient_reset_action[env_ids] = raw_action[env_ids] + env._reorient_reset_step[env_ids] = env.common_step_counter + + object_asset: Articulation | RigidObject = env.scene[object_cfg.name] + object_pose = object_asset.data.default_root_pose.torch[env_ids].clone() + object_velocity = torch.zeros_like(object_asset.data.default_root_vel.torch[env_ids]) + position_delta = math_utils.sample_uniform(-1.0, 1.0, (len(env_ids), 3), device=env.device) + object_pose[:, :3] += position_noise * position_delta + env.scene.env_origins[env_ids] + object_pose[:, 3:7] = random_xy_rotation(len(env_ids), env.device) + object_asset.write_root_pose_to_sim_index(root_pose=object_pose, env_ids=env_ids) + object_asset.write_root_velocity_to_sim_index(root_velocity=object_velocity, env_ids=env_ids) + + robot: Articulation = env.scene[robot_cfg.name] + default_position = robot.data.default_joint_pos.torch[env_ids] + limits = robot.data.joint_limits.torch[env_ids] + joint_position = sample_joint_positions_within_limits(default_position, limits, joint_position_noise) + velocity_sample = math_utils.sample_uniform(-1.0, 1.0, (len(env_ids), robot.num_joints), device=env.device) + joint_velocity = robot.data.default_joint_vel.torch[env_ids] + joint_velocity_noise * velocity_sample + robot.set_joint_position_target_index(target=joint_position, env_ids=env_ids) + robot.write_joint_position_to_sim_index(position=joint_position, env_ids=env_ids) + robot.write_joint_velocity_to_sim_index(velocity=joint_velocity, env_ids=env_ids) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/noisy_actions.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/noisy_actions.py new file mode 100644 index 000000000000..8016e506f053 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/noisy_actions.py @@ -0,0 +1,56 @@ +# 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 + +"""Action term implementations for the reorientation task family. + +Kept apart from :mod:`~isaaclab_tasks.core.reorient.mdp.actions` because importing the +base action class pulls in the USD stage bindings, which configuration loading must not +require. The configuration there names this module through ``class_type`` instead. +""" + +from __future__ import annotations + +from collections.abc import Sequence +from typing import TYPE_CHECKING + +import torch + +from isaaclab.envs.mdp.actions import EMAJointPositionToLimitsAction + +if TYPE_CHECKING: + from isaaclab.envs import ManagerBasedEnv + + from .actions import NoisyEMAJointPositionToLimitsActionCfg + + +class NoisyEMAJointPositionToLimitsAction(EMAJointPositionToLimitsAction): + """Apply a stateful noise model before EMA joint-position processing.""" + + def __init__(self, cfg: NoisyEMAJointPositionToLimitsActionCfg, env: ManagerBasedEnv): + """Initialize the noisy action term. + + Args: + cfg: Action configuration including the stateful noise model. + env: Manager-based environment containing the hand. + """ + super().__init__(cfg, env) + self._noise_model = cfg.noise_model.class_type(cfg.noise_model, num_envs=self.num_envs, device=self.device) + + def process_actions(self, actions: torch.Tensor) -> None: + """Apply noise to normalized actions before scaling and EMA filtering. + + Args: + actions: Normalized joint actions, shape ``(num_envs, num_actions)``. + """ + super().process_actions(self._noise_model(actions)) + + def reset(self, env_ids: Sequence[int] | None = None) -> None: + """Reset the noise state and standard EMA action buffers. + + Args: + env_ids: Environment indices to reset, or ``None`` for every environment. + """ + self._noise_model.reset(env_ids) + super().reset(env_ids) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/observations.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/observations.py index 8bff42fb7b41..869bff657db3 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/observations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/observations.py @@ -7,16 +7,19 @@ from __future__ import annotations +from collections.abc import Sequence from typing import TYPE_CHECKING import torch import isaaclab.utils.math as math_utils -from isaaclab.managers import SceneEntityCfg +from isaaclab.managers import ManagerTermBase, ObservationTermCfg, SceneEntityCfg +from isaaclab.utils.noise import NoiseModelCfg if TYPE_CHECKING: from isaaclab.assets import RigidObject from isaaclab.envs import ManagerBasedRLEnv + from isaaclab.sensors import JointWrenchSensor from .commands import ReorientCommand @@ -25,6 +28,7 @@ """Half side lengths [m] of the reorientation cube.""" +# -- cube keypoint helpers, shared by the camera and state observation terms def _cube_corner_offsets( size: tuple[float, float, float], num_keypoints: int, device: torch.device | str ) -> torch.Tensor: @@ -97,22 +101,139 @@ def cube_keypoints_from_quat( return rotated.reshape(num_envs, num_keypoints * 3) +# -- command terms def goal_quat_diff( env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg, command_name: str, make_quat_unique: bool ) -> torch.Tensor: """Goal orientation relative to the asset's root frame. - The quaternion is represented as (w, x, y, z). The real part is always positive. + The real part is always positive when ``make_quat_unique`` is set. + + Args: + env: The environment object. + asset_cfg: The scene entity whose root orientation is compared. + command_name: The command term to be used for extracting the goal. + make_quat_unique: Whether to keep the quaternion real part non-negative. + + Returns: + Per-environment quaternion error ``asset * conjugate(goal)`` in ``(x, y, z, w)`` order. """ - # extract useful elements asset: RigidObject = env.scene[asset_cfg.name] command_term: ReorientCommand = env.command_manager.get_term(command_name) + quat_error = math_utils.quat_mul( + asset.data.root_quat_w.torch, math_utils.quat_conjugate(command_term.quat_command_w) + ) + return math_utils.quat_unique(quat_error) if make_quat_unique else quat_error + + +# -- fingertip terms +def fingertip_pos(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: + """Flattened fingertip positions in the environment frame [m], shape ``(num_envs, num_fingertips * 3)``.""" + asset = env.scene[asset_cfg.name] + positions = asset.data.body_pos_w.torch[:, asset_cfg.body_ids] - env.scene.env_origins.unsqueeze(1) + return positions.reshape(env.num_envs, -1) + + +def fingertip_quat(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: + """Flattened fingertip ``(x, y, z, w)`` orientations, shape ``(num_envs, num_fingertips * 4)``.""" + asset = env.scene[asset_cfg.name] + return asset.data.body_quat_w.torch[:, asset_cfg.body_ids].reshape(env.num_envs, -1) + - # obtain the orientations - goal_quat_w = command_term.command[:, 3:7] - asset_quat_w = asset.data.root_quat_w.torch +def fingertip_vel(env: ManagerBasedRLEnv, asset_cfg: SceneEntityCfg) -> torch.Tensor: + """Flattened fingertip spatial velocities [m/s, rad/s], shape ``(num_envs, num_fingertips * 6)``.""" + asset = env.scene[asset_cfg.name] + return asset.data.body_vel_w.torch[:, asset_cfg.body_ids].reshape(env.num_envs, -1) - # compute quaternion difference - quat = math_utils.quat_mul(asset_quat_w, math_utils.quat_conjugate(goal_quat_w)) - # make sure the quaternion real-part is always positive - return math_utils.quat_unique(quat) if make_quat_unique else quat + +class fingertip_wrench(ManagerTermBase): + """Fingertip reaction wrenches [N, N·m] with Direct-compatible zero fallback.""" + + def __init__(self, cfg: ObservationTermCfg, env: ManagerBasedRLEnv): + super().__init__(cfg, env) + body_ids = cfg.params["sensor_cfg"].body_ids + # Direct-compatible fallback: report zero wrenches until the sensor produces data + self._zeros = torch.zeros(env.num_envs, len(body_ids) * 6, dtype=torch.float32, device=env.device) + + def __call__(self, env: ManagerBasedRLEnv, sensor_cfg: SceneEntityCfg) -> torch.Tensor: + """Return the flattened wrench block, shape ``(num_envs, num_fingertips * 6)``.""" + sensor: JointWrenchSensor = env.scene.sensors[sensor_cfg.name] + force_data = sensor.data.force + torque_data = sensor.data.torque + if force_data is None or torque_data is None: + return self._zeros + force = force_data.torch[:, sensor_cfg.body_ids] + torque = torque_data.torch[:, sensor_cfg.body_ids] + return torch.cat((force, torque), dim=-1).reshape(env.num_envs, -1) + + +# -- action terms +def reorient_last_action(env: ManagerBasedRLEnv, action_name: str) -> torch.Tensor: + """Return the Direct-compatible last action across same-step autoreset. + + Args: + env: Environment containing the action term and reset buffers. + action_name: Action term whose raw action is observed. + + Returns: + Raw actions, retaining each terminal action in its same-step reset observation. + """ + raw_action = env.action_manager.get_term(action_name).raw_actions + reset_action = getattr(env, "_reorient_reset_action", None) + reset_step = getattr(env, "_reorient_reset_step", None) + common_step_counter = getattr(env, "common_step_counter", None) + if reset_action is None or reset_step is None or common_step_counter is None: + return raw_action + return torch.where((reset_step == common_step_counter).unsqueeze(-1), reset_action, raw_action) + + +# -- composed observation groups +class openai_policy_observation(ManagerTermBase): + """Apply one stateful noise model to the concatenated OpenAI actor observation.""" + + def __init__(self, cfg: ObservationTermCfg, env: ManagerBasedRLEnv): + super().__init__(cfg, env) + noise_model: NoiseModelCfg = cfg.params["noise_model"] + self._noise_model = noise_model.class_type(noise_model, num_envs=self.num_envs, device=self.device) + # ObservationManager probes callable terms once for their shape and then + # calls reset. Keep that probe side-effect free so initialization matches + # DirectRLEnv's first noise-model reset and application. + self._shape_probe_pending = True + + def reset(self, env_ids: Sequence[int] | None = None) -> None: + """Reset the actor observation bias for selected environments. + + Args: + env_ids: Environment indices to reset, or ``None`` for every environment. + """ + if self._shape_probe_pending: + self._shape_probe_pending = False + return + self._noise_model.reset(env_ids) + + def __call__( + self, + env: ManagerBasedRLEnv, + command_name: str, + action_name: str, + noise_model: NoiseModelCfg, + robot_cfg: SceneEntityCfg, + object_cfg: SceneEntityCfg, + ) -> torch.Tensor: + """Return the corrupted 42-dimensional actor observation.""" + del noise_model + object_asset: RigidObject = env.scene[object_cfg.name] + object_pos = object_asset.data.root_pos_w.torch - env.scene.env_origins + command_term: ReorientCommand = env.command_manager.get_term(command_name) + quat_error = math_utils.quat_mul( + object_asset.data.root_quat_w.torch, math_utils.quat_conjugate(command_term.quat_command_w) + ) + fingertips = fingertip_pos(env, robot_cfg) + # Direct actor-observation order: fingertips, object position, goal quat error, last action + observation = torch.cat( + (fingertips, object_pos, quat_error, reorient_last_action(env, action_name)), + dim=-1, + ) + if self._shape_probe_pending: + return observation + return self._noise_model(observation) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/terminations.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/terminations.py index 32d6df6aa18a..a99a93a0bc4d 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/terminations.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/terminations.py @@ -7,11 +7,13 @@ from __future__ import annotations +from collections.abc import Sequence from typing import TYPE_CHECKING import torch -from isaaclab.managers import SceneEntityCfg +import isaaclab.utils.math as math_utils +from isaaclab.managers import ManagerTermBase, SceneEntityCfg, TerminationTermCfg if TYPE_CHECKING: from isaaclab.envs import ManagerBasedRLEnv @@ -60,28 +62,55 @@ def object_away_from_goal( return torch.linalg.norm(asset_pos_e - goal_pos_e, ord=2, dim=1) > threshold -def object_away_from_robot( - env: ManagerBasedRLEnv, - threshold: float, - asset_cfg: SceneEntityCfg = SceneEntityCfg("robot"), - object_cfg: SceneEntityCfg = SceneEntityCfg("object"), -) -> torch.Tensor: - """Check if object has gone far from the robot. +class reorient_timeout(ManagerTermBase): + """Time out an episode that has run its full length without reaching a goal. - The object is considered to be out-of-reach if the distance between the robot and the object is greater - than the threshold. + The timer restarts on every goal reach, so episodes extend across success streaks. + This matches the OpenAI Direct variant, which is the only configuration that enables + the behavior. Pair it with :func:`max_consecutive_success` to also stop on the streak + cap, and declare both with ``time_out=True``. Args: - env: The environment object. - threshold: The threshold for the distance between the robot and the object. - asset_cfg: The configuration for the robot entity. Default is "robot". - object_cfg: The configuration for the object entity. Default is "object". + cfg: Configuration object specifying term parameters. + env: The manager-based RL environment. """ - # extract useful elements - robot = env.scene[asset_cfg.name] - object = env.scene[object_cfg.name] - - # compute distance - dist = torch.linalg.norm(robot.data.root_pos_w.torch - object.data.root_pos_w.torch, dim=1) - return dist > threshold + def __init__(self, cfg: TerminationTermCfg, env: ManagerBasedRLEnv): + super().__init__(cfg, env) + self._steps_since_success = torch.zeros(self.num_envs, dtype=torch.long, device=self.device) + # resolved on first call: the command term does not exist yet during manager construction + self._command_term: ReorientCommand | None = None + + def reset(self, env_ids: Sequence[int] | None = None) -> None: + if env_ids is None: + env_ids = slice(None) + self._steps_since_success[env_ids] = 0 + + def __call__( + self, + env: ManagerBasedRLEnv, + command_name: str, + success_tolerance: float, + object_cfg: SceneEntityCfg = SceneEntityCfg("object"), + ) -> torch.Tensor: + """Return per-environment timeout flags. + + Args: + env: The environment object. + command_name: The command term to be used for extracting the goal. + success_tolerance: Maximum successful orientation error [rad]. + object_cfg: The configuration for the scene entity. Default is "object". + """ + asset = env.scene[object_cfg.name] + if self._command_term is None: + self._command_term = env.command_manager.get_term(command_name) + # Terminations run before the command manager, so the command's metrics still + # describe the previous step. Evaluate success here instead, matching the Direct + # environment, which refreshes the object pose inside its dones computation. + dtheta = math_utils.quat_error_magnitude(asset.data.root_quat_w.torch, self._command_term.quat_command_w) + goal_reached = dtheta <= success_tolerance + self._steps_since_success += 1 + # masked_fill_ rather than boolean indexing: the latter forces a host synchronization + self._steps_since_success.masked_fill_(goal_reached, 0) + + return self._steps_since_success >= env.max_episode_length - 1 diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_direct_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_direct_env.py index a2ada7e457a1..266f9786b5e0 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_direct_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_direct_env.py @@ -21,7 +21,6 @@ from isaaclab.utils.math import quat_conjugate, quat_mul, sample_uniform, saturate, scale_transform, unscale_transform from isaaclab_tasks.core.reorient.mdp.rewards import evaluate_reorient_success, reorient_reward -from isaaclab_tasks.core.reorient.reorient_common import GOAL_MARKER_POSITION, IN_HAND_POS_OFFSET from isaaclab_tasks.core.utils import EpisodeErrorRecorder, randomize_rotation, sample_joint_positions_within_limits if TYPE_CHECKING: @@ -64,11 +63,11 @@ def __init__(self, cfg: AllegroHandEnvCfg | ShadowHandEnvCfg, render_mode: str | # -- goal and success state -- # in-hand target = object default position + shared offset (mirrors ReorientCommand) self.in_hand_pos = self.object.data.default_root_pose.torch[:, 0:3].clone() - self.in_hand_pos += torch.tensor(IN_HAND_POS_OFFSET, dtype=torch.float, device=self.device) + self.in_hand_pos += torch.tensor(self.cfg.in_hand_pos_offset, dtype=torch.float, device=self.device) self.goal_rot = torch.zeros((self.num_envs, 4), dtype=torch.float, device=self.device) self.goal_rot[:, 3] = 1.0 # identity quaternion in (x, y, z, w) layout self.goal_pos = torch.zeros((self.num_envs, 3), dtype=torch.float, device=self.device) - self.goal_pos[:, :] = torch.tensor(GOAL_MARKER_POSITION, device=self.device) + self.goal_pos[:, :] = torch.tensor(self.cfg.goal_marker_position, device=self.device) self.reset_goal_buf = torch.zeros(self.num_envs, dtype=torch.bool, device=self.device) self.successes = torch.zeros(self.num_envs, dtype=torch.float, device=self.device) self.consecutive_successes = torch.zeros(1, dtype=torch.float, device=self.device) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/utils.py b/source/isaaclab_tasks/isaaclab_tasks/core/utils.py index 3a3790615ed9..14fa1c490553 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/utils.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/utils.py @@ -123,10 +123,7 @@ def random_xy_rotation(count: int, device: str | torch.device) -> torch.Tensor: random_values = math_utils.sample_uniform(-1.0, 1.0, (count, 2), device=device) x_unit = torch.tensor([1.0, 0.0, 0.0], device=device).repeat(count, 1) y_unit = torch.tensor([0.0, 1.0, 0.0], device=device).repeat(count, 1) - return math_utils.quat_mul( - math_utils.quat_from_angle_axis(random_values[:, 0] * torch.pi, x_unit), - math_utils.quat_from_angle_axis(random_values[:, 1] * torch.pi, y_unit), - ) + return randomize_rotation(random_values[:, 0], random_values[:, 1], x_unit, y_unit) @torch.jit.script diff --git a/source/isaaclab_tasks/test/core/test_reorient_value_parity.py b/source/isaaclab_tasks/test/core/test_reorient_value_parity.py new file mode 100644 index 000000000000..edcafc083ce9 --- /dev/null +++ b/source/isaaclab_tasks/test/core/test_reorient_value_parity.py @@ -0,0 +1,54 @@ +# 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 + +"""Value parity between the Direct and manager-based reorientation configurations. + +Covers the values that define the task rather than how it is solved: timing, the +success tolerance, and the termination thresholds. Reward weights are deliberately +excluded, since each workflow tunes them against its own RL agent configuration. + +This module is the check that the configuration comments refer to, so drift on +either side fails here rather than silently changing what a manager task trains on. +""" + +import pytest + +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 +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_direct_env_cfg import ( + ShadowHandEnvCfg, + ShadowHandOpenAIEnvCfg, +) +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_manager_env_cfg import ShadowHandManagerEnvCfg +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_openai_manager_env_cfg import ( + ShadowHandOpenAIManagerEnvCfg, +) + + +@pytest.mark.parametrize( + "direct_cls, manager_cls", + [ + pytest.param(AllegroHandEnvCfg, AllegroCubeEnvCfg, id="allegro"), + pytest.param(ShadowHandEnvCfg, ShadowHandManagerEnvCfg, id="shadow"), + pytest.param(ShadowHandOpenAIEnvCfg, ShadowHandOpenAIManagerEnvCfg, id="shadow-openai"), + ], +) +def test_manager_config_matches_direct_values(direct_cls, manager_cls): + direct, manager = direct_cls(), manager_cls() + + assert (manager.decimation, manager.episode_length_s, manager.sim.dt) == ( + direct.decimation, + direct.episode_length_s, + direct.sim.dt, + ) + assert manager.commands.object_pose.orientation_success_threshold == pytest.approx(direct.success_tolerance) + assert manager.terminations.object_out_of_reach.params["threshold"] == pytest.approx(direct.fall_dist) + # Both workflows publish ``Metrics/success_rate``; the curves only compare if the + # episode-success bit is drawn at the same goal count. + + # The Direct tasks fold the streak cap into their time-out signal. + streak_cap = getattr(manager.terminations, "max_consecutive_success", None) + assert (0 if streak_cap is None else streak_cap.params["num_success"]) == direct.max_consecutive_success + assert streak_cap is None or streak_cap.time_out From 6dc299d87abd375f2bfc758432ebc482fb4915b7 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Mon, 3 Aug 2026 03:28:58 -0700 Subject: [PATCH 03/12] Add the manager-based reorientation task configurations Register manager counterparts for the Allegro, Shadow and OpenAI Shadow reorientation tasks, each configured in its own per-robot module. Replace the single shared reorient_manager_env_cfg with those per-robot configurations: the shared base could only describe the Allegro task, so every Shadow variant overrode most of what it inherited. --- .../task-cleanup-dex-part08.major.rst | 52 +++ .../isaaclab_tasks/core/reorient/__init__.py | 7 +- .../allegro_hand/allegro_hand_common.py | 7 +- .../allegro_hand_direct_env_cfg.py | 15 +- .../allegro_hand_manager_env_cfg.py | 294 ++++++++++++++- .../reorient/config/shadow_hand/__init__.py | 60 ++- .../config/shadow_hand/shadow_hand_common.py | 53 +-- .../shadow_hand_direct_camera_env.py | 3 +- .../shadow_hand_direct_camera_env_cfg.py | 27 +- .../shadow_hand/shadow_hand_direct_env_cfg.py | 20 +- .../shadow_hand_manager_env_cfg.py | 205 +++++++++++ .../shadow_hand_openai_manager_env_cfg.py | 197 ++++++++++ .../core/reorient/reorient_common.py | 32 -- .../core/reorient/reorient_manager_env_cfg.py | 348 ------------------ 14 files changed, 825 insertions(+), 495 deletions(-) create mode 100644 source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_manager_env_cfg.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_openai_manager_env_cfg.py delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_common.py delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_manager_env_cfg.py diff --git a/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst b/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst new file mode 100644 index 000000000000..0554edb23b8d --- /dev/null +++ b/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst @@ -0,0 +1,52 @@ +Added +^^^^^ + +* Added manager-based counterparts for the Shadow cube reorientation task and + its OpenAI FF/LSTM observation variants, alongside the existing Allegro + manager task. +* Added :class:`~isaaclab_tasks.core.reorient.mdp.reorient_timeout`, which + restarts the episode timer on every goal reach so OpenAI-variant episodes + extend across success streaks. +* Added ``enable_domain_randomization`` to the manager-based Allegro + environment for turning off its startup randomization terms. +* Added Newton and OvPhysx physics presets to the manager-based reorientation + environments, selectable with ``physics=``. +* Added a Direct-versus-manager value-parity check covering timing, success + tolerance, fall distance, and the consecutive-success cap. + +Changed +^^^^^^^ + +* **Breaking:** Changed the manager-based Allegro reorientation environment to + match the Direct observation, action, reset, and termination contracts. The + observation space changes size, so existing manager checkpoints cannot be + loaded and must be retrained. +* **Breaking:** Changed the Shadow Hand reorientation tasks to apply the same + randomization on every physics backend. PhysX now also randomizes joint gains, + object mass, and gravity, and Newton now also randomizes contact materials. + Policies trained before this change must be retrained. +* **Breaking:** Moved the Shadow Hand camera benchmark task to the contributed + tasks as ``IsaacContrib-Reorient-Cube-Shadow-Camera-Benchmark-Direct``. The + released ``Isaac-Reorient-Cube-Shadow-Camera-Benchmark-Direct`` identifier + still resolves and warns; switch to the new identifier. +* Renamed the per-robot scene constants to name what they hold: ``ROBOT_CFG`` + becomes ``SHADOW_HAND_ROBOT_CFG`` or ``ALLEGRO_HAND_ROBOT_CFG``, + ``OBJECT_CFG`` becomes ``CUBE_CFG``, and ``ObjectCfg`` becomes ``CubeCfg``. + +Removed +^^^^^^^ + +* Removed ``ReorientObjectEnvCfg`` and the shared reorientation observation, + action, and command configurations. Each manager task now declares its own; + derive from :class:`~isaaclab.envs.ManagerBasedRLEnvCfg` directly. +* Removed ``reorient_common``. Its constants are declared by the tasks that use + them, and the in-hand offset and goal-marker position are now per-robot fields + on the Direct configurations. + +Fixed +^^^^^ + +* Fixed the manager-based reorientation tasks not reporting + ``Metrics/success_rate``. +* Fixed manager ``Metrics/success_rate`` counting goal attempts rather than the + per-episode success bit the Direct tasks report. diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/__init__.py index 81f1425e1bf8..b12e5252b837 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/__init__.py @@ -8,10 +8,9 @@ This package consolidates the direct-workflow and manager-based-workflow in-hand manipulation tasks, where a dexterous hand reorients an object to match a goal orientation. The shared direct base environment lives in -:mod:`~isaaclab_tasks.core.reorient.reorient_direct_env` and the shared manager-based -base configuration in :mod:`~isaaclab_tasks.core.reorient.reorient_manager_env_cfg`. -Robot-specific tasks are organized under the ``config`` subpackage -(``config/allegro_hand`` and ``config/shadow_hand``). +:mod:`~isaaclab_tasks.core.reorient.reorient_direct_env`; the manager-based +configurations live with their robot-specific tasks under the ``config`` +subpackage (``config/allegro_hand`` and ``config/shadow_hand``). These environments are based on the `dexterous cube manipulation`_ environments provided in IsaacGymEnvs repository from NVIDIA. However, they contain certain diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_common.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_common.py index 2605d401c76e..3023993391ff 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_common.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_common.py @@ -24,9 +24,11 @@ from isaaclab_assets.robots.allegro import ALLEGRO_HAND_CFG +ALLEGRO_HAND_ROBOT_CFG = ALLEGRO_HAND_CFG.replace(prim_path="/World/envs/env_.*/Robot") + @configclass -class ObjectCfg(PresetCfg): +class CubeCfg(PresetCfg): physx = RigidObjectCfg( prim_path="/World/envs/env_.*/object", spawn=sim_utils.UsdFileCfg( @@ -103,9 +105,6 @@ class PhysicsCfg(PresetCfg): default = newton_mjwarp -# Scene pieces shared verbatim by the manager-based variant. -ROBOT_CFG = ALLEGRO_HAND_CFG.replace(prim_path="/World/envs/env_.*/Robot") -OBJECT_CFG = ObjectCfg() GOAL_OBJECT_CFG = VisualizationMarkersCfg( prim_path="/Visuals/goal_marker", markers={ diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_direct_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_direct_env_cfg.py index 66c9ab696fb8..f5ef10d68fb1 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_direct_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_direct_env_cfg.py @@ -12,10 +12,9 @@ from isaaclab.utils.configclass import configclass from isaaclab_tasks.core.reorient.config.allegro_hand.allegro_hand_common import ( + ALLEGRO_HAND_ROBOT_CFG, GOAL_OBJECT_CFG, - OBJECT_CFG, - ROBOT_CFG, - ObjectCfg, + CubeCfg, PhysicsCfg, ) @@ -36,18 +35,18 @@ class AllegroHandEnvCfg(DirectRLEnvCfg): # simulation — values mirrored by the manager cfg (guarded by the value-parity test) sim: SimulationCfg = SimulationCfg( dt=1 / 120, - render_interval=4, + render_interval=decimation, physics_material=RigidBodyMaterialBaseCfg(static_friction=1.0, dynamic_friction=1.0), physics=PhysicsCfg(), ) # robot - robot_cfg: ArticulationCfg = ROBOT_CFG + robot_cfg: ArticulationCfg = ALLEGRO_HAND_ROBOT_CFG actuated_joint_names = ALLEGRO_ACTUATED_JOINT_NAMES fingertip_body_names = ALLEGRO_FINGERTIP_BODY_NAMES # in-hand object - object_cfg: ObjectCfg = OBJECT_CFG + object_cfg: CubeCfg = CubeCfg() # goal object goal_object_cfg: VisualizationMarkersCfg = GOAL_OBJECT_CFG # scene @@ -74,6 +73,10 @@ class AllegroHandEnvCfg(DirectRLEnvCfg): max_consecutive_success = 0 success_count_threshold: int = 1 """Minimum number of goals reached in an episode to count it as a successful episode.""" + in_hand_pos_offset: tuple[float, float, float] = (0.0, 0.0, -0.04) + """In-hand goal anchor, relative to the object's default position [m].""" + goal_marker_position: tuple[float, float, float] = (-0.2, -0.45, 0.68) + """Fixed goal-marker display position [m], environment frame.""" av_factor = 0.1 act_moving_average = 1.0 force_torque_obs_scale = 10.0 diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_manager_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_manager_env_cfg.py index 9615cd32d6a1..a12b9272c04c 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_manager_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_manager_env_cfg.py @@ -3,26 +3,294 @@ # # SPDX-License-Identifier: BSD-3-Clause +"""Manager-based counterpart of the Allegro Hand Direct reorientation task.""" + +import isaaclab.sim as sim_utils +from isaaclab.assets import ArticulationCfg, AssetBaseCfg +from isaaclab.envs import ManagerBasedRLEnvCfg, ViewerCfg +from isaaclab.managers import EventTermCfg as EventTerm +from isaaclab.managers import ObservationGroupCfg as ObsGroup +from isaaclab.managers import ObservationTermCfg as ObsTerm +from isaaclab.managers import RewardTermCfg as RewTerm +from isaaclab.managers import SceneEntityCfg +from isaaclab.managers import TerminationTermCfg as DoneTerm +from isaaclab.scene import InteractiveSceneCfg +from isaaclab.sim.simulation_cfg import SimulationCfg +from isaaclab.sim.spawners.materials import RigidBodyMaterialBaseCfg from isaaclab.utils.configclass import configclass -from isaaclab_tasks.core.reorient.reorient_manager_env_cfg import ReorientObjectEnvCfg +import isaaclab_tasks.core.reorient.mdp as mdp +from isaaclab_tasks.core.reorient.config.allegro_hand.allegro_hand_common import ( + ALLEGRO_HAND_ROBOT_CFG, + GOAL_OBJECT_CFG, + CubeCfg, + PhysicsCfg, +) +from isaaclab_tasks.utils import preset -## -# Pre-defined configs -## -from isaaclab_assets import ALLEGRO_HAND_CFG # isort: skip +from isaaclab_assets.robots.allegro import ALLEGRO_ACTUATED_JOINT_NAMES, ALLEGRO_FINGERTIP_BODY_NAMES @configclass -class AllegroCubeEnvCfg(ReorientObjectEnvCfg): - def __post_init__(self): - # post init of parent - super().__post_init__() +class AllegroCubeSceneCfg(InteractiveSceneCfg): + """Shared reorientation scene with the Allegro hand and a ground plane.""" + + # ``clone_in_fabric`` is the only backend-varying field: PhysX/OvPhysX use Fabric + # cloning for speed, Newton does not support it. + clone_in_fabric = preset(default=False, physx=True, ovphysx=True, newton_mjwarp=False) + + num_envs = 8192 + env_spacing = 0.75 + + robot: ArticulationCfg = ALLEGRO_HAND_ROBOT_CFG + object: CubeCfg = CubeCfg() + ground = AssetBaseCfg(prim_path="/World/ground", spawn=sim_utils.GroundPlaneCfg()) + light = AssetBaseCfg( + prim_path="/World/Light", + spawn=sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)), + ) + dome_light = None + + +@configclass +class CommandsCfg: + """Object pose goal matching the Direct in-hand target.""" + + object_pose = mdp.ReorientCommandCfg( + asset_name="object", + init_pos_offset=(0.0, 0.0, -0.04), + update_goal_on_success=True, + orientation_success_threshold=0.2, + make_quat_unique=False, + fixed_marker_pos=(-0.2, -0.45, 0.68), + goal_pose_visualizer_cfg=GOAL_OBJECT_CFG, + debug_vis=True, + ) + + +@configclass +class ActionsCfg: + """Sixteen actuated Allegro Hand joints in Direct order.""" + + joint_pos = mdp.EMAJointPositionToLimitsActionCfg( + asset_name="robot", + joint_names=ALLEGRO_ACTUATED_JOINT_NAMES, + alpha=1.0, + rescale_to_limits=True, + ) + + +@configclass +class ObservationsCfg: + """Full 124-dimensional state observation in Direct order.""" + + @configclass + class PolicyCfg(ObsGroup): + # -- robot + joint_pos = ObsTerm( + func=mdp.joint_pos_limit_normalized, + params={"asset_cfg": SceneEntityCfg("robot", joint_names=".*", preserve_order=False)}, + ) + joint_vel = ObsTerm( + func=mdp.joint_vel, + scale=0.2, + params={"asset_cfg": SceneEntityCfg("robot", joint_names=".*", preserve_order=False)}, + ) + # -- object + object_pos = ObsTerm(func=mdp.root_pos_w, params={"asset_cfg": SceneEntityCfg("object")}) + object_quat = ObsTerm( + func=mdp.root_quat_w, + params={"asset_cfg": SceneEntityCfg("object"), "make_quat_unique": False}, + ) + object_lin_vel = ObsTerm(func=mdp.root_lin_vel_w, params={"asset_cfg": SceneEntityCfg("object")}) + object_ang_vel = ObsTerm( + func=mdp.root_ang_vel_w, + scale=0.2, + params={"asset_cfg": SceneEntityCfg("object")}, + ) + # -- command + goal_pose = ObsTerm(func=mdp.generated_commands, params={"command_name": "object_pose"}) + goal_quat_diff = ObsTerm( + func=mdp.goal_quat_diff, + params={"asset_cfg": SceneEntityCfg("object"), "command_name": "object_pose", "make_quat_unique": False}, + ) + # -- robot fingertips + fingertip_pos = ObsTerm( + func=mdp.fingertip_pos, + params={ + "asset_cfg": SceneEntityCfg("robot", body_names=ALLEGRO_FINGERTIP_BODY_NAMES, preserve_order=False) + }, + ) + fingertip_quat = ObsTerm( + func=mdp.fingertip_quat, + params={ + "asset_cfg": SceneEntityCfg("robot", body_names=ALLEGRO_FINGERTIP_BODY_NAMES, preserve_order=False) + }, + ) + fingertip_vel = ObsTerm( + func=mdp.fingertip_vel, + params={ + "asset_cfg": SceneEntityCfg("robot", body_names=ALLEGRO_FINGERTIP_BODY_NAMES, preserve_order=False) + }, + ) + last_action = ObsTerm(func=mdp.reorient_last_action, params={"action_name": "joint_pos"}) + + def __post_init__(self): + self.enable_corruption = False + self.concatenate_terms = True + + policy: PolicyCfg = PolicyCfg() + + +@configclass +class EventCfg: + """Shared randomization terms with the Direct task's reset distribution. + + Gated by :attr:`AllegroCubeEnvCfg.enable_domain_randomization`. + """ + + robot_physics_material = EventTerm( + func=mdp.randomize_rigid_body_material, + mode="startup", + params={ + "asset_cfg": SceneEntityCfg("robot", body_names=".*"), + "static_friction_range": (0.7, 1.3), + "dynamic_friction_range": (0.7, 1.3), + "restitution_range": (0.0, 0.0), + "num_buckets": 250, + }, + ) + robot_scale_mass = EventTerm( + func=mdp.randomize_rigid_body_mass, + mode="startup", + params={ + "asset_cfg": SceneEntityCfg("robot", body_names=".*"), + "mass_distribution_params": (0.95, 1.05), + "operation": "scale", + }, + ) + robot_joint_stiffness_and_damping = EventTerm( + func=mdp.randomize_actuator_gains, + mode="startup", + params={ + "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), + "stiffness_distribution_params": (0.3, 3.0), # default: 3.0 + "damping_distribution_params": (0.75, 1.5), # default: 0.1 + "operation": "scale", + "distribution": "log_uniform", + }, + ) + + # -- object + object_physics_material = EventTerm( + func=mdp.randomize_rigid_body_material, + mode="startup", + params={ + "asset_cfg": SceneEntityCfg("object", body_names=".*"), + "static_friction_range": (0.7, 1.3), + "dynamic_friction_range": (0.7, 1.3), + "restitution_range": (0.0, 0.0), + "num_buckets": 250, + }, + ) + object_scale_mass = EventTerm( + func=mdp.randomize_rigid_body_mass, + mode="startup", + params={ + "asset_cfg": SceneEntityCfg("object"), + "mass_distribution_params": (0.4, 1.6), + "operation": "scale", + }, + ) + + reset_state = EventTerm( + func=mdp.reset_reorient_state, + mode="reset", + params={ + "position_noise": 0.01, + "joint_position_noise": 0.2, + "joint_velocity_noise": 0.0, + "action_name": "joint_pos", + }, + ) + - # switch robot to allegro hand - self.scene.robot = ALLEGRO_HAND_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") - # enable clone in fabric - self.scene.clone_in_fabric = True +@configclass +class RewardsCfg: + """Shared reward terms tuned to the Direct task's scales.""" + + track_orientation_inv_l2 = RewTerm( + func=mdp.track_orientation_inv_l2, + weight=1.0, + params={"object_cfg": SceneEntityCfg("object"), "rot_eps": 0.1, "command_name": "object_pose"}, + ) + success_bonus = RewTerm( + func=mdp.success_bonus, + weight=250.0, + params={"object_cfg": SceneEntityCfg("object"), "command_name": "object_pose"}, + ) + track_pos_l2 = RewTerm( + func=mdp.track_pos_l2, + weight=-10.0, + params={"command_name": "object_pose", "object_cfg": SceneEntityCfg("object")}, + ) + action_l2 = RewTerm(func=mdp.action_l2, weight=-0.0002) + + +@configclass +class TerminationsCfg: + """Shared terminations reduced to the Direct task's fall condition.""" + + time_out = DoneTerm(func=mdp.time_out, time_out=True) + object_out_of_reach = DoneTerm( + func=mdp.object_away_from_goal, + params={ + "threshold": 0.24, + "command_name": "object_pose", + "object_cfg": SceneEntityCfg("object"), + }, + ) + + +@configclass +class AllegroCubeEnvCfg(ManagerBasedRLEnvCfg): + """Manager-based Allegro Hand task with Direct-compatible semantics.""" + + scene: AllegroCubeSceneCfg = AllegroCubeSceneCfg() + decimation = 4 + episode_length_s = 10.0 + sim: SimulationCfg = SimulationCfg( + dt=1 / 120, + render_interval=decimation, + physics_material=RigidBodyMaterialBaseCfg(static_friction=1.0, dynamic_friction=1.0), + physics=PhysicsCfg(), + ) + observations: ObservationsCfg = ObservationsCfg() + actions: ActionsCfg = ActionsCfg() + commands: CommandsCfg = CommandsCfg() + rewards: RewardsCfg = RewardsCfg() + terminations: TerminationsCfg = TerminationsCfg() + events: EventCfg = EventCfg() + + enable_domain_randomization: bool = True + """Apply the startup domain-randomization terms. + + Set it on the configuration to disable them: ``__post_init__`` reads it while building + the configuration, before Hydra applies command-line overrides, so + ``env.enable_domain_randomization=false`` has no effect. Individual terms can still be + disabled from the command line, for example ``env.events.robot_scale_mass=null``. + Changing it requires retraining. + """ + + viewer: ViewerCfg = ViewerCfg(eye=(2.0, 2.0, 2.0)) + + def __post_init__(self): + if not self.enable_domain_randomization: + self.events.robot_physics_material = None + self.events.robot_scale_mass = None + self.events.robot_joint_stiffness_and_damping = None + self.events.object_physics_material = None + self.events.object_scale_mass = None def play_mode(self): # play-mode overrides of parent diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/__init__.py index 95a6cc7e599d..b8e68408ccb2 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/__init__.py @@ -17,6 +17,29 @@ reorient_direct_entry = "isaaclab_tasks.core.reorient.reorient_direct_env:ReorientDirectEnv" +gym.register( + id="Isaac-Reorient-Cube-Shadow", + entry_point="isaaclab.envs:ManagerBasedRLEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": f"{__name__}.shadow_hand_manager_env_cfg:ShadowHandManagerEnvCfg", + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:ShadowHandPPORunnerCfg", + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", + }, +) + +gym.register( + id="Isaac-Reorient-Cube-Shadow-Camera-Direct", + entry_point=f"{__name__}.shadow_hand_direct_camera_env:ShadowHandCameraEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": f"{__name__}.shadow_hand_direct_camera_env_cfg:ShadowHandCameraEnvCfg", + "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:ShadowHandCameraFFPPORunnerCfg", + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_camera_cfg.yaml", + }, +) + gym.register( id="Isaac-Reorient-Cube-Shadow-Direct", entry_point=reorient_direct_entry, @@ -30,11 +53,11 @@ ) gym.register( - id="Isaac-Reorient-Cube-Shadow-OpenAI-FF-Direct", - entry_point=reorient_direct_entry, + id="Isaac-Reorient-Cube-Shadow-OpenAI-FF", + entry_point="isaaclab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ - "env_cfg_entry_point": f"{__name__}.shadow_hand_direct_env_cfg:ShadowHandOpenAIEnvCfg", + "env_cfg_entry_point": f"{__name__}.shadow_hand_openai_manager_env_cfg:ShadowHandOpenAIManagerEnvCfg", "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_ff_cfg.yaml", "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:ShadowHandAsymFFPPORunnerCfg", "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ff_ppo_cfg.yaml", @@ -42,38 +65,35 @@ ) gym.register( - id="Isaac-Reorient-Cube-Shadow-OpenAI-LSTM-Direct", + id="Isaac-Reorient-Cube-Shadow-OpenAI-FF-Direct", entry_point=reorient_direct_entry, disable_env_checker=True, kwargs={ "env_cfg_entry_point": f"{__name__}.shadow_hand_direct_env_cfg:ShadowHandOpenAIEnvCfg", - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_lstm_cfg.yaml", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:ShadowHandAsymLSTMPPORunnerCfg", + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_ff_cfg.yaml", + "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:ShadowHandAsymFFPPORunnerCfg", + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ff_ppo_cfg.yaml", }, ) -# ------- -# Vision -# ------- - gym.register( - id="Isaac-Reorient-Cube-Shadow-Camera-Direct", - entry_point=f"{__name__}.shadow_hand_direct_camera_env:ShadowHandCameraEnv", + id="Isaac-Reorient-Cube-Shadow-OpenAI-LSTM", + entry_point="isaaclab.envs:ManagerBasedRLEnv", disable_env_checker=True, kwargs={ - "env_cfg_entry_point": f"{__name__}.shadow_hand_direct_camera_env_cfg:ShadowHandCameraEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:ShadowHandCameraFFPPORunnerCfg", - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_camera_cfg.yaml", + "env_cfg_entry_point": f"{__name__}.shadow_hand_openai_manager_env_cfg:ShadowHandOpenAIManagerEnvCfg", + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_lstm_cfg.yaml", + "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:ShadowHandAsymLSTMPPORunnerCfg", }, ) gym.register( - id="Isaac-Reorient-Cube-Shadow-Camera-Benchmark-Direct", - entry_point=f"{__name__}.shadow_hand_direct_camera_env:ShadowHandCameraEnv", + id="Isaac-Reorient-Cube-Shadow-OpenAI-LSTM-Direct", + entry_point=reorient_direct_entry, disable_env_checker=True, kwargs={ - "env_cfg_entry_point": f"{__name__}.shadow_hand_direct_camera_env_cfg:ShadowHandCameraBenchmarkEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:ShadowHandCameraFFPPORunnerCfg", - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_camera_cfg.yaml", + "env_cfg_entry_point": f"{__name__}.shadow_hand_direct_env_cfg:ShadowHandOpenAIEnvCfg", + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_lstm_cfg.yaml", + "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:ShadowHandAsymLSTMPPORunnerCfg", }, ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py index fa0d6c3fbe02..874f9db6a555 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py @@ -25,6 +25,7 @@ from isaaclab.utils.configclass import configclass from isaaclab.utils.noise import GaussianNoiseCfg, NoiseModelWithAdditiveBiasCfg +import isaaclab_tasks.core.reorient.mdp as reorient_mdp from isaaclab_tasks.utils import PresetCfg from isaaclab_assets.robots.shadow_hand import ( @@ -34,13 +35,8 @@ @configclass -class NewtonEventCfg: - """Event randomization config for the Newton physics backend. - - Includes joint-parameter, mass, and gravity randomization. - Material and tendon randomization are omitted: Newton does not expose - per-body friction-material buckets or fixed-tendon APIs. - """ +class ShadowHandEventCfg: + """Randomization of the hand and the object, applied on every physics backend.""" robot_joint_stiffness_and_damping = EventTerm( func=mdp.randomize_actuator_gains, @@ -93,10 +89,6 @@ class NewtonEventCfg: }, ) - -@configclass -class PhysxEventCfg: - # -- robot robot_physics_material = EventTerm( func=mdp.randomize_rigid_body_material, mode="reset", @@ -109,20 +101,7 @@ class PhysxEventCfg: "num_buckets": 250, }, ) - robot_tendon_properties = EventTerm( - func=mdp.randomize_fixed_tendon_parameters, - min_step_count_between_reset=720, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("robot", fixed_tendon_names=".*"), - "stiffness_distribution_params": (0.75, 1.5), - "damping_distribution_params": (0.3, 3.0), - "operation": "scale", - "distribution": "log_uniform", - }, - ) - # -- object object_physics_material = EventTerm( func=mdp.randomize_rigid_body_material, min_step_count_between_reset=720, @@ -138,13 +117,19 @@ class PhysxEventCfg: @configclass -class ShadowHandEventCfg(PresetCfg): - physx = PhysxEventCfg() - isaacsim_physx = physx - newton_mjwarp = NewtonEventCfg() - ovphysx = physx # OvPhysX is PhysX-based; reuse the PhysX randomization terms - default = newton_mjwarp - newton_kamino = newton_mjwarp +class ShadowHandManagerEventCfg(ShadowHandEventCfg): + """Randomization plus the state reset the manager tasks apply on every episode.""" + + reset_state = EventTerm( + func=reorient_mdp.reset_reorient_state, + mode="reset", + params={ + "position_noise": 0.01, # [m] + "joint_position_noise": 0.2, # [rad] + "joint_velocity_noise": 0.0, # [rad/s] + "action_name": "joint_pos", + }, + ) @configclass @@ -176,7 +161,7 @@ class ShadowHandRobotCfg(PresetCfg): @configclass -class ObjectCfg(PresetCfg): +class CubeCfg(PresetCfg): physx = RigidObjectCfg( prim_path="/World/envs/env_.*/object", spawn=sim_utils.UsdFileCfg( @@ -242,8 +227,8 @@ class PhysicsCfg(PresetCfg): # Scene pieces shared verbatim by the manager-based variants. -ROBOT_CFG = ShadowHandRobotCfg() -OBJECT_CFG = ObjectCfg() +SHADOW_HAND_ROBOT_CFG = ShadowHandRobotCfg() +CUBE_CFG = CubeCfg() GOAL_OBJECT_CFG = VisualizationMarkersCfg( prim_path="/Visuals/goal_marker", diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env.py index d424b46400e5..b56774c03f09 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env.py @@ -18,7 +18,6 @@ from isaaclab_tasks.core.reorient.config.shadow_hand.feature_extractor import FeatureExtractor from isaaclab_tasks.core.reorient.mdp.observations import compute_cube_keypoints -from isaaclab_tasks.core.reorient.reorient_common import CAMERA_GOAL_MARKER_POSITION from isaaclab_tasks.core.reorient.reorient_direct_env import ReorientDirectEnv if TYPE_CHECKING: @@ -42,7 +41,7 @@ def __init__(self, cfg: ShadowHandCameraEnvCfg, render_mode: str | None = None, width=self.cfg.tiled_camera.width, ) # hide goal cubes - self.goal_pos[:, :] = torch.tensor(CAMERA_GOAL_MARKER_POSITION, device=self.device) + self.goal_pos[:, :] = torch.tensor((-0.2, 0.1, 0.6), device=self.device) # inside the tiled camera frustum # keypoints buffer self.gt_keypoints = torch.ones(self.num_envs, 8, 3, dtype=torch.float32, device=self.device) self.goal_keypoints = torch.ones(self.num_envs, 8, 3, dtype=torch.float32, device=self.device) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py index 19292a97429f..f7d7a9e35e34 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py @@ -93,9 +93,9 @@ class ShadowHandTiledCameraCfg(PresetCfg): .. warning:: This preset is intended for **benchmarking only**. The keypoint-regression CNN - cannot be meaningfully trained from depth alone. Use it with - :class:`ShadowHandCameraBenchmarkEnvCfg` (``feature_extractor.enabled=False``) - to measure pure depth-rendering throughput, e.g.:: + cannot be meaningfully trained from depth alone. Use it with the contributed + benchmark task, which disables the feature extractor, to measure pure + depth-rendering throughput, e.g.:: presets=depth # depth rendering, default renderer presets=depth,newton_renderer # depth rendering with Newton renderer @@ -162,7 +162,7 @@ def validate_config(self): "Depth-only camera data type is intended for benchmarking only. " "The keypoint-regression CNN cannot be meaningfully trained from depth alone. " "Disable the feature extractor with 'feature_extractor.enabled=False' " - "(e.g. use Isaac-Reorient-Cube-Shadow-Camera-Benchmark-Direct), " + "(e.g. use IsaacContrib-Reorient-Cube-Shadow-Camera-Benchmark-Direct), " "or choose a data type that includes colour, e.g. presets=rgb." ) @@ -175,22 +175,3 @@ def play_mode(self): # inference for CNN self.feature_extractor.train = False self.feature_extractor.load_checkpoint = True - - -@configclass -class ShadowHandCameraBenchmarkEnvCfg(ShadowHandCameraEnvCfg): - """Benchmark configuration with the feature extractor CNN disabled. - - The tiled camera renders frames each step as normal, but the CNN forward pass is - bypassed — zero embeddings are returned instead. This isolates rendering throughput - from CNN inference overhead when profiling. - - The renderer backend and camera data types can still be selected via ``presets``:: - - presets = newton_renderer # benchmark with Newton renderer - presets = ovrtx # benchmark with OVRTX renderer - presets = rgb # benchmark RGB rendering only - presets = depth, newton_renderer # benchmark depth rendering with Newton - """ - - feature_extractor: FeatureExtractorCfg = FeatureExtractorCfg(enabled=False) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_env_cfg.py index a7a8c4830be7..c0353c3e30eb 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_env_cfg.py @@ -12,12 +12,12 @@ from isaaclab.utils.noise import NoiseModelWithAdditiveBiasCfg from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_common import ( + CUBE_CFG, GOAL_OBJECT_CFG, - OBJECT_CFG, OPENAI_ACTION_NOISE_CFG, OPENAI_OBSERVATION_NOISE_CFG, - ROBOT_CFG, - ObjectCfg, + SHADOW_HAND_ROBOT_CFG, + CubeCfg, PhysicsCfg, ShadowHandEventCfg, ShadowHandRobotCfg, @@ -55,18 +55,18 @@ class ShadowHandEnvCfg(DirectRLEnvCfg): # simulation — values mirrored by the manager cfg (guarded by the value-parity test) sim: SimulationCfg = SimulationCfg( dt=1 / 120, - render_interval=2, + render_interval=decimation, physics_material=RigidBodyMaterialBaseCfg(static_friction=1.0, dynamic_friction=1.0), physics=PhysicsCfg(), ) # robot - robot_cfg: ShadowHandRobotCfg = ROBOT_CFG + robot_cfg: ShadowHandRobotCfg = SHADOW_HAND_ROBOT_CFG actuated_joint_names = SHADOW_ACTUATED_JOINT_NAMES fingertip_body_names = SHADOW_FINGERTIP_BODY_NAMES # in-hand object - object_cfg: ObjectCfg = OBJECT_CFG + object_cfg: CubeCfg = CUBE_CFG # goal object goal_object_cfg: VisualizationMarkersCfg = GOAL_OBJECT_CFG # scene — clone_in_fabric is the only backend-varying field (Newton cannot use Fabric cloning) @@ -96,6 +96,10 @@ class ShadowHandEnvCfg(DirectRLEnvCfg): max_consecutive_success = 0 success_count_threshold: int = 1 """Minimum number of goals reached in an episode to count it as a successful episode.""" + in_hand_pos_offset: tuple[float, float, float] = (0.0, 0.0, -0.04) + """In-hand goal anchor, relative to the object's default position [m].""" + goal_marker_position: tuple[float, float, float] = (-0.2, -0.45, 0.68) + """Fixed goal-marker display position [m], environment frame.""" av_factor = 0.1 act_moving_average = 1.0 force_torque_obs_scale = 10.0 @@ -115,7 +119,7 @@ class ShadowHandOpenAIEnvCfg(ShadowHandEnvCfg): # simulation — values mirrored by the manager cfg (guarded by the value-parity test) sim: SimulationCfg = SimulationCfg( dt=1 / 60, - render_interval=3, + render_interval=decimation, physics_material=RigidBodyMaterialBaseCfg(static_friction=1.0, dynamic_friction=1.0), physics=PhysicsCfg(), ) @@ -138,7 +142,5 @@ class ShadowHandOpenAIEnvCfg(ShadowHandEnvCfg): 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 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 = OPENAI_OBSERVATION_NOISE_CFG diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_manager_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_manager_env_cfg.py new file mode 100644 index 000000000000..8ba024b6a6c9 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_manager_env_cfg.py @@ -0,0 +1,205 @@ +# 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 + +"""Manager-based counterpart of the state-based Shadow Hand reorientation task.""" + +import isaaclab.sim as sim_utils +from isaaclab.assets import AssetBaseCfg +from isaaclab.envs import ManagerBasedRLEnvCfg, ViewerCfg +from isaaclab.managers import ObservationGroupCfg as ObsGroup +from isaaclab.managers import ObservationTermCfg as ObsTerm +from isaaclab.managers import RewardTermCfg as RewTerm +from isaaclab.managers import SceneEntityCfg +from isaaclab.managers import TerminationTermCfg as DoneTerm +from isaaclab.scene import InteractiveSceneCfg +from isaaclab.sim.simulation_cfg import SimulationCfg +from isaaclab.sim.spawners.materials import RigidBodyMaterialBaseCfg +from isaaclab.utils.configclass import configclass + +import isaaclab_tasks.core.reorient.mdp as mdp +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_common import ( + CUBE_CFG, + GOAL_OBJECT_CFG, + SHADOW_HAND_ROBOT_CFG, + CubeCfg, + PhysicsCfg, + ShadowHandManagerEventCfg, +) +from isaaclab_tasks.utils import PresetCfg, preset + +from isaaclab_assets.robots.shadow_hand import SHADOW_ACTUATED_JOINT_NAMES, SHADOW_FINGERTIP_BODY_NAMES + +# ---------------------------------- state task ---------------------------------- + + +@configclass +class ShadowHandManagerSceneCfg(InteractiveSceneCfg): + """Shared reorientation scene with the Shadow hand and a ground plane.""" + + # ``clone_in_fabric`` is the only backend-varying field: PhysX/OvPhysX use Fabric + # cloning for speed, Newton does not support it. + clone_in_fabric = preset(default=False, physx=True, ovphysx=True, newton_mjwarp=False) + + num_envs = 8192 + env_spacing = 0.75 + + robot: PresetCfg = SHADOW_HAND_ROBOT_CFG + object: CubeCfg = CUBE_CFG + ground = AssetBaseCfg(prim_path="/World/ground", spawn=sim_utils.GroundPlaneCfg()) + light = AssetBaseCfg( + prim_path="/World/Light", + spawn=sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)), + ) + dome_light = None + + +@configclass +class CommandsCfg: + """Object pose goal matching the Direct in-hand target.""" + + object_pose = mdp.ReorientCommandCfg( + asset_name="object", + init_pos_offset=(0.0, 0.0, -0.04), + update_goal_on_success=True, + orientation_success_threshold=0.1, + make_quat_unique=False, + fixed_marker_pos=(-0.2, -0.45, 0.68), + goal_pose_visualizer_cfg=GOAL_OBJECT_CFG, + debug_vis=True, + ) + + +@configclass +class ActionsCfg: + """Twenty actuated Shadow Hand joints.""" + + joint_pos = mdp.EMAJointPositionToLimitsActionCfg( + asset_name="robot", + joint_names=SHADOW_ACTUATED_JOINT_NAMES, + alpha=1.0, + rescale_to_limits=True, + ) + + +@configclass +class FullStateObsCfg(ObsGroup): + """Shared first 137 dimensions of the full Shadow state, before the action terms.""" + + # -- robot + joint_pos = ObsTerm( + func=mdp.joint_pos_limit_normalized, + params={"asset_cfg": SceneEntityCfg("robot", joint_names=".*", preserve_order=False)}, + ) + joint_vel = ObsTerm( + func=mdp.joint_vel, + scale=0.2, + params={"asset_cfg": SceneEntityCfg("robot", joint_names=".*", preserve_order=False)}, + ) + # -- object + object_pos = ObsTerm(func=mdp.root_pos_w, params={"asset_cfg": SceneEntityCfg("object")}) + object_quat = ObsTerm( + func=mdp.root_quat_w, + params={"asset_cfg": SceneEntityCfg("object"), "make_quat_unique": False}, + ) + object_lin_vel = ObsTerm(func=mdp.root_lin_vel_w, params={"asset_cfg": SceneEntityCfg("object")}) + object_ang_vel = ObsTerm( + func=mdp.root_ang_vel_w, + scale=0.2, + params={"asset_cfg": SceneEntityCfg("object")}, + ) + # -- command + goal_pose = ObsTerm(func=mdp.generated_commands, params={"command_name": "object_pose"}) + goal_quat_diff = ObsTerm( + func=mdp.goal_quat_diff, + params={"asset_cfg": SceneEntityCfg("object"), "command_name": "object_pose", "make_quat_unique": False}, + ) + # -- robot fingertips + fingertip_pos = ObsTerm( + func=mdp.fingertip_pos, + params={"asset_cfg": SceneEntityCfg("robot", body_names=SHADOW_FINGERTIP_BODY_NAMES, preserve_order=False)}, + ) + fingertip_quat = ObsTerm( + func=mdp.fingertip_quat, + params={"asset_cfg": SceneEntityCfg("robot", body_names=SHADOW_FINGERTIP_BODY_NAMES, preserve_order=False)}, + ) + fingertip_vel = ObsTerm( + func=mdp.fingertip_vel, + params={"asset_cfg": SceneEntityCfg("robot", body_names=SHADOW_FINGERTIP_BODY_NAMES, preserve_order=False)}, + ) + + def __post_init__(self): + self.enable_corruption = False + self.concatenate_terms = True + + +@configclass +class ObservationsCfg: + """Full 157-dimensional state observation in Direct order.""" + + @configclass + class PolicyCfg(FullStateObsCfg): + last_action = ObsTerm(func=mdp.reorient_last_action, params={"action_name": "joint_pos"}) + + policy: PolicyCfg = PolicyCfg() + + +@configclass +class RewardsCfg: + """Shared reward terms tuned to the Direct task's scales.""" + + track_orientation_inv_l2 = RewTerm( + func=mdp.track_orientation_inv_l2, + weight=1.0, + params={"object_cfg": SceneEntityCfg("object"), "rot_eps": 0.1, "command_name": "object_pose"}, + ) + success_bonus = RewTerm( + func=mdp.success_bonus, + weight=250.0, + params={"object_cfg": SceneEntityCfg("object"), "command_name": "object_pose"}, + ) + track_pos_l2 = RewTerm( + func=mdp.track_pos_l2, + weight=-10.0, + params={"command_name": "object_pose", "object_cfg": SceneEntityCfg("object")}, + ) + action_l2 = RewTerm(func=mdp.action_l2, weight=-0.0002) + + +@configclass +class TerminationsCfg: + """Shared terminations reduced to the Direct task's fall condition.""" + + time_out = DoneTerm(func=mdp.time_out, time_out=True) + object_out_of_reach = DoneTerm( + func=mdp.object_away_from_goal, + params={ + "threshold": 0.24, + "command_name": "object_pose", + "object_cfg": SceneEntityCfg("object"), + }, + ) + + +@configclass +class ShadowHandManagerEnvCfg(ManagerBasedRLEnvCfg): + """Manager-based state Shadow Hand task with Direct-compatible semantics.""" + + scene: ShadowHandManagerSceneCfg = ShadowHandManagerSceneCfg() + decimation = 2 + episode_length_s = 10.0 + sim: SimulationCfg = SimulationCfg( + dt=1 / 120, + render_interval=decimation, + physics_material=RigidBodyMaterialBaseCfg(static_friction=1.0, dynamic_friction=1.0), + physics=PhysicsCfg(), + ) + observations: ObservationsCfg = ObservationsCfg() + actions: ActionsCfg = ActionsCfg() + commands: CommandsCfg = CommandsCfg() + rewards: RewardsCfg = RewardsCfg() + terminations: TerminationsCfg = TerminationsCfg() + events: ShadowHandManagerEventCfg = ShadowHandManagerEventCfg() + + viewer: ViewerCfg = ViewerCfg(eye=(2.0, 2.0, 2.0)) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_openai_manager_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_openai_manager_env_cfg.py new file mode 100644 index 000000000000..f50e4da9a6b6 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_openai_manager_env_cfg.py @@ -0,0 +1,197 @@ +# 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 + +"""Manager-based counterpart of the OpenAI Shadow Hand reorientation variants (FF and LSTM). + +The observation, action-noise, and episode conventions follow OpenAI et al., "Learning +Dexterous In-Hand Manipulation" (https://arxiv.org/abs/1808.00177). +""" + +from isaaclab.envs import ManagerBasedRLEnvCfg, ViewerCfg +from isaaclab.managers import ObservationGroupCfg as ObsGroup +from isaaclab.managers import ObservationTermCfg as ObsTerm +from isaaclab.managers import RewardTermCfg as RewTerm +from isaaclab.managers import SceneEntityCfg +from isaaclab.managers import TerminationTermCfg as DoneTerm +from isaaclab.sensors import JointWrenchSensorCfg +from isaaclab.sim.simulation_cfg import SimulationCfg +from isaaclab.sim.spawners.materials import RigidBodyMaterialBaseCfg +from isaaclab.utils.configclass import configclass + +import isaaclab_tasks.core.reorient.mdp as mdp +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_common import ( + GOAL_OBJECT_CFG, + OPENAI_ACTION_NOISE_CFG, + OPENAI_OBSERVATION_NOISE_CFG, + PhysicsCfg, + ShadowHandManagerEventCfg, +) +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_manager_env_cfg import ( + FullStateObsCfg, + ShadowHandManagerSceneCfg, +) + +from isaaclab_assets.robots.shadow_hand import SHADOW_ACTUATED_JOINT_NAMES, SHADOW_FINGERTIP_BODY_NAMES + + +@configclass +class CommandsCfg: + """OpenAI goal command with its wider success tolerance.""" + + object_pose = mdp.ReorientCommandCfg( + asset_name="object", + init_pos_offset=(0.0, 0.0, -0.04), + update_goal_on_success=True, + orientation_success_threshold=0.4, + make_quat_unique=False, + fixed_marker_pos=(-0.2, -0.45, 0.68), + goal_pose_visualizer_cfg=GOAL_OBJECT_CFG, + debug_vis=True, + ) + + +@configclass +class ActionsCfg: + """OpenAI actions with Direct-compatible EMA and stateful noise.""" + + joint_pos = mdp.NoisyEMAJointPositionToLimitsActionCfg( + asset_name="robot", + joint_names=SHADOW_ACTUATED_JOINT_NAMES, + alpha=0.3, + rescale_to_limits=True, + noise_model=OPENAI_ACTION_NOISE_CFG, + ) + + +@configclass +class ObservationsCfg: + """OpenAI 42-dimensional actor and 187-dimensional critic observations.""" + + @configclass + class PolicyCfg(ObsGroup): + openai = ObsTerm( + func=mdp.openai_policy_observation, + params={ + "command_name": "object_pose", + "action_name": "joint_pos", + "noise_model": OPENAI_OBSERVATION_NOISE_CFG, + "robot_cfg": SceneEntityCfg("robot", body_names=SHADOW_FINGERTIP_BODY_NAMES, preserve_order=False), + "object_cfg": SceneEntityCfg("object"), + }, + ) + + def __post_init__(self): + self.enable_corruption = False + self.concatenate_terms = True + + @configclass + class CriticCfg(FullStateObsCfg): + fingertip_wrench = ObsTerm( + func=mdp.fingertip_wrench, + scale=10.0, + params={ + "sensor_cfg": SceneEntityCfg( + "joint_wrench", body_names=SHADOW_FINGERTIP_BODY_NAMES, preserve_order=False + ) + }, + ) + last_action = ObsTerm(func=mdp.reorient_last_action, params={"action_name": "joint_pos"}) + + policy: PolicyCfg = PolicyCfg() + critic: CriticCfg = CriticCfg() + + +@configclass +class ShadowHandOpenAIManagerSceneCfg(ShadowHandManagerSceneCfg): + """Shadow Hand scene with fingertip joint-wrench sensing.""" + + joint_wrench = JointWrenchSensorCfg(prim_path="{ENV_REGEX_NS}/Robot") + + +@configclass +class RewardsCfg: + """Shared reward terms tuned to the Direct OpenAI variant's scales.""" + + track_orientation_inv_l2 = RewTerm( + func=mdp.track_orientation_inv_l2, + weight=1.0, + params={"object_cfg": SceneEntityCfg("object"), "rot_eps": 0.1, "command_name": "object_pose"}, + ) + success_bonus = RewTerm( + func=mdp.success_bonus, + weight=250.0, + params={"object_cfg": SceneEntityCfg("object"), "command_name": "object_pose"}, + ) + track_pos_l2 = RewTerm( + func=mdp.track_pos_l2, + weight=-10.0, + params={"command_name": "object_pose", "object_cfg": SceneEntityCfg("object")}, + ) + action_l2 = RewTerm(func=mdp.action_l2, weight=-0.0002) + object_away_penalty = RewTerm( + func=mdp.is_terminated_term, + weight=-50.0, + params={"term_keys": "object_out_of_reach"}, + ) + + +@configclass +class TerminationsCfg: + """Shared terminations with the OpenAI streak cap and success-extended timer. + + The Direct variant reports both the streak cap and the elapsed-time limit as + truncations, so both carry ``time_out=True``. + """ + + object_out_of_reach = DoneTerm( + func=mdp.object_away_from_goal, + params={ + "threshold": 0.24, + "command_name": "object_pose", + "object_cfg": SceneEntityCfg("object"), + }, + ) + max_consecutive_success = DoneTerm( + func=mdp.max_consecutive_success, + time_out=True, + params={"num_success": 50, "command_name": "object_pose"}, + ) + time_out = DoneTerm( + func=mdp.reorient_timeout, + time_out=True, + params={ + "command_name": "object_pose", + "success_tolerance": 0.4, + "object_cfg": SceneEntityCfg("object"), + }, + ) + + +@configclass +class ShadowHandOpenAIManagerEnvCfg(ManagerBasedRLEnvCfg): + """Manager counterpart shared by the OpenAI FF and LSTM variants. + + Standalone rather than a subclass of :class:`ShadowHandManagerEnvCfg`: + every section differs from the state task, so this block is the complete + recipe. + """ + + scene: ShadowHandOpenAIManagerSceneCfg = ShadowHandOpenAIManagerSceneCfg() + decimation = 3 + episode_length_s = 8.0 + sim: SimulationCfg = SimulationCfg( + dt=1 / 60, + render_interval=decimation, + physics_material=RigidBodyMaterialBaseCfg(static_friction=1.0, dynamic_friction=1.0), + physics=PhysicsCfg(), + ) + observations: ObservationsCfg = ObservationsCfg() + actions: ActionsCfg = ActionsCfg() + commands: CommandsCfg = CommandsCfg() + rewards: RewardsCfg = RewardsCfg() + terminations: TerminationsCfg = TerminationsCfg() + events: ShadowHandManagerEventCfg = ShadowHandManagerEventCfg() + + viewer: ViewerCfg = ViewerCfg(eye=(2.0, 2.0, 2.0)) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_common.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_common.py deleted file mode 100644 index 946f1d2e2f08..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_common.py +++ /dev/null @@ -1,32 +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 - -"""Task-family identity shared by the Direct and manager-based reorientation tasks. - -Geometry constants only — task tunables live inline in the workflow -configuration files, and robot identity lives in the per-robot ``*_common`` -modules. -""" - -GOAL_MARKER_POSITION: tuple[float, float, float] = (-0.2, -0.45, 0.68) -"""Fixed goal-marker display position [m], environment frame (state-based tasks).""" - -CAMERA_GOAL_MARKER_POSITION: tuple[float, float, float] = (-0.2, 0.1, 0.6) -"""Goal-marker display position [m] for the camera tasks. - -Deviates from :data:`GOAL_MARKER_POSITION` so the goal cube sits inside the -tiled camera's frustum. -""" - -IN_HAND_POS_OFFSET: tuple[float, float, float] = (0.0, 0.0, -0.04) -"""Offset from the object's default position to the in-hand goal anchor [m]. - -Defines the Direct/manager goal-position parity: the Direct environments and -the manager command terms derive the same in-hand target point from the -object's default root position plus this offset. -""" - -CAMERA_PLAY_NUM_ENVS: int = 64 -"""Camera-task environment count for checkpoint playback.""" diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_manager_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_manager_env_cfg.py deleted file mode 100644 index ef1639a61e32..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/reorient_manager_env_cfg.py +++ /dev/null @@ -1,348 +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 - -from __future__ import annotations - -from dataclasses import MISSING - -from isaaclab_physx.physics import PhysxCfg - -import isaaclab.sim as sim_utils -from isaaclab.assets import ArticulationCfg, AssetBaseCfg, RigidObjectCfg -from isaaclab.envs import ManagerBasedRLEnvCfg -from isaaclab.managers import EventTermCfg as EventTerm -from isaaclab.managers import ObservationGroupCfg as ObsGroup -from isaaclab.managers import ObservationTermCfg as ObsTerm -from isaaclab.managers import RewardTermCfg as RewTerm -from isaaclab.managers import SceneEntityCfg -from isaaclab.managers import TerminationTermCfg as DoneTerm -from isaaclab.scene import InteractiveSceneCfg -from isaaclab.sim.simulation_cfg import SimulationCfg -from isaaclab.sim.spawners.materials import RigidBodyMaterialCfg -from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR -from isaaclab.utils.configclass import configclass -from isaaclab.utils.noise import GaussianNoiseCfg as Gnoise - -import isaaclab_tasks.core.reorient.mdp as mdp - -## -# Scene definition -## - - -@configclass -class ReorientObjectSceneCfg(InteractiveSceneCfg): - """Configuration for a scene with an object and a dexterous hand.""" - - # robots - robot: ArticulationCfg = MISSING - - # objects - object: RigidObjectCfg = RigidObjectCfg( - prim_path="{ENV_REGEX_NS}/object", - spawn=sim_utils.UsdFileCfg( - usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd", - rigid_props=sim_utils.RigidBodyPropertiesCfg( - kinematic_enabled=False, - disable_gravity=False, - enable_gyroscopic_forces=True, - solver_position_iteration_count=8, - solver_velocity_iteration_count=0, - sleep_threshold=0.005, - stabilization_threshold=0.0025, - max_depenetration_velocity=1000.0, - ), - mass_props=sim_utils.MassPropertiesCfg(density=400.0), - ), - init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.19, 0.56), rot=(0.0, 0.0, 0.0, 1.0)), - ) - - # lights - light = AssetBaseCfg( - prim_path="/World/light", - spawn=sim_utils.DistantLightCfg(color=(0.95, 0.95, 0.95), intensity=1000.0), - ) - - dome_light = AssetBaseCfg( - prim_path="/World/domeLight", - spawn=sim_utils.DomeLightCfg(color=(0.02, 0.02, 0.02), intensity=1000.0), - ) - - -## -# MDP settings -## - - -@configclass -class CommandsCfg: - """Command specifications for the MDP.""" - - object_pose = mdp.ReorientCommandCfg( - asset_name="object", - init_pos_offset=(0.0, 0.0, -0.04), - update_goal_on_success=True, - orientation_success_threshold=0.1, - make_quat_unique=False, - marker_pos_offset=(-0.2, -0.06, 0.08), - debug_vis=True, - ) - - -@configclass -class ActionsCfg: - """Action specifications for the MDP.""" - - joint_pos = mdp.EMAJointPositionToLimitsActionCfg( - asset_name="robot", - joint_names=[".*"], - alpha=0.95, - rescale_to_limits=True, - ) - - -@configclass -class ObservationsCfg: - """Observation specifications for the MDP.""" - - @configclass - class KinematicObsGroupCfg(ObsGroup): - """Observations with full-kinematic state information. - - This does not include acceleration or force information. - """ - - # observation terms (order preserved) - # -- robot terms - joint_pos = ObsTerm(func=mdp.joint_pos_limit_normalized, noise=Gnoise(std=0.005)) - joint_vel = ObsTerm(func=mdp.joint_vel_rel, scale=0.2, noise=Gnoise(std=0.01)) - - # -- object terms - object_pos = ObsTerm( - func=mdp.root_pos_w, noise=Gnoise(std=0.002), params={"asset_cfg": SceneEntityCfg("object")} - ) - object_quat = ObsTerm( - func=mdp.root_quat_w, params={"asset_cfg": SceneEntityCfg("object"), "make_quat_unique": False} - ) - object_lin_vel = ObsTerm( - func=mdp.root_lin_vel_w, noise=Gnoise(std=0.002), params={"asset_cfg": SceneEntityCfg("object")} - ) - object_ang_vel = ObsTerm( - func=mdp.root_ang_vel_w, - scale=0.2, - noise=Gnoise(std=0.002), - params={"asset_cfg": SceneEntityCfg("object")}, - ) - - # -- command terms - goal_pose = ObsTerm(func=mdp.generated_commands, params={"command_name": "object_pose"}) - goal_quat_diff = ObsTerm( - func=mdp.goal_quat_diff, - params={"asset_cfg": SceneEntityCfg("object"), "command_name": "object_pose", "make_quat_unique": False}, - ) - - # -- action terms - last_action = ObsTerm(func=mdp.last_action) - - def __post_init__(self): - self.enable_corruption = True - self.concatenate_terms = True - - @configclass - class NoVelocityKinematicObsGroupCfg(KinematicObsGroupCfg): - """Observations with partial kinematic state information. - - In contrast to the full-kinematic state group, this group does not include velocity information - about the robot joints and the object root frame. This is useful for tasks where velocity information - is not available or has a lot of noise. - """ - - def __post_init__(self): - # call parent post init - super().__post_init__() - # set unused terms to None - self.joint_vel = None - self.object_lin_vel = None - self.object_ang_vel = None - - # observation groups - policy: KinematicObsGroupCfg = KinematicObsGroupCfg() - - -@configclass -class EventCfg: - """Configuration for randomization.""" - - # startup - # -- robot - robot_physics_material = EventTerm( - func=mdp.randomize_rigid_body_material, - mode="startup", - params={ - "asset_cfg": SceneEntityCfg("robot", body_names=".*"), - "static_friction_range": (0.7, 1.3), - "dynamic_friction_range": (0.7, 1.3), - "restitution_range": (0.0, 0.0), - "num_buckets": 250, - }, - ) - robot_scale_mass = EventTerm( - func=mdp.randomize_rigid_body_mass, - mode="startup", - params={ - "asset_cfg": SceneEntityCfg("robot", body_names=".*"), - "mass_distribution_params": (0.95, 1.05), - "operation": "scale", - }, - ) - robot_joint_stiffness_and_damping = EventTerm( - func=mdp.randomize_actuator_gains, - mode="startup", - params={ - "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), - "stiffness_distribution_params": (0.3, 3.0), # default: 3.0 - "damping_distribution_params": (0.75, 1.5), # default: 0.1 - "operation": "scale", - "distribution": "log_uniform", - }, - ) - - # -- object - object_physics_material = EventTerm( - func=mdp.randomize_rigid_body_material, - mode="startup", - params={ - "asset_cfg": SceneEntityCfg("object", body_names=".*"), - "static_friction_range": (0.7, 1.3), - "dynamic_friction_range": (0.7, 1.3), - "restitution_range": (0.0, 0.0), - "num_buckets": 250, - }, - ) - object_scale_mass = EventTerm( - func=mdp.randomize_rigid_body_mass, - mode="startup", - params={ - "asset_cfg": SceneEntityCfg("object"), - "mass_distribution_params": (0.4, 1.6), - "operation": "scale", - }, - ) - - # reset - reset_object = EventTerm( - func=mdp.reset_root_state_uniform, - mode="reset", - params={ - "pose_range": {"x": [-0.01, 0.01], "y": [-0.01, 0.01], "z": [-0.01, 0.01]}, - "velocity_range": {}, - "asset_cfg": SceneEntityCfg("object", body_names=".*"), - }, - ) - reset_robot_joints = EventTerm( - func=mdp.reset_joints_within_limits_range, - mode="reset", - params={ - "position_range": {".*": [0.2, 0.2]}, - "velocity_range": {".*": [0.0, 0.0]}, - "use_default_offset": True, - "operation": "scale", - }, - ) - - -@configclass -class RewardsCfg: - """Reward terms for the MDP.""" - - # -- task - # track_pos_l2 = RewTerm( - # func=mdp.track_pos_l2, - # weight=-10.0, - # params={"object_cfg": SceneEntityCfg("object"), "command_name": "object_pose"}, - # ) - track_orientation_inv_l2 = RewTerm( - func=mdp.track_orientation_inv_l2, - weight=1.0, - params={"object_cfg": SceneEntityCfg("object"), "rot_eps": 0.1, "command_name": "object_pose"}, - ) - success_bonus = RewTerm( - func=mdp.success_bonus, - weight=250.0, - params={"object_cfg": SceneEntityCfg("object"), "command_name": "object_pose"}, - ) - - # -- penalties - joint_vel_l2 = RewTerm(func=mdp.joint_vel_l2, weight=-2.5e-5) - action_l2 = RewTerm(func=mdp.action_l2, weight=-0.0001) - action_rate_l2 = RewTerm(func=mdp.action_rate_l2, weight=-0.01) - - # -- optional penalties (these are disabled by default) - # object_away_penalty = RewTerm( - # func=mdp.is_terminated_term, - # weight=-0.0, - # params={"term_keys": "object_out_of_reach"}, - # ) - - -@configclass -class TerminationsCfg: - """Termination terms for the MDP.""" - - time_out = DoneTerm(func=mdp.time_out, time_out=True) - - max_consecutive_success = DoneTerm( - func=mdp.max_consecutive_success, params={"num_success": 50, "command_name": "object_pose"} - ) - - object_out_of_reach = DoneTerm(func=mdp.object_away_from_robot, params={"threshold": 0.3}) - - # object_out_of_reach = DoneTerm( - # func=mdp.object_away_from_goal, params={"threshold": 0.24, "command_name": "object_pose"} - # ) - - -## -# Environment configuration -## - - -@configclass -class ReorientObjectEnvCfg(ManagerBasedRLEnvCfg): - """Configuration for the in hand reorientation environment.""" - - # Scene settings - scene: ReorientObjectSceneCfg = ReorientObjectSceneCfg(num_envs=8192, env_spacing=0.6) - # Simulation settings - sim: SimulationCfg = SimulationCfg( - physics_material=RigidBodyMaterialCfg( - static_friction=1.0, - dynamic_friction=1.0, - ), - physics=PhysxCfg( - bounce_threshold_velocity=0.2, - gpu_max_rigid_contact_count=2**20, - gpu_max_rigid_patch_count=2**23, - ), - ) - # Basic settings - observations: ObservationsCfg = ObservationsCfg() - actions: ActionsCfg = ActionsCfg() - commands: CommandsCfg = CommandsCfg() - # MDP settings - rewards: RewardsCfg = RewardsCfg() - terminations: TerminationsCfg = TerminationsCfg() - events: EventCfg = EventCfg() - - def __post_init__(self): - """Post initialization.""" - # general settings - self.decimation = 4 - self.episode_length_s = 20.0 - # simulation settings - self.sim.dt = 1.0 / 120.0 - self.sim.render_interval = self.decimation - # change viewer settings - self.viewer.eye = (2.0, 2.0, 2.0) From 445ad89c7f9f9257ff62a9ef30eeb9bebd446529 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Mon, 3 Aug 2026 03:29:10 -0700 Subject: [PATCH 04/12] Move the Shadow camera benchmark task to the contributed tasks The benchmark variant exists to measure rendering throughput rather than to train a policy, so it belongs alongside the other contributed tasks. Keep the released task ID working as a deprecated alias. --- docs/source/setup/quickstart_details.rst | 2 +- .../contrib/reorient/__init__.py | 6 +++ .../contrib/reorient/config/__init__.py | 6 +++ .../reorient/config/shadow_hand/__init__.py | 41 +++++++++++++++++++ .../shadow_hand_camera_benchmark_env_cfg.py | 30 ++++++++++++++ 5 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/__init__.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/__init__.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/shadow_hand/__init__.py create mode 100644 source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/shadow_hand/shadow_hand_camera_benchmark_env_cfg.py diff --git a/docs/source/setup/quickstart_details.rst b/docs/source/setup/quickstart_details.rst index ad4cc9663d3f..3176c31782f2 100644 --- a/docs/source/setup/quickstart_details.rst +++ b/docs/source/setup/quickstart_details.rst @@ -57,7 +57,7 @@ options (observation modes, camera configs, etc.). They fold into Hydra override # OVRTX rendering (kit-less, no Kit visualizer) uv run isaaclab train --rl_library rsl_rl \ - --task=Isaac-Reorient-Cube-Shadow-Camera-Benchmark-Direct \ + --task=IsaacContrib-Reorient-Cube-Shadow-Camera-Benchmark-Direct \ --num_envs=16 --max_iterations=10 \ physics=newton_mjwarp renderer=ovrtx presets=simple_shading_diffuse_mdl diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/__init__.py new file mode 100644 index 000000000000..26defb14af38 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/__init__.py @@ -0,0 +1,6 @@ +# 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 + +"""Contributed variants of the in-hand reorientation tasks.""" diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/__init__.py new file mode 100644 index 000000000000..cf7a6f752621 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/__init__.py @@ -0,0 +1,6 @@ +# 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 + +"""Configurations for the contributed reorientation environments.""" diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/shadow_hand/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/shadow_hand/__init__.py new file mode 100644 index 000000000000..949475cab7e5 --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/shadow_hand/__init__.py @@ -0,0 +1,41 @@ +# 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 + +"""Shadow Hand rendering-throughput benchmark task.""" + +import gymnasium as gym +from gymnasium.envs.registration import EnvSpec, registry + +from isaaclab_tasks.core.reorient.config.shadow_hand import agents + +gym.register( + id="IsaacContrib-Reorient-Cube-Shadow-Camera-Benchmark-Direct", + entry_point="isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_direct_camera_env:ShadowHandCameraEnv", + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": f"{__name__}.shadow_hand_camera_benchmark_env_cfg:ShadowHandCameraBenchmarkEnvCfg", + "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:ShadowHandCameraFFPPORunnerCfg", + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_camera_cfg.yaml", + }, +) + +# Retain the released ID as a deprecated alias by inserting its spec directly. +registry.update( + { + "Isaac-Reorient-Cube-Shadow-Camera-Benchmark-Direct": EnvSpec( + id="Isaac-Reorient-Cube-Shadow-Camera-Benchmark-Direct", + entry_point=( + "isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_direct_camera_env:ShadowHandCameraEnv" + ), + disable_env_checker=True, + kwargs={ + "env_cfg_entry_point": f"{__name__}.shadow_hand_camera_benchmark_env_cfg:ShadowHandCameraBenchmarkEnvCfg", + "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:ShadowHandCameraFFPPORunnerCfg", + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_camera_cfg.yaml", + "deprecated": {"alias": "--task IsaacContrib-Reorient-Cube-Shadow-Camera-Benchmark-Direct"}, + }, + ), + } +) diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/shadow_hand/shadow_hand_camera_benchmark_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/shadow_hand/shadow_hand_camera_benchmark_env_cfg.py new file mode 100644 index 000000000000..4b879f6d52cd --- /dev/null +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/shadow_hand/shadow_hand_camera_benchmark_env_cfg.py @@ -0,0 +1,30 @@ +# 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 + +"""Rendering-throughput benchmark variant of the Shadow Hand camera task.""" + +from isaaclab.utils.configclass import configclass + +from isaaclab_tasks.core.reorient.config.shadow_hand.feature_extractor import FeatureExtractorCfg +from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_direct_camera_env_cfg import ShadowHandCameraEnvCfg + + +@configclass +class ShadowHandCameraBenchmarkEnvCfg(ShadowHandCameraEnvCfg): + """Benchmark configuration with the feature extractor CNN disabled. + + The tiled camera renders frames each step as normal, but the CNN forward pass is + bypassed — zero embeddings are returned instead. This isolates rendering throughput + from CNN inference overhead when profiling. + + The renderer backend and camera data types can still be selected via ``presets``:: + + presets = newton_renderer # benchmark with Newton renderer + presets = ovrtx # benchmark with OVRTX renderer + presets = rgb # benchmark RGB rendering only + presets = depth, newton_renderer # benchmark depth rendering with Newton + """ + + feature_extractor: FeatureExtractorCfg = FeatureExtractorCfg(enabled=False) From 56e855879b06fca3a4bd8fe24d4c729191e08b56 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Mon, 3 Aug 2026 16:12:14 -0700 Subject: [PATCH 05/12] Align manager reorient tasks with Direct counterparts The manager-based reorientation tasks were not comparable to the Direct environments they mirror: they reported success differently, randomized physics the Direct tasks leave fixed, and trained under different PPO hyper-parameters. Benchmarking one workflow against the other therefore measured the configuration gap rather than the workflow. Report Metrics/success_rate as a per-episode bit drawn at success_count_threshold, matching how the Direct environments define it and how the other 18 success_rate producers in the tree behave; the per-attempt ratio was the sole outlier. Gate domain randomization behind enable_domain_randomization on the Shadow tasks as the Allegro task already did, and default it to the value its Direct counterpart uses: off for Allegro and Shadow, on for the OpenAI variants. Move the Allegro randomization terms from startup to reset so they follow the same schedule as Shadow's. Point the Allegro manager task at the Direct agent configurations instead of its own copies, so the two run identical hyper-parameters. Drop the unreferenced EventCfg from the handover configuration; its own docstring recorded that it was never wired into HandoverEnvCfg.events. Remove the play_mode override that cleared the time-out termination, the only such line in the tree and residue from an earlier refactor. --- .../task-cleanup-dex-part08.major.rst | 17 ++- .../core/handover/handover_env_cfg.py | 102 ------------------ .../reorient/config/allegro_hand/__init__.py | 10 +- .../agents/rl_games_manager_ppo_cfg.yaml | 90 ---------------- ...ect_ppo_cfg.yaml => rl_games_ppo_cfg.yaml} | 0 .../allegro_hand/agents/rsl_rl_ppo_cfg.py | 33 ------ .../agents/skrl_manager_ppo_cfg.yaml | 85 --------------- ..._direct_ppo_cfg.yaml => skrl_ppo_cfg.yaml} | 0 .../allegro_hand_manager_env_cfg.py | 30 ++---- .../shadow_hand_manager_env_cfg.py | 18 ++++ .../shadow_hand_openai_manager_env_cfg.py | 19 ++++ .../core/reorient/mdp/commands.py | 17 ++- .../test/core/test_reorient_value_parity.py | 4 +- 13 files changed, 82 insertions(+), 343 deletions(-) delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/rl_games_manager_ppo_cfg.yaml rename source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/{rl_games_direct_ppo_cfg.yaml => rl_games_ppo_cfg.yaml} (100%) delete mode 100644 source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/skrl_manager_ppo_cfg.yaml rename source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/{skrl_direct_ppo_cfg.yaml => skrl_ppo_cfg.yaml} (100%) diff --git a/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst b/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst index 0554edb23b8d..63dc5472877a 100644 --- a/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst +++ b/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst @@ -7,7 +7,7 @@ Added * Added :class:`~isaaclab_tasks.core.reorient.mdp.reorient_timeout`, which restarts the episode timer on every goal reach so OpenAI-variant episodes extend across success streaks. -* Added ``enable_domain_randomization`` to the manager-based Allegro +* Added ``enable_domain_randomization`` to the manager-based Allegro and Shadow environment for turning off its startup randomization terms. * Added Newton and OvPhysx physics presets to the manager-based reorientation environments, selectable with ``physics=``. @@ -17,6 +17,21 @@ Added Changed ^^^^^^^ +* **Breaking:** Changed the manager-based reorientation tasks to report + ``Metrics/success_rate`` as a per-episode success bit, drawn at + ``ReorientCommandCfg.success_count_threshold`` like the Direct environments, + instead of a per-attempt ratio. Curves from earlier runs are not comparable. +* **Breaking:** Changed domain randomization to default off on the Allegro and + Shadow manager tasks, so they match their Direct counterparts, which randomize + nothing beyond the reset distributions. Set ``enable_domain_randomization`` on + the configuration to restore it. The OpenAI variants keep it enabled, matching + their Direct counterparts. +* **Breaking:** Changed the manager-based Allegro task to use the same RL agent + configurations as its Direct counterpart, so the two are comparable. The + ``rl_games_manager_ppo_cfg.yaml``, ``skrl_manager_ppo_cfg.yaml`` and + ``AllegroCubePPORunnerCfg`` entries are removed; use ``rl_games_ppo_cfg.yaml``, + ``skrl_ppo_cfg.yaml`` and ``AllegroHandPPORunnerCfg``. + * **Breaking:** Changed the manager-based Allegro reorientation environment to match the Direct observation, action, reset, and termination contracts. The observation space changes size, so existing manager checkpoints cannot be 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 53aa2d740159..aa7a47ff838d 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 @@ -8,13 +8,10 @@ from isaaclab_ovphysx.physics import OvPhysxCfg from isaaclab_physx.physics import PhysxCfg -import isaaclab.envs.mdp as mdp import isaaclab.sim as sim_utils import isaaclab.utils.math as math_utils from isaaclab.assets import ArticulationCfg, RigidObjectCfg from isaaclab.envs import DirectMARLEnvCfg -from isaaclab.managers import EventTermCfg as EventTerm -from isaaclab.managers import SceneEntityCfg from isaaclab.markers import VisualizationMarkersCfg from isaaclab.physics import PhysxAutoCfg from isaaclab.scene import InteractiveSceneCfg @@ -33,105 +30,6 @@ from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_CFG, SHADOW_HAND_NEWTON_CFG -@configclass -class EventCfg: - """Configuration for randomization (PhysX path). - - Note: this config is currently not wired into ``HandoverEnvCfg.events`` - - it is kept as a reference for future event-randomization work. The event - terms here use PhysX-only APIs (rigid-body materials, fixed tendons), so - they would need a Newton variant before being enabled in the env. - """ - - # -- robot - robot_physics_material = EventTerm( - func=mdp.randomize_rigid_body_material, - mode="reset", - min_step_count_between_reset=720, - params={ - "asset_cfg": SceneEntityCfg("right_hand"), - "static_friction_range": (0.7, 1.3), - "dynamic_friction_range": (1.0, 1.0), - "restitution_range": (1.0, 1.0), - "num_buckets": 250, - }, - ) - robot_joint_stiffness_and_damping = EventTerm( - func=mdp.randomize_actuator_gains, - min_step_count_between_reset=720, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("right_hand", joint_names=".*"), - "stiffness_distribution_params": (0.75, 1.5), - "damping_distribution_params": (0.3, 3.0), - "operation": "scale", - "distribution": "log_uniform", - }, - ) - robot_joint_pos_limits = EventTerm( - func=mdp.randomize_joint_parameters, - min_step_count_between_reset=720, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("right_hand", joint_names=".*"), - "lower_limit_distribution_params": (0.00, 0.01), - "upper_limit_distribution_params": (0.00, 0.01), - "operation": "add", - "distribution": "gaussian", - }, - ) - robot_tendon_properties = EventTerm( - func=mdp.randomize_fixed_tendon_parameters, - min_step_count_between_reset=720, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("right_hand", fixed_tendon_names=".*"), - "stiffness_distribution_params": (0.75, 1.5), - "damping_distribution_params": (0.3, 3.0), - "operation": "scale", - "distribution": "log_uniform", - }, - ) - - # -- object - object_physics_material = EventTerm( - func=mdp.randomize_rigid_body_material, - min_step_count_between_reset=720, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("object"), - "static_friction_range": (0.7, 1.3), - "dynamic_friction_range": (1.0, 1.0), - "restitution_range": (1.0, 1.0), - "num_buckets": 250, - }, - ) - object_scale_mass = EventTerm( - func=mdp.randomize_rigid_body_mass, - min_step_count_between_reset=720, - mode="reset", - params={ - "asset_cfg": SceneEntityCfg("object"), - "mass_distribution_params": (0.5, 1.5), - "operation": "scale", - "distribution": "uniform", - }, - ) - - # -- scene - reset_gravity = EventTerm( - func=mdp.randomize_physics_scene_gravity, - mode="interval", - is_global_time=True, - interval_range_s=(36.0, 36.0), # time_s = num_steps * (decimation * dt) - params={ - "gravity_distribution_params": ([0.0, 0.0, 0.0], [0.0, 0.0, 0.4]), - "operation": "add", - "distribution": "gaussian", - }, - ) - - def _shadow_hand_cfg( prim_path: str, init_pos: tuple[float, float, float], diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/__init__.py index 90fb41f30541..52c3862ae4b0 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/__init__.py @@ -19,9 +19,9 @@ disable_env_checker=True, kwargs={ "env_cfg_entry_point": f"{__name__}.allegro_hand_manager_env_cfg:AllegroCubeEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:AllegroCubePPORunnerCfg", - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_manager_ppo_cfg.yaml", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_manager_ppo_cfg.yaml", + "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:AllegroHandPPORunnerCfg", + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", }, ) @@ -35,8 +35,8 @@ disable_env_checker=True, kwargs={ "env_cfg_entry_point": f"{__name__}.allegro_hand_direct_env_cfg:AllegroHandEnvCfg", - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_direct_ppo_cfg.yaml", + "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_cfg.yaml", "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:AllegroHandPPORunnerCfg", - "skrl_cfg_entry_point": f"{agents.__name__}:skrl_direct_ppo_cfg.yaml", + "skrl_cfg_entry_point": f"{agents.__name__}:skrl_ppo_cfg.yaml", }, ) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/rl_games_manager_ppo_cfg.yaml b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/rl_games_manager_ppo_cfg.yaml deleted file mode 100644 index 2fa70902ab61..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/rl_games_manager_ppo_cfg.yaml +++ /dev/null @@ -1,90 +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 - -params: - seed: 42 - - # environment wrapper clipping - env: - clip_observations: 5.0 - clip_actions: 1.0 - - algo: - name: a2c_continuous - - model: - name: continuous_a2c_logstd - - network: - name: actor_critic - separate: False - - space: - continuous: - mu_activation: None - sigma_activation: None - mu_init: - name: default - sigma_init: - name: const_initializer - val: 0 - fixed_sigma: True - - mlp: - units: [512, 256, 128] - activation: elu - d2rl: False - - initializer: - name: default - regularizer: - name: None - - load_checkpoint: False - load_path: '' - - config: - name: allegro_cube - env_name: rlgpu - device: 'cuda:0' - device_name: 'cuda:0' - multi_gpu: False - ppo: True - mixed_precision: False - normalize_input: True - normalize_value: True - value_bootstrap: True - num_actors: -1 # configured from the script (based on num_envs) - reward_shaper: - scale_value: 0.1 - normalize_advantage: True - gamma: 0.998 - tau: 0.95 - learning_rate: 5e-4 - lr_schedule: adaptive - schedule_type: standard - kl_threshold: 0.016 - score_to_win: 100000 - max_epochs: 5000 - save_best_after: 500 - save_frequency: 200 - print_stats: True - grad_norm: 1.0 - entropy_coef: 0.002 - truncate_grads: True - e_clip: 0.2 - horizon_length: 24 - minibatch_size: 16384 # 32768 - mini_epochs: 5 - critic_coef: 4 - clip_value: True - seq_length: 4 - bounds_loss_coef: 0.0005 - - player: - #render: True - deterministic: True - games_num: 100000 - print_stats: True diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/rl_games_direct_ppo_cfg.yaml b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/rl_games_ppo_cfg.yaml similarity index 100% rename from source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/rl_games_direct_ppo_cfg.yaml rename to source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/rl_games_ppo_cfg.yaml diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/rsl_rl_ppo_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/rsl_rl_ppo_cfg.py index bec125790b57..784a18239f6e 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/rsl_rl_ppo_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/rsl_rl_ppo_cfg.py @@ -8,39 +8,6 @@ from isaaclab_rl.rsl_rl import RslRlMLPModelCfg, RslRlOnPolicyRunnerCfg, RslRlPpoAlgorithmCfg -@configclass -class AllegroCubePPORunnerCfg(RslRlOnPolicyRunnerCfg): - num_steps_per_env = 24 - max_iterations = 5000 - save_interval = 50 - experiment_name = "allegro_cube" - actor = RslRlMLPModelCfg( - hidden_dims=[512, 256, 128], - activation="elu", - obs_normalization=True, - distribution_cfg=RslRlMLPModelCfg.GaussianDistributionCfg(init_std=1.0), - ) - critic = RslRlMLPModelCfg( - hidden_dims=[512, 256, 128], - activation="elu", - obs_normalization=True, - ) - algorithm = RslRlPpoAlgorithmCfg( - value_loss_coef=1.0, - use_clipped_value_loss=True, - clip_param=0.2, - entropy_coef=0.002, - num_learning_epochs=5, - num_mini_batches=4, - learning_rate=0.001, - schedule="adaptive", - gamma=0.998, - lam=0.95, - desired_kl=0.01, - max_grad_norm=1.0, - ) - - @configclass class AllegroHandPPORunnerCfg(RslRlOnPolicyRunnerCfg): num_steps_per_env = 16 diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/skrl_manager_ppo_cfg.yaml b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/skrl_manager_ppo_cfg.yaml deleted file mode 100644 index f61e7f50132d..000000000000 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/skrl_manager_ppo_cfg.yaml +++ /dev/null @@ -1,85 +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 - -seed: 42 - - -# Models are instantiated using skrl's model instantiator utility -# https://skrl.readthedocs.io/en/latest/api/utils/model_instantiators.html -models: - separate: False - policy: # see gaussian_model parameters - class: GaussianMixin - clip_actions: False - clip_log_std: True - min_log_std: -20.0 - max_log_std: 2.0 - initial_log_std: 0.0 - network: - - name: net - input: OBSERVATIONS - layers: [512, 256, 128] - activations: elu - output: ACTIONS - value: # see deterministic_model parameters - class: DeterministicMixin - clip_actions: False - network: - - name: net - input: OBSERVATIONS - layers: [512, 256, 128] - activations: elu - output: ONE - - -# Rollout memory -# https://skrl.readthedocs.io/en/latest/api/memories/random.html -memory: - class: RandomMemory - memory_size: -1 # automatically determined (same as agent:rollouts) - - -# PPO agent configuration (field names are from PPO_DEFAULT_CONFIG) -# https://skrl.readthedocs.io/en/latest/api/agents/ppo.html -agent: - class: PPO - rollouts: 24 - learning_epochs: 5 - mini_batches: 12 - discount_factor: 0.998 - lambda: 0.95 - learning_rate: 5.0e-04 - learning_rate_scheduler: KLAdaptiveLR - learning_rate_scheduler_kwargs: - kl_threshold: 0.016 - state_preprocessor: RunningStandardScaler - state_preprocessor_kwargs: null - value_preprocessor: RunningStandardScaler - value_preprocessor_kwargs: null - random_timesteps: 0 - learning_starts: 0 - grad_norm_clip: 1.0 - ratio_clip: 0.2 - value_clip: 0.2 - clip_predicted_values: True - entropy_loss_scale: 0.002 - value_loss_scale: 2.0 - kl_threshold: 0.0 - rewards_shaper_scale: 0.1 - time_limit_bootstrap: False - # logging and checkpoint - experiment: - directory: "allegro_cube" - experiment_name: "" - write_interval: auto - checkpoint_interval: auto - - -# Sequential trainer -# https://skrl.readthedocs.io/en/latest/api/trainers/sequential.html -trainer: - class: SequentialTrainer - timesteps: 120000 - environment_info: log diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/skrl_direct_ppo_cfg.yaml b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/skrl_ppo_cfg.yaml similarity index 100% rename from source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/skrl_direct_ppo_cfg.yaml rename to source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/agents/skrl_ppo_cfg.yaml diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_manager_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_manager_env_cfg.py index a12b9272c04c..2974c40c5f9e 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_manager_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_manager_env_cfg.py @@ -151,7 +151,7 @@ class EventCfg: robot_physics_material = EventTerm( func=mdp.randomize_rigid_body_material, - mode="startup", + mode="reset", params={ "asset_cfg": SceneEntityCfg("robot", body_names=".*"), "static_friction_range": (0.7, 1.3), @@ -162,7 +162,7 @@ class EventCfg: ) robot_scale_mass = EventTerm( func=mdp.randomize_rigid_body_mass, - mode="startup", + mode="reset", params={ "asset_cfg": SceneEntityCfg("robot", body_names=".*"), "mass_distribution_params": (0.95, 1.05), @@ -171,7 +171,7 @@ class EventCfg: ) robot_joint_stiffness_and_damping = EventTerm( func=mdp.randomize_actuator_gains, - mode="startup", + mode="reset", params={ "asset_cfg": SceneEntityCfg("robot", joint_names=".*"), "stiffness_distribution_params": (0.3, 3.0), # default: 3.0 @@ -184,7 +184,7 @@ class EventCfg: # -- object object_physics_material = EventTerm( func=mdp.randomize_rigid_body_material, - mode="startup", + mode="reset", params={ "asset_cfg": SceneEntityCfg("object", body_names=".*"), "static_friction_range": (0.7, 1.3), @@ -195,7 +195,7 @@ class EventCfg: ) object_scale_mass = EventTerm( func=mdp.randomize_rigid_body_mass, - mode="startup", + mode="reset", params={ "asset_cfg": SceneEntityCfg("object"), "mass_distribution_params": (0.4, 1.6), @@ -272,14 +272,13 @@ class AllegroCubeEnvCfg(ManagerBasedRLEnvCfg): terminations: TerminationsCfg = TerminationsCfg() events: EventCfg = EventCfg() - enable_domain_randomization: bool = True - """Apply the startup domain-randomization terms. + enable_domain_randomization: bool = False + """Apply the domain-randomization event terms. - Set it on the configuration to disable them: ``__post_init__`` reads it while building - the configuration, before Hydra applies command-line overrides, so - ``env.enable_domain_randomization=false`` has no effect. Individual terms can still be - disabled from the command line, for example ``env.events.robot_scale_mass=null``. - Changing it requires retraining. + Off by default so the task matches its Direct counterpart, which randomizes nothing beyond + the reset distributions. ``__post_init__`` reads it while building the configuration, before + Hydra applies command-line overrides, so ``env.enable_domain_randomization=true`` has no + effect -- set it on the configuration. Changing it requires retraining. """ viewer: ViewerCfg = ViewerCfg(eye=(2.0, 2.0, 2.0)) @@ -291,10 +290,3 @@ def __post_init__(self): self.events.robot_joint_stiffness_and_damping = None self.events.object_physics_material = None self.events.object_scale_mass = None - - def play_mode(self): - # play-mode overrides of parent - super().play_mode() - - # remove termination due to timeouts - self.terminations.time_out = None diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_manager_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_manager_env_cfg.py index 8ba024b6a6c9..b5728b5aaad9 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_manager_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_manager_env_cfg.py @@ -202,4 +202,22 @@ class ShadowHandManagerEnvCfg(ManagerBasedRLEnvCfg): terminations: TerminationsCfg = TerminationsCfg() events: ShadowHandManagerEventCfg = ShadowHandManagerEventCfg() + enable_domain_randomization: bool = False + """Apply the domain-randomization event terms. + + Off by default so the task matches its Direct counterpart, which randomizes nothing beyond + the reset distributions. ``__post_init__`` reads it while building the configuration, before + Hydra applies command-line overrides, so ``env.enable_domain_randomization=true`` has no + effect -- set it on the configuration. Changing it requires retraining. + """ + viewer: ViewerCfg = ViewerCfg(eye=(2.0, 2.0, 2.0)) + + def __post_init__(self): + if not self.enable_domain_randomization: + self.events.robot_joint_stiffness_and_damping = None + self.events.object_scale_mass = None + self.events.reset_gravity = None + self.events.robot_tendon_properties = None + self.events.robot_physics_material = None + self.events.object_physics_material = None diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_openai_manager_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_openai_manager_env_cfg.py index f50e4da9a6b6..f81e618df43d 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_openai_manager_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_openai_manager_env_cfg.py @@ -194,4 +194,23 @@ class ShadowHandOpenAIManagerEnvCfg(ManagerBasedRLEnvCfg): terminations: TerminationsCfg = TerminationsCfg() events: ShadowHandManagerEventCfg = ShadowHandManagerEventCfg() + enable_domain_randomization: bool = True + """Apply the domain-randomization event terms. + + On by default: unlike the other reorientation tasks, the OpenAI Direct environment + randomizes as well, so the two workflows only match with these enabled. ``__post_init__`` + reads it while building the configuration, before Hydra applies command-line overrides, so + ``env.enable_domain_randomization=false`` has no effect -- set it on the configuration. + Changing it requires retraining. + """ + viewer: ViewerCfg = ViewerCfg(eye=(2.0, 2.0, 2.0)) + + def __post_init__(self): + if not self.enable_domain_randomization: + self.events.robot_joint_stiffness_and_damping = None + self.events.object_scale_mass = None + self.events.reset_gravity = None + self.events.robot_tendon_properties = None + self.events.robot_physics_material = None + self.events.object_physics_material = None diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/commands.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/commands.py index bcd205832991..04c30305ec03 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/commands.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/commands.py @@ -77,8 +77,7 @@ def __init__(self, cfg: ReorientCommandCfg, env: ManagerBasedRLEnv): self.metrics["position_error"] = torch.zeros(self.num_envs, device=self.device) self.metrics["consecutive_success"] = torch.zeros(self.num_envs, device=self.device) self.metrics["success_rate"] = torch.zeros(self.num_envs, device=self.device) - # -- per-attempt success accounting: each success-driven resample completes one attempt; - # the trailing attempt at episode end counts as one unsuccessful attempt. + # -- goals reached this episode; each success-driven resample completes one. self._completed_attempts = torch.zeros(self.num_envs, device=self.device) # An auto-reset lands immediately before CommandManager.compute(); suppress success # handling for those environments until one new physics step has run. @@ -123,12 +122,13 @@ def _update_metrics(self): self.metrics["consecutive_success"] += successes.float() def reset(self, env_ids: Sequence[int] | None = None) -> dict[str, float]: - # Snapshot per-attempt success rate BEFORE the base class logs and zeros metrics. - # success_rate = completed_attempts / (completed_attempts + 1 trailing in-progress). + # Snapshot the episode-success bit BEFORE the base class logs and zeros metrics. The + # Direct environment draws it the same way: goals reached this episode against + # ``success_count_threshold``. if env_ids is None: env_ids = slice(None) completed = self._completed_attempts[env_ids] - self.metrics["success_rate"][env_ids] = completed / (completed + 1.0) + self.metrics["success_rate"][env_ids] = (completed >= self.cfg.success_count_threshold).float() extras = super().reset(env_ids) # super().reset() invoked _resample_command for the new initial goal, which # incremented _completed_attempts; zero it back out so the new episode starts clean. @@ -227,6 +227,13 @@ class ReorientCommandCfg(CommandTermCfg): update_goal_on_success: bool = MISSING """Whether to update the goal orientation when the goal orientation is reached.""" + success_count_threshold: int = 1 + """Goals an episode must reach to count as successful in ``Metrics/success_rate``. + + Mirrors the Direct environment's configuration field of the same name, so both workflows + draw the episode-success bit at the same goal count. + """ + marker_pos_offset: tuple[float, float, float] = (0.0, 0.0, 0.0) """Position offset of the marker from the object's desired position. diff --git a/source/isaaclab_tasks/test/core/test_reorient_value_parity.py b/source/isaaclab_tasks/test/core/test_reorient_value_parity.py index edcafc083ce9..43bba092f781 100644 --- a/source/isaaclab_tasks/test/core/test_reorient_value_parity.py +++ b/source/isaaclab_tasks/test/core/test_reorient_value_parity.py @@ -45,9 +45,7 @@ def test_manager_config_matches_direct_values(direct_cls, manager_cls): ) assert manager.commands.object_pose.orientation_success_threshold == pytest.approx(direct.success_tolerance) assert manager.terminations.object_out_of_reach.params["threshold"] == pytest.approx(direct.fall_dist) - # Both workflows publish ``Metrics/success_rate``; the curves only compare if the - # episode-success bit is drawn at the same goal count. - + assert manager.commands.object_pose.success_count_threshold == direct.success_count_threshold # The Direct tasks fold the streak cap into their time-out signal. streak_cap = getattr(manager.terminations, "max_consecutive_success", None) assert (0 if streak_cap is None else streak_cap.params["num_success"]) == direct.max_consecutive_success From 8ebd90fe68d3fcd71f94d3ab4c9e82e968e7bf58 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Mon, 3 Aug 2026 18:41:45 -0700 Subject: [PATCH 06/12] Drop dead scene flags and the fake articulation cube Review of the reorientation configurations surfaced three settings that describe the simulator inaccurately. ``clone_in_fabric`` no longer reaches the PhysX replicator: the scene configuration documents it as a deprecated legacy flag and nothing reads it, so ``useFabricForReplication`` is always false. The per-backend presets built around it therefore selected between identical scenes. Removing it leaves the Shadow Direct scene with no backend-varying field, so its six-way preset collapses to a single scene configuration. The in-hand cube was declared as an articulation on the Newton backend with no actuators, no joints and an empty articulation root path. It is a rigid body, nothing addresses it through the articulation API, and the other backends already declare it as one. The camera benchmark task keeps only its contributed identifier. The released alias is removed rather than deprecated, since the task is a rendering-throughput probe rather than a trainable environment. Also drop three names from the reorientation MDP stub that no module defines. ``lazy_export`` builds the package namespace from the stub, so accessing any of them raised ``AttributeError`` and a star import failed. --- .../task-cleanup-dex-part08.major.rst | 11 ++++++++-- .../reorient/config/shadow_hand/__init__.py | 20 ------------------- .../allegro_hand/allegro_hand_common.py | 10 +++------- .../allegro_hand_direct_env_cfg.py | 1 - .../allegro_hand_manager_env_cfg.py | 5 ----- .../config/shadow_hand/shadow_hand_common.py | 10 +++------- .../shadow_hand_direct_camera_env_cfg.py | 10 +++------- .../shadow_hand/shadow_hand_direct_env_cfg.py | 19 +++--------------- .../shadow_hand_manager_env_cfg.py | 6 +----- .../core/reorient/mdp/__init__.pyi | 6 ------ 10 files changed, 22 insertions(+), 76 deletions(-) diff --git a/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst b/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst index 63dc5472877a..ab39ef560494 100644 --- a/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst +++ b/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst @@ -42,8 +42,15 @@ Changed Policies trained before this change must be retrained. * **Breaking:** Moved the Shadow Hand camera benchmark task to the contributed tasks as ``IsaacContrib-Reorient-Cube-Shadow-Camera-Benchmark-Direct``. The - released ``Isaac-Reorient-Cube-Shadow-Camera-Benchmark-Direct`` identifier - still resolves and warns; switch to the new identifier. + released ``Isaac-Reorient-Cube-Shadow-Camera-Benchmark-Direct`` identifier no + longer resolves; use the contributed identifier instead. +* **Breaking:** Changed the in-hand cube to a rigid body on the Newton backend, + where it was previously declared as an articulation with no joints or + actuators. Code that resolved the object through + :class:`~isaaclab.assets.Articulation` must use + :class:`~isaaclab.assets.RigidObject`. +* Removed the ``clone_in_fabric`` settings from the reorientation scenes. The + flag no longer reaches the replicator, so the value had no effect. * Renamed the per-robot scene constants to name what they hold: ``ROBOT_CFG`` becomes ``SHADOW_HAND_ROBOT_CFG`` or ``ALLEGRO_HAND_ROBOT_CFG``, ``OBJECT_CFG`` becomes ``CUBE_CFG``, and ``ObjectCfg`` becomes ``CubeCfg``. diff --git a/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/shadow_hand/__init__.py b/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/shadow_hand/__init__.py index 949475cab7e5..ea3dc061aa17 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/shadow_hand/__init__.py +++ b/source/isaaclab_tasks/isaaclab_tasks/contrib/reorient/config/shadow_hand/__init__.py @@ -6,7 +6,6 @@ """Shadow Hand rendering-throughput benchmark task.""" import gymnasium as gym -from gymnasium.envs.registration import EnvSpec, registry from isaaclab_tasks.core.reorient.config.shadow_hand import agents @@ -20,22 +19,3 @@ "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_camera_cfg.yaml", }, ) - -# Retain the released ID as a deprecated alias by inserting its spec directly. -registry.update( - { - "Isaac-Reorient-Cube-Shadow-Camera-Benchmark-Direct": EnvSpec( - id="Isaac-Reorient-Cube-Shadow-Camera-Benchmark-Direct", - entry_point=( - "isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_direct_camera_env:ShadowHandCameraEnv" - ), - disable_env_checker=True, - kwargs={ - "env_cfg_entry_point": f"{__name__}.shadow_hand_camera_benchmark_env_cfg:ShadowHandCameraBenchmarkEnvCfg", - "rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:ShadowHandCameraFFPPORunnerCfg", - "rl_games_cfg_entry_point": f"{agents.__name__}:rl_games_ppo_camera_cfg.yaml", - "deprecated": {"alias": "--task IsaacContrib-Reorient-Cube-Shadow-Camera-Benchmark-Direct"}, - }, - ), - } -) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_common.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_common.py index 3023993391ff..ef6c443d9828 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_common.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_common.py @@ -14,7 +14,7 @@ from isaaclab_physx.physics import PhysxCfg import isaaclab.sim as sim_utils -from isaaclab.assets import ArticulationCfg, RigidObjectCfg +from isaaclab.assets import RigidObjectCfg from isaaclab.markers import VisualizationMarkersCfg from isaaclab.physics import PhysxAutoCfg from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR @@ -48,18 +48,14 @@ class CubeCfg(PresetCfg): ), init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.17, 0.56), rot=(0.0, 0.0, 0.0, 1.0)), ) - newton_mjwarp = ArticulationCfg( + newton_mjwarp = RigidObjectCfg( prim_path="/World/envs/env_.*/object", spawn=sim_utils.UsdFileCfg( usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd", mass_props=sim_utils.MassPropertiesCfg(density=400.0), scale=(1.2, 1.2, 1.2), ), - init_state=ArticulationCfg.InitialStateCfg( - pos=(0.0, -0.17, 0.565), rot=(0.0, 0.0, 0.0, 1.0), joint_pos={}, joint_vel={} - ), - actuators={}, - articulation_root_prim_path="", + init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.17, 0.565), rot=(0.0, 0.0, 0.0, 1.0)), ) ovphysx = RigidObjectCfg( prim_path="/World/envs/env_.*/object", diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_direct_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_direct_env_cfg.py index f5ef10d68fb1..3562e6f55e1d 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_direct_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_direct_env_cfg.py @@ -54,7 +54,6 @@ class AllegroHandEnvCfg(DirectRLEnvCfg): num_envs=8192, env_spacing=0.75, replicate_physics=True, - clone_in_fabric=True, ) # reset reset_position_noise = 0.01 # range of position at reset diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_manager_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_manager_env_cfg.py index 2974c40c5f9e..c03c2c0cbf9f 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_manager_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_manager_env_cfg.py @@ -26,7 +26,6 @@ CubeCfg, PhysicsCfg, ) -from isaaclab_tasks.utils import preset from isaaclab_assets.robots.allegro import ALLEGRO_ACTUATED_JOINT_NAMES, ALLEGRO_FINGERTIP_BODY_NAMES @@ -35,10 +34,6 @@ class AllegroCubeSceneCfg(InteractiveSceneCfg): """Shared reorientation scene with the Allegro hand and a ground plane.""" - # ``clone_in_fabric`` is the only backend-varying field: PhysX/OvPhysX use Fabric - # cloning for speed, Newton does not support it. - clone_in_fabric = preset(default=False, physx=True, ovphysx=True, newton_mjwarp=False) - num_envs = 8192 env_spacing = 0.75 diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py index 874f9db6a555..619ebbe230c3 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py @@ -183,7 +183,7 @@ class CubeCfg(PresetCfg): ) isaacsim_physx = physx - newton_mjwarp = ArticulationCfg( + newton_mjwarp = RigidObjectCfg( prim_path="/World/envs/env_.*/object", spawn=sim_utils.UsdFileCfg( usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd", @@ -191,13 +191,9 @@ class CubeCfg(PresetCfg): semantic_tags=[("class", "cube")], scale=(0.9, 0.9, 0.9), ), - init_state=ArticulationCfg.InitialStateCfg( - pos=(0.0, -0.36, 0.535), rot=(0.0, 0.0, 0.0, 1.0), joint_pos={}, joint_vel={} - ), - actuators={}, - articulation_root_prim_path="", + init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.36, 0.535), rot=(0.0, 0.0, 0.0, 1.0)), ) - ovphysx = physx # OvPhysX is PhysX-based; use the rigid-body cube, not Newton's articulation + ovphysx = physx # OvPhysX is PhysX-based; it keeps the PhysX cube's density and pose. default = newton_mjwarp newton_kamino = newton_mjwarp diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py index f7d7a9e35e34..39fbe3fc578f 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_camera_env_cfg.py @@ -122,13 +122,9 @@ class ShadowHandCameraEnvCfg(ShadowHandEnvCfg): state_space = 187 + 27 # asymmetric states + vision CNN embedding def __post_init__(self): - # The vision env renders through the Isaac RTX tiled camera, whose render - # products require the Fabric cloning path. The Newton backend disables Fabric - # cloning (see the base env's ``clone_in_fabric`` scene preset), so under Newton - # the ``rgb`` annotator has no render products for ``num_envs > 1`` and the - # default RGB/depth/semantic render fails. Default the vision env to PhysX so it - # renders out of the box; Newton stays selectable via ``physics=newton_mjwarp`` - # for the depth-only Newton-warp-renderer benchmark path (``presets=newton_renderer``). + # Only the Isaac RTX tiled camera renders the default RGB/depth/semantic set, so + # the vision env defaults to PhysX. Newton stays selectable via + # ``physics=newton_mjwarp`` for the depth-only benchmark path. super().__post_init__() for backend_cfg in (self.sim.physics, self.robot_cfg, self.object_cfg): backend_cfg.default = backend_cfg.physx diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_env_cfg.py index c0353c3e30eb..7fa4d4511fad 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_direct_env_cfg.py @@ -22,19 +22,13 @@ ShadowHandEventCfg, ShadowHandRobotCfg, ) -from isaaclab_tasks.utils import preset from isaaclab_assets.robots.shadow_hand import SHADOW_ACTUATED_JOINT_NAMES, SHADOW_FINGERTIP_BODY_NAMES @configclass class ShadowHandSceneCfg(InteractiveSceneCfg): - """Shadow Direct scene defaults. - - ``clone_in_fabric`` is the only backend-varying field: PhysX/OvPhysX use Fabric - cloning for speed; Newton does not support it. The per-backend value is selected - inline at the scene field via :func:`~isaaclab_tasks.utils.preset`. - """ + """Shadow Direct scene defaults.""" num_envs = 8192 env_spacing = 0.75 @@ -69,15 +63,8 @@ class ShadowHandEnvCfg(DirectRLEnvCfg): object_cfg: CubeCfg = CUBE_CFG # goal object goal_object_cfg: VisualizationMarkersCfg = GOAL_OBJECT_CFG - # scene — clone_in_fabric is the only backend-varying field (Newton cannot use Fabric cloning) - scene: InteractiveSceneCfg = preset( - default=ShadowHandSceneCfg(clone_in_fabric=False), - physx=ShadowHandSceneCfg(clone_in_fabric=True), - isaacsim_physx=ShadowHandSceneCfg(clone_in_fabric=True), - ovphysx=ShadowHandSceneCfg(clone_in_fabric=True), - newton_mjwarp=ShadowHandSceneCfg(clone_in_fabric=False), - newton_kamino=ShadowHandSceneCfg(clone_in_fabric=False), - ) + # scene + scene: InteractiveSceneCfg = ShadowHandSceneCfg() # reset reset_position_noise = 0.01 # range of position at reset diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_manager_env_cfg.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_manager_env_cfg.py index b5728b5aaad9..5bab033564cd 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_manager_env_cfg.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_manager_env_cfg.py @@ -27,7 +27,7 @@ PhysicsCfg, ShadowHandManagerEventCfg, ) -from isaaclab_tasks.utils import PresetCfg, preset +from isaaclab_tasks.utils import PresetCfg from isaaclab_assets.robots.shadow_hand import SHADOW_ACTUATED_JOINT_NAMES, SHADOW_FINGERTIP_BODY_NAMES @@ -38,10 +38,6 @@ class ShadowHandManagerSceneCfg(InteractiveSceneCfg): """Shared reorientation scene with the Shadow hand and a ground plane.""" - # ``clone_in_fabric`` is the only backend-varying field: PhysX/OvPhysX use Fabric - # cloning for speed, Newton does not support it. - clone_in_fabric = preset(default=False, physx=True, ovphysx=True, newton_mjwarp=False) - num_envs = 8192 env_spacing = 0.75 diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/__init__.pyi b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/__init__.pyi index 9bd01bd20972..13881dfbaea5 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/__init__.pyi +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/mdp/__init__.pyi @@ -17,9 +17,6 @@ __all__ = [ "fingertip_wrench", "reorient_last_action", "openai_policy_observation", - "ShadowHandCameraFeatures", - "shadow_hand_camera_cached_features", - "shadow_hand_goal_keypoints", "goal_quat_diff", "success_bonus", "track_orientation_inv_l2", @@ -36,9 +33,6 @@ from .events import reset_reorient_state from .noisy_actions import NoisyEMAJointPositionToLimitsAction from .actions import NoisyEMAJointPositionToLimitsActionCfg from .observations import ( - ShadowHandCameraFeatures, - shadow_hand_camera_cached_features, - shadow_hand_goal_keypoints, fingertip_pos, fingertip_quat, fingertip_vel, From f44db1dbb43ad94c96b58798e0e91fc146da7a77 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Mon, 3 Aug 2026 21:37:34 -0700 Subject: [PATCH 07/12] Give the Newton cube its rigid-body properties The in-hand cube was previously declared as an articulation on the Newton backend, and carried none of the rigid-body configuration the other backends set, because an articulation derives its per-link properties from the tree instead. Declaring it as a rigid body without restoring those properties left it under-specified: unlike every other Newton rigid object in the tree, it spawned with whatever the USD happened to carry. Set the same rigid-body and collision properties the handover object uses. The PhysX-only solver fields stay out, since the MJWarp solver takes its iteration counts and thresholds from the solver configuration rather than per-body. Spawn positions are unchanged. Collapse the Allegro OvPhysX cube onto the PhysX one it duplicated verbatim, matching how the Shadow configuration already expresses it. --- .../config/allegro_hand/allegro_hand_common.py | 18 +++--------------- .../config/shadow_hand/shadow_hand_common.py | 6 ++++++ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_common.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_common.py index ef6c443d9828..9b3e1d36e1c7 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_common.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/allegro_hand/allegro_hand_common.py @@ -49,15 +49,6 @@ class CubeCfg(PresetCfg): init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.17, 0.56), rot=(0.0, 0.0, 0.0, 1.0)), ) newton_mjwarp = RigidObjectCfg( - prim_path="/World/envs/env_.*/object", - spawn=sim_utils.UsdFileCfg( - usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd", - mass_props=sim_utils.MassPropertiesCfg(density=400.0), - scale=(1.2, 1.2, 1.2), - ), - init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.17, 0.565), rot=(0.0, 0.0, 0.0, 1.0)), - ) - ovphysx = RigidObjectCfg( prim_path="/World/envs/env_.*/object", spawn=sim_utils.UsdFileCfg( usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd", @@ -65,17 +56,14 @@ class CubeCfg(PresetCfg): kinematic_enabled=False, disable_gravity=False, enable_gyroscopic_forces=True, - solver_position_iteration_count=8, - solver_velocity_iteration_count=0, - sleep_threshold=0.005, - stabilization_threshold=0.0025, - max_depenetration_velocity=1000.0, ), + collision_props=sim_utils.CollisionPropertiesCfg(), mass_props=sim_utils.MassPropertiesCfg(density=400.0), scale=(1.2, 1.2, 1.2), ), - init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.17, 0.56), rot=(0.0, 0.0, 0.0, 1.0)), + init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.17, 0.565), rot=(0.0, 0.0, 0.0, 1.0)), ) + ovphysx = physx # OvPhysX is PhysX-based; it shares the PhysX cube verbatim. isaacsim_physx = physx default = newton_mjwarp diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py index 619ebbe230c3..849995571f9a 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/reorient/config/shadow_hand/shadow_hand_common.py @@ -187,6 +187,12 @@ class CubeCfg(PresetCfg): prim_path="/World/envs/env_.*/object", spawn=sim_utils.UsdFileCfg( usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Blocks/DexCube/dex_cube_instanceable.usd", + rigid_props=sim_utils.RigidBodyPropertiesCfg( + kinematic_enabled=False, + disable_gravity=False, + enable_gyroscopic_forces=True, + ), + collision_props=sim_utils.CollisionPropertiesCfg(), mass_props=sim_utils.MassPropertiesCfg(density=400.0), semantic_tags=[("class", "cube")], scale=(0.9, 0.9, 0.9), From ea34351b6ae853b7dd35316279a69f5c27796df3 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Mon, 3 Aug 2026 22:14:50 -0700 Subject: [PATCH 08/12] Resolve only the backend presets the Shadow env still has The visualizer golden test resolved every backend-varying field on the Shadow Hand configuration by name, including the scene. Now that the scene carries no backend-varying field, it is a plain scene configuration rather than a preset, and the lookup raised ``AttributeError`` for all four Shadow Hand golden cases while cartpole and ANYmal, whose scenes were never presets, kept passing. Resolve a field only when it is a preset, and leave the rest alone. --- .../changelog.d/task-cleanup-dex-part08.skip | 0 .../test/visualizer_integration_utils.py | 16 +++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 source/isaaclab_visualizers/changelog.d/task-cleanup-dex-part08.skip diff --git a/source/isaaclab_visualizers/changelog.d/task-cleanup-dex-part08.skip b/source/isaaclab_visualizers/changelog.d/task-cleanup-dex-part08.skip new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/source/isaaclab_visualizers/test/visualizer_integration_utils.py b/source/isaaclab_visualizers/test/visualizer_integration_utils.py index 78bd01560234..b2b703cbf01f 100644 --- a/source/isaaclab_visualizers/test/visualizer_integration_utils.py +++ b/source/isaaclab_visualizers/test/visualizer_integration_utils.py @@ -50,6 +50,7 @@ from isaaclab_tasks.core.reorient.config.shadow_hand.shadow_hand_direct_env_cfg import ShadowHandEnvCfg from isaaclab_tasks.core.reorient.reorient_direct_env import ReorientDirectEnv from isaaclab_tasks.core.velocity.config.anymal_d.flat_env_cfg import AnymalDFlatEnvCfg +from isaaclab_tasks.utils.hydra import PresetCfg # Debugging mode configs. @@ -1524,12 +1525,17 @@ def _make_shadow_hand_env( """ env_cfg = copy.deepcopy(ShadowHandEnvCfg()) preset_key = "newton_mjwarp" if backend_kind == "newton" else "physx" - env_cfg.sim.physics = getattr(env_cfg.sim.physics, preset_key) - env_cfg.scene = getattr(env_cfg.scene, preset_key) - env_cfg.robot_cfg = getattr(env_cfg.robot_cfg, preset_key) - env_cfg.object_cfg = getattr(env_cfg.object_cfg, preset_key) + + def resolve(cfg): + """Pick the backend variant, leaving fields that do not vary by backend alone.""" + return getattr(cfg, preset_key) if isinstance(cfg, PresetCfg) else cfg + + env_cfg.sim.physics = resolve(env_cfg.sim.physics) + env_cfg.scene = resolve(env_cfg.scene) + env_cfg.robot_cfg = resolve(env_cfg.robot_cfg) + env_cfg.object_cfg = resolve(env_cfg.object_cfg) if env_cfg.events is not None: - env_cfg.events = getattr(env_cfg.events, preset_key) + env_cfg.events = resolve(env_cfg.events) env_cfg.scene.num_envs = ( _SHADOW_HAND_TILED_CAMERA_INTEGRATION_NUM_ENVS if tiled_camera else _SHADOW_HAND_INTEGRATION_NUM_ENVS ) From a94a403ba63f019536e5b00d33fa50aa0639c153 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Mon, 3 Aug 2026 22:20:37 -0700 Subject: [PATCH 09/12] Annotate the random-rotation composition ``randomize_rotation`` is reachable from the task package and took four untyped parameters, which the repository's type-hint rule does not allow for a public interface. TorchScript accepts the annotations unchanged. --- .../isaaclab_tasks/isaaclab_tasks/core/utils.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/source/isaaclab_tasks/isaaclab_tasks/core/utils.py b/source/isaaclab_tasks/isaaclab_tasks/core/utils.py index 14fa1c490553..f6947d8ac420 100644 --- a/source/isaaclab_tasks/isaaclab_tasks/core/utils.py +++ b/source/isaaclab_tasks/isaaclab_tasks/core/utils.py @@ -127,8 +127,20 @@ def random_xy_rotation(count: int, device: str | torch.device) -> torch.Tensor: @torch.jit.script -def randomize_rotation(rand0, rand1, x_unit_tensor, y_unit_tensor): - """Compose ``[-pi, pi]``-scaled random X- and Y-axis rotations into ``(x, y, z, w)`` quaternions.""" +def randomize_rotation( + rand0: torch.Tensor, rand1: torch.Tensor, x_unit_tensor: torch.Tensor, y_unit_tensor: torch.Tensor +) -> torch.Tensor: + """Compose ``[-pi, pi]``-scaled random X- and Y-axis rotations into ``(x, y, z, w)`` quaternions. + + Args: + rand0: Rotation amounts about the X axis in ``[-1, 1]``, scaled to ``[-pi, pi]`` [rad]. + rand1: Rotation amounts about the Y axis in ``[-1, 1]``, scaled to ``[-pi, pi]`` [rad]. + x_unit_tensor: Per-sample X axis, shape ``(count, 3)``. + y_unit_tensor: Per-sample Y axis, shape ``(count, 3)``. + + Returns: + Composed ``(x, y, z, w)`` unit quaternions, shape ``(count, 4)``. + """ return quat_mul( quat_from_angle_axis(rand0 * np.pi, x_unit_tensor), quat_from_angle_axis(rand1 * np.pi, y_unit_tensor) ) From 362a47034d96da06223e4546e9c68d72cbd11c59 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Tue, 4 Aug 2026 01:33:18 -0700 Subject: [PATCH 10/12] Stiffen the Newton Shadow Hand fingers A single environment's articulation solve intermittently returned NaN during reorientation training, at a rate of roughly one run in five. The first non-finite buffer is the robot's joint positions, with every joint of one environment failing at once while the rest of the population is unremarkable, so the fault is in the solve rather than in any observation or reward term. The constraint budget is not implicated: the solver uses 90 of 200 rows and converges in 14 of 100 iterations. MJWarp's implicit-PD path has neither PhysX's fixed-tendon limit stiffness nor its solver-iteration torque amplification, so gains carried over from the PhysX configuration leave the joints too soft. The handover task already raised them to 20.0 and 2.0 for its catch, and is the only Shadow task on this backend that has never produced the fault. Adopting those gains as the asset's own removes the fault in four runs covering the three seeds that previously failed, and improves both the success rate and the goals reached per episode. An intermediate stiffness of 5.0 still fails, so this is a threshold rather than a trend. Handover's override becomes the default and is dropped. --- .../task-cleanup-dex-part08.minor.rst | 9 +++++++++ .../isaaclab_assets/robots/shadow_hand.py | 20 +++++++++---------- .../task-cleanup-dex-part08.major.rst | 3 +++ .../core/handover/handover_env_cfg.py | 4 ---- 4 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 source/isaaclab_assets/changelog.d/task-cleanup-dex-part08.minor.rst diff --git a/source/isaaclab_assets/changelog.d/task-cleanup-dex-part08.minor.rst b/source/isaaclab_assets/changelog.d/task-cleanup-dex-part08.minor.rst new file mode 100644 index 000000000000..7b4d1f4ffa86 --- /dev/null +++ b/source/isaaclab_assets/changelog.d/task-cleanup-dex-part08.minor.rst @@ -0,0 +1,9 @@ +Changed +^^^^^^^ + +* **Breaking:** Changed the Newton Shadow Hand finger gains to a stiffness of + ``20.0`` and a damping of ``2.0``, from ``1.0`` and ``0.1``. MJWarp's + implicit-PD path has neither PhysX's fixed-tendon limit stiffness nor its + solver-iteration torque amplification, so the softer gains let a joint drift + far enough for a single environment's articulation solve to return NaN. + Configurations that already raised these gains can drop their override. diff --git a/source/isaaclab_assets/isaaclab_assets/robots/shadow_hand.py b/source/isaaclab_assets/isaaclab_assets/robots/shadow_hand.py index 3d3c96876c2f..9277d85fac63 100644 --- a/source/isaaclab_assets/isaaclab_assets/robots/shadow_hand.py +++ b/source/isaaclab_assets/isaaclab_assets/robots/shadow_hand.py @@ -145,21 +145,21 @@ "robot0_THJ(2|1)": 0.99, "robot0_THJ0": 0.81, }, - # Default gains match the PhysX cfg (wrists 5.0/0.5, fingers 1.0/0.1). Tasks that - # need more joint authority override these -- e.g. the handover catch on MJWarp - # raises them to 20.0/2.0, since MJWarp's implicit-PD path lacks PhysX's - # fixed-tendon limit stiffness + solver-iteration torque amplification. + # Finger gains are 20.0/2.0 rather than the PhysX cfg's 1.0/0.1: MJWarp's + # implicit-PD path lacks PhysX's fixed-tendon limit stiffness and its + # solver-iteration torque amplification, so the softer gains let a joint drift + # far enough for a single environment's articulation solve to return NaN. stiffness={ "robot0_WRJ.*": 5.0, - "robot0_(FF|MF|RF|LF|TH)J(3|2|1)": 1.0, - "robot0_(LF|TH)J4": 1.0, - "robot0_THJ0": 1.0, + "robot0_(FF|MF|RF|LF|TH)J(3|2|1)": 20.0, + "robot0_(LF|TH)J4": 20.0, + "robot0_THJ0": 20.0, }, damping={ "robot0_WRJ.*": 0.5, - "robot0_(FF|MF|RF|LF|TH)J(3|2|1)": 0.1, - "robot0_(LF|TH)J4": 0.1, - "robot0_THJ0": 0.1, + "robot0_(FF|MF|RF|LF|TH)J(3|2|1)": 2.0, + "robot0_(LF|TH)J4": 2.0, + "robot0_THJ0": 2.0, }, friction=1e-2, armature=2e-3, diff --git a/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst b/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst index ab39ef560494..d3db258f4ab6 100644 --- a/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst +++ b/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst @@ -17,6 +17,9 @@ Added Changed ^^^^^^^ +* Removed the handover task's Shadow Hand gain override. The gains it set are + now the asset's own, so the task inherits them. + * **Breaking:** Changed the manager-based reorientation tasks to report ``Metrics/success_rate`` as a per-episode success bit, drawn at ``ReorientCommandCfg.success_count_threshold`` like the Direct environments, 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 aa7a47ff838d..325e87ebf973 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 @@ -61,10 +61,6 @@ def _shadow_hand_cfg( newton_mjwarp_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), - actuators={ - **SHADOW_HAND_NEWTON_CFG.actuators, - "fingers": SHADOW_HAND_NEWTON_CFG.actuators["fingers"].replace(stiffness=20.0, damping=2.0), - }, ) ovphysx_cfg = SHADOW_HAND_CFG.replace( prim_path=prim_path, From 0e7356ba784f53ebfdab5891ace5219e256f6361 Mon Sep 17 00:00:00 2001 From: jichuanh Date: Tue, 4 Aug 2026 02:11:03 -0700 Subject: [PATCH 11/12] Revert "Stiffen the Newton Shadow Hand fingers" This reverts commit 362a47034d96da06223e4546e9c68d72cbd11c59. --- .../task-cleanup-dex-part08.minor.rst | 9 --------- .../isaaclab_assets/robots/shadow_hand.py | 20 +++++++++---------- .../task-cleanup-dex-part08.major.rst | 3 --- .../core/handover/handover_env_cfg.py | 4 ++++ 4 files changed, 14 insertions(+), 22 deletions(-) delete mode 100644 source/isaaclab_assets/changelog.d/task-cleanup-dex-part08.minor.rst diff --git a/source/isaaclab_assets/changelog.d/task-cleanup-dex-part08.minor.rst b/source/isaaclab_assets/changelog.d/task-cleanup-dex-part08.minor.rst deleted file mode 100644 index 7b4d1f4ffa86..000000000000 --- a/source/isaaclab_assets/changelog.d/task-cleanup-dex-part08.minor.rst +++ /dev/null @@ -1,9 +0,0 @@ -Changed -^^^^^^^ - -* **Breaking:** Changed the Newton Shadow Hand finger gains to a stiffness of - ``20.0`` and a damping of ``2.0``, from ``1.0`` and ``0.1``. MJWarp's - implicit-PD path has neither PhysX's fixed-tendon limit stiffness nor its - solver-iteration torque amplification, so the softer gains let a joint drift - far enough for a single environment's articulation solve to return NaN. - Configurations that already raised these gains can drop their override. diff --git a/source/isaaclab_assets/isaaclab_assets/robots/shadow_hand.py b/source/isaaclab_assets/isaaclab_assets/robots/shadow_hand.py index 9277d85fac63..3d3c96876c2f 100644 --- a/source/isaaclab_assets/isaaclab_assets/robots/shadow_hand.py +++ b/source/isaaclab_assets/isaaclab_assets/robots/shadow_hand.py @@ -145,21 +145,21 @@ "robot0_THJ(2|1)": 0.99, "robot0_THJ0": 0.81, }, - # Finger gains are 20.0/2.0 rather than the PhysX cfg's 1.0/0.1: MJWarp's - # implicit-PD path lacks PhysX's fixed-tendon limit stiffness and its - # solver-iteration torque amplification, so the softer gains let a joint drift - # far enough for a single environment's articulation solve to return NaN. + # Default gains match the PhysX cfg (wrists 5.0/0.5, fingers 1.0/0.1). Tasks that + # need more joint authority override these -- e.g. the handover catch on MJWarp + # raises them to 20.0/2.0, since MJWarp's implicit-PD path lacks PhysX's + # fixed-tendon limit stiffness + solver-iteration torque amplification. stiffness={ "robot0_WRJ.*": 5.0, - "robot0_(FF|MF|RF|LF|TH)J(3|2|1)": 20.0, - "robot0_(LF|TH)J4": 20.0, - "robot0_THJ0": 20.0, + "robot0_(FF|MF|RF|LF|TH)J(3|2|1)": 1.0, + "robot0_(LF|TH)J4": 1.0, + "robot0_THJ0": 1.0, }, damping={ "robot0_WRJ.*": 0.5, - "robot0_(FF|MF|RF|LF|TH)J(3|2|1)": 2.0, - "robot0_(LF|TH)J4": 2.0, - "robot0_THJ0": 2.0, + "robot0_(FF|MF|RF|LF|TH)J(3|2|1)": 0.1, + "robot0_(LF|TH)J4": 0.1, + "robot0_THJ0": 0.1, }, friction=1e-2, armature=2e-3, diff --git a/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst b/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst index d3db258f4ab6..ab39ef560494 100644 --- a/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst +++ b/source/isaaclab_tasks/changelog.d/task-cleanup-dex-part08.major.rst @@ -17,9 +17,6 @@ Added Changed ^^^^^^^ -* Removed the handover task's Shadow Hand gain override. The gains it set are - now the asset's own, so the task inherits them. - * **Breaking:** Changed the manager-based reorientation tasks to report ``Metrics/success_rate`` as a per-episode success bit, drawn at ``ReorientCommandCfg.success_count_threshold`` like the Direct environments, 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 325e87ebf973..aa7a47ff838d 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 @@ -61,6 +61,10 @@ def _shadow_hand_cfg( newton_mjwarp_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), + actuators={ + **SHADOW_HAND_NEWTON_CFG.actuators, + "fingers": SHADOW_HAND_NEWTON_CFG.actuators["fingers"].replace(stiffness=20.0, damping=2.0), + }, ) ovphysx_cfg = SHADOW_HAND_CFG.replace( prim_path=prim_path, From 63bd92c16d4267e0f27569c41dfc373fbe91ac9f Mon Sep 17 00:00:00 2001 From: jichuanh Date: Tue, 4 Aug 2026 02:31:42 -0700 Subject: [PATCH 12/12] List the manager reorientation tasks in the environment catalog The registry-backed catalog check failed: the three manager-based reorientation tasks this branch registers were absent from the comprehensive table. Regenerate it with the repository's updater. --- docs/source/overview/environments.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/source/overview/environments.rst b/docs/source/overview/environments.rst index 47da6c69d8c3..44a61a71318a 100644 --- a/docs/source/overview/environments.rst +++ b/docs/source/overview/environments.rst @@ -1092,11 +1092,15 @@ including disabling runtime perturbations used for training. * - Isaac-Reorient-Cube-Allegro - Manager Based - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) - - + - **physics=** ``isaacsim_physx``, ``newton_mjwarp``, ``ovphysx``, ``physx`` * - Isaac-Reorient-Cube-Allegro-Direct - Direct - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) - **physics=** ``isaacsim_physx``, ``newton_mjwarp``, ``ovphysx``, ``physx`` + * - Isaac-Reorient-Cube-Shadow + - Manager Based + - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) + - **physics=** ``isaacsim_physx``, ``newton_kamino``, ``newton_mjwarp``, ``ovphysx``, ``physx`` * - Isaac-Reorient-Cube-Shadow-Camera-Direct - Direct - **rl_games** (PPO), **rsl_rl** (PPO) @@ -1107,10 +1111,18 @@ including disabling runtime perturbations used for training. - Direct - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) - **physics=** ``isaacsim_physx``, ``newton_kamino``, ``newton_mjwarp``, ``ovphysx``, ``physx`` + * - Isaac-Reorient-Cube-Shadow-OpenAI-FF + - Manager Based + - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) + - **physics=** ``isaacsim_physx``, ``newton_kamino``, ``newton_mjwarp``, ``ovphysx``, ``physx`` * - Isaac-Reorient-Cube-Shadow-OpenAI-FF-Direct - Direct - **rl_games** (PPO), **rsl_rl** (PPO), **skrl** (PPO) - **physics=** ``isaacsim_physx``, ``newton_kamino``, ``newton_mjwarp``, ``ovphysx``, ``physx`` + * - Isaac-Reorient-Cube-Shadow-OpenAI-LSTM + - Manager Based + - **rl_games** (PPO), **rsl_rl** (PPO) + - **physics=** ``isaacsim_physx``, ``newton_kamino``, ``newton_mjwarp``, ``ovphysx``, ``physx`` * - Isaac-Reorient-Cube-Shadow-OpenAI-LSTM-Direct - Direct - **rl_games** (PPO), **rsl_rl** (PPO)