Compare commits

..

4 Commits

Author SHA1 Message Date
377a676863 Update README.md 2026-06-14 04:17:40 +08:00
9db480c474 Upload files to "img" 2026-06-14 04:13:40 +08:00
3a8aecbf71 Update README.md
add visualization
2026-06-09 17:28:57 +08:00
7c8beaa3b9 Upload files to "kine_ctrl/visual/mjc_ik_test1" 2026-06-09 17:23:17 +08:00
7 changed files with 97 additions and 116 deletions

View File

@ -9,9 +9,9 @@ Key specifications:
2. Success rate 2. Success rate
3. Minial joint variation. 3. Minial joint variation.
Next:\ Cost:
Comparison with Realman official IK method.
Embedded with current demo. <img src="img/cost.jpg" alt="Cost" width="400">
### Comparison (05June2026): ### Comparison (05June2026):
@ -24,6 +24,9 @@ lb = np.array([-150.0, -30.0, -170.0, -130, -175.0, -125.0, -179.0])
the success rates for **qp-based ik** and **realman Algo ik** are **63%** and **46%**.\ the success rates for **qp-based ik** and **realman Algo ik** are **63%** and **46%**.\
At least one solver works out the ik, rate = **74%**. At least one solver works out the ik, rate = **74%**.
- With realman-75 physical joint limit, - With realman-75 physical joint limit,
``` ```
ub = np.array([179.0, 129.0, 179.0, 134, 179.0, 127.0, 359.0]) ub = np.array([179.0, 129.0, 179.0, 134, 179.0, 127.0, 359.0])
@ -32,3 +35,9 @@ lb = -ub
the success rates for **qp-based ik** and **realman Algo ik** are **76%** and **51%**.\ the success rates for **qp-based ik** and **realman Algo ik** are **76%** and **51%**.\
At least one solver works out the ik, rate = **84%**. At least one solver works out the ik, rate = **84%**.
Visualization:
![demo](kine_ctrl/visual/mjc_ik_test1/mjc_ik.gif)

BIN
img/cost.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -1,6 +0,0 @@
#!/bin/bash
echo "Fixing robotics environment..."
conda activate coppeliasim
export PYTHONPATH="/home/zl/miniforge3/envs/coppeliasim/lib/python3.10/site-packages"
pip install osqp==0.6.2.post8 --force-reinstall
python -c "import osqp; print(f'OSQP version: {osqp.__version__}')"

View File

@ -32,11 +32,11 @@ def main():
if True: if True:
ub = np.array([150.0, 110.0, 170.0, 130, 175.0, 125.0, 179.0])/180*pi # ub = np.array([150.0, 110.0, 170.0, 130, 175.0, 125.0, 179.0])
lb = np.array([-150.0, -30.0, -170.0, -130, -175.0, -125.0, -179.0])/180*pi # lb = np.array([-150.0, -30.0, -170.0, -130, -175.0, -125.0, -179.0])
# ub = np.array([179.0, 129.0, 179.0, 134, 179.0, 127.0, 359.0])/180*pi ub = np.array([179.0, 129.0, 179.0, 134, 179.0, 127.0, 359.0])/180*pi
# lb = -ub lb = -ub
robot_kine_qp.cfg_j_limit(min_j=lb, max_j=ub, rad_flag=True) robot_kine_qp.cfg_j_limit(min_j=lb, max_j=ub, rad_flag=True)

View File

