Fix XR teleop: body-frame IK control for mobile chassis

Switch arm IK from world-frame to body-frame control so that
pushing the XR controller forward always moves the arm in the
robot's forward direction, regardless of chassis rotation.

Key changes:
- dual_arm_agent: convert EEF observations to body frame before
  passing to XR controller; send body-frame IK targets directly
  (removed convert_action_world_to_root)
- xr_controller: XR deltas treated as body-frame deltas (no yaw
  rotation needed — VR view tracks robot heading naturally)
- streaming: add debug frame save for stereo alignment diagnostics
- mindrobot_2i_cfg: IdealPDActuator for trunk, disabled gravity
- Author headers updated to Yutang Li, SIAT

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 11:32:28 +08:00
parent 05a146bca6
commit bfe28b188a
12 changed files with 82 additions and 882 deletions

View File

@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2022-2025, The Isaac Lab Project Developers.
# Copyright (c) 2025, Yutang Li, SIAT (yt.li2@siat.ac.cn)
# SPDX-License-Identifier: BSD-3-Clause
"""XR teleoperation controller with absolute IK target accumulation and grip clutch."""
@@ -50,6 +50,7 @@ class XrTeleopController:
self.grip_engage_threshold = 0.8
self.grip_release_threshold = 0.2
self.callbacks = {}
self._root_yaw_rad = 0.0 # current chassis heading, updated each frame
def add_callback(self, name: str, func: Callable):
self.callbacks[name] = func
@@ -63,6 +64,10 @@ class XrTeleopController:
self.target_eef_quat = None
self.require_grip_reengage = True
def set_root_yaw(self, yaw_rad: float):
"""Update the chassis heading so XR deltas are in body frame."""
self._root_yaw_rad = yaw_rad
def close(self):
if self._owns_client:
self.xr_client.close()
@@ -143,6 +148,16 @@ class XrTeleopController:
prev_xr_world_pos, prev_xr_world_quat_xyzw = transform_xr_pose(self.prev_xr_pos, self.prev_xr_quat)
world_delta_pos = (xr_world_pos - prev_xr_world_pos) * self.pos_sensitivity
# In VR teleop, user "inhabits" the robot: their physical forward (XR +X)
# maps directly to robot forward (body +X). No rotation needed — the VR view
# already rotates with the robot, so XR deltas naturally align with body frame.
# Diagnostic
if self.frame_count % 30 == 0 and self.target_eef_pos is not None:
print(f"[XR {self.arm}] delta=({world_delta_pos[0]:+.4f},{world_delta_pos[1]:+.4f},{world_delta_pos[2]:+.4f}) "
f"target=({self.target_eef_pos[0]:.3f},{self.target_eef_pos[1]:.3f},{self.target_eef_pos[2]:.3f})")
pos_norm = np.linalg.norm(world_delta_pos)
if pos_norm > self.max_pos_per_step:
world_delta_pos *= self.max_pos_per_step / pos_norm