Skip to content

Commit a9f2ac7

Browse files
committed
Stabilise the hands demo on MJWarp and command its tendons
The Shadow Hand's tendon-coupled fingers diverge past their joint limits under the default explicit integrator, which corrupted memory and surfaced as a SIGSEGV in an unrelated collision kernel. Measured over 260 steps against a 1.571 rad limit: euler reaches 3.275 with 15 joints out of range, implicitfast 0.944 with none, matching develop's 1.041. The demo also never commanded the tendons, so the eight joints they span took no command at all, and posed the hand with the reorientation task's spawn pose, which left the palm facing down beside the Allegro. Resolve the MuJoCo actuator rows of an articulation spawned outside a cloned scene, where the importer leaves actuator_world at -1 and partitioning by world selected nothing. Without it the tendon control never binds and commanding a tendon reports the asset as authoring no tendon actuator.
1 parent cf2516f commit a9f2ac7

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

scripts/demos/hands.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
from isaaclab_assets.robots.shadow_hand import (
5555
SHADOW_HAND_NEWTON_CFG,
5656
SHADOW_HAND_PHYSX_CFG,
57+
TENDON_POSITION_LIMITS,
5758
)
5859

5960
if TYPE_CHECKING:
@@ -98,7 +99,14 @@ def design_scene() -> tuple[dict, list[list[float]]]:
9899
sim_utils.create_prim("/World/Origin2", "Xform", translation=origins[1])
99100
# -- Robot
100101
shadow_hand_cfg = SHADOW_HAND_NEWTON_CFG if args_cli.physics == "newton_mjwarp" else SHADOW_HAND_PHYSX_CFG
101-
shadow_hand_cfg = shadow_hand_cfg.replace(prim_path="/World/Origin2/Robot")
102+
# Pose for this side-by-side scene; the asset's own pose is the reorientation task's.
103+
shadow_hand_cfg = shadow_hand_cfg.replace(
104+
prim_path="/World/Origin2/Robot",
105+
init_state=shadow_hand_cfg.init_state.replace(
106+
pos=(0.0, 0.2, 0.5),
107+
rot=(0.52296271, -0.47593067, 0.47593067, 0.52296271),
108+
),
109+
)
102110
shadow_hand = shadow_hand_cfg.class_type(shadow_hand_cfg)
103111

104112
# return the scene information
@@ -151,6 +159,15 @@ def run_simulator(sim: "sim_utils.SimulationContext", entities: dict[str, "Artic
151159
joint_pos_target = robot.data.soft_joint_pos_limits.torch[..., grasp_mode]
152160
# apply action to the robot
153161
robot.set_joint_position_target_index(target=joint_pos_target)
162+
# A tendon has no actuator on its spanned joints, so it needs its own command.
163+
# Span comes from the asset: a fixed tendon authors no position limit of its own.
164+
if robot.num_fixed_tendons > 0:
165+
tendon_pos_target = torch.full(
166+
(robot.num_instances, robot.num_fixed_tendons),
167+
TENDON_POSITION_LIMITS[grasp_mode],
168+
device=robot.device,
169+
)
170+
robot.set_fixed_tendon_position_target_index(target=tendon_pos_target)
154171
# write data to sim
155172
robot.write_data_to_sim()
156173
# perform step
@@ -168,6 +185,9 @@ def main():
168185
with launch_simulation(cfg=PhysicsCfg(), launcher_args=args_cli) as physics_cfg:
169186
# The default newton mjwarp solver configuration needs to be tuned for these hands.
170187
if isinstance(physics_cfg, NewtonCfg) and isinstance(physics_cfg.solver_cfg, MJWarpSolverCfg):
188+
# The tendon-coupled fingers diverge past their limits under the default explicit
189+
# integrator; the reorientation task's preset uses this one for the same reason.
190+
physics_cfg.solver_cfg.integrator = "implicitfast"
171191
physics_cfg.solver_cfg.njmax = 200
172192
physics_cfg.solver_cfg.nconmax = 70
173193
physics_cfg.solver_cfg.impratio = 10.0

source/isaaclab_newton/isaaclab_newton/physics/_mjwarp_view_compat.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,18 @@ def _actuator_rows_per_instance(view: ArticulationView, model: Model, tendon_cou
200200
tendon_worlds = _to_numpy(model.mujoco.tendon_world).astype(np.int64, copy=False)
201201
ordinal = _ordinal_within_world(view, tendon_count)
202202

203+
# An articulation spawned outside a cloned scene keeps ``actuator_world`` at the -1 the
204+
# importer writes, so partitioning by world would select nothing and silently report the
205+
# articulation as having no actuators. Every row belongs to this view's single world in that
206+
# case -- MEASURED on the hands demo: 20 actuators all at -1 against tendons at world 0.
207+
if actuator_worlds.size and int(actuator_worlds.max()) < 0:
208+
if view.count != 1:
209+
raise ValueError(
210+
f"MuJoCo actuator rows carry no world assignment, so the {view.count} articulations"
211+
" this view selects cannot be told apart."
212+
)
213+
return np.arange(actuator_worlds.size, dtype=np.int64).reshape(1, -1)
214+
203215
per_instance = []
204216
for world in range(view.world_count):
205217
rows = np.flatnonzero(actuator_worlds == world)

0 commit comments

Comments
 (0)