Skip to content

Commit fba97cd

Browse files
committed
Add Newton backend support for Shadow-Hand-Over (MAPPO)
Port ``Isaac-Shadow-Hand-Over-Direct-v0`` to dual PhysX/Newton backend, mirroring the single-agent Shadow Hand Newton port pattern. Selectable via ``--preset newton`` / Hydra preset resolution; PhysX behavior unchanged. Newton-specific wiring in ``_newton_shadow_hand_cfg``: * Loads ``shadow_hand_instanceable_newton.usd`` (USD that explicitly declares ``ImplicitActuator`` prims). * Re-applies the ``rot`` argument via spawn because Newton's ``import_usd.py`` discards the root body's native USD orientation when it bakes the rotation into ``joint_X_p`` for the root fixed joint. PhysX/USD is unaffected. * Sets ``drive_type="force"``, ``activate_contact_sensors=False``, ``disable_gravity=True`` to match the single-agent Newton config. Two ``ImplicitActuatorCfg``s on the hand: * ``fingers`` covers ``WR.*``, ``J(3|2|1)``, ``LFJ4/THJ4``, ``THJ0`` with ``stiffness=20.0`` / ``damping=2.0``. PhysX uses 1.0 / 0.1 on the same actuator but layers ``fixed_tendons_props(limit_stiffness=30, damping=0.1)`` and runs ``solver_position_iteration_count=8`` per substep — both amplify the effective torque per unit nominal gain. Newton's MJWarp implicit-PD path has neither, so the same nominal stiffness produces ~20× weaker authority. With the bump, MAPPO mean reward at iter 200 / 2048 envs goes from ~27 (no catch learned) to ~777, comparable to the PhysX baseline of ~247. * ``distal_passive`` covers the four ``robot0_(FF|MF|RF|LF)J0`` joints with ``stiffness=10.0`` / ``damping=0.1``. The Newton USD bakes ``stiffness=286 / damping=57`` on these joints from the MJCF→USD translation, which fights the ``MjcTendon`` coupling and bounces the ball. ``stiffness=10`` (1/3 of PhysX ``limit_stiffness=30``) keeps the joints near-passive while the tendon constraint dominates. Bumps ``isaaclab_tasks`` 1.5.33 → 1.5.34 with one CHANGELOG entry covering both the port (``Added``) and the gain calibration fix (``Fixed``).
1 parent 412c255 commit fba97cd

4 files changed

Lines changed: 274 additions & 43 deletions

File tree

source/isaaclab_tasks/config/extension.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
# Note: Semantic Versioning is used: https://semver.org/
4-
version = "1.5.33"
4+
version = "1.5.34"
55

66
# Description
77
title = "Isaac Lab Environments"

source/isaaclab_tasks/docs/CHANGELOG.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,40 @@
11
Changelog
22
---------
33

4+
1.5.34 (2026-05-01)
5+
~~~~~~~~~~~~~~~~~~~
6+
7+
Added
8+
^^^^^
9+
10+
* Added Newton backend support for the multi-agent
11+
``Isaac-Shadow-Hand-Over-Direct-v0`` (MAPPO/IPPO) env. Mirrors the
12+
single-agent Shadow Hand Newton port: per-hand
13+
:class:`ImplicitActuatorCfg`, ``shadow_hand_instanceable_newton.usd``,
14+
per-backend :class:`~isaaclab_tasks.utils.PresetCfg` wrappers for sim
15+
physics, scene cloning (``clone_in_fabric=False`` on Newton), the
16+
hand-over object (``RigidObjectCfg`` on both backends, dropping
17+
PhysX-only knobs on Newton), and the two robot configs. Selectable via
18+
``--preset newton`` / Hydra preset resolution; PhysX behavior unchanged.
19+
20+
Fixed
21+
^^^^^
22+
23+
* Fixed Newton training failing to learn the catch in
24+
``Isaac-Shadow-Hand-Over-Direct-v0`` MAPPO. The Newton-side
25+
:class:`~isaaclab.actuators.ImplicitActuatorCfg` for the Shadow Hand
26+
fingers now uses ``stiffness=20.0`` / ``damping=2.0`` (was
27+
``stiffness=1.0`` / ``damping=0.1`` mirrored from the PhysX cfg). PhysX
28+
layers an additional ``fixed_tendons_props(limit_stiffness=30, damping=0.1)``
29+
on top of the implicit drive and runs ``solver_position_iteration_count=8``
30+
per substep, both of which amplify the effective torque per unit
31+
nominal gain; Newton's MJWarp implicit-PD path has neither, so the
32+
same nominal stiffness produces a much weaker control authority. With
33+
the bump, MAPPO mean reward at iter 200 / 2048 envs goes from ~27 (no
34+
catch learned) to ~777, comparable to or above the PhysX baseline of
35+
~247. PhysX path is unchanged.
36+
37+
438
1.5.33 (2026-04-30)
539
~~~~~~~~~~~~~~~~~~~
640

