实验课

一些信息

机器人离线编程与仿真
0167103

解析

见此页

代码

kinematic_invforw2_detail.py
# %% 导入库 (Import Libraries)
import numpy as np
import sympy as sp
import math
import matplotlib.pyplot as plt
# mpl_toolkits 在新版本 matplotlib 中不再需要显式导入 Axes3D
# from mpl_toolkits.mplot3d import Axes3D # 用于 3D 绘图 (For 3D plotting)
import time # 用于动画暂停 (For animation pause)

# 使用 SymPy 的美观打印 (Use SymPy's pretty printing)
sp.init_printing(use_unicode=True)

# %% 机器人参数 (Robot Parameters)
def get_robot_params():
    """返回包含机器人尺度参数的字典"""
    return {
        'a11': 135.0,   # 移动平台上连接点到平台中心的距离
        'a22': 135.0,   # 同上
        'a33': 135.0,   # 同上
        'b11': 570.0,   # 固定平台上连接点到平台中心的距离
        'b22': 320.0,   # 同上
        'b33': 320.0,   # 同上
        'e': 345.0      # 中心柱固定长度
    }

# %% 位置逆解函数 (Inverse Kinematics Function)
def inverse_kinematics(xp, yp, zp, params):
    """
    计算机器人逆运动学。
    输入:
        xp, yp, zp: 末端执行器目标位置 (float)
        params: 包含机器人尺寸的字典 (dict)
    输出:
        一个包含逆解结果的字典,如果无法求解则返回 None:
        {
            'q1', 'q2', 'q3': 驱动杆计算长度 (float) - 注意其定义可能不标准
            'q4': 中心杆可变长度 (float)
            'theta1', 'theta2': 平台姿态角 (rad) (float)
            'rp': 末端位置向量 (numpy.ndarray, shape (3,1))
            'R4': 移动平台旋转矩阵 (numpy.ndarray, shape (3,3))
            'w4': 中心杆方向向量 (numpy.ndarray, shape (3,1))
            'error': 错误信息 (str or None)
        }
    """
    a11 = params['a11']
    a22 = params['a22']
    a33 = params['a33']
    b11 = params['b11']
    b22 = params['b22']
    b33 = params['b33']
    e = params['e']

    rp = np.array([[xp], [yp], [zp]]) # 列向量
    norm_rp = np.linalg.norm(rp)

    if norm_rp < e - 1e-6: # 添加容差以避免浮点问题
        return {'error': f"警告:末端位置 (范数 {norm_rp:.2f}) 在中心柱固定长度 (e={e}) 范围内,无法达到。"}
        # 返回 None 或包含错误信息的字典

    q4 = norm_rp - e
    if q4 < 0: q4 = 0 # q4 代表可变长度,不能为负

    # 避免除以零
    denominator = q4 + e
    if abs(denominator) < 1e-9:
        return {'error': "错误:q4 + e 接近零,无法计算角度。"}

    # 计算 theta2
    sin_theta2 = xp / denominator
    sin_theta2 = np.clip(sin_theta2, -1.0, 1.0) # 限制范围
    theta2 = np.arcsin(sin_theta2)

    # 计算 theta1
    cos_theta2 = np.cos(theta2)
    if abs(cos_theta2) < 1e-9:
        #  theta2 接近 +/- pi/2 (平台几乎垂直于 Z )
        # 此时偏航角定义可能不稳定,通常发生在奇异点附近
        # 如果 yp  zp 同时接近 0,则 theta1 未定义,可设为 0
        if abs(yp) < 1e-9 and abs(zp) < 1e-9:
            theta1 = 0.0
            # print("警告:cos(theta2) 接近零,且 yp, zp 接近零,theta1 设为 0。")
        else:
            # 使用 atan2 更稳健地处理四象限
            theta1 = np.arctan2(-yp, zp)
            # print("警告:cos(theta2) 接近零,使用 atan2(-yp, zp) 计算 theta1。")
    else:
        arg1 = (-yp / denominator) / cos_theta2
        arg2 = (zp / denominator) / cos_theta2
        theta1 = np.arctan2(arg1, arg2)

    # 计算旋转矩阵 R4
    c1, s1 = np.cos(theta1), np.sin(theta1)
    c2, s2 = np.cos(theta2), np.sin(theta2)
    R4 = np.array([
        [c2,   0,  s2],
        [s1*s2, c1, -s1*c2],
        [-c1*s2, s1, c1*c2]
    ])

    # 固定平台连接点 (在固定坐标系)
    b1 = np.array([[0], [-b11], [0]])
    b2 = np.array([[b22], [0], [0]])
    b3 = np.array([[-b33], [0], [0]])

    # 移动平台连接点 (在移动平台自身坐标系)
    a10 = np.array([[0], [-a11], [0]])
    a20 = np.array([[a22], [0], [0]])
    a30 = np.array([[-a33], [0], [0]])

    # 移动平台连接点 (在固定坐标系中的姿态,相对平台中心 rp)
    a1 = R4 @ a10
    a2 = R4 @ a20
    a3 = R4 @ a30

    # 中心杆方向向量 w4 (固定坐标系)
    w4 = np.array([[s2], [-s1*c2], [c1*c2]])
    # 验证 w4 是否等于 rp / norm(rp)
    # if norm_rp > 1e-6:
    #     w4_check = rp / norm_rp
    #     if not np.allclose(w4, w4_check):
    #          print(f"警告: w4 计算不一致! w4={w4.flatten()}, rp/norm(rp)={w4_check.flatten()}")

    # 计算驱动杆长度 q1, q2, q3 (根据原始 MATLAB 公式)
    # 再次注意:这个公式可能不是标准的物理连杆长度
    # 标准长度 L_i = || (rp + a_i) - b_i ||
    q1 = np.linalg.norm(rp - b1 + a1 - e*w4)
    q2 = np.linalg.norm(rp - b2 + a2 - e*w4)
    q3 = np.linalg.norm(rp - b3 + a3 - e*w4)

    return {
        'q1': q1, 'q2': q2, 'q3': q3,
        'q4': q4, 'theta1': theta1, 'theta2': theta2,
        'rp': rp, 'R4': R4, 'w4': w4,
        'error': None
    }