@ -8,7 +8,6 @@ import osqp
from scipy import sparse from scipy import sparse
from math import radians, degrees, pi, cos, sin from math import radians, degrees, pi, cos, sin
import time import time
import threading
@ -22,8 +21,75 @@ class KinematicsSolver():
print(f' ------------ the qp based kinematic initialising -----------') print(f' ------------ the qp based kinematic initialising -----------')
self.model, collision_model, visual_model = pin.buildModelsFromUrdf(urdf_path, mesh_dir) self.model, collision_model, visual_model = pin.buildModelsFromUrdf(urdf_path, mesh_dir)
# -------------------------------------------------
# ee
# -------------------------------------------------
ee_offset = pin.SE3(np.eye(3), np.array([0, 0, 0.0]))
self.model.addFrame(
pin.Frame(
"ee",
self.model.getJointId("joint_7"),
self.model.getFrameId("link_7"),
ee_offset,
pin.FrameType.OP_FRAME
)
)
# -------------------------------------------------
# Scissor tool
# -------------------------------------------------
scissor_offset = pin.SE3(
np.eye(3),
np.array([0.0, 0.0, 0.144])
)
self.model.addFrame(
pin.Frame(
"scissor",
self.model.getJointId("joint_7"),
self.model.getFrameId("link_7"),
scissor_offset,
pin.FrameType.OP_FRAME
)
)
# -------------------------------------------------
# Camera tool
# -------------------------------------------------
camera_rotation = pin.rpy.rpyToMatrix(
radians(-90),
0,
radians(-90)
)
camera_offset = pin.SE3(
camera_rotation,
np.array([0.05, 0.02, 0.10])
)
self.model.addFrame(
pin.Frame(
"camera",
self.model.getJointId("joint_7"),
self.model.getFrameId("link_7"),
camera_offset,
pin.FrameType.OP_FRAME
)
)
# -------------------------------------------------
# Store tool frame IDs
# -------------------------------------------------
self.tool_frames = {
"scissor": self.model.getFrameId("scissor"),
"camera": self.model.getFrameId("camera"),
"ee": self.model.getFrameId("ee")
}
self.data = self.model.createData()
self.cfg_j_limit() self.cfg_j_limit()
# ---------- for reused qp_solver ------------------ # ---------- for reused qp_solver ------------------
@ -52,30 +118,6 @@ class KinematicsSolver():
self.W = np.diag([1, 1, 1, 0.4, 0.4, 0.4]) self.W = np.diag([1, 1, 1, 0.4, 0.4, 0.4])
def add_frame(self,frame_name, position, rotationXYZ):
'''
:param frame_name: str
:param position: [x, y, z] target position (meters)
:param rotationXYZ: [x, y, z] target rotation (rad)
'''
camera_rotation = pin.rpy.rpyToMatrix( rotationXYZ[0], rotationXYZ[1], rotationXYZ[2] )
camera_offset = pin.SE3(
camera_rotation,
np.array(position)
)
self.model.addFrame( pin.Frame( frame_name, self.model.getJointId("joint_7"), self.model.getFrameId("link_7"), camera_offset, pin.FrameType.OP_FRAME ) )
def add_tool_frames(self,dict_frames):
self.tool_frames ={}
for tool_name in dict_frames:
tool_attr = dict_frames[tool_name]
position = tool_attr[0][0:3]
rotationXYZ = self.quaternion_to_euler(tool_attr[0][3:7])
self.add_frame(tool_name, position, rotationXYZ)
self.tool_frames.update({tool_name: self.model.getFrameId(tool_name)})
self.data = self.model.createData()
def cfg_j_limit(self, min_j=None, max_j=None, rad_flag = True): def cfg_j_limit(self, min_j=None, max_j=None, rad_flag = True):
if min_j is None: if min_j is None:
min_j = [-3.14159, -2.2689, -3.14159, -2.3562, -3.14159, -2.234, -6.14159] min_j = [-3.14159, -2.2689, -3.14159, -2.3562, -3.14159, -2.234, -6.14159]
@ -90,7 +132,7 @@ class KinematicsSolver():
self.model.lowerPositionLimit[i] = min_j[i] / 180 * pi self.model.lowerPositionLimit[i] = min_j[i] / 180 * pi
self.model.upperPositionLimit[i] = max_j[i] / 180 * pi self.model.upperPositionLimit[i] = max_j[i] / 180 * pi
def forward_kinematics(self, joint_angles, tool="omnipic"): def forward_kinematics(self, joint_angles, tool="ee"):
""" """
Compute forward kinematics. Compute forward kinematics.
@ -159,7 +201,8 @@ class KinematicsSolver():
""" """
# Build target SE3 placement # Build target SE3 placement
if target_quat is not None: if target_quat is not None:
quat = pin.Quaternion(target_quat[3], target_quat[0], target_quat[1], target_quat[2]) quat = pin.Quaternion(target_quat[3], target_quat[0],
target_quat[1], target_quat[2])
target_rotation = quat.matrix() target_rotation = quat.matrix()
elif target_rpy is not None: elif target_rpy is not None:
target_rotation = pin.rpy.rpyToMatrix(target_rpy[0], target_rotation = pin.rpy.rpyToMatrix(target_rpy[0],
@ -321,39 +364,10 @@ class KinematicsSolver():
if best_solution is not None: if best_solution is not None:
# return best_solution, True, best_error, iter_count # return best_solution, True, best_error, iter_count
return 0, best_solution.tolist() return 0, best_solution
else: else:
# return q[:7].copy(), False, error_norm, iter_count # return q[:7].copy(), False, error_norm, iter_count
return -1, q[:7].copy().tolist() return -1, q[:7].copy()
def quaternion_to_euler(self, q):
"""
Convert quaternion to Euler angles (roll, pitch, yaw)
Args:
qx, qy, qz, qw: quaternion components
Returns:
tuple: (roll, pitch, yaw) in radians
"""
# Roll (x-axis rotation)
sinr_cosp = 2.0 * (q[3] * q[0] + q[1] * q[2])
cosr_cosp = 1.0 - 2.0 * (q[0] * q[0] + q[1] * q[1])
roll = np.arctan2(sinr_cosp, cosr_cosp)
# Pitch (y-axis rotation)
sinp = 2.0 * (q[3] * q[1] - q[2] * q[0])
if abs(sinp) >= 1:
pitch = np.copysign(np.pi / 2, sinp) # Use 90 degrees if out of range
else:
pitch = np.arcsin(sinp)
# Yaw (z-axis rotation)
siny_cosp = 2.0 * (q[3] * q[2] + q[0] * q[1])
cosy_cosp = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2])
yaw = np.arctan2(siny_cosp, cosy_cosp)
return [roll, pitch, yaw]
# def invese_kinematics_velocity(self, target_position, target_rpy=None, # def invese_kinematics_velocity(self, target_position, target_rpy=None,
# target_quat=None, initial_guess=None, tool="ee"): # target_quat=None, initial_guess=None, tool="ee"):

View File

@ -14,11 +14,16 @@ class rm75_kine_api():
self.cfg_j_limit() self.cfg_j_limit()
self.tool_frames = {
'ee': rm_frame_t(frame_name="ee", pose=(0.0, 0.0, 0.0, 0.0, 0, 0.0), payload=1, x=0, y=0, z=0),
'scissor': rm_frame_t(frame_name="scissor", pose=(0.0, 0.0, 0.144, 0.0, 0, 0.0), payload=1, x=0, y=0, z=72),
'camera': rm_frame_t(frame_name="camera", pose=(0.05, 0.02, 0.10, -1.57, 0, -1.57), payload=1, x=0, y=0, z=72)
}
self.work_frames = { self.work_frames = {
'work': rm_frame_t(frame_name="work", pose=(0.0, 0.0, 0.0, 0.0, 0, 0.0), payload=1, x=0, y=0, z=0), 'work': rm_frame_t(frame_name="ee", pose=(0.0, 0.0, 0.0, 0.0, 0, 0.0), payload=1, x=0, y=0, z=0),
} }
self.tool_name = "no_tool" self.tool_name = "ee"
self.work_name = "work" self.work_name = "work"
def cfg_j_limit(self, min_j=None, max_j=None, rad_flag = True): def cfg_j_limit(self, min_j=None, max_j=None, rad_flag = True):
@ -27,8 +32,6 @@ class rm75_kine_api():
if min_j is None: if min_j is None:
min_j = np.array([ -3.14159, -2.2689, -3.14159, -2.3562, -3.14159, -2.234, -3.14159 ]) min_j = np.array([ -3.14159, -2.2689, -3.14159, -2.3562, -3.14159, -2.234, -3.14159 ])
max_j = np.array(max_j)
min_j = np.array(min_j)
if rad_flag: if rad_flag:
self.robot_kine_rm.rm_algo_set_joint_max_limit((max_j * 180 / math.pi).tolist()) self.robot_kine_rm.rm_algo_set_joint_max_limit((max_j * 180 / math.pi).tolist())
self.robot_kine_rm.rm_algo_set_joint_min_limit((min_j * 180 / math.pi).tolist()) self.robot_kine_rm.rm_algo_set_joint_min_limit((min_j * 180 / math.pi).tolist())
@ -48,46 +51,7 @@ class rm75_kine_api():
def get_tool_frame(self): def get_tool_frame(self):
return self.robot_kine_rm.rm_algo_get_curr_toolframe() return self.robot_kine_rm.rm_algo_get_curr_toolframe()
def quaternion_to_euler(self, q): def forward_kinematics(self, joint_angles, flag = 1 , tool="ee", work="work"):
"""
Convert quaternion to Euler angles (roll, pitch, yaw)
Args:
qx, qy, qz, qw: quaternion components
Returns:
tuple: (roll, pitch, yaw) in radians
"""
# Roll (x-axis rotation)
sinr_cosp = 2.0 * (q[3] * q[0] + q[1] * q[2])
cosr_cosp = 1.0 - 2.0 * (q[0] * q[0] + q[1] * q[1])
roll = np.arctan2(sinr_cosp, cosr_cosp)
# Pitch (y-axis rotation)
sinp = 2.0 * (q[3] * q[1] - q[2] * q[0])
if abs(sinp) >= 1:
pitch = np.copysign(np.pi / 2, sinp) # Use 90 degrees if out of range
else:
pitch = np.arcsin(sinp)
# Yaw (z-axis rotation)
siny_cosp = 2.0 * (q[3] * q[2] + q[0] * q[1])
cosy_cosp = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2])
yaw = np.arctan2(siny_cosp, cosy_cosp)
return [roll, pitch, yaw]
def add_tool_frames(self, dict_frames):
self.tool_frames = {}
for tool_name in dict_frames:
tool_attr = dict_frames[tool_name]
position = tool_attr[0][0:3]
rotationXYZ = self.quaternion_to_euler(tool_attr[0][3:7])
f = rm_frame_t(frame_name=tool_name, pose=(position[0], position[1], position[2], rotationXYZ[0], rotationXYZ[1], rotationXYZ[2]), payload=1, x=0, y=0, z=0)
self.tool_frames.update({tool_name:f})
def forward_kinematics(self, joint_angles, flag = 1 , tool="omnipic", work="work"):
''' '''
:param joint_angles: list of joint values, in rad :param joint_angles: list of joint values, in rad
:param flag: 0: return list [x,y,z,w,x,y,z]. 1: return list [x,y,z,rx,ry,rz] :param flag: 0: return list [x,y,z,w,x,y,z]. 1: return list [x,y,z,rx,ry,rz]
@ -102,7 +66,7 @@ class rm75_kine_api():
return self.robot_kine_rm.rm_algo_forward_kinematics(joint=[q_s*180/math.pi for q_s in joint_angles] , flag=flag) return self.robot_kine_rm.rm_algo_forward_kinematics(joint=[q_s*180/math.pi for q_s in joint_angles] , flag=flag)
def inverse_kinematics(self, target_position, target_rpy=None, initial_guess=None, tool="omnipic", work="work"): def inverse_kinematics(self, target_position, target_rpy=None, initial_guess=None, tool="ee", work="work"):
''' '''
:param target_position: list of position values, m :param target_position: list of position values, m
:param target_rpy: list of rpy values, rad :param target_rpy: list of rpy values, rad

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 KiB