source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand_over/shadow_hand_over_env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def __init__(self, cfg: ShadowHandOverEnvCfg, render_mode: str | None = None, **
6464
self.num_fingertips = len(self.finger_bodies)
6565

6666
# joint limits
67-
joint_pos_limits = wp.to_torch(self.right_hand.root_view.get_dof_limits()).to(self.device)
67+
joint_pos_limits = wp.to_torch(self.right_hand.data.joint_limits).to(self.device)
6868
self.hand_dof_lower_limits = joint_pos_limits[..., 0]
6969
self.hand_dof_upper_limits = joint_pos_limits[..., 1]
7070

source/isaaclab_tasks/isaaclab_tasks/direct/shadow_hand_over/shadow_hand_over_env_cfg.py

Lines changed: 238 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
#
44
# SPDX-License-Identifier: BSD-3-Clause
55

6+
from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg
67
from isaaclab_physx.physics import PhysxCfg
78

89
import isaaclab.envs.mdp as mdp
910
import isaaclab.sim as sim_utils
11+
from isaaclab.actuators import ImplicitActuatorCfg
1012
from isaaclab.assets import ArticulationCfg, RigidObjectCfg
1113
from isaaclab.envs import DirectMARLEnvCfg
1214
from isaaclab.managers import EventTermCfg as EventTerm
@@ -16,13 +18,22 @@
1618
from isaaclab.sim import SimulationCfg
1719
from isaaclab.sim.spawners.materials.physics_materials_cfg import RigidBodyMaterialCfg
1820
from isaaclab.utils import configclass
21+
from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
22+
23+
from isaaclab_tasks.utils import PresetCfg
1924

2025
from isaaclab_assets.robots.shadow_hand import SHADOW_HAND_CFG
2126

2227

2328
@configclass
2429
class EventCfg:
25-
"""Configuration for randomization."""
30+
"""Configuration for randomization (PhysX path).
31+
32+
Note: this config is currently not wired into ``ShadowHandOverEnvCfg.events`` -
33+
it is kept as a reference for future event-randomization work. The event
34+
terms here use PhysX-only APIs (rigid-body materials, fixed tendons), so
35+
they would need a Newton variant before being enabled in the env.
36+
"""
2637

2738
# -- robot
2839
robot_physics_material = EventTerm(
@@ -113,6 +124,226 @@ class EventCfg:
113124
)
114125

115126

127+
# Newton requires a USD that explicitly declares ImplicitActuators. The PhysX
128+
# Shadow Hand USD relies on tendons that Newton cannot import, so the Newton
129+
# variant uses a separate USD file shipped alongside the standard one.
130+
_SHADOW_HAND_NEWTON_USD = f"{ISAAC_NUCLEUS_DIR}/Robots/ShadowRobot/ShadowHand/shadow_hand_instanceable_newton.usd"
131+
132+
133+
def _newton_shadow_hand_cfg(
134+
prim_path: str, init_pos: tuple[float, float, float], init_rot: tuple[float, float, float, float]
135+
) -> ArticulationCfg:
136+
"""Newton variant of the Shadow Hand articulation cfg.
137+
138+
Mirrors the single-agent shadow_hand_env_cfg Newton port: explicit
139+
:class:`ImplicitActuatorCfg` per finger, USD that pre-declares actuators,
140+
and a deliberate ``rot`` re-application via spawn (Newton's
141+
``import_usd.py`` discards the root body's native USD orientation).
142+
"""
143+
return ArticulationCfg(
144+
prim_path=prim_path,
145+
spawn=sim_utils.UsdFileCfg(
146+
usd_path=_SHADOW_HAND_NEWTON_USD,
147+
activate_contact_sensors=False,
148+
rigid_props=sim_utils.RigidBodyPropertiesCfg(
149+
disable_gravity=True,
150+
retain_accelerations=True,
151+
max_depenetration_velocity=1000.0,
152+
),
153+
articulation_props=sim_utils.ArticulationRootPropertiesCfg(enabled_self_collisions=True),
154+
joint_drive_props=sim_utils.JointDrivePropertiesCfg(drive_type="force"),
155+
),
156+
init_state=ArticulationCfg.InitialStateCfg(
157+
pos=init_pos,
158+
# WARNING(Octi): Newton's import_usd.py bakes the USD body xformOp rotation into
159+
# joint_X_p for the root fixed joint, which cancels with the matching localPose1
160+
# rotation in joint_X_c during FK (joint_X_p * inv(joint_X_c) ≈ identity). This
161+
# discards the root body's native USD orientation, so we must re-apply it here as a
162+
# spawn rotation. PhysX or USD does not have this issue. Remove once Newton fixes
163+
# root joint transform handling in import_usd.py.
164+
rot=init_rot,
165+
joint_pos={".*": 0.0},
166+
),
167+
actuators={
168+
"fingers": ImplicitActuatorCfg(
169+
joint_names_expr=["robot0_WR.*", "robot0_(FF|MF|RF|LF|TH)J(3|2|1)", "robot0_(LF|TH)J4", "robot0_THJ0"],
170+
effort_limit_sim={
171+
"robot0_WRJ1": 4.785,
172+
"robot0_WRJ0": 2.175,
173+
"robot0_(FF|MF|RF|LF)J1": 0.7245,
174+
"robot0_FFJ(3|2)": 0.9,
175+
"robot0_MFJ(3|2)": 0.9,
176+
"robot0_RFJ(3|2)": 0.9,
177+
"robot0_LFJ(4|3|2)": 0.9,
178+
"robot0_THJ4": 2.3722,
179+
"robot0_THJ3": 1.45,
180+
"robot0_THJ(2|1)": 0.99,
181+
"robot0_THJ0": 0.81,
182+
},
183+
# PhysX uses stiffness=1.0 / damping=0.1 here, but layers an
184+
# additional fixed_tendons_props(limit_stiffness=30, damping=0.1)
185+
# contribution and runs solver_position_iteration_count=8 per
186+
# substep — both amplify the effective torque per unit nominal
187+
# gain. Newton's MJWarp implicit-PD path has neither: the
188+
# MjcTendon constraint contributes coupling but no extra
189+
# actuator stiffness, and the solver does not multi-iterate
190+
# corrections. Empirically Newton needs gains ~20× larger to
191+
# produce comparable joint authority; with stiffness=1.0 the
192+
# MAPPO mean reward at iter 200 is ~27 (vs PhysX's ~247),
193+
# and the policy never learns the catch. With stiffness=20.0 /
194+
# damping=2.0 it reaches ~777 mean reward and learns reliably.
195+
stiffness=20.0,
196+
damping=2.0,
197+
friction=1e-2,
198+
armature=2e-3,
199+
),
200+
# J0 distal joints are passive in PhysX (fixed tendon, limit_stiffness=30,
201+
# damping=0.1). The Newton USD bakes stiffness=286/damping=57 from the
202+
# MJCF translation, which fights the MjcTendon coupling and bounces the
203+
# ball. Override with stiffness=10 (1/3 of PhysX limit_stiffness=30) so
204+
# the tendon constraint dominates without going fully limp.
205+
"distal_passive": ImplicitActuatorCfg(
206+
joint_names_expr=["robot0_(FF|MF|RF|LF)J0"],
207+
stiffness=10.0,
208+
damping=0.1,
209+
friction=1e-2,
210+
armature=2e-3,
211+
),
212+
},
213+
soft_joint_pos_limit_factor=1.0,
214+
)
215+
216+
217+
@configclass
218+
class RightRobotCfg(PresetCfg):
219+
physx = SHADOW_HAND_CFG.replace(prim_path="/World/envs/env_.*/RightRobot").replace(
220+
init_state=ArticulationCfg.InitialStateCfg(
221+
pos=(0.0, 0.0, 0.5),
222+
rot=(0.0, 0.0, 0.0, 1.0),
223+
joint_pos={".*": 0.0},
224+
)
225+
)
226+
newton = _newton_shadow_hand_cfg(
227+
prim_path="/World/envs/env_.*/RightRobot",
228+
init_pos=(0.0, 0.0, 0.5),
229+
init_rot=(0.0, 0.0, 0.0, 1.0),
230+
)
231+
default = physx
232+
233+
234+
@configclass
235+
class LeftRobotCfg(PresetCfg):
236+
physx = SHADOW_HAND_CFG.replace(prim_path="/World/envs/env_.*/LeftRobot").replace(
237+
init_state=ArticulationCfg.InitialStateCfg(
238+
pos=(0.0, -1.0, 0.5),
239+
rot=(0.0, 0.0, 1.0, 0.0),
240+
joint_pos={".*": 0.0},
241+
)
242+
)
243+
newton = _newton_shadow_hand_cfg(
244+
prim_path="/World/envs/env_.*/LeftRobot",
245+
init_pos=(0.0, -1.0, 0.5),
246+
init_rot=(0.0, 0.0, 1.0, 0.0),
247+
)
248+
default = physx
249+
250+
251+
@configclass
252+
class ObjectCfg(PresetCfg):
253+
"""Hand-over object preset.
254+
255+
Both backends spawn the same procedural sphere as a free rigid body:
256+
Newton's :class:`~isaaclab_newton.assets.RigidObject` resolves the
257+
asset via the ``UsdPhysics.RigidBodyAPI`` that
258+
:class:`~isaaclab.sim.RigidBodyPropertiesCfg` applies. The Newton
259+
variant drops PhysX-only knobs (per-shape solver iterations, sleep
260+
thresholds, max depenetration velocity, custom physics material).
261+
"""
262+
263+
physx = RigidObjectCfg(
264+
prim_path="/World/envs/env_.*/object",
265+
spawn=sim_utils.SphereCfg(
266+
radius=0.0335,
267+
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.8, 1.0, 0.0)),
268+
physics_material=sim_utils.RigidBodyMaterialCfg(static_friction=0.7),
269+
rigid_props=sim_utils.RigidBodyPropertiesCfg(
270+
kinematic_enabled=False,
271+
disable_gravity=False,
272+
enable_gyroscopic_forces=True,
273+
solver_position_iteration_count=8,
274+
solver_velocity_iteration_count=0,
275+
sleep_threshold=0.005,
276+
stabilization_threshold=0.0025,
277+
max_depenetration_velocity=1000.0,
278+
),
279+
collision_props=sim_utils.CollisionPropertiesCfg(),
280+
mass_props=sim_utils.MassPropertiesCfg(density=500.0),
281+
),
282+
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.39, 0.54), rot=(0.0, 0.0, 0.0, 1.0)),
283+
)
284+
newton = RigidObjectCfg(
285+
prim_path="/World/envs/env_.*/object",
286+
spawn=sim_utils.SphereCfg(
287+
radius=0.0335,
288+
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.8, 1.0, 0.0)),
289+
rigid_props=sim_utils.RigidBodyPropertiesCfg(
290+
kinematic_enabled=False,
291+
disable_gravity=False,
292+
enable_gyroscopic_forces=True,
293+
),
294+
collision_props=sim_utils.CollisionPropertiesCfg(),
295+
mass_props=sim_utils.MassPropertiesCfg(density=500.0),
296+
),
297+
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.39, 0.54), rot=(0.0, 0.0, 0.0, 1.0)),
298+
)
299+
default = physx
300+
301+
302+
@configclass
303+
class ShadowHandOverSceneCfg(PresetCfg):
304+
"""Scene preset.
305+
306+
PhysX supports ``clone_in_fabric=True`` for faster cloning. Newton's
307+
cloning path does not, so the Newton variant disables Fabric cloning.
308+
"""
309+
310+
physx: InteractiveSceneCfg = InteractiveSceneCfg(
311+
num_envs=2048, env_spacing=1.5, replicate_physics=True, clone_in_fabric=True
312+
)
313+
newton: InteractiveSceneCfg = InteractiveSceneCfg(
314+
num_envs=2048, env_spacing=1.5, replicate_physics=True, clone_in_fabric=False
315+
)
316+
default: InteractiveSceneCfg = physx
317+
318+
319+
@configclass
320+
class PhysicsCfg(PresetCfg):
321+
"""Physics-backend preset (PhysX vs Newton/MJWarp).
322+
323+
Newton settings mirror the single-agent ShadowHand Newton port: elliptic
324+
cone, ``impratio=10`` (favors normal contacts over friction), 100 solver
325+
iterations, 2 substeps. Empirically converges on the single-agent ShadowHand
326+
tasks; tuning may be needed for handover-specific contact dynamics.
327+
"""
328+
329+
physx = PhysxCfg(bounce_threshold_velocity=0.2)
330+
newton = NewtonCfg(
331+
solver_cfg=MJWarpSolverCfg(
332+
solver="newton",
333+
integrator="implicitfast",
334+
njmax=200,
335+
nconmax=70,
336+
impratio=10.0,
337+
cone="elliptic",
338+
update_data_interval=2,
339+
iterations=100,
340+
),
341+
num_substeps=2,
342+
debug_mode=False,
343+
)
344+
default = physx
345+
346+
116347
@configclass
117348
class ShadowHandOverEnvCfg(DirectMARLEnvCfg):
118349
# env
@@ -131,25 +362,11 @@ class ShadowHandOverEnvCfg(DirectMARLEnvCfg):
131362
static_friction=1.0,
132363
dynamic_friction=1.0,
133364
),
134-
physics=PhysxCfg(
135-
bounce_threshold_velocity=0.2,
136-
),
365+
physics=PhysicsCfg(),
137366
)
138367
# robot
139-
right_robot_cfg: ArticulationCfg = SHADOW_HAND_CFG.replace(prim_path="/World/envs/env_.*/RightRobot").replace(
140-
init_state=ArticulationCfg.InitialStateCfg(
141-
pos=(0.0, 0.0, 0.5),
142-
rot=(0.0, 0.0, 0.0, 1.0),
143-
joint_pos={".*": 0.0},
144-
)
145-
)
146-
left_robot_cfg: ArticulationCfg = SHADOW_HAND_CFG.replace(prim_path="/World/envs/env_.*/LeftRobot").replace(
147-
init_state=ArticulationCfg.InitialStateCfg(
148-
pos=(0.0, -1.0, 0.5),
149-
rot=(0.0, 0.0, 1.0, 0.0),
150-
joint_pos={".*": 0.0},
151-
)
152-
)
368+
right_robot_cfg: RightRobotCfg = RightRobotCfg()
369+
left_robot_cfg: LeftRobotCfg = LeftRobotCfg()
153370
actuated_joint_names = [
154371
"robot0_WRJ1",
155372
"robot0_WRJ0",
@@ -181,27 +398,7 @@ class ShadowHandOverEnvCfg(DirectMARLEnvCfg):
181398
]
182399