# %% 位置正解 - 符号设置与数值函数生成 (Forward Kinematics - Symbolic Setup)
def define_symbolic_fk(params, q1_in, q2_in, q3_in):
    """
    定义正解的符号方程、雅可比矩阵,并返回数值计算函数。
    输入:
        params: 机器人参数字典
        q1_in, q2_in, q3_in: 输入的驱动杆计算长度
    输出:
        (Func_sym, J_sym, num_func, num_jac, X_sym)
        Func_sym: 符号方程组 (SymPy Matrix)
        J_sym: 符号雅可比矩阵 (SymPy Matrix)
        num_func: Func_sym 的数值计算函数 (callable)
        num_jac: J_sym 的数值计算函数 (callable)
        X_sym: 符号变量向量 [q4, theta1, theta2] (SymPy Matrix)
    """
    a11 = params['a11']
    a22 = params['a22']
    a33 = params['a33']
    b11 = params['b11']
    b22 = params['b22']
    b33 = params['b33']
    # e = params['e'] # e 不直接出现在 F, G, H 方程中

    # 定义符号变量
    q4_sym, theta1_sym, theta2_sym = sp.symbols('q4_sym theta1_sym theta2_sym')
    X_sym = sp.Matrix([q4_sym, theta1_sym, theta2_sym])

    # 定义约束方程 F, G, H (来自连杆闭环)
    # 这些方程将输入的 q1_in, q2_in, q3_in 与未知的 q4, theta1, theta2 联系起来
    F_sym = a11**2 + b11**2 + q4_sym**2 - q1_in**2 \
            - 2*a11*b11*sp.cos(theta1_sym) \
            - 2*b11*q4_sym*sp.sin(theta1_sym)*sp.cos(theta2_sym)

    G_sym = a22**2 + b22**2 + q4_sym**2 - q2_in**2 \
            - 2*b22*q4_sym*sp.sin(theta2_sym) \
            - 2*a22*b22*sp.cos(theta2_sym)

    H_sym = a33**2 + b33**2 + q4_sym**2 - q3_in**2 \
            + 2*b33*q4_sym*sp.sin(theta2_sym) \
            - 2*a33*b33*sp.cos(theta2_sym)

    Func_sym = sp.Matrix([F_sym, G_sym, H_sym])

    # 计算雅可比矩阵 J = d(Func)/d(X)
    J_sym = Func_sym.jacobian(X_sym)

    # 转换为快速数值函数
    num_func = sp.lambdify((q4_sym, theta1_sym, theta2_sym), Func_sym, 'numpy')
    num_jac = sp.lambdify((q4_sym, theta1_sym, theta2_sym), J_sym, 'numpy')

    return Func_sym, J_sym, num_func, num_jac, X_sym

