python的机器人基础原理工业场景模拟第二十四篇:装配工位,遍历连续关节角度序列,逐点计算雅可比行列式,标记奇异危险点位。
《Python机器人基础原理》第二十四篇:装配工位——遍历连续关节角度序列,逐点计算雅可比行列式,标记奇异危险点位
1. 实际工业应用场景
周三上午,某汽车零部件产线的涡轮壳螺栓装配工位。一台3-DOF垂直关节机械臂(基座偏航θ₁+肘部俯仰θ₂+末端拧紧轴旋转θ₃)正在执行M8×1.25螺栓的定扭矩装配。机器人构型:大臂长550 mm、质量3.8 kg(质心距基座0.26 m),小臂长400 mm、质量2.2 kg(质心距肘部0.18 m),末端拧紧轴及套筒1.2 kg(质心距肘部0.10 m,工具轴向偏置d₄=18 mm——拧紧轴长度导致末端执行器参考点不在腕关节旋转轴上)。关节限位:θ₁±170°、最大角速度2.5 rad/s、最大角加速度5.0 rad/s²;θ₂±140°、最大角速度2.0 rad/s、最大角加速度4.0 rad/s²;θ₃±180°、最大角速度3.0 rad/s、最大角加速度6.0 rad/s²。
周二下午小赵在离线仿真里跑了一条装配轨迹——从取料点θ=[10°, -60°, 0°]经过5个中间路点到达装配点θ=[80°, -100°, 90°],全程T=4.0 s,采样200个点。周三一早他把雅可比行列式算出来,发现第127个采样点附近行列式值只有-0.0002,几乎为零。
小李盯着屏幕:"小赵,你这条轨迹第127点θ₂=-100°、θ₃=+95.5°,θ₂+θ₃=-4.5°——大臂小臂几乎完全折叠共线。雅可比行列式接近零意味着什么?"
"末端自由度退化。机器人在这个位形下,末端沿某个方向的速度趋近于零——不管关节电机转多快,末端就是动不了。"
"对。如果在线运行时走到这个点,伺服会拼命输出转矩试图维持末端速度指令,结果就是过流报警或者末端猛地弹开。M8螺栓的定扭矩装配要求轴向力控制在±5 N范围内,一旦奇异点触发,轴向力直接失控。"
"我之前用简化模型算过——忽略工具偏置,行列式应该是L1×L2×sin(θ₂+θ₃)=0.55×0.40×sin(-4.5°)=-0.0173。绝对值0.0173,按咱们的阈值在'危险区'边缘但还没到致命。为什么完整模型算出来是-0.0002?"
"因为你忽略了拧紧轴18 mm的轴向偏置。末端位置表达式里多了d₄×sin(θ₂+θ₃)和d₄×cos(θ₂+θ₃)项,雅可比矩阵前两行第三列都变了。行列式展开后多了一项(L1+L2)×d₄×cos(θ₂+θ₃)。在θ₂+θ₃=-4.5°时cos≈0.997,修正项是+0.0171,几乎完全抵消了主项-0.0173。简化模型标记'危险',完整模型标记'致命'——差了一个数量级。"
"那离线阶段怎么防?"
"逐点遍历你的关节角度序列,每个采样点都算一次完整雅可比行列式(含工具偏置)。设定阈值——|det(J)|<0.01标记致命区,<0.05标记危险区。轨迹规划阶段直接把这些点踢掉或者绕路。"
本篇解决的问题:给定一条连续的3-DOF关节角度序列(离线装配轨迹),逐点计算含工具偏置的完整几何雅可比矩阵行列式值,标记奇异危险点位,输出可视化报告。用于装配工位轨迹的离线奇异校验。
⚠️ 声明:本篇全部代码为课程离线仿真,运行在普通PC上,用于装配轨迹的雅可比奇异点检测。不可直接用于真实机器人控制器、伺服驱动器或产线设备。真实工业现场需结合实时奇异回避算法、冗余自由度配置、软硬限位保护。代码作者不对直接使用本代码于真实设备造成的任何后果负责。
2. 痛点分析
课程模块 本篇对应的工程痛点
第3章 连杆参数及运动学求解 → 3.3 雅可比矩阵 装配轨迹经过奇异位形→雅可比行列式趋零→末端自由度退化→定扭矩装配轴向力失控
第3章 连杆参数及运动学求解 → 3.4 奇异性分析 忽略工具偏置导致奇异判断偏差一个数量级→简化模型标记"危险"、完整模型标记"致命"
第6章 轨迹规划与控制 → 6.1 轨迹可行性校验 离线轨迹未做完整雅可比奇异检测→在线运行时伺服过流/末端弹跳→M8螺栓螺纹损伤
本篇代码解决 L2 离线轨迹校验层。输入:连续关节角度序列(N个采样点)、DH参数(含工具偏置d₄)、连杆长度。逐点计算3×3几何雅可比行列式。输出:行列式曲线、危险点标记、统计报告。代码不是设备实时控制器,不做在线奇异回避。它做的事情是:遍历已有轨迹,逐点校验奇异风险,为轨迹重规划提供依据。
不覆盖:在线奇异回避、阻尼最小二乘法、冗余自由度解析、力/力矩雅可比、动力学计算。
3. 核心逻辑讲解
物理直觉
雅可比矩阵是关节速度和末端速度之间的"翻译器":ẋ = J·θ̇。行列式|det(J)|衡量这个翻译器"压缩"空间的倍率。行列式为1意味着关节空间到末端空间是等比例映射;行列式接近0意味着关节空间的一个小位移被压缩成末端空间的几乎零位移——末端在这个方向上"僵死"了。
对于带工具偏置的3-DOF垂直关节臂,末端位置为:
- x = L₁·cos(θ₁)·cos(θ₂) + L₂·cos(θ₁)·cos(θ₂+θ₃) + d₄·cos(θ₁)·sin(θ₂+θ₃)
- y = L₁·sin(θ₁)·cos(θ₂) + L₂·sin(θ₁)·cos(θ₂+θ₃) + d₄·sin(θ₁)·sin(θ₂+θ₃)
- z = L₁·sin(θ₂) + L₂·sin(θ₂+θ₃) - d₄·cos(θ₂+θ₃)
当θ₂+θ₃接近0°时(大臂小臂完全折叠共线),sin项趋零、cos项趋1。无偏置时行列式= L₁·L₂·sin(θ₂+θ₃)趋零;有偏置时行列式= L₁·L₂·sin(θ₂+θ₃) + (L₁+L₂)·d₄·cos(θ₂+θ₃),偏置修正项恰好在sin趋零时最大——可以几乎完全抵消主项,让奇异比简化模型预测的更早到来。
业务逻辑 → 代码映射
输入:关节角度序列 q_seq[N×3]、连杆长度 L1=0.55 m, L2=0.40 m、工具偏置 d4=0.018 m
↓
遍历每个采样点 i = 0 到 N-1:
读取 q_i = [θ₁, θ₂, θ₃]
构建完整几何雅可比矩阵 J(3×3):
由末端位置对时间的导数推导各偏导项
J_11 = -L1·sin(θ₁)cos(θ₂) - L2·sin(θ₁)cos(θ₂+θ₃) + d4·sin(θ₁)sin(θ₂+θ₃)
J_12 = -L1·cos(θ₁)sin(θ₂) - L2·cos(θ₁)sin(θ₂+θ₃) + d4·cos(θ₁)cos(θ₂+θ₃)
J_13 = -L2·cos(θ₁)sin(θ₂+θ₃) + d4·cos(θ₁)cos(θ₂+θ₃)
J_21 = L1·cos(θ₁)cos(θ₂) + L2·cos(θ₁)cos(θ₂+θ₃) - d4·cos(θ₁)sin(θ₂+θ₃)
J_22 = -L1·sin(θ₁)sin(θ₂) - L2·sin(θ₁)sin(θ₂+θ₃) + d4·sin(θ₁)cos(θ₂+θ₃)
J_23 = -L2·sin(θ₁)sin(θ₂+θ₃) + d4·sin(θ₁)cos(θ₂+θ₃)
J_31 = d4·cos(θ₂+θ₃)
J_32 = 1
J_33 = 1
计算 det(J_i) = np.linalg.det(J)
判断 |det(J_i)| 分级:>0.10安全, 0.05-0.10警告, 0.01-0.05危险, <0.01致命
↓
输出:行列式序列 det_seq[N]、危险点索引列表、4张图表
关键公式文字翻译
完整雅可比行列式:det(J) = L₁·L₂·sin(θ₂+θ₃) + (L₁+L₂)·d₄·cos(θ₂+θ₃)。第一项是经典的主项(无偏置时的全部),第二项是工具偏置引入的修正。当θ₂+θ₃接近0°时sin趋零、cos趋1,修正项最大化——偏置18 mm可使修正量达0.017,与主项同量级。
奇异条件:det(J)=0 → tan(θ₂+θ₃) = -(L₁+L₂)·d₄/(L₁·L₂)。本篇参数下tan(φ)≈-0.0777 → φ≈-4.45°或175.55°。也就是说,奇异发生在θ₂+θ₃≈-4.45°附近——大臂小臂接近完全折叠(而非完全伸直)。这与无偏置时的直觉(θ₂+θ₃=0)偏移了4.45°,但后果更严重因为偏置让行列式更快趋零。
阈值分级说明:阈值数值(0.10/0.05/0.01)仅在长度单位m、角度单位rad的体系下有效。如果读者改用mm或deg,阈值需等比缩放。这是混合量纲(m²·rad)的固有特性。
单位:角度 rad,长度 m,雅可比行列式量纲 m²·rad(位置映射×角度映射的混合量)。
4. OOP代码实现
技术栈
严格限定:numpy, scipy, matplotlib。不引入pybullet、roboticstoolbox等任何第三方机器人库。复用前篇的
"trajectory/models.py"。
项目结构树
singularity_detection_sim/
├── run_singularity_sim.py
├── requirements.txt
├── robot/
│ ├── __init__.py
│ ├── models.py
│ ├── jacobian.py
│ └── visualize_singularity.py
├── tests/
│ ├── __init__.py
│ └── test_singularity_sim.py
└── results/
核心源码
"robot/models.py" — 数据模型
"""
机器人数据模型:DH参数、连杆物理属性、关节限位、轨迹路点
"""
from dataclasses import dataclass, field
from typing import List
@dataclass
class DHLink:
"""单连杆DH参数结构体(修正DH约定)"""
link_length: float # a_i, 连杆长度 (m)
link_twist: float # alpha_i, 连杆扭角 (rad)
link_offset: float # d_i, 连杆偏距 (m)
joint_angle: float # theta_i, 关节角 (rad)
mass: float # 连杆质量 (kg)
com_x: float = 0.0 # 质心x坐标 (m)
com_y: float = 0.0 # 质心y坐标 (m)
com_z: float = 0.0 # 质心z坐标 (m)
joint_name: str = ""
@dataclass
class JointLimit:
"""单关节限位结构体"""
joint_name: str
min_angle: float # 最小角度 (rad)
max_angle: float # 最大角度 (rad)
max_velocity: float # 最大角速度 (rad/s)
max_acceleration: float # 最大角加速度 (rad/s²)
@dataclass
class JointLimitSet:
"""关节限位集合"""
limits: List[JointLimit] = field(default_factory=list)
def check_all_positions(self, angles: List[float]) -> tuple:
"""
校验一组关节角度是否全部在位置限位内
返回 (bool通过标志, list超限索引列表)
"""
violations = []
for i, angle in enumerate(angles):
if i < len(self.limits):
if angle < self.limits[i].min_angle or angle > self.limits[i].max_angle:
violations.append(i)
return len(violations) == 0, violations
@dataclass
class TrajectoryWaypoint:
"""轨迹路点结构体"""
joint1: float
joint2: float
joint3: float
timestamp: float = 0.0
"robot/jacobian.py" — 完整雅可比矩阵与奇异检测
"""
3-DOF垂直关节机械臂完整几何雅可比矩阵计算与奇异检测
构型:基座偏航θ₁ + 肘部俯仰θ₂ + 末端旋转θ₃ + 工具轴向偏置d₄
末端位置(含偏置):
x = L1·cos(θ₁)cos(θ₂) + L2·cos(θ₁)cos(θ₂+θ₃) + d4·cos(θ₁)sin(θ₂+θ₃)
y = L1·sin(θ₁)cos(θ₂) + L2·sin(θ₁)cos(θ₂+θ₃) + d4·sin(θ₁)sin(θ₂+θ₃)
z = L1·sin(θ₂) + L2·sin(θ₂+θ₃) - d4·cos(θ₂+θ₃)
行列式解析展开:det(J) = L1·L2·sin(θ₂+θ₃) + (L1+L2)·d4·cos(θ₂+θ₃)
"""
import numpy as np
from typing import List, Tuple, Optional
class GeometricJacobian3DOF:
"""
3-DOF垂直关节臂完整几何雅可比计算器(含工具轴向偏置)
"""
def __init__(self, upper_arm_length: float, forearm_length: float,
tool_offset: float = 0.0):
if upper_arm_length <= 0 or forearm_length <= 0:
raise ValueError(
f"连杆长度必须为正数: upper_arm={upper_arm_length}, forearm={forearm_length}"
)
if tool_offset < 0:
raise ValueError(f"工具偏置不能为负: {tool_offset}")
self.L1 = upper_arm_length # 大臂长度 (m)
self.L2 = forearm_length # 小臂长度 (m)
self.d4 = tool_offset # 工具轴向偏置 (m)
def compute_jacobian(self, theta1: float, theta2: float, theta3: float) -> np.ndarray:
"""
计算给定关节角度下的3×3完整几何雅可比矩阵
由末端位置对时间求导得到各偏导项
"""
if not all(np.isfinite([theta1, theta2, theta3])):
raise ValueError("关节角度必须为有限数值")
J = np.zeros((3, 3))
sin_t1 = np.sin(theta1)
cos_t1 = np.cos(theta1)
sin_t2 = np.sin(theta2)
cos_t2 = np.cos(theta2)
sin_t23 = np.sin(theta2 + theta3)
cos_t23 = np.cos(theta2 + theta3)
# 第一行:末端X方向速度对关节速度的映射
J[0, 0] = -self.L1 * sin_t1 * cos_t2 - self.L2 * sin_t1 * cos_t23 + self.d4 * sin_t1 * sin_t23
J[0, 1] = -self.L1 * cos_t1 * sin_t2 - self.L2 * cos_t1 * sin_t23 + self.d4 * cos_t1 * cos_t23
J[0, 2] = -self.L2 * cos_t1 * sin_t23 + self.d4 * cos_t1 * cos_t23
# 第二行:末端Y方向速度对关节速度的映射
J[1, 0] = self.L1 * cos_t1 * cos_t2 + self.L2 * cos_t1 * cos_t23 - self.d4 * cos_t1 * sin_t23
J[1, 1] = -self.L1 * sin_t1 * sin_t2 - self.L2 * sin_t1 * sin_t23 + self.d4 * sin_t1 * cos_t23
J[1, 2] = -self.L2 * sin_t1 * sin_t23 + self.d4 * sin_t1 * cos_t23
# 第三行:末端Z方向速度对关节速度的映射(含偏置耦合)
J[2, 0] = self.d4 * cos_t23
J[2, 1] = 1.0
J[2, 2] = 1.0
return J
def compute_determinant(self, theta1: float, theta2: float, theta3: float) -> float:
"""
计算完整雅可比行列式(数值法,通过np.linalg.det)
含工具偏置,结果与解析公式一致
"""
J = self.compute_jacobian(theta1, theta2, theta3)
det_val = np.linalg.det(J)
# 浮点精度保护:绝对值小于1e-12视为零
if abs(det_val) < 1e-12:
det_val = 0.0
return float(det_val)
def compute_determinant_analytic(self, theta2: float, theta3: float) -> float:
"""
解析行列式(含工具偏置)
det(J) = L1*L2*sin(θ₂+θ₃) + (L1+L2)*d4*cos(θ₂+θ₃)
用于批量计算时的性能优化和交叉验证
"""
phi = theta2 + theta3
return self.L1 * self.L2 * np.sin(phi) + (self.L1 + self.L2) * self.d4 * np.cos(phi)
def evaluate_trajectory(self, waypoints: List[Tuple[float, float, float]],
timestamps: Optional[List[float]] = None,
danger_threshold: float = 0.05) -> dict:
"""
遍历连续关节角度序列,逐点计算雅可比行列式
输入:waypoints = [(θ₁, θ₂, θ₃), ...]
输出:字典包含 determinants, timestamps, singular_points(分级)
"""
n_points = len(waypoints)
if n_points == 0:
raise ValueError("路点序列不能为空")
if timestamps is None:
timestamps = list(range(n_points))
det_values = np.zeros(n_points)
singular_points = [] # (索引, 行列式值, 分级标签)
for i, (t1, t2, t3) in enumerate(waypoints):
det_val = self.compute_determinant(t1, t2, t3)
det_values[i] = det_val
abs_det = abs(det_val)
if abs_det <= 0.01:
level = "FATAL"
elif abs_det <= 0.05:
level = "DANGER"
elif abs_det <= 0.10:
level = "WARNING"
else:
level = "SAFE"
if level != "SAFE":
singular_points.append((i, det_val, level))
return {
'determinants': det_values,
'timestamps': np.array(timestamps),
'singular_points': singular_points,
'n_points': n_points,
}
def check_singularity_at_config(self, theta1: float, theta2: float, theta3: float,
danger_threshold: float = 0.05) -> Tuple[bool, float, str]:
"""
单点奇异检测
返回 (是否危险, 行列式值, 分级标签)
"""
det_val = self.compute_determinant(theta1, theta2, theta3)
abs_det = abs(det_val)
if abs_det <= 0.01:
level = "FATAL"
elif abs_det <= 0.05:
level = "DANGER"
elif abs_det <= 0.10:
level = "WARNING"
else:
level = "SAFE"
is_dangerous = abs_det <= danger_threshold
return is_dangerous, det_val, level
"robot/visualize_singularity.py" — 可视化模块
"""
装配工位奇异检测可视化
4张核心图表:行列式曲线、危险点散点标记、工作空间奇异分布热力图、轨迹关节角变化
"""
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
class SingularityVisualizer:
def __init__(self, results_dir="results"):
self.results_dir = results_dir
import os
os.makedirs(results_dir, exist_ok=True)
def plot_determinant_curve(self, timestamps, det_values, singular_points=None):
"""
① 雅可比行列式随轨迹点演变曲线:核心输出,直接读出奇异危险区间
"""
fig, ax = plt.subplots(figsize=(12, 5))
# 绘制原始行列式值(可正可负)
ax.plot(timestamps, det_values, '-', color='steelblue',
linewidth=1.2, label='det(J)')
# 绘制绝对值曲线
abs_det = np.abs(det_values)
ax.plot(timestamps, abs_det, '--', color='darkblue',
linewidth=0.8, alpha=0.6, label='|det(J)|')
# 阈值线
ax.axhline(y=0.10, color='orange', linestyle='--', linewidth=1.0, label='Warning (0.10)')
ax.axhline(y=0.05, color='red', linestyle='--', linewidth=1.0, label='Danger (0.05)')
ax.axhline(y=0.01, color='darkred', linestyle='-', linewidth=1.0, label='Fatal (0.01)')
ax.axhline(y=0, color='black', linestyle='-', linewidth=0.5, alpha=0.5)
# 标记危险点竖线
if singular_points is not None:
for idx, _, level in singular_points:
if idx < len(timestamps):
color = 'red' if level in ('DANGER', 'FATAL') else 'orange'
ax.axvline(x=timestamps[idx], color=color,
linestyle=':', linewidth=0.6, alpha=0.4)
ax.set_xlabel('Trajectory Point Index')
ax.set_ylabel('Jacobian Determinant (m²·rad)')
ax.set_title('Jacobian Determinant Along Assembly Trajectory — Singularity Check')
ax.legend(loc='upper right', fontsize=8)
ax.grid(True, alpha=0.3)
plt.tight_layout()
path = f"{self.results_dir}/01_determinant_curve.png"
plt.savefig(path, dpi=150)
plt.close()
return path
def plot_singular_point_scatter(self, timestamps, det_values, singular_points):
"""
② 奇异危险点散点标记图:用颜色区分安全/警告/危险/致命
"""
fig, ax = plt.subplots(figsize=(12, 5))
colors = {'WARNING': 'yellow', 'DANGER': 'orange', 'FATAL': 'red'}
abs_det = np.abs(det_values)
# 安全点底色
safe_mask = abs_det > 0.10
ax.scatter(timestamps[safe_mask], det_values[safe_mask],
c='green', s=8, alpha=0.4, label='Safe (>0.10)')
# 分级标记
for idx, det_val, level in singular_points:
if idx < len(timestamps):
ax.scatter(timestamps[idx], det_val,
c=colors.get(level, 'gray'), s=30,
marker='x' if level in ('DANGER', 'FATAL') else 'o',
label=f'{level} (idx={idx})' if idx < 3 else "")
# 去重图例
handles, labels = ax.get_legend_handles_labels()
by_label = dict(zip(labels, handles))
ax.legend(by_label.values(), by_label.keys(), loc='upper right', fontsize=8)
ax.set_xlabel('Trajectory Point Index')
ax.set_ylabel('Jacobian Determinant (m²·rad)')
ax.set_title('Singular Point Detection — Color-Coded by Risk Level')
ax.grid(True, alpha=0.3)
plt.tight_layout()
path = f"{self.results_dir}/02_singular_point_scatter.png"
plt.savefig(path, dpi=150)
plt.close()
return path
def plot_workspace_singularity_heatmap(self, theta2_range, theta3_range, L1, L2, d4):
"""
③ 工作空间奇异分布热力图:θ₂ vs θ₃平面上|det(J)|的分布
含偏置:det(J) = L1*L2*sin(θ₂+θ₃) + (L1+L2)*d4*cos(θ₂+θ₃)
"""
T2, T3 = np.meshgrid(theta2_range, theta3_range)
Det = L1 * L2 * np.sin(T2 + T3) + (L1 + L2) * d4 * np.cos(T2 + T3)
fig, ax = plt.subplots(figsize=(8, 6))
c = ax.contourf(T2 * 180 / np.pi, T3 * 180 / np.pi, np.abs(Det),
levels=20, cmap='viridis')
plt.colorbar(c, ax=ax, label='|det(J)| (m²·rad)')
# 标记奇异线 det(J)=0 → tan(θ₂+θ₃) = -(L1+L2)*d4/(L1*L2)
singular_angle = np.arctan(-(L1 + L2) * d4 / (L1 * L2))
singular_line = []
for t2 in theta2_range:
t3_val = singular_angle - t2
if theta3_range[0] <= t3_val <= theta3_range[-1]:
singular_line.append((t2 * 180 / np.pi, t3_val * 180 / np.pi))
if singular_line:
sl_x, sl_y = zip(*singular_line)
ax.plot(sl_x, sl_y, 'r--', linewidth=1.5,
label=f'Singular Line (θ₂+θ₃={singular_angle*180/np.pi:.1f}°)')
# 标记第二条奇异线(加π)
singular_angle2 = singular_angle + np.pi
singular_line2 = []
for t2 in theta2_range:
t3_val = singular_angle2 - t2
if theta3_range[0] <= t3_val <= theta3_range[-1]:
singular_line2.append((t2 * 180 / np.pi, t3_val * 180 / np.pi))
if singular_line2:
sl2_x, sl2_y = zip(*singular_line2)
ax.plot(sl2_x, sl2_y, 'r-.', linewidth=1.0,
label=f'Singular Line (θ₂+θ₃={singular_angle2*180/np.pi:.1f}°)')
ax.set_xlabel('θ₂ (Elbow Pitch) [deg]')
ax.set_ylabel('θ₃ (End Rotate) [deg]')
ax.set_title('Workspace Singularity Heatmap — |det(J)| vs (θ₂, θ₃)')
ax.legend(loc='upper right', fontsize=8)
ax.grid(True, alpha=0.3)
plt.tight_layout()
path = f"{self.results_dir}/03_workspace_singularity_heatmap.png"
plt.savefig(path, dpi=150)
plt.close()
return path
def plot_joint_angles_trajectory(self, timestamps, waypoints):
"""
④ 关节角度变化曲线:辅助判断奇异点对应的关节位形
"""
fig, ax = plt.subplots(figsize=(12, 5))
waypoints = np.array(waypoints)
ax.plot(timestamps, waypoints[:, 0] * 180 / np.pi, '-',
color='steelblue', linewidth=1.2, label='θ₁ (Base Yaw)')
ax.plot(timestamps, waypoints[:, 1] * 180 / np.pi, '-',
color='orange', linewidth=1.2, label='θ₂ (Elbow Pitch)')
ax.plot(timestamps, waypoints[:, 2] * 180 / np.pi, '-',
color='green', linewidth=1.2, label='θ₃ (End Rotate)')
ax.set_xlabel('Trajectory Point Index')
ax.set_ylabel('Joint Angle (deg)')
ax.set_title('Joint Angles Along Assembly Trajectory — Context for Singular Points')
ax.legend(loc='upper right', fontsize=8)
ax.grid(True, alpha=0.3)
plt.tight_layout()
path = f"{self.results_dir}/04_joint_angles_trajectory.png"
plt.savefig(path, dpi=150)
plt.close()
return path
5. 可视化要求
序号 图表文件 服务结论
①
"01_determinant_curve.png" 行列式沿轨迹演变曲线,阈值线标记安全/警告/危险/致命区,直接读出奇异危险区间
②
"02_singular_point_scatter.png" 逐点散点标记,颜色分级(绿/黄/橙/红),精确定位危险点索引
③
"03_workspace_singularity_heatmap.png" θ₂-θ₃工作空间奇异分布热力图,红色虚线标记含偏置的奇异线,指导轨迹避障
④
"04_joint_angles_trajectory.png" 关节角度变化曲线,辅助判断奇异点对应的具体关节位形
6. README使用说明 + 核心知识点卡片
快速开始
pip install -r requirements.txt
python -m pytest tests/test_singularity_sim.py -v # 3/3 测试通过
python run_singularity_sim.py #
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)