Browse Source
Fix body tracking joint indices (Meta→Godot enum) + IK elbow constraints
Fix body tracking joint indices (Meta→Godot enum) + IK elbow constraints
body_tracker.gd used Meta's raw XR_FB_body_tracking indices but Godot's XRBodyTracker.get_joint_transform() expects Godot's own enum values. The OpenXR vendors plugin remaps between them, so e.g. index 16 (Meta RIGHT_ARM_LOWER) was actually returning LEFT_FOOT data in Godot, explaining why right elbow tracking showed floor-level positions. Also switches retarget_bridge.py from Euler angle decomposition to IK solver (Pinocchio/CasADi) with elbow position constraints from body tracking l_lower/r_lower joints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>master
4 changed files with 354 additions and 130 deletions
-
87scripts/body_tracker.gd
-
6scripts/vr_ui_pointer.gd
-
143server/arm_ik_elbow.py
-
248server/retarget_bridge.py
@ -0,0 +1,143 @@ |
|||
"""Extended G1 arm IK solver with elbow position constraints. |
|||
|
|||
Subclasses G1_29_ArmIK to add an elbow position cost term that resolves |
|||
the elbow ambiguity inherent in wrist-only IK. Uses l_lower/r_lower |
|||
positions from OpenXR body tracking as elbow targets. |
|||
""" |
|||
|
|||
import numpy as np |
|||
import casadi |
|||
import pinocchio as pin |
|||
from pinocchio import casadi as cpin |
|||
|
|||
from robot_arm_ik import G1_29_ArmIK |
|||
|
|||
|
|||
class G1_29_ArmIK_Elbow(G1_29_ArmIK): |
|||
"""IK solver that constrains both wrist AND elbow positions. |
|||
|
|||
Cost function: |
|||
50 * wrist_position² + rotation² + elbow_weight * elbow_position² |
|||
+ 0.02 * regularization² + 0.1 * smoothing² |
|||
""" |
|||
|
|||
def __init__(self, elbow_weight=30.0, **kwargs): |
|||
super().__init__(**kwargs) |
|||
|
|||
# Find elbow frame IDs in the reduced model |
|||
self.L_elbow_id = self.reduced_robot.model.getFrameId("left_elbow_joint") |
|||
self.R_elbow_id = self.reduced_robot.model.getFrameId("right_elbow_joint") |
|||
|
|||
# Symbolic elbow target parameters |
|||
self.cElbow_l = casadi.SX.sym("elbow_l", 3, 1) |
|||
self.cElbow_r = casadi.SX.sym("elbow_r", 3, 1) |
|||
|
|||
# Elbow position error: FK(q) elbow pos - tracked elbow pos |
|||
self.elbow_error = casadi.Function( |
|||
"elbow_error", |
|||
[self.cq, self.cElbow_l, self.cElbow_r], |
|||
[casadi.vertcat( |
|||
self.cdata.oMf[self.L_elbow_id].translation - self.cElbow_l, |
|||
self.cdata.oMf[self.R_elbow_id].translation - self.cElbow_r, |
|||
)], |
|||
) |
|||
|
|||
# Rebuild the optimization problem with elbow cost |
|||
self.opti = casadi.Opti() |
|||
self.var_q = self.opti.variable(self.reduced_robot.model.nq) |
|||
self.var_q_last = self.opti.parameter(self.reduced_robot.model.nq) |
|||
self.param_tf_l = self.opti.parameter(4, 4) |
|||
self.param_tf_r = self.opti.parameter(4, 4) |
|||
self.param_elbow_l = self.opti.parameter(3, 1) |
|||
self.param_elbow_r = self.opti.parameter(3, 1) |
|||
|
|||
# Re-evaluate cost functions with new opti variables |
|||
self.translational_cost = casadi.sumsqr( |
|||
self.translational_error(self.var_q, self.param_tf_l, self.param_tf_r)) |
|||
self.rotation_cost = casadi.sumsqr( |
|||
self.rotational_error(self.var_q, self.param_tf_l, self.param_tf_r)) |
|||
self.regularization_cost = casadi.sumsqr(self.var_q) |
|||
self.smooth_cost = casadi.sumsqr(self.var_q - self.var_q_last) |
|||
self.elbow_cost = casadi.sumsqr( |
|||
self.elbow_error(self.var_q, self.param_elbow_l, self.param_elbow_r)) |
|||
|
|||
# Joint limits |
|||
self.opti.subject_to(self.opti.bounded( |
|||
self.reduced_robot.model.lowerPositionLimit, |
|||
self.var_q, |
|||
self.reduced_robot.model.upperPositionLimit) |
|||
) |
|||
|
|||
# Combined objective |
|||
self.opti.minimize( |
|||
50 * self.translational_cost + |
|||
self.rotation_cost + |
|||
0.02 * self.regularization_cost + |
|||
0.1 * self.smooth_cost + |
|||
elbow_weight * self.elbow_cost |
|||
) |
|||
|
|||
opts = { |
|||
'expand': True, |
|||
'detect_simple_bounds': True, |
|||
'calc_lam_p': False, |
|||
'print_time': False, |
|||
'ipopt.sb': 'yes', |
|||
'ipopt.print_level': 0, |
|||
'ipopt.max_iter': 30, |
|||
'ipopt.tol': 1e-4, |
|||
'ipopt.acceptable_tol': 5e-4, |
|||
'ipopt.acceptable_iter': 5, |
|||
'ipopt.warm_start_init_point': 'yes', |
|||
'ipopt.derivative_test': 'none', |
|||
'ipopt.jacobian_approximation': 'exact', |
|||
} |
|||
self.opti.solver("ipopt", opts) |
|||
|
|||
def solve_ik(self, left_wrist, right_wrist, |
|||
current_lr_arm_motor_q=None, current_lr_arm_motor_dq=None, |
|||
left_elbow_pos=None, right_elbow_pos=None): |
|||
"""Solve IK with wrist + elbow position targets. |
|||
|
|||
Args: |
|||
left_wrist: 4x4 SE(3) left wrist target |
|||
right_wrist: 4x4 SE(3) right wrist target |
|||
current_lr_arm_motor_q: current 14-DOF arm angles (warm start) |
|||
current_lr_arm_motor_dq: current velocities (API compat, unused) |
|||
left_elbow_pos: 3-vector left elbow position (waist frame) |
|||
right_elbow_pos: 3-vector right elbow position (waist frame) |
|||
|
|||
Returns: |
|||
(sol_q, sol_tauff) — 14-DOF joint angles and feedforward torques |
|||
""" |
|||
if current_lr_arm_motor_q is not None: |
|||
self.init_data = current_lr_arm_motor_q |
|||
self.opti.set_initial(self.var_q, self.init_data) |
|||
|
|||
self.opti.set_value(self.param_tf_l, left_wrist) |
|||
self.opti.set_value(self.param_tf_r, right_wrist) |
|||
self.opti.set_value(self.var_q_last, self.init_data) |
|||
|
|||
self.opti.set_value(self.param_elbow_l, |
|||
left_elbow_pos if left_elbow_pos is not None else np.zeros(3)) |
|||
self.opti.set_value(self.param_elbow_r, |
|||
right_elbow_pos if right_elbow_pos is not None else np.zeros(3)) |
|||
|
|||
try: |
|||
sol = self.opti.solve() |
|||
sol_q = self.opti.value(self.var_q) |
|||
except Exception as e: |
|||
sol_q = self.opti.debug.value(self.var_q) |
|||
|
|||
self.smooth_filter.add_data(sol_q) |
|||
sol_q = self.smooth_filter.filtered_data |
|||
|
|||
v = (current_lr_arm_motor_dq * 0.0 if current_lr_arm_motor_dq is not None |
|||
else (sol_q - self.init_data) * 0.0) |
|||
|
|||
self.init_data = sol_q |
|||
sol_tauff = pin.rnea( |
|||
self.reduced_robot.model, self.reduced_robot.data, |
|||
sol_q, v, np.zeros(self.reduced_robot.model.nv)) |
|||
|
|||
return sol_q, sol_tauff |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue