🦴 Adds Simulator from OpenQuadruped/spot_mini_mini

This commit is contained in:
Rune Harlyk
2024-03-04 15:55:45 +01:00
parent e673a50fa2
commit 1753e539db
162 changed files with 11940 additions and 1 deletions
@@ -0,0 +1,97 @@
#!/usr/bin/env python
# https://www.researchgate.net/publication/320307716_Inverse_Kinematic_Analysis_Of_A_Quadruped_Robot
import numpy as np
class LegIK():
def __init__(self,
legtype="RIGHT",
shoulder_length=0.04,
elbow_length=0.1,
wrist_length=0.125,
hip_lim=[-0.548, 0.548],
shoulder_lim=[-2.17, 0.97],
leg_lim=[-0.1, 2.59]):
self.legtype = legtype
self.shoulder_length = shoulder_length
self.elbow_length = elbow_length
self.wrist_length = wrist_length
self.hip_lim = hip_lim
self.shoulder_lim = shoulder_lim
self.leg_lim = leg_lim
def get_domain(self, x, y, z):
"""
Calculates the leg's Domain and caps it in case of a breach
:param x,y,z: hip-to-foot distances in each dimension
:return: Leg Domain D
"""
D = (y**2 + (-z)**2 - self.shoulder_length**2 +
(-x)**2 - self.elbow_length**2 - self.wrist_length**2) / (
2 * self.wrist_length * self.elbow_length)
if D > 1 or D < -1:
# DOMAIN BREACHED
# print("---------DOMAIN BREACH---------")
D = np.clip(D, -1.0, 1.0)
return D
else:
return D
def solve(self, xyz_coord):
"""
Generic Leg Inverse Kinematics Solver
:param xyz_coord: hip-to-foot distances in each dimension
:return: Joint Angles required for desired position
"""
x = xyz_coord[0]
y = xyz_coord[1]
z = xyz_coord[2]
D = self.get_domain(x, y, z)
if self.legtype == "RIGHT":
return self.RightIK(x, y, z, D)
else:
return self.LeftIK(x, y, z, D)
def RightIK(self, x, y, z, D):
"""
Right Leg Inverse Kinematics Solver
:param x,y,z: hip-to-foot distances in each dimension
:param D: leg domain
:return: Joint Angles required for desired position
"""
wrist_angle = np.arctan2(-np.sqrt(1 - D**2), D)
sqrt_component = y**2 + (-z)**2 - self.shoulder_length**2
if sqrt_component < 0.0:
# print("NEGATIVE SQRT")
sqrt_component = 0.0
shoulder_angle = -np.arctan2(z, y) - np.arctan2(
np.sqrt(sqrt_component), -self.shoulder_length)
elbow_angle = np.arctan2(-x, np.sqrt(sqrt_component)) - np.arctan2(
self.wrist_length * np.sin(wrist_angle),
self.elbow_length + self.wrist_length * np.cos(wrist_angle))
joint_angles = np.array([-shoulder_angle, elbow_angle, wrist_angle])
return joint_angles
def LeftIK(self, x, y, z, D):
"""
Left Leg Inverse Kinematics Solver
:param x,y,z: hip-to-foot distances in each dimension
:param D: leg domain
:return: Joint Angles required for desired position
"""
wrist_angle = np.arctan2(-np.sqrt(1 - D**2), D)
sqrt_component = y**2 + (-z)**2 - self.shoulder_length**2
if sqrt_component < 0.0:
print("NEGATIVE SQRT")
sqrt_component = 0.0
shoulder_angle = -np.arctan2(z, y) - np.arctan2(
np.sqrt(sqrt_component), self.shoulder_length)
elbow_angle = np.arctan2(-x, np.sqrt(sqrt_component)) - np.arctan2(
self.wrist_length * np.sin(wrist_angle),
self.elbow_length + self.wrist_length * np.cos(wrist_angle))
joint_angles = np.array([-shoulder_angle, elbow_angle, wrist_angle])
return joint_angles
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env python
import numpy as np
# NOTE: Code snippets from Modern Robotics at Northwestern University:
# See https://github.com/NxRLab/ModernRobotics
def RpToTrans(R, p):
"""
Converts a rotation matrix and a position vector into homogeneous
transformation matrix
:param R: A 3x3 rotation matrix
:param p: A 3-vector
:return: A homogeneous transformation matrix corresponding to the inputs
Example Input:
R = np.array([[1, 0, 0],
[0, 0, -1],
[0, 1, 0]])
p = np.array([1, 2, 5])
Output:
np.array([[1, 0, 0, 1],
[0, 0, -1, 2],
[0, 1, 0, 5],
[0, 0, 0, 1]])
"""
return np.r_[np.c_[R, p], [[0, 0, 0, 1]]]
def TransToRp(T):
"""
Converts a homogeneous transformation matrix into a rotation matrix
and position vector
:param T: A homogeneous transformation matrix
:return R: The corresponding rotation matrix,
:return p: The corresponding position vector.
Example Input:
T = np.array([[1, 0, 0, 0],
[0, 0, -1, 0],
[0, 1, 0, 3],
[0, 0, 0, 1]])
Output:
(np.array([[1, 0, 0],
[0, 0, -1],
[0, 1, 0]]),
np.array([0, 0, 3]))
"""
T = np.array(T)
return T[0:3, 0:3], T[0:3, 3]
def TransInv(T):
"""
Inverts a homogeneous transformation matrix
:param T: A homogeneous transformation matrix
:return: The inverse of T
Uses the structure of transformation matrices to avoid taking a matrix
inverse, for efficiency.
Example input:
T = np.array([[1, 0, 0, 0],
[0, 0, -1, 0],
[0, 1, 0, 3],
[0, 0, 0, 1]])
Output:
np.array([[1, 0, 0, 0],
[0, 0, 1, -3],
[0, -1, 0, 0],
[0, 0, 0, 1]])
"""
R, p = TransToRp(T)
Rt = np.array(R).T
return np.r_[np.c_[Rt, -np.dot(Rt, p)], [[0, 0, 0, 1]]]
def Adjoint(T):
"""
Computes the adjoint representation of a homogeneous transformation
matrix
:param T: A homogeneous transformation matrix
:return: The 6x6 adjoint representation [AdT] of T
Example Input:
T = np.array([[1, 0, 0, 0],
[0, 0, -1, 0],
[0, 1, 0, 3],
[0, 0, 0, 1]])
Output:
np.array([[1, 0, 0, 0, 0, 0],
[0, 0, -1, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 0, 3, 1, 0, 0],
[3, 0, 0, 0, 0, -1],
[0, 0, 0, 0, 1, 0]])
"""
R, p = TransToRp(T)
return np.r_[np.c_[R, np.zeros((3, 3))], np.c_[np.dot(VecToso3(p), R), R]]
def VecToso3(omg):
"""
Converts a 3-vector to an so(3) representation
:param omg: A 3-vector
:return: The skew symmetric representation of omg
Example Input:
omg = np.array([1, 2, 3])
Output:
np.array([[ 0, -3, 2],
[ 3, 0, -1],
[-2, 1, 0]])
"""
return np.array([[0, -omg[2], omg[1]], [omg[2], 0, -omg[0]],
[-omg[1], omg[0], 0]])
def RPY(roll, pitch, yaw):
"""
Creates a Roll, Pitch, Yaw Transformation Matrix
:param roll: roll component of matrix
:param pitch: pitch component of matrix
:param yaw: yaw component of matrix
:return: The transformation matrix
Example Input:
roll = 0.0
pitch = 0.0
yaw = 0.0
Output:
np.array([[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]])
"""
Roll = np.array([[1, 0, 0, 0], [0, np.cos(roll), -np.sin(roll), 0],
[0, np.sin(roll), np.cos(roll), 0], [0, 0, 0, 1]])
Pitch = np.array([[np.cos(pitch), 0, np.sin(pitch), 0], [0, 1, 0, 0],
[-np.sin(pitch), 0, np.cos(pitch), 0], [0, 0, 0, 1]])
Yaw = np.array([[np.cos(yaw), -np.sin(yaw), 0, 0],
[np.sin(yaw), np.cos(yaw), 0, 0], [0, 0, 1, 0],
[0, 0, 0, 1]])
return np.matmul(np.matmul(Roll, Pitch), Yaw)
def RotateTranslate(rotation, position):
"""
Creates a Transformation Matrix from a Rotation, THEN, a Translation
:param rotation: pure rotation matrix
:param translation: pure translation matrix
:return: The transformation matrix
"""
trans = np.eye(4)
trans[0, 3] = position[0]
trans[1, 3] = position[1]
trans[2, 3] = position[2]
return np.dot(rotation, trans)
def TransformVector(xyz_coord, rotation, translation):
"""
Transforms a vector by a specified Rotation THEN Translation Matrix
:param xyz_coord: the vector to transform
:param rotation: pure rotation matrix
:param translation: pure translation matrix
:return: The transformed vector
"""
xyz_vec = np.append(xyz_coord, 1.0)
Transformed = np.dot(RotateTranslate(rotation, translation), xyz_vec)
return Transformed[:3]
+224
View File
@@ -0,0 +1,224 @@
#!/usr/bin/env python
import numpy as np
from Kinematics.LegKinematics import LegIK
from Kinematics.LieAlgebra import RpToTrans, TransToRp, TransInv, RPY, TransformVector
from collections import OrderedDict
class SpotModel:
def __init__(
self,
shoulder_length=0.055,
elbow_length=0.10652,
wrist_length=0.145,
hip_x=0.23,
hip_y=0.075,
foot_x=0.23,
foot_y=0.185,
height=0.20,
com_offset=0.016,
shoulder_lim=[-0.548, 0.548],
elbow_lim=[-2.17, 0.97],
wrist_lim=[-0.1, 2.59],
):
"""
Spot Micro Kinematics
"""
# COM offset in x direction
self.com_offset = com_offset
# Leg Parameters
self.shoulder_length = shoulder_length
self.elbow_length = elbow_length
self.wrist_length = wrist_length
# Leg Vector desired_positions
# Distance Between Hips
# Length
self.hip_x = hip_x
# Width
self.hip_y = hip_y
# Distance Between Feet
# Length
self.foot_x = foot_x
# Width
self.foot_y = foot_y
# Body Height
self.height = height
# Joint Parameters
self.shoulder_lim = shoulder_lim
self.elbow_lim = elbow_lim
self.wrist_lim = wrist_lim
# Dictionary to store Leg IK Solvers
self.Legs = OrderedDict()
self.Legs["FL"] = LegIK(
"LEFT",
self.shoulder_length,
self.elbow_length,
self.wrist_length,
self.shoulder_lim,
self.elbow_lim,
self.wrist_lim,
)
self.Legs["FR"] = LegIK(
"RIGHT",
self.shoulder_length,
self.elbow_length,
self.wrist_length,
self.shoulder_lim,
self.elbow_lim,
self.wrist_lim,
)
self.Legs["BL"] = LegIK(
"LEFT",
self.shoulder_length,
self.elbow_length,
self.wrist_length,
self.shoulder_lim,
self.elbow_lim,
self.wrist_lim,
)
self.Legs["BR"] = LegIK(
"RIGHT",
self.shoulder_length,
self.elbow_length,
self.wrist_length,
self.shoulder_lim,
self.elbow_lim,
self.wrist_lim,
)
# Dictionary to store Hip and Foot Transforms
# Transform of Hip relative to world frame
# With Body Centroid also in world frame
Rwb = np.eye(3)
self.WorldToHip = OrderedDict()
self.ph_FL = np.array([self.hip_x / 2.0, self.hip_y / 2.0, 0])
self.WorldToHip["FL"] = RpToTrans(Rwb, self.ph_FL)
self.ph_FR = np.array([self.hip_x / 2.0, -self.hip_y / 2.0, 0])
self.WorldToHip["FR"] = RpToTrans(Rwb, self.ph_FR)
self.ph_BL = np.array([-self.hip_x / 2.0, self.hip_y / 2.0, 0])
self.WorldToHip["BL"] = RpToTrans(Rwb, self.ph_BL)
self.ph_BR = np.array([-self.hip_x / 2.0, -self.hip_y / 2.0, 0])
self.WorldToHip["BR"] = RpToTrans(Rwb, self.ph_BR)
# Transform of Foot relative to world frame
# With Body Centroid also in world frame
self.WorldToFoot = OrderedDict()
self.pf_FL = np.array([self.foot_x / 2.0, self.foot_y / 2.0, -self.height])
self.WorldToFoot["FL"] = RpToTrans(Rwb, self.pf_FL)
self.pf_FR = np.array([self.foot_x / 2.0, -self.foot_y / 2.0, -self.height])
self.WorldToFoot["FR"] = RpToTrans(Rwb, self.pf_FR)
self.pf_BL = np.array([-self.foot_x / 2.0, self.foot_y / 2.0, -self.height])
self.WorldToFoot["BL"] = RpToTrans(Rwb, self.pf_BL)
self.pf_BR = np.array([-self.foot_x / 2.0, -self.foot_y / 2.0, -self.height])
self.WorldToFoot["BR"] = RpToTrans(Rwb, self.pf_BR)
def HipToFoot(self, orn, pos, T_bf):
"""
Converts a desired position and orientation wrt Spot's
home position, with a desired body-to-foot Transform
into a body-to-hip Transform, which is used to extract
and return the Hip To Foot Vector.
:param orn: A 3x1 np.array([]) with Spot's Roll, Pitch, Yaw angles
:param pos: A 3x1 np.array([]) with Spot's X, Y, Z coordinates
:param T_bf: Dictionary of desired body-to-foot Transforms.
:return: Hip To Foot Vector for each of Spot's Legs.
"""
# Following steps in attached document: SpotBodyIK.
# TODO: LINK DOC
# Only get Rot component
Rb, _ = TransToRp(RPY(orn[0], orn[1], orn[2]))
pb = pos
T_wb = RpToTrans(Rb, pb)
# Dictionary to store vectors
HipToFoot_List = OrderedDict()
for i, (key, T_wh) in enumerate(self.WorldToHip.items()):
# ORDER: FL, FR, FR, BL, BR
# Extract vector component
_, p_bf = TransToRp(T_bf[key])
# Step 1, get T_bh for each leg
T_bh = np.dot(TransInv(T_wb), T_wh)
# Step 2, get T_hf for each leg
# VECTOR ADDITION METHOD
_, p_bh = TransToRp(T_bh)
p_hf0 = p_bf - p_bh
# TRANSFORM METHOD
T_hf = np.dot(TransInv(T_bh), T_bf[key])
_, p_hf1 = TransToRp(T_hf)
# They should yield the same result
if p_hf1.all() != p_hf0.all():
print("NOT EQUAL")
p_hf = p_hf1
HipToFoot_List[key] = p_hf
return HipToFoot_List
def IK(self, orn, pos, T_bf):
"""
Uses HipToFoot() to convert a desired position
and orientation wrt Spot's home position into a
Hip To Foot Vector, which is fed into the LegIK solver.
Finally, the resultant joint angles are returned
from the LegIK solver for each leg.
:param orn: A 3x1 np.array([]) with Spot's Roll, Pitch, Yaw angles
:param pos: A 3x1 np.array([]) with Spot's X, Y, Z coordinates
:param T_bf: Dictionary of desired body-to-foot Transforms.
:return: Joint angles for each of Spot's joints.
"""
# Following steps in attached document: SpotBodyIK.
# TODO: LINK DOC
# Modify x by com offset
pos[0] += self.com_offset
# 4 legs, 3 joints per leg
joint_angles = np.zeros((4, 3))
# print("T_bf: {}".format(T_bf))
# Steps 1 and 2 of pipeline here
HipToFoot = self.HipToFoot(orn, pos, T_bf)
for i, (key, p_hf) in enumerate(HipToFoot.items()):
# ORDER: FL, FR, FR, BL, BR
# print("LEG: {} \t HipToFoot: {}".format(key, p_hf))
# Step 3, compute joint angles from T_hf for each leg
joint_angles[i, :] = self.Legs[key].solve(p_hf)
# print("-----------------------------")
return joint_angles