https://github.com/stack-of-tasks/pinocchio 分享一个求解运动学逆解的第三方库 pinocchio, 并且根据其urdf文件中描述的关节极限范围内进行逆运动学求解的样例。

import numpy as np
from numpy.linalg import norm, solve
from IPython import embed
import pinocchio
from copy import deepcopy


class pinocchio_kinematics(object):

    def __init__(self, urdf_path: str):
        """Initializes the kinematics solver with a robot model."""
        self.urdf_path = urdf_path
        self.model = pinocchio.buildModelFromUrdf(self.urdf_path)
        self.model_data = self.model.createData()
        self.joint_id = self.model.getJointId("ee_link") - 1
        self.eps = 1e-4
        self.IT_MAX = 5000
        self.DT = 1e-1
        self.damp = 1e-12

    def qpos_to_limits(self, q: np.ndarray, upperPositionLimit: np.ndarray,
                       lowerPositionLimit: np.ndarray, joint_seed: np.ndarray,
                       ik_weight: np.ndarray):
        """Adjusts the joint positions (q) to be within specified limits and as close as possible to the joint seed,  
        while minimizing the total weighted difference.  
    
        Args:  
            q (np.ndarray): The original joint positions.  
            upperPositionLimit (np.ndarray): The upper limits for the joint positions.  
            lowerPositionLimit (np.ndarray): The lower limits for the joint positions.  
            joint_seed (np.ndarray): The desired (seed) joint positions.  
            ik_weight (np.ndarray): The weights to apply for each joint in the total difference calculation.  
    
        Returns:  
            np.ndarray: The adjusted joint positions within the specified limits.  
        """
        qpos_limit = np.copy(q)
        best_qpos_limit = np.copy(q)
        best_total_q_diff = float('inf')

        if ik_weight is None:
            ik_weight = np.ones_like(q)

        for i in range(len(q)):
            # Generate multiple candidates by adding or subtracting 2*pi multiples
            candidates = []
            for k in range(
                    -5, 6
            ):  # You can adjust the range of k to explore more possibilities
                candidate = q[i] + k * 2 * np.pi
                if lowerPositionLimit[i] <= candidate <= upperPositionLimit[i]:
                    candidates.append(candidate)

            # If no candidates are within the limits, just use the original value adjusted with 2*pi multiples
            if not candidates:
                candidate = (q[i] - joint_seed[i]) % (2 * np.pi) + joint_seed[i]
                while candidate < lowerPositionLimit[i]:
                    candidate += 2 * np.pi
                while candidate > upperPositionLimit[i]:
                    candidate -= 2 * np.pi
                candidates.append(candidate)

            # Find the candidate that gives the smallest total_q_diff
            best_candidate_diff = float('inf')
            best_candidate = candidates[0]
            for candidate in candidates:
                qpos_limit[i] = candidate
                total_q_diff = np.sum(
                    np.abs(qpos_limit - joint_seed) * ik_weight)
                if total_q_diff < best_candidate_diff:
                    best_candidate_diff = total_q_diff
                    best_candidate = candidate

            qpos_limit[i] = best_candidate
            if best_candidate_diff < best_total_q_diff:
                best_total_q_diff = best_candidate_diff
                best_qpos_limit = np.copy(qpos_limit)

        return best_qpos_limit

    def get_ik(self,
               target_pose: np.ndarray,
               joint_seed: np.ndarray,
               ik_weight: np.ndarray = None):
        """Computes the inverse kinematics for a given target pose."""
        if not isinstance(target_pose,
                          np.ndarray) or target_pose.shape != (4, 4):
            raise ValueError("target_pose must be a 4x4 numpy array")
        if not isinstance(joint_seed, np.ndarray):
            raise ValueError("joint_seed must be of type np.ndarray")
        target_pose_SE3 = pinocchio.SE3(target_pose)
        q = deepcopy(joint_seed).astype(np.float64)

        for i in range(self.IT_MAX):
            pinocchio.forwardKinematics(self.model, self.model_data, q)
            dMi = target_pose_SE3.actInv(self.model_data.oMi[self.joint_id])
            err = pinocchio.log(dMi).vector
            if norm(err) < self.eps:
                print("Pin:Convergence achieved!")
                
                q = self.qpos_to_limits(q, self.model.upperPositionLimit,
                                        self.model.lowerPositionLimit,
                                        joint_seed, ik_weight)
                return True, q

            J = pinocchio.computeJointJacobian(self.model, self.model_data, q,
                                               self.joint_id)
            v = -J.T.dot(solve(J.dot(J.T) + self.damp * np.eye(6), err))
            q = pinocchio.integrate(self.model, q, v * self.DT)
            if not i % 10:
                print("Pin:{} error = {}!".format(i, err.T))

        print(
            "Pin:The iterative algorithm has not reached convergence to the desired precision"
        )
        return False, q


if __name__ == "__main__":
    np.set_printoptions(4, suppress=True)
	urdf_path = "../Agile/Diana_7/Diana_7.urdf"
    kin_solver = pinocchio_kinematics(urdf_path)

    pose1 = np.array([[-0., -1., 0., 0.2465], [-1., -0., -0., -0.1029],
                      [0., 0., -1., 0.9301], [0., 0., 0., 1.]])
    joint_seed = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
    res, joint = kin_solver.get_ik(pose1, joint_seed)

    embed()