# %% 位置正解 - 牛顿迭代求解器 (Forward Kinematics - Newton Solver)
def forward_kinematics(q1_in, q2_in, q3_in, params, initial_guess, max_iter=100, tolerance=1e-6):
    """
    使用牛顿法迭代求解正运动学。
    输入:
        q1_in, q2_in, q3_in: 驱动杆计算长度 (float)
        params: 机器人参数字典 (dict)
        initial_guess: [q4_guess, theta1_guess, theta2_guess] (list or tuple)
        max_iter: 最大迭代次数 (int)
        tolerance: 收敛阈值 (float)
    输出:
        一个包含正解结果的字典,如果失败则包含错误信息:
        {
            'xp_fk', 'yp_fk', 'zp_fk': 计算得到的末端位置 (float)
            'q4_k', 'theta1_k', 'theta2_k': 计算得到的姿态变量 (float)
            'iterations': 实际迭代次数 (int)
            'final_error_norm': 最终方程误差范数 (float)
            'converged': 是否收敛 (bool)
            'error': 错误信息 (str or None)
        }
    """
    e = params['e']

    # 获取符号定义和数值函数
    Func_sym, J_sym, num_func, num_jac, X_sym = define_symbolic_fk(params, q1_in, q2_in, q3_in)

    # 初始猜测值
    q4_k, theta1_k, theta2_k = initial_guess
    X_k = np.array(initial_guess, dtype=float) # 使用 numpy 数组

    iterations = 0
    converged = False
    final_error_norm = float('inf')

    # print("\n--- 开始牛顿迭代求解正解 ---")
    # print(f"初始猜测: q4={q4_k:.4f}, theta1={theta1_k:.4f}, theta2={theta2_k:.4f}")
    # print(f"目标 q1={q1_in:.4f}, q2={q2_in:.4f}, q3={q3_in:.4f}")

    for i in range(max_iter):
        iterations = i
        # 计算当前点的函数值 F(X_k) (需要是列向量)
        F_k_mat = num_func(X_k[0], X_k[1], X_k[2])
        F_k = F_k_mat.flatten() # 转换为 1D 数组

        # 检查收敛性
        current_error_norm = np.linalg.norm(F_k)
        # print(f"Iter {i}: q4={X_k[0]:.6f}, t1={X_k[1]:.6f}, t2={X_k[2]:.6f}, ErrorNorm={current_error_norm:.6e}")
        if current_error_norm < tolerance:
            converged = True
            final_error_norm = current_error_norm
            # print(f"\n在 {i} 次迭代后收敛。")
            break

        # 计算当前点的雅可比矩阵 J(X_k)
        J_k = num_jac(X_k[0], X_k[1], X_k[2])

        # 求解线性方程组 J(X_k) * delta_X = -F(X_k)
        try:
            delta_X = np.linalg.solve(J_k, -F_k)
        except np.linalg.LinAlgError:
            # print(f"\n迭代 {i} 时雅可比矩阵奇异,无法求解。检查初始猜测值或模型。")
            return {
                'xp_fk': None, 'yp_fk': None, 'zp_fk': None,
                'q4_k': X_k[0], 'theta1_k': X_k[1], 'theta2_k': X_k[2],
                'iterations': i, 'final_error_norm': current_error_norm,
                'converged': False, 'error': "雅可比矩阵奇异"
            }

        # 更新变量 X_{k+1} = X_k + delta_X
        X_k += delta_X

    else: # while 循环正常结束 ( break),意味着达到最大迭代次数
        final_error_norm = np.linalg.norm(num_func(X_k[0], X_k[1], X_k[2]).flatten())
        # print(f"\n达到最大迭代次数 {max_iter},可能未收敛。")
        # print(f"当前误差范数: {final_error_norm:.6e}")
        return {
            'xp_fk': None, 'yp_fk': None, 'zp_fk': None,
            'q4_k': X_k[0], 'theta1_k': X_k[1], 'theta2_k': X_k[2],
            'iterations': max_iter, 'final_error_norm': final_error_norm,
            'converged': False, 'error': "达到最大迭代次数"
        }

    # 迭代成功,计算最终的末端执行器位置
    q4_final, theta1_final, theta2_final = X_k
    xp_fk = (q4_final + e) * np.sin(theta2_final)
    yp_fk = (q4_final + e) * (-np.sin(theta1_final) * np.cos(theta2_final))
    zp_fk = (q4_final + e) * np.cos(theta1_final) * np.cos(theta2_final)

    return {
        'xp_fk': xp_fk, 'yp_fk': yp_fk, 'zp_fk': zp_fk,
        'q4_k': q4_final, 'theta1_k': theta1_final, 'theta2_k': theta2_final,
        'iterations': iterations, 'final_error_norm': final_error_norm,
        'converged': True, 'error': None
    }