183400
# in-hand object
184-
object_cfg: RigidObjectCfg = RigidObjectCfg(
185-
prim_path="/World/envs/env_.*/object",
186-
spawn=sim_utils.SphereCfg(
187-
radius=0.0335,
188-
visual_material=sim_utils.PreviewSurfaceCfg(diffuse_color=(0.8, 1.0, 0.0)),
189-
physics_material=sim_utils.RigidBodyMaterialCfg(static_friction=0.7),
190-
rigid_props=sim_utils.RigidBodyPropertiesCfg(
191-
kinematic_enabled=False,
192-
disable_gravity=False,
193-
enable_gyroscopic_forces=True,
194-
solver_position_iteration_count=8,
195-
solver_velocity_iteration_count=0,
196-
sleep_threshold=0.005,
197-
stabilization_threshold=0.0025,
198-
max_depenetration_velocity=1000.0,
199-
),
200-
collision_props=sim_utils.CollisionPropertiesCfg(),
201-
mass_props=sim_utils.MassPropertiesCfg(density=500.0),
202-
),
203-
init_state=RigidObjectCfg.InitialStateCfg(pos=(0.0, -0.39, 0.54), rot=(0.0, 0.0, 0.0, 1.0)),
204-
)
401+
object_cfg: ObjectCfg = ObjectCfg()
205402
# goal object
206403
goal_object_cfg: VisualizationMarkersCfg = VisualizationMarkersCfg(
207404
prim_path="/Visuals/goal_marker",
@@ -212,8 +409,8 @@ class ShadowHandOverEnvCfg(DirectMARLEnvCfg):
212409
),
213410
},
214411
)
215-
# scene
216-
scene: InteractiveSceneCfg = InteractiveSceneCfg(num_envs=2048, env_spacing=1.5, replicate_physics=True)
412+
# scene - use ShadowHandOverSceneCfg so that --preset newton disables clone_in_fabric automatically
413+
scene: ShadowHandOverSceneCfg = ShadowHandOverSceneCfg()
217414

218415
# reset
219416
reset_position_noise = 0.01 # range of position at reset

0 commit comments

Comments
 (0)