mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-31 21:49:45 +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
|
# 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
|
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
|
# Camera configurations
|
||||||
cameras: dict[str, CameraConfig] = field(default_factory=dict)
|
cameras: dict[str, CameraConfig] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from functools import cached_property
|
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.cameras import make_cameras_from_configs
|
||||||
from lerobot.lerobot_types import RobotAction, RobotObservation
|
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.motors.damiao import DamiaoMotorsBus
|
||||||
from lerobot.utils.decorators import check_if_already_connected, check_if_not_connected
|
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 ..robot import Robot
|
||||||
from ..utils import ensure_safe_goal_position
|
from ..utils import ensure_safe_goal_position
|
||||||
from .config_openarm_follower import (
|
from .config_openarm_follower import (
|
||||||
@@ -120,6 +123,33 @@ class OpenArmFollower(Robot):
|
|||||||
"""Action features."""
|
"""Action features."""
|
||||||
return self._motors_ft
|
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
|
@property
|
||||||
def is_connected(self) -> bool:
|
def is_connected(self) -> bool:
|
||||||
"""Check if robot is connected."""
|
"""Check if robot is connected."""
|
||||||
|
|||||||
Reference in New Issue
Block a user