# %% 可视化函数 (Visualization Function)
def plot_robot(ax, rp, R4, params, title="Robot Configuration"):
    """
    在给定的 3D 坐标轴上绘制机器人结构。
    输入:
        ax: Matplotlib 3D 坐标轴对象 (Axes3D)
        rp: 末端位置向量 (numpy.ndarray, shape (3,1))
        R4: 移动平台旋转矩阵 (numpy.ndarray, shape (3,3))
        params: 机器人参数字典 (dict)
        title: 图表标题 (str)
    """
    a11 = params['a11']
    a22 = params['a22']
    a33 = params['a33']
    b11 = params['b11']
    b22 = params['b22']
    b33 = params['b33']
    e = params['e']

    # 清除之前的绘图
    ax.cla()

    # --- 计算关键点坐标 ---
    # 原点 O
    O = np.array([0, 0, 0])
    # 固定平台连接点 B1, B2, B3 (在固定坐标系)
    B1 = np.array([0, -b11, 0])
    B2 = np.array([b22, 0, 0])
    B3 = np.array([-b33, 0, 0])
    # 移动平台连接点 A10, A20, A30 (在移动平台自身坐标系)
    A10 = np.array([0, -a11, 0])
    A20 = np.array([a22, 0, 0])
    A30 = np.array([-a33, 0, 0])
    # 移动平台连接点 A1, A2, A3 (相对平台中心 rp 的向量,在固定坐标系)
    A1_vec = R4 @ A10.reshape(3, 1)
    A2_vec = R4 @ A20.reshape(3, 1)
    A3_vec = R4 @ A30.reshape(3, 1)
    # 移动平台连接点 A1', A2', A3' 的绝对坐标 (在固定坐标系)
    P = rp.flatten() # 末端平台中心点
    A1_prime = P + A1_vec.flatten()
    A2_prime = P + A2_vec.flatten()
    A3_prime = P + A3_vec.flatten()
    # 中心柱顶端 E (固定坐标系)
    # 需要 w4,可以从 IK 结果获取,或从 FK 结果 (theta1, theta2) 重新计算
    s2 = np.sin(np.arcsin(np.clip(P[0] / np.linalg.norm(P), -1, 1))) # 近似 theta2
    # 更准确地,如果知道 theta1, theta2
    ik_result_temp = inverse_kinematics(P[0], P[1], P[2], params) # 临时计算获取角度
    if ik_result_temp and ik_result_temp['error'] is None:
        theta1_vis = ik_result_temp['theta1']
        theta2_vis = ik_result_temp['theta2']
        c1_vis, s1_vis = np.cos(theta1_vis), np.sin(theta1_vis)
        c2_vis, s2_vis = np.cos(theta2_vis), np.sin(theta2_vis)
        w4_vis = np.array([s2_vis, -s1_vis*c2_vis, c1_vis*c2_vis])
        E = e * w4_vis
    else: # 如果逆解失败或 P 在原点,用 Z 轴近似
         w4_vis = np.array([0, 0, 1]) if np.linalg.norm(P) < 1e-6 else P / np.linalg.norm(P)
         E = e * w4_vis


    # --- 绘制结构 ---
    # 固定平台 (灰色虚线)
    fixed_platform_pts = np.array([B1, B2, B3, B1])
    ax.plot(fixed_platform_pts[:, 0], fixed_platform_pts[:, 1], fixed_platform_pts[:, 2], 'k--', label='Fixed Platform', color='gray')
    ax.scatter(fixed_platform_pts[:-1, 0], fixed_platform_pts[:-1, 1], fixed_platform_pts[:-1, 2], c='black', marker='s', s=50) # 连接点 B_i

    # 移动平台 (蓝色实线)
    moving_platform_pts = np.array([A1_prime, A2_prime, A3_prime, A1_prime])
    ax.plot(moving_platform_pts[:, 0], moving_platform_pts[:, 1], moving_platform_pts[:, 2], 'b-', label='Moving Platform')
    ax.scatter(moving_platform_pts[:-1, 0], moving_platform_pts[:-1, 1], moving_platform_pts[:-1, 2], c='blue', marker='o', s=50) # 连接点 A'_i

    # 驱动杆 (红色实线) - 绘制物理连杆 B_i  A'_i
    ax.plot([B1[0], A1_prime[0]], [B1[1], A1_prime[1]], [B1[2], A1_prime[2]], 'r-', label='Driving Rods')
    ax.plot([B2[0], A2_prime[0]], [B2[1], A2_prime[1]], [B2[2], A2_prime[2]], 'r-')
    ax.plot([B3[0], A3_prime[0]], [B3[1], A3_prime[1]], [B3[2], A3_prime[2]], 'r-')

    # 中心柱 (绿色实线)
    ax.plot([O[0], E[0]], [O[1], E[1]], [O[2], E[2]], 'g-', linewidth=3, label='Central Column (Fixed Part)')
    ax.plot([E[0], P[0]], [E[1], P[1]], [E[2], P[2]], 'g--', linewidth=2, label='Central Column (Variable Part)') # 可变部分

    # 标出末端点 P
    ax.scatter(P[0], P[1], P[2], c='magenta', marker='*', s=100, label='End Effector (P)')
    # 标出原点 O
    ax.scatter(O[0], O[1], O[2], c='black', marker='x', s=50, label='Origin (O)')

    # --- 设置绘图属性 ---
    ax.set_xlabel('X (mm)')
    ax.set_ylabel('Y (mm)')
    ax.set_zlabel('Z (mm)')
    ax.set_title(title)

    # 设置大致的坐标轴范围,可以根据需要调整
    max_range = max(b11, b22, b33, np.linalg.norm(rp) + max(a11,a22,a33)) + 50
    ax.set_xlim(-max_range, max_range)
    ax.set_ylim(-max_range, max_range)
    ax.set_zlim(0, max(e+500, np.linalg.norm(rp)+100)) # Z 轴通常从 0 开始

    # 尝试设置等比例轴 ( 3D 中可能效果有限)
    try:
        ax.set_aspect('equal', adjustable='box')
    except NotImplementedError:
        print("警告: 3D 等比例轴可能不受支持。使用自动缩放。")
        # ax.axis('auto') # 默认行为
        # 手动设置比例接近
        x_limits = ax.get_xlim()
        y_limits = ax.get_ylim()
        z_limits = ax.get_zlim()
        x_range = abs(x_limits[1] - x_limits[0])
        y_range = abs(y_limits[1] - y_limits[0])
        z_range = abs(z_limits[1] - z_limits[0])
        plot_radius = 0.5 * max([x_range, y_range, z_range])
        mid_x = 0.5 * (x_limits[0] + x_limits[1])
        mid_y = 0.5 * (y_limits[0] + y_limits[1])
        mid_z = 0.5 * (z_limits[0] + z_limits[1])
        ax.set_xlim(mid_x - plot_radius, mid_x + plot_radius)
        ax.set_ylim(mid_y - plot_radius, mid_y + plot_radius)
        ax.set_zlim(mid_z - plot_radius, mid_z + plot_radius)


    ax.legend()
    ax.grid(True)

