Source code for isaaclab.devices.openxr.retargeters.manipulator.gripper_retargeter
# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
import numpy as np
from typing import Final
from isaaclab.devices import OpenXRDevice
from isaaclab.devices.retargeter_base import RetargeterBase
[docs]class GripperRetargeter(RetargeterBase):
"""Retargeter specifically for gripper control based on hand tracking data.
This retargeter analyzes the distance between thumb and index finger tips to determine
whether the gripper should be open or closed. It includes hysteresis to prevent rapid
toggling between states when the finger distance is near the threshold.
Features:
- Tracks thumb and index finger distance
- Implements hysteresis for stable gripper control
- Outputs boolean command (True = close gripper, False = open gripper)
"""
GRIPPER_CLOSE_METERS: Final[float] = 0.03
GRIPPER_OPEN_METERS: Final[float] = 0.05
[docs] def __init__(
self,
bound_hand: OpenXRDevice.TrackingTarget,
):
"""Initialize the gripper retargeter."""
# Store the hand to track
if bound_hand not in [OpenXRDevice.TrackingTarget.HAND_LEFT, OpenXRDevice.TrackingTarget.HAND_RIGHT]:
raise ValueError(
"bound_hand must be either OpenXRDevice.TrackingTarget.HAND_LEFT or"
" OpenXRDevice.TrackingTarget.HAND_RIGHT"
)
self.bound_hand = bound_hand
# Initialize gripper state
self._previous_gripper_command = False
[docs] def retarget(self, data: dict) -> bool:
"""Convert hand joint poses to gripper command.
Args:
data: Dictionary mapping tracking targets to joint data dictionaries.
The joint names are defined in isaaclab.devices.openxr.common.HAND_JOINT_NAMES
Returns:
bool: Gripper command where True = close gripper, False = open gripper
"""
# Extract key joint poses
hand_data = data[self.bound_hand]
thumb_tip = hand_data["thumb_tip"]
index_tip = hand_data["index_tip"]
# Calculate gripper command with hysteresis
gripper_command = self._calculate_gripper_command(thumb_tip[:3], index_tip[:3])
return gripper_command
def _calculate_gripper_command(self, thumb_pos: np.ndarray, index_pos: np.ndarray) -> bool:
"""Calculate gripper command from finger positions with hysteresis.
Args:
thumb_pos: 3D position of thumb tip
index_pos: 3D position of index tip
Returns:
bool: Gripper command (True = close, False = open)
"""
distance = np.linalg.norm(thumb_pos - index_pos)
# Apply hysteresis to prevent rapid switching
if distance > self.GRIPPER_OPEN_METERS:
self._previous_gripper_command = False
elif distance < self.GRIPPER_CLOSE_METERS:
self._previous_gripper_command = True
return self._previous_gripper_command