mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-31 13:39:40 +00:00
feat(openarm): add end-effector kinematics support
Expose optional URDF-based forward/inverse kinematics for the OpenArm follower so it can be recorded/commanded in end-effector (Cartesian) space, mirroring the SO-100 kinematics pattern. - config: add optional urdf_path / target_frame_name fields - robot: add arm_motor_names property and make_kinematics() helper that builds a RobotKinematics solver (lazy import; placo only needed when used) No behavior change when urdf_path is unset (joint-space only). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -74,6 +74,14 @@ class OpenArmFollowerConfigBase:
|
||||
# Set to a positive scalar for all motors, or a dict mapping motor names to limits
|
||||
max_relative_target: float | dict[str, float] | None = None
|
||||
|
||||
# End-effector kinematics (optional). Point `urdf_path` at the OpenArm URDF and set
|
||||
# `target_frame_name` to the end-effector link in that URDF to enable forward/inverse
|
||||
# kinematics in Cartesian (EE) space -- via `lerobot.model.RobotKinematics` and the
|
||||
# shared FK/IK processor steps. Left as None, the robot behaves exactly as before
|
||||
# (joint space only). The URDF is user-supplied, so no large asset is vendored here.
|
||||
urdf_path: str | None = None
|
||||
target_frame_name: str | None = None
|
||||
|
||||
# Camera configurations
|
||||
cameras: dict[str, CameraConfig] = field(default_factory=dict)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import logging
|
||||
import time
|
||||
from functools import cached_property
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from lerobot.cameras import make_cameras_from_configs
|
||||
from lerobot.lerobot_types import RobotAction, RobotObservation
|
||||
@@ -25,6 +25,9 @@ from lerobot.motors import Motor, MotorCalibration, MotorNormMode
|
||||
from lerobot.motors.damiao import DamiaoMotorsBus
|
||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from lerobot.model import RobotKinematics
|
||||
|
||||
from ..robot import Robot
|
||||
from ..utils import ensure_safe_goal_position
|
||||
from .config_openarm_follower import (
|
||||
@@ -120,6 +123,33 @@ class OpenArmFollower(Robot):
|
||||
"""Action features."""
|
||||
return self._motors_ft
|
||||
|
||||
@property
|
||||
def arm_motor_names(self) -> list[str]:
|
||||
"""Arm joints forming the kinematic chain to the end-effector (excludes the gripper)."""
|
||||
return [motor for motor in self.bus.motors if motor != "gripper"]
|
||||
|
||||
def make_kinematics(self) -> "RobotKinematics":
|
||||
"""Build a solver for end-effector forward/inverse kinematics.
|
||||
|
||||
Requires ``config.urdf_path`` (path to the OpenArm URDF) and
|
||||
``config.target_frame_name`` (the end-effector link in that URDF). Pair the returned
|
||||
solver with the shared FK/IK processor steps in
|
||||
``lerobot.robots.so_follower.robot_kinematic_processor`` to record or command the arm
|
||||
in end-effector (Cartesian) space instead of raw joint angles.
|
||||
"""
|
||||
from lerobot.model import RobotKinematics
|
||||
|
||||
if self.config.urdf_path is None or self.config.target_frame_name is None:
|
||||
raise ValueError(
|
||||
"OpenArm end-effector kinematics require config.urdf_path and "
|
||||
"config.target_frame_name (the OpenArm URDF and its end-effector link)."
|
||||
)
|
||||
return RobotKinematics(
|
||||
urdf_path=self.config.urdf_path,
|
||||
target_frame_name=self.config.target_frame_name,
|
||||
joint_names=self.arm_motor_names,
|
||||
)
|
||||
|
||||
@property
|
||||
def is_connected(self) -> bool:
|
||||
"""Check if robot is connected."""
|
||||
|
||||
Reference in New Issue
Block a user