# %% --- 主程序与测试用例 ---

if __name__ == "__main__":

    # 获取机器人参数
    robot_params = get_robot_params()
    print("--- 机器人参数 ---")
    for key, value in robot_params.items():
        print(f"{key}: {value}")

    # === 测试 1: 单点逆解与正解验证 ===
    print("\n\n=== 测试 1: 单点逆解与正解 ===")
    # 目标末端位置 (与原始代码相同)
    xp_target = 10.0
    yp_target = -20.0
    zp_target = 1234.0
    print(f"\n--- 目标位置 (IK Input) ---")
    print(f"xp = {xp_target:.4f}")
    print(f"yp = {yp_target:.4f}")
    print(f"zp = {zp_target:.4f}")

    # --- 运行逆解 ---
    ik_result = inverse_kinematics(xp_target, yp_target, zp_target, robot_params)

    if ik_result['error']:
        print(f"\n逆解错误: {ik_result['error']}")
    else:
        print("\n--- 逆解结果 (IK Output) ---")
        print(f"计算得到的 q1: {ik_result['q1']:.4f} (注意: 此 q1 定义可能非标准)")
        print(f"计算得到的 q2: {ik_result['q2']:.4f} (注意: 此 q2 定义可能非标准)")
        print(f"计算得到的 q3: {ik_result['q3']:.4f} (注意: 此 q3 定义可能非标准)")
        print(f"计算得到的 q4 (中心杆可变长度): {ik_result['q4']:.4f}")
        print(f"计算得到的 theta1 (rad): {ik_result['theta1']:.4f}")
        print(f"计算得到的 theta2 (rad): {ik_result['theta2']:.4f}")

        # --- 使用逆解结果运行正解 ---
        q1_ik = ik_result['q1']
        q2_ik = ik_result['q2']
        q3_ik = ik_result['q3']

        # 正解需要初始猜测值,可以使用逆解结果加扰动,或一个通用值
        # 使用逆解结果作为猜测值通常收敛最快
        fk_initial_guess = [ik_result['q4'], ik_result['theta1'], ik_result['theta2']]
        # 或者使用一个固定的猜测值
        # fk_initial_guess = [1000.0, 0.1, 0.1]

        print("\n--- 运行正解 (FK) ---")
        print(f"输入 q1={q1_ik:.4f}, q2={q2_ik:.4f}, q3={q3_ik:.4f}")
        print(f"初始猜测 [q4, t1, t2]: [{fk_initial_guess[0]:.4f}, {fk_initial_guess[1]:.4f}, {fk_initial_guess[2]:.4f}]")

        # 显示符号方程 (只显示一次)
        if 'fk_sym_defined' not in globals(): # 检查变量是否已定义
             Func_sym, J_sym, _, _, X_sym = define_symbolic_fk(robot_params, q1_ik, q2_ik, q3_ik)
             print("\n正解约束方程 F(q4, t1, t2) = 0:")
             sp.pprint(Func_sym)
             print("\n正解雅可比矩阵 J = dF/dX:")
             sp.pprint(J_sym)
             fk_sym_defined = True # 标记已定义

        fk_result = forward_kinematics(q1_ik, q2_ik, q3_ik, robot_params, fk_initial_guess)

        if fk_result['error']:
            print(f"\n正解错误: {fk_result['error']}")
            print(f"迭代次数: {fk_result['iterations']}")
            print(f"最终误差范数: {fk_result['final_error_norm']:.6e}")
        else:
            print("\n--- 正解结果 (FK Output) ---")
            print(f"迭代次数: {fk_result['iterations']}")
            print(f"是否收敛: {fk_result['converged']}")
            print(f"最终误差范数: {fk_result['final_error_norm']:.6e}")
            print(f"计算得到的 q4: {fk_result['q4_k']:.4f}")
            print(f"计算得到的 theta1 (rad): {fk_result['theta1_k']:.4f}")
            print(f"计算得到的 theta2 (rad): {fk_result['theta2_k']:.4f}")
            print(f"计算得到的 xp: {fk_result['xp_fk']:.4f}")
            print(f"计算得到的 yp: {fk_result['yp_fk']:.4f}")
            print(f"计算得到的 zp: {fk_result['zp_fk']:.4f}")

            # --- 对比 ---
            error_fk = np.sqrt((fk_result['xp_fk'] - xp_target)**2 +
                               (fk_result['yp_fk'] - yp_target)**2 +
                               (fk_result['zp_fk'] - zp_target)**2)
            print("\n--- 对比: 目标位置 vs 正解位置 ---")
            print(f"目标 (IK Input): xp={xp_target:.4f}, yp={yp_target:.4f}, zp={zp_target:.4f}")
            print(f"正解 (FK Output): xp={fk_result['xp_fk']:.4f}, yp={fk_result['yp_fk']:.4f}, zp={fk_result['zp_fk']:.4f}")
            print(f"位置误差 (欧氏距离): {error_fk:.6f}")

        # --- 可视化单点结果 ---
        print("\n--- 绘制单点姿态 ---")
        fig_single = plt.figure(figsize=(8, 8))
        ax_single = fig_single.add_subplot(111, projection='3d')
        plot_robot(ax_single, ik_result['rp'], ik_result['R4'], robot_params,
                   title=f"Robot Pose at ({xp_target:.1f}, {yp_target:.1f}, {zp_target:.1f})")
        plt.show(block=False) # 显示图形,非阻塞

    # === 测试 2: 轨迹示例 - 垂直升降 ===
    print("\n\n=== 测试 2: 轨迹示例 - 垂直升降 ===")
    fig_traj = plt.figure(figsize=(8, 8))
    ax_traj = fig_traj.add_subplot(111, projection='3d')
    plt.ion() # 开启交互模式,用于动画

    # 轨迹参数
    x_traj = 50.0
    y_traj = 50.0
    z_start = robot_params['e'] + 100 # 起始高度,略高于中心柱
    z_end = z_start + 600           # 结束高度
    num_steps = 50                  # 轨迹点数量
    pause_time = 0.05               # 每帧暂停时间

    print(f"轨迹: 从 ({x_traj}, {y_traj}, {z_start:.1f}) 到 ({x_traj}, {y_traj}, {z_end:.1f})")

    trajectory_points = [] # 存储轨迹点用于绘制

    try:
        for i in range(num_steps + 1):
            # 计算当前目标位置
            z_current = z_start + (z_end - z_start) * (i / num_steps)
            xp_traj = x_traj
            yp_traj = y_traj
            zp_traj = z_current

            # 运行逆解
            ik_traj_result = inverse_kinematics(xp_traj, yp_traj, zp_traj, robot_params)

            if ik_traj_result['error']:
                print(f"轨迹点 {i} 逆解错误: {ik_traj_result['error']} @ Z={zp_traj:.1f}")
                time.sleep(1) # 暂停一下让用户看到错误
                continue # 跳过这个点

            # 存储轨迹点
            trajectory_points.append(ik_traj_result['rp'].flatten())

            # 更新绘图
            plot_robot(ax_traj, ik_traj_result['rp'], ik_traj_result['R4'], robot_params,
                       title=f"Trajectory Step {i}/{num_steps} - Z={zp_traj:.1f}")

            # 绘制已经过的轨迹线
            if len(trajectory_points) > 1:
                traj_array = np.array(trajectory_points)
                ax_traj.plot(traj_array[:, 0], traj_array[:, 1], traj_array[:, 2], 'm:', label='Trajectory Path' if i==1 else "") # 只加一次标签

            plt.draw()
            plt.pause(pause_time)

    except KeyboardInterrupt:
        print("\n轨迹动画被用户中断。")
    finally:
        plt.ioff() # 关闭交互模式
        print("\n轨迹动画结束。关闭绘图窗口以继续。")
        plt.show() # 保持最后一个窗口打开,直到用户关闭


    # === 测试 3: (可选) 轨迹示例 - 水平圆周 ===
    # print("\n\n=== 测试 3: 轨迹示例 - 水平圆周 ===")
    # ... (类似测试 2 的结构,但改变 xp_traj 和 yp_traj 的计算方式)
    # radius = 200
    # z_circle = robot_params['e'] + 400
    # num_steps_circle = 60
    # fig_circle = plt.figure(figsize=(8, 8))
    # ax_circle = fig_circle.add_subplot(111, projection='3d')
    # plt.ion()
    # trajectory_circle = []
    # try:
    #     for i in range(num_steps_circle + 1):
    #         angle = 2 * np.pi * i / num_steps_circle
    #         xp_traj = radius * np.cos(angle)
    #         yp_traj = radius * np.sin(angle)
    #         zp_traj = z_circle
    #         # ... (运行IK, 绘图, 暂停) ...
    # except KeyboardInterrupt:
    #     print("\n圆周轨迹动画被用户中断。")
    # finally:
    #     plt.ioff()
    #     print("\n圆周轨迹动画结束。")
    #     plt.show()
