2025-02-12 17:04:18 +08:00
|
|
|
import os
|
|
|
|
from typing import Tuple
|
|
|
|
from contextlib import contextmanager
|
|
|
|
from pathlib import Path
|
|
|
|
import time
|
|
|
|
|
2025-02-13 20:14:56 +08:00
|
|
|
import yaml
|
2025-02-12 17:04:18 +08:00
|
|
|
import numpy as np
|
|
|
|
import torch as th
|
2025-02-13 20:14:56 +08:00
|
|
|
from yourdfpy import URDF
|
2025-02-12 17:04:18 +08:00
|
|
|
|
|
|
|
import pinocchio as pin
|
|
|
|
import pink
|
|
|
|
from pink.tasks import FrameTask
|
|
|
|
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def with_dir(d):
|
|
|
|
d0 = os.getcwd()
|
|
|
|
try:
|
|
|
|
os.chdir(d)
|
|
|
|
yield
|
|
|
|
finally:
|
|
|
|
os.chdir(d0)
|
|
|
|
|
|
|
|
|
|
|
|
def xyzw2wxyz(q_xyzw: th.Tensor, dim: int = -1):
|
|
|
|
if isinstance(q_xyzw, np.ndarray):
|
|
|
|
return np.roll(q_xyzw, 1, axis=dim)
|
|
|
|
return th.roll(q_xyzw, 1, dims=dim)
|
|
|
|
|
|
|
|
|
|
|
|
def wxyz2xyzw(q_wxyz: th.Tensor, dim: int = -1):
|
|
|
|
if isinstance(q_wxyz, np.ndarray):
|
|
|
|
return np.roll(q_wxyz, -1, axis=dim)
|
|
|
|
return th.roll(q_wxyz, -1, dims=dim)
|
|
|
|
|
|
|
|
|
|
|
|
def dls_ik(
|
|
|
|
dpose: np.ndarray,
|
|
|
|
jac: np.ndarray,
|
|
|
|
sqlmda: float
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
Arg:
|
|
|
|
dpose: task-space error (A[..., err])
|
|
|
|
jac: jacobian (A[..., err, dof])
|
|
|
|
sqlmda: DLS damping factor.
|
|
|
|
Return:
|
|
|
|
joint residual (A[..., dof])
|
|
|
|
"""
|
|
|
|
if isinstance(dpose, tuple):
|
|
|
|
dpose = np.concatenate([dpose[0], dpose[1]], axis=-1)
|
|
|
|
J = jac
|
|
|
|
A = J @ J.T
|
|
|
|
# NOTE(ycho): add to view of diagonal
|
|
|
|
a = np.einsum('...ii->...i', A)
|
|
|
|
a += sqlmda
|
|
|
|
dq = (J.T @ np.linalg.solve(A, dpose[..., None]))[..., 0]
|
|
|
|
return dq
|
|
|
|
|
|
|
|
|
|
|
|
class IKCtrl:
|
|
|
|
def __init__(self,
|
|
|
|
urdf_path: str,
|
2025-02-13 20:14:56 +08:00
|
|
|
act_joints: Tuple[str, ...],
|
|
|
|
frame: str = 'left_hand_palm_link',
|
2025-02-12 17:04:18 +08:00
|
|
|
sqlmda: float = 0.05**2):
|
|
|
|
path = Path(urdf_path)
|
|
|
|
with with_dir(path.parent):
|
2025-02-13 19:04:46 +08:00
|
|
|
robot = pin.RobotWrapper.BuildFromURDF(filename=path.name,
|
2025-02-12 17:04:18 +08:00
|
|
|
package_dirs=["."],
|
|
|
|
root_joint=None)
|
|
|
|
self.robot = robot
|
2025-02-13 20:14:56 +08:00
|
|
|
# NOTE(ycho): we skip joint#0(="universe")
|
|
|
|
joint_names = list(self.robot.model.names)
|
|
|
|
assert (joint_names[0] == 'universe')
|
|
|
|
self.joint_names = joint_names[1:]
|
|
|
|
|
|
|
|
# NOTE(ycho): build index map between pin.q and other set(s) of ordered
|
|
|
|
# joints.
|
|
|
|
act_from_pin = []
|
|
|
|
for j in act_joints:
|
|
|
|
act_from_pin.append(robot.index(j) - 1)
|
|
|
|
self.frame = frame
|
|
|
|
self.act_from_pin = np.asarray(act_from_pin, dtype=np.int32)
|
|
|
|
self.task = FrameTask(frame, position_cost=1.0, orientation_cost=0.0)
|
2025-02-12 17:04:18 +08:00
|
|
|
self.sqlmda = sqlmda
|
|
|
|
self.cfg = pink.Configuration(robot.model, robot.data,
|
|
|
|
np.zeros_like(robot.q0))
|
|
|
|
|
2025-02-13 20:14:56 +08:00
|
|
|
def fk(self, q: np.ndarray):
|
|
|
|
robot = self.robot
|
|
|
|
return pink.Configuration(
|
|
|
|
robot.model, robot.data, q).get_transform_frame_to_world(
|
|
|
|
self.frame)
|
|
|
|
|
2025-02-12 17:04:18 +08:00
|
|
|
def __call__(self,
|
|
|
|
q0: np.ndarray,
|
|
|
|
target_pose: np.ndarray,
|
2025-02-13 20:14:56 +08:00
|
|
|
rel: bool = False
|
2025-02-12 17:04:18 +08:00
|
|
|
):
|
|
|
|
"""
|
|
|
|
Arg:
|
|
|
|
q0: Current robot joints; A[..., 43?]
|
|
|
|
target_pose:
|
|
|
|
Policy output. A[..., 7] formatted as (xyz, q_{wxyz})
|
|
|
|
Given as world frame absolute pose, for some reason.
|
|
|
|
Return:
|
|
|
|
joint residual: A[..., 7]
|
|
|
|
"""
|
|
|
|
robot = self.robot
|
|
|
|
|
|
|
|
# source pose
|
|
|
|
self.cfg.update(q0)
|
2025-02-13 20:14:56 +08:00
|
|
|
T0 = self.cfg.get_transform_frame_to_world(self.frame)
|
2025-02-12 17:04:18 +08:00
|
|
|
|
|
|
|
# target pose
|
|
|
|
dst_xyz = target_pose[..., 0:3]
|
|
|
|
dst_quat = pin.Quaternion(wxyz2xyzw(target_pose[..., 3:7]))
|
|
|
|
T1 = pin.SE3(dst_quat, dst_xyz)
|
|
|
|
if rel:
|
2025-02-13 19:04:46 +08:00
|
|
|
TL = pin.SE3.Identity()
|
|
|
|
TL.translation = dst_xyz
|
|
|
|
TR = pin.SE3.Identity()
|
|
|
|
TR.rotation = dst_quat.toRotationMatrix()
|
|
|
|
T1 = TL * T0 * TR
|
2025-02-12 17:04:18 +08:00
|
|
|
|
|
|
|
# jacobian
|
|
|
|
self.task.set_target(T0)
|
|
|
|
jac = self.task.compute_jacobian(self.cfg)
|
2025-02-13 20:14:56 +08:00
|
|
|
jac = jac[:, self.act_from_pin]
|
2025-02-12 17:04:18 +08:00
|
|
|
|
|
|
|
# error&ik
|
|
|
|
dT = T1.actInv(T0)
|
|
|
|
dpose = pin.log(dT).vector
|
|
|
|
dq = dls_ik(dpose, jac, self.sqlmda)
|
|
|
|
return dq
|
|
|
|
|
2025-02-13 20:14:56 +08:00
|
|
|
|
|
|
|
def main():
|
|
|
|
urdf_path = '../../resources/robots/g1_description/g1_29dof_with_hand_rev_1_0.urdf'
|
|
|
|
|
|
|
|
# == init yourdfpy robot ==
|
|
|
|
path = Path(urdf_path)
|
|
|
|
with with_dir(path.parent):
|
|
|
|
viz = URDF.load(path.name,
|
|
|
|
build_collision_scene_graph=True,
|
|
|
|
load_meshes=False,
|
|
|
|
load_collision_meshes=True)
|
|
|
|
|
|
|
|
# == populate with defaults ==
|
|
|
|
with open('./configs/ik.yaml', 'r') as fp:
|
|
|
|
data = yaml.safe_load(fp)
|
|
|
|
q_mot = np.asarray(data['default_angles'])
|
|
|
|
q_viz = np.zeros((len(viz.actuated_joint_names)))
|
|
|
|
|
|
|
|
viz_from_mot = np.zeros(len(data['motor_joint']),
|
|
|
|
dtype=np.int32)
|
|
|
|
for i_mot, j in enumerate(data['motor_joint']):
|
|
|
|
i_viz = viz.actuated_joint_names.index(j)
|
|
|
|
viz_from_mot[i_mot] = i_viz
|
|
|
|
q_viz[viz_from_mot] = q_mot
|
|
|
|
|
|
|
|
ctrl = IKCtrl(urdf_path, data['act_joint'])
|
|
|
|
|
|
|
|
q_pin = np.zeros_like(ctrl.cfg.q)
|
|
|
|
pin_from_mot = np.zeros(len(data['motor_joint']),
|
|
|
|
dtype=np.int32)
|
|
|
|
for i_mot, j in enumerate(data['motor_joint']):
|
|
|
|
i_pin = ctrl.joint_names.index(j)
|
|
|
|
pin_from_mot[i_mot] = i_pin
|
|
|
|
|
|
|
|
mot_from_act = np.zeros(len(data['act_joint']),
|
|
|
|
dtype=np.int32)
|
|
|
|
for i_act, j in enumerate(data['act_joint']):
|
|
|
|
i_mot = data['motor_joint'].index(j)
|
|
|
|
mot_from_act[i_act] = i_mot
|
|
|
|
|
|
|
|
viz.update_cfg(q_viz)
|
|
|
|
# viz.show(collision_geometry=True)
|
|
|
|
|
|
|
|
if True:
|
|
|
|
current_pose = viz.get_transform(ctrl.frame)
|
|
|
|
print('curpose (viz)', current_pose)
|
|
|
|
print('curpose (pin)', ctrl.fk(q_pin).homogeneous)
|
|
|
|
|
|
|
|
def callback(scene):
|
|
|
|
if True:
|
|
|
|
current_pose = viz.get_transform(ctrl.frame)
|
|
|
|
T = pin.SE3()
|
|
|
|
T.translation = (current_pose[..., :3, 3]
|
|
|
|
+ [0.01, 0.0, 0.0])
|
|
|
|
T.rotation = current_pose[..., :3, :3]
|
|
|
|
target_pose = pin.SE3ToXYZQUAT(T)
|
|
|
|
target_pose[..., 3:7] = xyzw2wxyz(target_pose[..., 3:7])
|
|
|
|
q_pin[pin_from_mot] = q_mot
|
|
|
|
dq = ctrl(q_pin, target_pose, rel=False)
|
|
|
|
q_mot[mot_from_act] += dq
|
|
|
|
|
|
|
|
q_viz[viz_from_mot] = q_mot
|
|
|
|
viz.update_cfg(q_viz)
|
|
|
|
|
|
|
|
viz.show(collision_geometry=True,
|
|
|
|
callback=callback)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|