Skip to content

Commit 216eb96

Browse files
committed
Add manager-based counterparts for handover and camera tasks
Rebuilt from the reviewed lump so the handover and camera manager counterparts land with the review round folded in: - Backend scene presets declare only backend-specific fields; shared invariants live as inner-class defaults. - Single-consumer scene helper cfgs are nested in their consumers. - Reset events use torch.zeros_like for the pose buffers. - The value-parity test covers reset-noise distributions and the timeout max-successes contract. - The camera manager cfg inherits the shared command/reward/termination sections from the state manager cfg instead of re-declaring them. Lump review commits folded: 792e400, 42675b6, 1f05f85, 173e9dc, 2727616, 1732493, 3659a33, ee272c9.
1 parent e52279c commit 216eb96

18 files changed

Lines changed: 961 additions & 2 deletions
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Added
2+
^^^^^
3+
4+
* Added manager-based counterparts for the Shadow handover and Shadow camera
5+
reorientation tasks, completing the manager coverage of the dexterous task
6+
families.
7+
* Added Direct-vs-manager scalar value-parity checks for the dexterous task
8+
families.

source/isaaclab_tasks/isaaclab_tasks/core/handover/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515
# Register Gym environments.
1616
##
1717

18+
gym.register(
19+
id="Isaac-Shadow-Handover",
20+
entry_point="isaaclab.envs:ManagerBasedRLEnv",
21+
disable_env_checker=True,
22+
kwargs={
23+
"env_cfg_entry_point": f"{__name__}.handover_manager_env_cfg:HandoverManagerEnvCfg",
24+
"rsl_rl_cfg_entry_point": f"{agents.__name__}.rsl_rl_ppo_cfg:HandoverPPORunnerCfg",
25+
},
26+
)
27+
1828
gym.register(
1929
id="Isaac-Shadow-Handover-Direct",
2030
entry_point=f"{__name__}.handover_env:HandoverEnv",
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
"""Manager-based counterpart of the Shadow Hand handover task."""
7+
8+
import isaaclab.sim as sim_utils
9+
from isaaclab.assets import AssetBaseCfg
10+
from isaaclab.envs import ManagerBasedRLEnvCfg
11+
from isaaclab.managers import EventTermCfg as EventTerm
12+
from isaaclab.managers import ObservationGroupCfg as ObsGroup
13+
from isaaclab.managers import ObservationTermCfg as ObsTerm
14+
from isaaclab.managers import RewardTermCfg as RewTerm
15+
from isaaclab.managers import SceneEntityCfg
16+
from isaaclab.managers import TerminationTermCfg as DoneTerm
17+
from isaaclab.scene import InteractiveSceneCfg
18+
from isaaclab.utils.configclass import configclass
19+
20+
import isaaclab_tasks.core.handover.mdp as mdp
21+
from isaaclab_tasks.core.handover.handover_env_cfg import (
22+
LEFT_HAND_CFG,
23+
RIGHT_HAND_CFG,
24+
HandoverTaskCfgBase,
25+
ObjectCfg,
26+
)
27+
from isaaclab_tasks.core.handover.handover_task_base import (
28+
ACTUATED_JOINT_NAMES_PRESET,
29+
FINGERTIP_BODY_NAMES,
30+
)
31+
from isaaclab_tasks.utils import PresetCfg
32+
33+
34+
@configclass
35+
class HandoverManagerSceneCfg(PresetCfg):
36+
"""Backend-specific scene cloning settings for handover."""
37+
38+
@configclass
39+
class SceneCfg(InteractiveSceneCfg):
40+
"""Scene shared by the handover Manager backend alternatives."""
41+
42+
num_envs = 2048
43+
env_spacing = 1.5
44+
replicate_physics = True
45+
46+
ground = AssetBaseCfg(
47+
prim_path="/World/ground",
48+
spawn=sim_utils.GroundPlaneCfg(),
49+
)
50+
right_hand: PresetCfg = RIGHT_HAND_CFG
51+
left_hand: PresetCfg = LEFT_HAND_CFG
52+
object: ObjectCfg = ObjectCfg()
53+
light = AssetBaseCfg(
54+
prim_path="/World/Light",
55+
spawn=sim_utils.DomeLightCfg(intensity=2000.0, color=(0.75, 0.75, 0.75)),
56+
)
57+
58+
physx = SceneCfg(clone_in_fabric=True)
59+
newton_mjwarp = SceneCfg(clone_in_fabric=False)
60+
ovphysx = physx
61+
default = physx
62+
63+
64+
@configclass
65+
class CommandsCfg:
66+
"""Handover goal command."""
67+
68+
object_pose = mdp.HandoverCommandCfg(asset_name="object", debug_vis=True)
69+
70+
71+
@configclass
72+
class ActionsCfg:
73+
"""Two-hand action terms, ordered right then left like the Direct adapter."""
74+
75+
right_hand = mdp.EMAJointPositionToLimitsActionCfg(
76+
asset_name="right_hand",
77+
joint_names=ACTUATED_JOINT_NAMES_PRESET,
78+
alpha=1.0,
79+
rescale_to_limits=True,
80+
)
81+
left_hand = mdp.EMAJointPositionToLimitsActionCfg(
82+
asset_name="left_hand",
83+
joint_names=ACTUATED_JOINT_NAMES_PRESET,
84+
alpha=1.0,
85+
rescale_to_limits=True,
86+
)
87+
88+
89+
def _hand_entity(name: str) -> SceneEntityCfg:
90+
return SceneEntityCfg(name, joint_names=".*")
91+
92+
93+
def _fingertip_entity(name: str) -> SceneEntityCfg:
94+
return SceneEntityCfg(name, body_names=FINGERTIP_BODY_NAMES)
95+
96+
97+
@configclass
98+
class ObservationsCfg:
99+
"""Single-agent observations matching the Direct MARL adapter."""
100+
101+
@configclass
102+
class PolicyCfg(ObsGroup):
103+
# Right agent: 133 hand dimensions followed by 24 object/goal dimensions.
104+
# soft limits equal the hard limits here: soft_joint_pos_limits_factor defaults to 1.0
105+
right_joint_pos = ObsTerm(func=mdp.joint_pos_limit_normalized, params={"asset_cfg": _hand_entity("right_hand")})
106+
right_joint_vel = ObsTerm(func=mdp.joint_vel, scale=0.2, params={"asset_cfg": _hand_entity("right_hand")})
107+
right_fingertip_pos = ObsTerm(func=mdp.fingertip_pos, params={"asset_cfg": _fingertip_entity("right_hand")})
108+
right_fingertip_quat = ObsTerm(func=mdp.fingertip_quat, params={"asset_cfg": _fingertip_entity("right_hand")})
109+
right_fingertip_vel = ObsTerm(func=mdp.fingertip_vel, params={"asset_cfg": _fingertip_entity("right_hand")})
110+
right_action = ObsTerm(func=mdp.hand_action, params={"action_name": "right_hand"})
111+
right_object_goal = ObsTerm(
112+
func=mdp.object_goal,
113+
params={"command_name": "object_pose", "object_cfg": SceneEntityCfg("object"), "vel_obs_scale": 0.2},
114+
)
115+
116+
# Left agent: the same 157-dimensional layout.
117+
# soft limits equal the hard limits here: soft_joint_pos_limits_factor defaults to 1.0
118+
left_joint_pos = ObsTerm(func=mdp.joint_pos_limit_normalized, params={"asset_cfg": _hand_entity("left_hand")})
119+
left_joint_vel = ObsTerm(func=mdp.joint_vel, scale=0.2, params={"asset_cfg": _hand_entity("left_hand")})
120+
left_fingertip_pos = ObsTerm(func=mdp.fingertip_pos, params={"asset_cfg": _fingertip_entity("left_hand")})
121+
left_fingertip_quat = ObsTerm(func=mdp.fingertip_quat, params={"asset_cfg": _fingertip_entity("left_hand")})
122+
left_fingertip_vel = ObsTerm(func=mdp.fingertip_vel, params={"asset_cfg": _fingertip_entity("left_hand")})
123+
left_action = ObsTerm(func=mdp.hand_action, params={"action_name": "left_hand"})
124+
left_object_goal = ObsTerm(
125+
func=mdp.object_goal,
126+
params={"command_name": "object_pose", "object_cfg": SceneEntityCfg("object"), "vel_obs_scale": 0.2},
127+
)
128+
129+
def __post_init__(self):
130+
self.enable_corruption = False
131+
self.concatenate_terms = True
132+
133+
policy: PolicyCfg = PolicyCfg()
134+
135+
136+
@configclass
137+
class EventCfg:
138+
"""Reset distributions matching the Direct handover environment."""
139+
140+
reset_handover = EventTerm(
141+
func=mdp.reset_handover_state,
142+
mode="reset",
143+
params={
144+
"position_noise": 0.01,
145+
"joint_position_noise": 0.2,
146+
"joint_velocity_noise": 0.0,
147+
"action_names": ("right_hand", "left_hand"),
148+
},
149+
)
150+
151+
152+
@configclass
153+
class RewardsCfg:
154+
"""Summed two-agent reward exposed by the Direct single-agent adapter."""
155+
156+
handover = RewTerm(
157+
func=mdp.HandoverReward,
158+
weight=1.0,
159+
params={
160+
"command_name": "object_pose",
161+
"distance_scale": 20.0,
162+
"success_distance_threshold": 0.1,
163+
"object_cfg": SceneEntityCfg("object"),
164+
},
165+
)
166+
167+
168+
@configclass
169+
class TerminationsCfg:
170+
"""Termination conditions matching the Direct single-agent adapter."""
171+
172+
object_out_of_reach = DoneTerm(
173+
func=mdp.root_height_below_minimum,
174+
params={"minimum_height": 0.24, "asset_cfg": SceneEntityCfg("object")},
175+
)
176+
time_out = DoneTerm(func=mdp.time_out, time_out=True)
177+
178+
179+
@configclass
180+
class HandoverManagerEnvCfg(HandoverTaskCfgBase, ManagerBasedRLEnvCfg):
181+
"""Manager-based handover environment matching the Direct RSL-RL view."""
182+
183+
scene: HandoverManagerSceneCfg = HandoverManagerSceneCfg()
184+
observations: ObservationsCfg = ObservationsCfg()
185+
actions: ActionsCfg = ActionsCfg()
186+
commands: CommandsCfg = CommandsCfg()
187+
rewards: RewardsCfg = RewardsCfg()
188+
terminations: TerminationsCfg = TerminationsCfg()
189+
events: EventCfg = EventCfg()
190+
191+
def __post_init__(self):
192+
self.decimation = 2
193+
self.episode_length_s = 7.5
194+
self.sim.render_interval = self.decimation
195+
self.viewer.eye = (2.0, 2.0, 2.0)

source/isaaclab_tasks/isaaclab_tasks/core/handover/mdp/__init__.pyi

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,27 @@
44
# SPDX-License-Identifier: BSD-3-Clause
55

66
__all__ = [
7+
"HandoverCommand",
8+
"HandoverCommandCfg",
9+
"reset_handover_state",
10+
"fingertip_pos",
11+
"fingertip_quat",
12+
"fingertip_vel",
13+
"hand_action",
14+
"object_goal",
15+
"HandoverReward",
716
"handover_reward",
817
"evaluate_handover_success",
918
]
1019

11-
from .rewards import evaluate_handover_success, handover_reward
20+
from .commands import HandoverCommand, HandoverCommandCfg
21+
from .events import reset_handover_state
22+
from .observations import (
23+
fingertip_pos,
24+
fingertip_quat,
25+
fingertip_vel,
26+
hand_action,
27+
object_goal,
28+
)
29+
from .rewards import HandoverReward, evaluate_handover_success, handover_reward
1230
from isaaclab.envs.mdp import *
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
# All rights reserved.
3+
#
4+
# SPDX-License-Identifier: BSD-3-Clause
5+
6+
"""Goal-pose command for the manager-based handover task."""
7+
8+
from __future__ import annotations
9+
10+
from collections.abc import Sequence
11+
from dataclasses import MISSING
12+
from typing import TYPE_CHECKING
13+
14+
import torch
15+
16+
import isaaclab.utils.math as math_utils
17+
from isaaclab.managers import CommandTerm, CommandTermCfg
18+
from isaaclab.markers import VisualizationMarkers, VisualizationMarkersCfg
19+
from isaaclab.utils.configclass import configclass
20+
21+
from isaaclab_tasks.core.handover.handover_task_base import GOAL_MARKER_CFG, GOAL_POSITION_OFFSET
22+
23+
if TYPE_CHECKING:
24+
from isaaclab.assets import RigidObject
25+
from isaaclab.envs import ManagerBasedRLEnv
26+
27+
28+
class HandoverCommand(CommandTerm):
29+
"""Sample the fixed-position, random-orientation handover goal pose."""
30+
31+
cfg: HandoverCommandCfg
32+
33+
def __init__(self, cfg: HandoverCommandCfg, env: ManagerBasedRLEnv):
34+
super().__init__(cfg, env)
35+
self._object: RigidObject = env.scene[cfg.asset_name]
36+
offset = torch.tensor(cfg.position_offset, dtype=torch.float, device=self.device)
37+
self.pos_command_e = self._object.data.default_root_pose.torch[:, :3] + offset
38+
self.quat_command_w = torch.zeros(self.num_envs, 4, device=self.device)
39+
self.quat_command_w[:, 3] = 1.0 # identity quaternion in (x, y, z, w) layout
40+
# persistent (num_envs, 7) pose command: the position half is static and written once
41+
# here, the quaternion half is refreshed by _resample_command; `command` returns this
42+
# buffer directly instead of allocating a torch.cat every call
43+
self._command_buf = torch.cat((self.pos_command_e, self.quat_command_w), dim=-1)
44+
self._x_unit = torch.tensor([1.0, 0.0, 0.0], device=self.device).repeat(self.num_envs, 1)
45+
self._y_unit = torch.tensor([0.0, 1.0, 0.0], device=self.device).repeat(self.num_envs, 1)
46+
47+
@property
48+
def command(self) -> torch.Tensor:
49+
"""Goal pose in the environment frame [m, unit quaternion].
50+
51+
The returned tensor is a persistent buffer refreshed in place; consumers that
52+
store it across steps must copy it.
53+
"""
54+
return self._command_buf
55+
56+
def _update_metrics(self) -> None:
57+
pass
58+
59+
def _resample_command(self, env_ids: Sequence[int]) -> None:
60+
random_values = 2.0 * torch.rand((len(env_ids), 2), device=self.device) - 1.0
61+
self.quat_command_w[env_ids] = math_utils.quat_mul(
62+
math_utils.quat_from_angle_axis(random_values[:, 0] * torch.pi, self._x_unit[env_ids]),
63+
math_utils.quat_from_angle_axis(random_values[:, 1] * torch.pi, self._y_unit[env_ids]),
64+
)
65+
# keep the persistent pose-command buffer current (position half is static)
66+
self._command_buf[env_ids, 3:] = self.quat_command_w[env_ids]
67+
68+
def _update_command(self) -> None:
69+
pass
70+
71+
def _set_debug_vis_impl(self, debug_vis: bool) -> None:
72+
if debug_vis:
73+
if not hasattr(self, "_goal_visualizer"):
74+
self._goal_visualizer = VisualizationMarkers(self.cfg.goal_visualizer_cfg)
75+
self._goal_visualizer.set_visibility(True)
76+
elif hasattr(self, "_goal_visualizer"):
77+
self._goal_visualizer.set_visibility(False)
78+
79+
def _debug_vis_callback(self, event) -> None:
80+
del event
81+
self._goal_visualizer.visualize(
82+
translations=self.pos_command_e + self._env.scene.env_origins,
83+
orientations=self.quat_command_w,
84+
)
85+
86+
87+
@configclass
88+
class HandoverCommandCfg(CommandTermCfg):
89+
"""Configuration for :class:`HandoverCommand`."""
90+
91+
class_type: type[HandoverCommand] = HandoverCommand
92+
resampling_time_range: tuple[float, float] = (1.0e6, 1.0e6)
93+
asset_name: str = MISSING
94+
position_offset: tuple[float, float, float] = GOAL_POSITION_OFFSET
95+
"""Goal-position offset from the object's default position [m]."""
96+
goal_visualizer_cfg: VisualizationMarkersCfg = GOAL_MARKER_CFG.replace(prim_path="/Visuals/Command/goal_marker")

0 commit comments

Comments
 (0)