kinematic_invforw2_detail.m
%% 位置正逆解模型 (Position Forward and Inverse Kinematics Model)
% 2017.11.14
clc            % 清除命令窗口 (Clear command window)
clear all      % 清除工作区所有变量 (Clear all variables in workspace)
close all      % 关闭所有图窗 (Close all figure windows)

%% 尺度参数 (Dimensional Parameters)
a11=135;       % 移动平台上连接点到平台中心的距离 (Distance from connection point to center of moving platform)
a22=135;       % 同上,不同连接点 (Same as above, different connection point)
a33=135;       % 同上,不同连接点 (Same as above, different connection point)
b11=570;       % 固定平台上连接点到平台中心的距离 (Distance from connection point to center of fixed platform)
b22=320;       % 同上,不同连接点 (Same as above, different connection point)
b33=320;       % 同上,不同连接点 (Same as above, different connection point)
e=345;         % 中心柱长度 (Length of central column)
dv=120;        % 可能是运动范围参数 (Possibly a motion range parameter)
pi=3.1415926;  % 圆周率 (Pi value)

%% 位置逆解 (Inverse Kinematics)
% 已知末端执行器位置,求各驱动杆的长度
% (Given end-effector position, calculate the length of each actuating rod)

xp=10          % 末端执行器位置X坐标 (X-coordinate of end-effector position)
yp=-20         % 末端执行器位置Y坐标 (Y-coordinate of end-effector position)
zp=1234        % 末端执行器位置Z坐标 (Z-coordinate of end-effector position)
rp=[xp;yp;zp]; % 末端执行器位置向量 (End-effector position vector)

