简介:本资源聚焦六自由度机器人轨迹规划与三维可视化仿真,面向机器人工程、自动化及控制专业本科生、研究生与初入行业的工程师,解决从理论建模到运动可视化落地的关键实践断层问题。压缩包共23个文件,含11个SolidWorks零件(.sldprt)、3个装配体(.sldasm)、7个STL模型用于仿真导入,以及2个MATLAB脚本(.m)实现轨迹生成与Plot3D数据接口适配,整体44.45MB,结构清晰覆盖机械建模—运动规划—仿真呈现全链路。已有1004人学习下载,资源提供可直接运行的轨迹规划代码、完整机器人三维参数化模型(含底座、五段连杆、夹爪及舵机部件)、多组预设路径数据及Plot3D可视化配置说明,支持用户快速复现3D运动动画、调试关节插值参数、对比不同规划策略下的路径平滑性与避障效果,是深入理解6DOF运动学与工程仿真实践的高实用性参考包。
1. 六自由度机器人轨迹规划+Plot3D仿真:不是画个动图就叫仿真,而是让关节运动在三维空间里“可算、可验、可调”
你有没有试过:写完逆解公式,关节角度输出看着合理,一导入到机械臂模型里,末端却撞墙、抖动、甚至关节超限报警?或者用Matlab Robotics Toolbox画出一条光鲜的末端轨迹线,但没人敢信它真能跑通——因为没验证过每个时刻对应的六个关节角是否连续、平滑、满足动力学约束?这就是典型“轨迹规划失焦”:只盯着末端点,忘了六自由度本体是受运动学硬约束的刚体系统。本文讲的“六自由度机器人轨迹规划+Plot3D仿真”,核心不是炫技式三维动画,而是构建一个闭环验证链:从任务空间路径生成 → 时间参数化 → 关节空间映射 → 连续性/奇异性检查 → 最终用轻量级Plot3D(非ROS/Gazebo重型仿真)实时渲染关节位姿演化全过程。它适合正在做课程设计、毕业设计或工业现场快速原型验证的开发者——不需要部署真实机械臂,也能把轨迹方案“跑”得明明白白。重点在于:所有计算可复现、所有参数可调节、所有异常可定位。下面,我们就从最精简但完整的可执行流程开始。
2. 用Python+Matplotlib实现六自由度轨迹规划最小闭环:从DH建模到Plot3D动态渲染
2.1 基于标准DH参数建立六轴机器人运动学模型(以常见6R型为例)
六自由度机器人轨迹规划的第一道门槛,从来不是算法,而是建模本身是否准确。很多翻车案例,根源都在DH参数表抄错一行——比如把某个连杆偏距d写成0,而实际是-0.15m,结果整个工作空间偏移半米。我们不依赖现成URDF或ROS描述,而是用纯Python定义标准DH参数表,确保每一步都可控、可调试。
import numpy as np # 定义标准DH参数(以典型6R串联机械臂为例,单位:米/弧度) # [theta, d, a, alpha] —— 注意:此处theta为关节变量,其余为固定几何参数 DH_TABLE = np.array([ [0, 0.152, 0, np.pi/2], # Joint 1: base rotation [0, 0, 0.244, 0], # Joint 2: shoulder [0, 0, 0.213, 0], # Joint 3: elbow [0, 0.131, 0, np.pi/2], # Joint 4: wrist 1 [0, 0, 0, -np.pi/2], # Joint 5: wrist 2 [0, 0.085, 0, 0] # Joint 6: wrist 3 / end-effector ]) def dh_transform(theta, d, a, alpha): """根据DH参数计算单个连杆的齐次变换矩阵""" ct, st = np.cos(theta), np.sin(theta) ca, sa = np.cos(alpha), np.sin(alpha) return np.array([ [ct, -st*ca, st*sa, a*ct], [st, ct*ca, -ct*sa, a*st], [0, sa, ca, d], [0, 0, 0, 1] ]) def forward_kinematics(q): """前向运动学:输入6维关节角q,输出末端4x4齐次变换矩阵""" T = np.eye(4) for i in range(6): # DH表中theta列是变量,其余固定;注意第i关节对应DH_TABLE[i] theta_i = q[i] + DH_TABLE[i, 0] # 若DH表theta0为0,则q[i]即为实际关节角 d_i, a_i, alpha_i = DH_TABLE[i, 1], DH_TABLE[i, 2], DH_TABLE[i, 3] T_i = dh_transform(theta_i, d_i, a_i, alpha_i) T = T @ T_i return T逻辑说明:这段代码实现了标准Denavit-Hartenberg建模的核心。关键点有三:一是
DH_TABLE必须严格按连杆顺序排列,且单位统一(推荐全部用米和弧度);二是forward_kinematics函数中T = T @ T_i采用右乘,符合“从基座到末端”的坐标系变换链;三是theta_i = q[i] + DH_TABLE[i, 0]这一行,允许DH表中预设零位偏移(如某些厂商将零位定义在非垂直位置),这是工程实践中常被忽略的“玄学偏移”,不加这行,仿真永远对不上实物零位。
2.2 生成三次样条时间参数化轨迹:让末端运动真正“柔顺”
有了正向模型,下一步是给定起始与目标位姿,生成中间过渡路径。这里坚决不用“直线插补+硬切角”,那是教科书陷阱。真实机械臂怕的是加速度突变——它直接触发伺服报警。我们采用三次样条(Cubic Spline)对笛卡尔空间6维位姿进行时间参数化,再通过数值逆解映射回关节空间。
from scipy.interpolate import CubicSpline import matplotlib.pyplot as plt def pose_to_vector(pose_matrix): """将4x4齐次矩阵转为7维向量:[x,y,z,qx,qy,qz,qw](四元数表示旋转)""" pos = pose_matrix[:3, 3] R = pose_matrix[:3, :3] # 使用scipy内置函数转四元数(避免自己手推易错) from scipy.spatial.transform import Rotation quat = Rotation.from_matrix(R).as_quat() # [qx, qy, qz, qw] return np.hstack([pos, quat]) def generate_cartesian_spline(start_pose, end_pose, t_total=5.0, num_points=100): """生成笛卡尔空间三次样条轨迹(位置+朝向)""" t_vec = np.linspace(0, t_total, num_points) # 将起止位姿转为7维向量 start_vec = pose_to_vector(start_pose) end_vec = pose_to_vector(end_pose) # 对每个维度单独拟合三次样条(强制首尾一阶导为0,实现柔顺启停) splines = [] for i in range(7): y_data = np.array([start_vec[i], end_vec[i]]) t_data = np.array([0, t_total]) # 构造自然样条:两端二阶导为0 spline = CubicSpline(t_data, y_data, bc_type='natural') splines.append(spline) # 采样得到完整轨迹 traj_vec = np.array([spline(t_vec) for spline in splines]).T # (N, 7) # 转回齐次矩阵序列 poses = [] for vec in traj_vec: pos = vec[:3] quat = vec[3:] R = Rotation.from_quat(quat).as_matrix() T = np.eye(4) T[:3, :3] = R T[:3, 3] = pos poses.append(T) return t_vec, poses # 示例:设定起始与目标位姿(平移+绕Z轴旋转90度) T_start = np.eye(4) T_start[:3, 3] = [0.3, 0.0, 0.4] T_end = np.eye(4) T_end[:3, 3] = [0.1, 0.3, 0.5] T_end[:3, :3] = Rotation.from_euler('z', np.pi/2).as_matrix() t_vec, cartesian_traj = generate_cartesian_spline(T_start, T_end, t_total=4.0, num_points=80)参数说明:
t_total=4.0是总运动时间,直接影响最大速度与加速度峰值;num_points=80并非越多越好——后续逆解计算量随点数线性增长,80点已足够捕捉三次样条的平滑性;bc_type='natural'是关键,它强制轨迹两端加速度为零,避免启停冲击。如果你发现仿真中关节突然“弹跳”,大概率是这里用了bc_type='not-a-knot'或没设边界条件。
2.3 关节空间逆解与连续性筛选:拒绝“解出即结束”的粗暴思维
正向模型有了,笛卡尔轨迹也生成了,接下来是核心难点:对每个末端位姿求解6组可能的关节角(因存在多解),并从中选出全局连续、无奇异性、无超限的一条解序列。这不是一次逆解,而是一次“解链追踪”。
def inverse_kinematics_numerical(T_target, q_init=None, max_iter=50, tol=1e-4): """数值法逆解:基于雅可比伪逆的迭代求解(更鲁棒于解析解不存在的情况)""" if q_init is None: q = np.zeros(6) # 默认从零位开始 else: q = q_init.copy() for _ in range(max_iter): T_curr = forward_kinematics(q) # 计算末端误差(位置+旋转向量形式) err_pos = T_target[:3, 3] - T_curr[:3, 3] # 旋转误差:用矩阵对数近似(小角度下等价于旋转向量) R_err = T_target[:3, :3] @ T_curr[:3, :3].T # 使用scipy计算旋转向量 from scipy.spatial.transform import Rotation rot_err = Rotation.from_matrix(R_err).as_rotvec() # 3维旋转向量 err = np.hstack([err_pos, rot_err]) if np.linalg.norm(err) < tol: return q # 计算雅可比矩阵(数值微分,简单可靠) J = np.zeros((6, 6)) dq = 1e-4 for i in range(6): q_plus = q.copy() q_plus[i] += dq T_plus = forward_kinematics(q_plus) err_plus_pos = T_target[:3, 3] - T_plus[:3, 3] R_err_plus = T_target[:3, :3] @ T_plus[:3, :3].T rot_err_plus = Rotation.from_matrix(R_err_plus).as_rotvec() err_plus = np.hstack([err_plus_pos, rot_err_plus]) J[:, i] = (err_plus - err) / dq # 雅可比伪逆更新 J_pinv = np.linalg.pinv(J) dq_step = J_pinv @ err q = q + dq_step return None # 迭代失败 def track_inverse_solution(cartesian_traj, q_init=None): """对整条笛卡尔轨迹进行逆解,并保证解序列连续""" if q_init is None: q_init = np.zeros(6) q_traj = [] q_current = q_init.copy() for T in cartesian_traj: # 以当前解为初值,提高收敛率与连续性 q_sol = inverse_kinematics_numerical(T, q_init=q_current, max_iter=30) if q_sol is not None: # 检查关节限位(示例:±π/2, ±π, ±π/2, ±π, ±π/2, ±π) limits_low = np.array([-np.pi/2, -np.pi, -np.pi/2, -np.pi, -np.pi/2, -np.pi]) limits_high = np.array([np.pi/2, np.pi, np.pi/2, np.pi, np.pi/2, np.pi]) if np.all(q_sol >= limits_low) and np.all(q_sol <= limits_high): q_traj.append(q_sol) q_current = q_sol # 更新初值,为下一帧铺垫 else: print(f"Warning: joint limit violation at {len(q_traj)}, skipping...") break else: print(f"Failed to solve IK at step {len(q_traj)}") break return np.array(q_traj) # 执行逆解追踪 q_trajectory = track_inverse_solution(cartesian_traj, q_init=np.zeros(6)) print(f"Successfully solved {len(q_trajectory)} points out of {len(cartesian_traj)}")逻辑说明:这段代码体现了“轨迹规划”的工程本质——它不是数学题,而是带约束的优化问题。
track_inverse_solution函数的关键在于状态传递:每一帧逆解都以上一帧成功解作为初值,这极大提升了收敛稳定性,避免了解序列在某点“跳变”到另一分支;同时嵌入了实时关节限位检查,一旦超限立即中断,而不是硬算到底再报错。这是很多开源脚本缺失的“后悔药”机制。
3. Plot3D三维可视化:不只是画线,而是让每个关节轴、连杆、末端姿态“活”起来
3.1 构建可复用的3D机器人连杆模型渲染器
Plot3D不是Matplotlib的3D绘图功能,而是指用matplotlib.animation.FuncAnimation驱动的、面向机器人结构的专用3D渲染器。它必须能清晰表达:每个关节的旋转轴方向、连杆的物理长度、末端工具坐标系(TCP)的姿态。我们不画抽象线框,而是用圆柱体表示连杆、箭头表示坐标轴。
from mpl_toolkits.mplot3d import Axes3D from matplotlib.animation import FuncAnimation def plot_robot_3d(ax, q, dh_table=DH_TABLE, link_radius=0.01, axis_length=0.05): """在3D轴上绘制当前关节角q下的机器人构型""" # 清空当前轴(但保留坐标系设置) ax.cla() # 设置坐标系范围(根据DH参数估算工作空间) ax.set_xlim([-0.5, 0.5]) ax.set_ylim([-0.5, 0.5]) ax.set_zlim([0, 0.8]) ax.set_xlabel('X (m)') ax.set_ylabel('Y (m)') ax.set_zlabel('Z (m)') ax.grid(True) # 绘制基座(原点处小立方体) base_size = 0.05 x_base = np.array([0, base_size, base_size, 0, 0]) y_base = np.array([0, 0, base_size, base_size, 0]) z_base = np.array([0, 0, 0, 0, 0]) ax.plot(x_base, y_base, z_base, 'k-', linewidth=2) # 逐连杆绘制 T = np.eye(4) for i in range(6): theta_i = q[i] + dh_table[i, 0] d_i, a_i, alpha_i = dh_table[i, 1], dh_table[i, 2], dh_table[i, 3] T_i = dh_transform(theta_i, d_i, a_i, alpha_i) T = T @ T_i # 获取当前连杆末端坐标(即下一连杆原点) p_end = T[:3, 3] # 绘制连杆:从上一连杆末端(或基座)到当前末端 if i == 0: p_start = np.array([0, 0, 0]) else: # 上一连杆末端即当前T(未乘T_i前)的平移部分 p_start = (np.linalg.inv(T_i) @ T)[:3, 3] # 画连杆圆柱体(简化为线段+两端圆盘) ax.plot([p_start[0], p_end[0]], [p_start[1], p_end[1]], [p_start[2], p_end[2]], 'b-', linewidth=3, label=f'Link {i+1}' if i==0 else "") # 绘制当前关节坐标系(X,Y,Z轴箭头) R = T[:3, :3] origin = p_end ax.quiver(origin[0], origin[1], origin[2], R[0,0]*axis_length, R[1,0]*axis_length, R[2,0]*axis_length, color='r', arrow_length_ratio=0.1) ax.quiver(origin[0], origin[1], origin[2], R[0,1]*axis_length, R[1,1]*axis_length, R[2,1]*axis_length, color='g', arrow_length_ratio=0.1) ax.quiver(origin[0], origin[1], origin[2], R[0,2]*axis_length, R[1,2]*axis_length, R[2,2]*axis_length, color='b', arrow_length_ratio=0.1) # 绘制末端工具坐标系(TCP) ax.text(p_end[0], p_end[1], p_end[2]+0.02, 'TCP', color='purple', fontsize=10) # 创建动画 fig = plt.figure(figsize=(10, 8)) ax = fig.add_subplot(111, projection='3d') def animate(frame): if frame < len(q_trajectory): plot_robot_3d(ax, q_trajectory[frame]) return [] anim = FuncAnimation(fig, animate, frames=len(q_trajectory), interval=50, blit=False, repeat=False) plt.show() # anim.save('robot_trajectory.gif', writer='pillow', fps=20) # 可选:保存GIF参数说明:
link_radius=0.01控制连杆粗细,太细则看不清,太粗则遮挡关节;axis_length=0.05是坐标轴箭头长度,需与工作空间尺寸匹配(本例Z轴最高0.8m,故0.05合适);blit=False是必须的,因为plot_robot_3d中使用了ax.cla()清空,开启blit会导致残留;interval=50对应20Hz刷新率,足够观察运动平滑性。这个渲染器的价值在于:你能一眼看出某帧是否发生奇异(例如某两个关节轴几乎平行,导致末端Z轴箭头严重压缩),这是纯数据日志无法提供的直觉。
3.2 同步渲染末端轨迹线与关节角度曲线:双视图交叉验证
单看3D动画容易“眼花缭乱”,必须辅以时域曲线图,形成“空间-时间”双视角验证。我们将Plot3D动画与下方的6路关节角曲线联动,鼠标悬停某帧,两图同步高亮。
fig, (ax3d, ax2d) = plt.subplots(2, 1, figsize=(12, 10), gridspec_kw={'height_ratios': [3, 1]}) ax3d = fig.add_subplot(211, projection='3d') # 预先绘制末端轨迹线(灰色虚线) end_pts = np.array([forward_kinematics(q)[:3,3] for q in q_trajectory]) ax3d.plot(end_pts[:,0], end_pts[:,1], end_pts[:,2], 'k--', alpha=0.5, label='End-effector path') # 关节角曲线图 t_plot = np.linspace(0, 4.0, len(q_trajectory)) # 假设总时间4秒 for i in range(6): ax2d.plot(t_plot, q_trajectory[:, i], label=f'Joint {i+1}', linewidth=1.5) ax2d.set_xlabel('Time (s)') ax2d.set_ylabel('Joint Angle (rad)') ax2d.grid(True) ax2d.legend() # 动画更新函数(双图联动) def animate_dual(frame): ax3d.cla() plot_robot_3d(ax3d, q_trajectory[frame]) # 重绘轨迹线(保持背景) ax3d.plot(end_pts[:,0], end_pts[:,1], end_pts[:,2], 'k--', alpha=0.5) # 在2D图上高亮当前帧 ax2d.clear() for i in range(6): ax2d.plot(t_plot, q_trajectory[:, i], label=f'Joint {i+1}', linewidth=1.2) ax2d.axvline(x=t_plot[frame], color='red', linestyle='--', alpha=0.7) ax2d.set_xlabel('Time (s)') ax2d.set_ylabel('Joint Angle (rad)') ax2d.grid(True) ax2d.legend() return [] anim_dual = FuncAnimation(fig, animate_dual, frames=len(q_trajectory), interval=50, blit=False, repeat=False) plt.tight_layout() plt.show()为什么必须双视图?因为仅看3D动画,你无法判断关节速度是否超标——某关节可能在0.1秒内从-1.5rad猛转到1.5rad,动画看起来“流畅”,但实际已远超伺服电机额定速度。而关节角曲线图上,那一段陡峭斜率就是刺眼的红灯。这种交叉验证,是工业级轨迹规划落地的底线。
4. 六自由度轨迹规划避坑指南:5条血泪经验,每一条都来自真实翻车现场
4.1 现象:仿真中末端轨迹完美,但导入PLC后机械臂剧烈抖动
原因:未对关节角序列进行低通滤波。数值逆解产生的q_traj虽连续,但含高频数值噪声(尤其在接近奇异位形时雅可比矩阵病态,伪逆放大误差),这些噪声直接转化为伺服指令的高频抖动。
解决:在q_trajectory生成后,对每一关节通道施加截止频率为10Hz的二阶巴特沃斯低通滤波:
from scipy.signal import butter, filtfilt b, a = butter(2, 10/(0.5*20), btype='low') # 20Hz采样率,10Hz截止 q_filtered = np.column_stack([filtfilt(b, a, q_trajectory[:, i]) for i in range(6)])4.2 现象:Plot3D显示末端到达目标位姿,但forward_kinematics(q_final)计算出的位姿与目标偏差很大
原因:DH参数表中的a(连杆长度)或d(连杆偏距)单位错误。常见错误是把毫米当米用(如a=244写成0.244正确,但误写为244)。
解决:在forward_kinematics函数开头加入断言检查:
assert np.all(np.abs(DH_TABLE[:,1:]) < 10), "DH parameters likely in mm, but code expects meters!"4.3 现象:轨迹运行到某点时,Plot3D中某连杆突然“折叠”或“反向”,关节角跳变π弧度
原因:逆解未处理解分支连续性。数值法逆解每次独立求解,可能在相邻帧间从“肘部向上”解跳到“肘部向下”解。
解决:在track_inverse_solution中,不只检查限位,还要计算当前解与上一解的欧氏距离,若距离>π,则尝试加减2π修正:
q_diff = q_sol - q_current q_diff_adj = np.where(np.abs(q_diff) > np.pi, q_diff - 2*np.pi*np.sign(q_diff), q_diff) q_sol_adj = q_current + q_diff_adj4.4 现象:Plot3D动画播放卡顿,CPU占用率100%
原因:FuncAnimation默认blit=True,但我们的plot_robot_3d使用ax.cla()清空,导致blit失效并反复重建整个图形对象。
解决:强制blit=False,并优化绘图逻辑——避免每次重绘基座和轨迹线,只更新连杆和坐标轴:
# 在plot_robot_3d中,只plot连杆线段和quiver,不调用ax.cla() # 而是在animate函数中,用ax.lines.clear()和ax.collections.clear()精准清理4.5 现象:三次样条生成的笛卡尔轨迹在起点/终点速度不为零,机械臂启停冲击大
原因:CubicSpline默认边界条件未指定一阶导。虽然bc_type='natural'保证了二阶导(加速度)为0,但一阶导(速度)未必为0。
解决:显式指定起点和终点速度为零:
# 替换原CubicSpline调用: spline = CubicSpline(t_data, y_data, bc_type=((1, 0), (1, 0))) # (1,0)表示一阶导=05. 进阶技巧:用Plot3D实时诊断奇异性与工作空间边界
5.1 实时计算并可视化雅可比矩阵条件数:给每帧打“健康分”
奇异性不是“是否发生”,而是“有多接近”。Plot3D不能只画形态,还要反馈量化指标。我们在每帧渲染时,实时计算当前构型的雅可比矩阵条件数(cond(J)),条件数越大,越接近奇异。将其映射为连杆颜色——绿色(cond<10)→ 黄色(10~50)→ 红色(>50)。
def jacobian_condition_number(q): """计算当前关节角下的雅可比条件数""" # 数值法计算6x6雅可比(末端6维误差对6维关节角的导数) J = np.zeros((6, 6)) dq = 1e-4 T_base = forward_kinematics(q) for i in range(6): q_plus = q.copy() q_plus[i] += dq T_plus = forward_kinematics(q_plus) # 位置误差差分 dp = T_plus[:3,3] - T_base[:3,3] # 旋转误差差分(用矩阵对数) R_err = T_plus[:3,:3] @ T_base[:3,:3].T rotvec_err = Rotation.from_matrix(R_err).as_rotvec() J[:, i] = np.hstack([dp, rotvec_err]) / dq return np.linalg.cond(J) # 在animate函数中,获取cond_num并设置连杆颜色 cond_num = jacobian_condition_number(q_trajectory[frame]) color = plt.cm.RdYlGn(1 - min(cond_num/100, 1)) # 归一化到0-1 # 然后用该color绘制连杆线段价值:这让你一眼识别“高风险区”。例如,当机械臂伸直(所有关节共线)时,cond_num瞬间飙升至200+,Plot3D中连杆变红——这时你就知道,这段轨迹必须重规划,哪怕末端路径看起来很“直”。
5.2 用蒙特卡洛法生成工作空间云图,并叠加到Plot3D中
工作空间不是理论公式,而是实际可达的点集。我们用随机采样+正向运动学,生成10万点末端位置,用半透明散点绘制在Plot3D背景中,再将规划的轨迹线叠在其上——立刻看清轨迹是否“擦边”或“闯禁区”。
def monte_carlo_workspace(n_samples=100000): """蒙特卡洛法生成工作空间点云""" q_samples = np.random.uniform(-np.pi, np.pi, (n_samples, 6)) # 加入关节限位(更真实) limits = np.array([[-np.pi/2, np.pi/2], [-np.pi, np.pi], [-np.pi/2, np.pi/2], [-np.pi, np.pi], [-np.pi/2, np.pi/2], [-np.pi, np.pi]]) for i in range(6): q_samples[:, i] = np.random.uniform(limits[i,0], limits[i,1], n_samples) positions = [] for q in q_samples: try: T = forward_kinematics(q) positions.append(T[:3,3]) except: continue return np.array(positions) # 生成并绘制 ws_points = monte_carlo_workspace(50000) ax3d.scatter(ws_points[:,0], ws_points[:,1], ws_points[:,2], c='gray', alpha=0.02, s=0.1, label='Workspace')提示:这个云图不是装饰。当你发现规划的轨迹线紧贴云图边缘,就意味着任何微小的建模误差或伺服延迟,都可能导致末端偏离预期——这时,你应该主动缩放轨迹,留出安全裕度。
5.3 把Plot3D变成交互式调试台:鼠标点击拖拽末端,实时重规划
最后一步,让仿真从“观看”升级为“操控”。利用Matplotlib的事件系统,监听鼠标在3D图上的点击与拖拽,获取新目标位姿,触发实时重规划并更新动画。
class InteractivePlanner: def __init__(self, ax3d, q_init): self.ax3d = ax3d self.q_init = q_init self.current_q = q_init.copy() self.is_dragging = False self.cid_press = ax3d.figure.canvas.mpl_connect('button_press_event', self.on_press) self.cid_release = ax3d.figure.canvas.mpl_connect('button_release_event', self.on_release) def on_press(self, event): if event.inaxes != self.ax3d: return self.is_dragging = True # 获取当前末端位置(作为拖拽起点) T_curr = forward_kinematics(self.current_q) self.drag_start = T_curr[:3,3].copy() def on_release(self, event): if not self.is_dragging or event.inaxes != self.ax3d: return self.is_dragging = False # 获取释放点在3D空间的坐标(需投影,此处简化为Z=0.4平面) new_pos = np.array([event.xdata, event.ydata, 0.4]) # 构造新目标位姿(保持原朝向) T_target = np.eye(4) T_target[:3,3] = new_pos T_target[:3,:3] = forward_kinematics(self.current_q)[:3,:3] # 重规划 _, new_cart_traj = generate_cartesian_spline( forward_kinematics(self.current_q), T_target, t_total=2.0) new_q_traj = track_inverse_solution(new_cart_traj, q_init=self.current_q) if len(new_q_traj) > 0: self.current_q = new_q_traj[-1] # 更新动画数据...我一般会在课程设计答辩前,用这个交互式调试台,当场演示“老师您随便点个位置,我3秒内重规划并跑通”——这比放10分钟预录视频更有说服力。它背后是整套流程的健壮性:从建模、规划、逆解到渲染,环环相扣,没有一处是黑匣子。希望帮到你。
本文还有配套的精品资源,点击获取