q4=sqrt(xp*xp+yp*yp+zp*zp)-e;  % 计算q4,为中心连接杆的长度 (Calculate q4, length of central connecting rod)
theta2=asin(xp/(q4+e));        % 计算theta2,机构俯仰角 (Calculate theta2, pitch angle of mechanism)
theta1=atan2((-yp/(q4+e))/(cos(theta2)),(zp/(q4+e))/(cos(theta2)));  % 计算theta1,机构偏航角 (Calculate theta1, yaw angle of mechanism)

% 旋转矩阵R4,描述移动平台相对于固定平台的姿态
% (Rotation matrix R4, describing the attitude of moving platform relative to fixed platform)
R4=[cos(theta2)  0 sin(theta2);
    sin(theta1)*sin(theta2)   cos(theta1)    -sin(theta1)*cos(theta2);
   -cos(theta1)*sin(theta2)  sin(theta1)  cos(theta1)*cos(theta2)];

% 固定平台上三个连接点的位置矢量(在固定坐标系中)
% (Position vectors of three connection points on fixed platform in fixed coordinate system)
a10=[0;-a11;0];
a20=[a22;0;0];
a30=[-a33;0;0];

% 移动平台上三个连接点的位置矢量(在固定坐标系中)
% (Position vectors of three connection points on moving platform in fixed coordinate system)
b1=[0;-b11;0];
b2=[b22;0;0];
b3=[-b33;0;0];

% 将移动平台上的连接点位置变换到当前姿态
% (Transform connection points on moving platform to current attitude)
a1=R4*a10;
a2=R4*a20;
a3=R4*a30;

% 计算方向向量w4,表示中心连接杆的空间方向
% (Calculate direction vector w4, representing spatial direction of central connecting rod)
w4=[sin(theta2);-sin(theta1)*cos(theta2);cos(theta1)*cos(theta2)];

% 计算三条驱动杆的长度(q1,q2,q3)
% (Calculate the lengths of three driving rods (q1, q2, q3))
q1=norm(rp-b1+a1-e*w4)  % 第一条驱动杆长度 (Length of first driving rod)
q2=norm(rp-b2+a2-e*w4)  % 第二条驱动杆长度 (Length of second driving rod)
q3=norm(rp-b3+a3-e*w4)  % 第三条驱动杆长度 (Length of third driving rod)
qi=[q1;q2;q3];          % 驱动杆长度向量 (Vector of driving rod lengths)
 
%% 位置正解 (Forward Kinematics)
%% 迭代法 (Iterative Method)
% 已知三个驱动杆长度(q1,q2,q3),求末端执行器位置(xp,yp,zp)
% (Given three driving rod lengths (q1,q2,q3), calculate end-effector position (xp,yp,zp))

q4=1000;       % 初始猜测值 - 中心连接杆长度 (Initial guess - length of central connecting rod)
theta1=0.5;    % 初始猜测值 - 偏航角 (Initial guess - yaw angle)
theta2=0.5;    % 初始猜测值 - 俯仰角 (Initial guess - pitch angle)

% 计算约束方程F,G,H的初始值
% (Calculate initial values of constraint equations F, G, H)
F=a11*a11+b11*b11+q4*q4-q1*q1-2*a11*b11*cos(theta1)-2*b11*q4*sin(theta1)*cos(theta2);
G=a22*a22+b22*b22+q4*q4-q2*q2-2*b22*q4*sin(theta2)-2*a22*b22*cos(theta2);
H=a33*a33+b33*b33+q4*q4-q3*q3+2*b33*q4*sin(theta2)-2*a33*b33*cos(theta2);

i=0;  % 迭代计数器 (Iteration counter)

% 牛顿-拉夫森迭代求解方程组
% (Newton-Raphson iteration to solve the system of equations)
while(abs(F)>=0.0001|abs(G)>=0.0001|abs(H)>=0.0001)  % 当任一方程误差超过阈值时继续迭代 (Continue iteration when any equation error exceeds threshold)
    % 计算各约束方程对各变量的偏导数
    % (Calculate partial derivatives of each constraint equation with respect to each variable)
    dF1=2*q4-2*b11*sin(theta1)*cos(theta2);         % dF/dq4
    dF2=-2*b11*q4*cos(theta1)*cos(theta2)+2*a11*b11*sin(theta1);  % dF/dtheta1
    dF3=2*q4*b11*sin(theta1)*sin(theta2);           % dF/dtheta2
    
    dG1=2*q4-2*b22*sin(theta2);                     % dG/dq4
    dG2=0;                                          % dG/dtheta1
    dG3=-2*b22*q4*cos(theta2)+2*a22*b22*sin(theta2); % dG/dtheta2
    
    dH1=2*q4+2*b33*sin(theta2);                     % dH/dq4
    dH2=0;                                          % dH/dtheta1
    dH3=2*b33*q4*cos(theta2)+2*a33*b33*sin(theta2); % dH/dtheta2
    
    % 利用克莱默法则计算变量的增量
    % (Using Cramer's rule to calculate variable increments)
    dq4=(G*(dF2*dH3 - dF3*dH2))/(dF1*dG2*dH3 - dF1*dG3*dH2 - dF2*dG1*dH3 + dF2*dG3*dH1 + dF3*dG1*dH2 - dF3*dG2*dH1) - (F*(dG2*dH3 - dG3*dH2))/(dF1*dG2*dH3 - dF1*dG3*dH2 - dF2*dG1*dH3 + dF2*dG3*dH1 + dF3*dG1*dH2 - dF3*dG2*dH1) - (H*(dF2*dG3 - dF3*dG2))/(dF1*dG2*dH3 - dF1*dG3*dH2 - dF2*dG1*dH3 + dF2*dG3*dH1 + dF3*dG1*dH2 - dF3*dG2*dH1);
    dtheta1=(F*(dG1*dH3 - dG3*dH1))/(dF1*dG2*dH3 - dF1*dG3*dH2 - dF2*dG1*dH3 + dF2*dG3*dH1 + dF3*dG1*dH2 - dF3*dG2*dH1) - (G*(dF1*dH3 - dF3*dH1))/(dF1*dG2*dH3 - dF1*dG3*dH2 - dF2*dG1*dH3 + dF2*dG3*dH1 + dF3*dG1*dH2 - dF3*dG2*dH1) + (H*(dF1*dG3 - dF3*dG1))/(dF1*dG2*dH3 - dF1*dG3*dH2 - dF2*dG1*dH3 + dF2*dG3*dH1 + dF3*dG1*dH2 - dF3*dG2*dH1);
    dtheta2=(G*(dF1*dH2 - dF2*dH1))/(dF1*dG2*dH3 - dF1*dG3*dH2 - dF2*dG1*dH3 + dF2*dG3*dH1 + dF3*dG1*dH2 - dF3*dG2*dH1) - (F*(dG1*dH2 - dG2*dH1))/(dF1*dG2*dH3 - dF1*dG3*dH2 - dF2*dG1*dH3 + dF2*dG3*dH1 + dF3*dG1*dH2 - dF3*dG2*dH1) - (H*(dF1*dG2 - dF2*dG1))/(dF1*dG2*dH3 - dF1*dG3*dH2 - dF2*dG1*dH3 + dF2*dG3*dH1 + dF3*dG1*dH2 - dF3*dG2*dH1);
    
    % 更新变量值
    % (Update variable values)
    q4=q4+dq4;
    theta1=theta1+dtheta1;
    theta2=theta2+dtheta2;
    i=i+1;  % 迭代次数加1 (Increment iteration count)
    
    % 用更新后的变量重新计算约束方程的值
    % (Recalculate values of constraint equations with updated variables)
    F=a11*a11+b11*b11+q4*q4-q1*q1-2*a11*b11*cos(theta1)-2*b11*q4*sin(theta1)*cos(theta2);
    G=a22*a22+b22*b22+q4*q4-q2*q2-2*b22*q4*sin(theta2)-2*a22*b22*cos(theta2);
    H=a33*a33+b33*b33+q4*q4-q3*q3+2*b33*q4*sin(theta2)-2*a33*b33*cos(theta2);
end

% 计算并输出末端执行器的位置坐标
% (Calculate and output position coordinates of end-effector)
xp=(q4+e)*sin(theta2)
yp=(q4+e)*(-sin(theta1)*cos(theta2))
zp=(q4+e)*cos(theta1)*cos(theta2)