ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

python的先进制造技术工业场景模拟第七十九篇:编写机器人关节运动仿真,给定目标点位,模拟关节转动,校验关节限位碰撞。

python的先进制造技术工业场景模拟第七十九篇:编写机器人关节运动仿真,给定目标点位,模拟关节转动,校验关节限位碰撞。 周二上午机器人单元联调。“这个新取件位示教完一跑第二轴直接报超程”机器人维护老周拍着控制柜“以前靠示教器拖着走眼睛看角度表到边界就急停。有次没看出来J2转到85°机械限位咔住齿轮箱震了一下后面节拍全停。”我接上导出的目标点位表和DH参数。“这里面有啥”我问。“目标点XYZ工具坐标系还有各关节软限位都有”老周说“但系统只给逆解后的角度不校验这组角度会不会撞本体、会不会超软限位、多个目标点之间走直线会不会甩出去。想加一个新工位得真机拖一遍撞了再改。”“最亏的是改线”老周补一句“新车型上料点加三个示教员拖半天超程两次撞了防护栏一次。排产按理想节拍算实际全耗在改点上。”“我就想干一件事”老周说“给一组目标点位自己算逆运动学出各关节角度先校验软限位再校验本体自碰再模拟关节插补运动标出哪段超了、哪段会碰像个小离线验证台不用上机拖。”“机器人不是看末端到没到”我接话“是看‘每个关节转多少度、转的时候别超界别互撞’。用 numpy 做正逆解插补pandas 管点位表scipy 做角度包络距离判定matplotlib 画关节角曲线工作空间碰撞热力networkx 建‘目标点→关节→限位/碰撞’关联图sklearn 做超限风险分类。”“对”老周点头“要能说清‘目标点P3逆解后J287°超软限位±85°J3与J5连杆间距12mm安全距20mm判自碰插补过程J2最大角速度42°/s在0.8s处越界建议P3下探30mm或换姿态’。”“OOP 封好”我开工程“点位加载器、逆解器、关节插补器、限位碰撞校验器、风险分类器、可视化器合成多目标点多姿态数据下载就能跑。”敲了行原型# 目标: 目标点位 → 逆解关节角 → 插补运动 → 限位自碰校验# 方法: DH正逆解 线性插补 关节包络 连杆距离 RF分类老周凑近看“那以后看报告各关节角度随插补时间曲线标软限位红线工作空间散点超界点标红自碰连杆距离曲线风险散点红黄绿关联图指J2/J3是主要风险源。新点位上机前先跑一遍超界的不让下发。”“对”我接话“机器人离线验证不是‘画个3D模型’是‘提前看见哪个关节先越线’。数字孪生里挂这个关节看板就是示教员的‘防撞台’。”一、实际应用场景真实痛点场景设定六轴工业机器人做上下料/取件新增目标点位时需校验关节角是否超软限位、连杆是否自碰、插补过程是否瞬态越界。现场依赖示教器拖拽试走超程急停、撞本体、撞护栏时有发生改线成本高。现场原话叙事化“不是末端没对准”老周说“是关节先到头了。J2转到85°表上看着还行再走一点就顶限位齿轮箱咔咔响。”“最亏的是加工位”老周说“三个新取件点示教拖了半天超程两次J3和J5连杆蹭了一下防护栏也撞了。排产按理想节拍排实际全耗在改点上。”核心矛盾“示教拖拽看角度表上机试走” 与 “目标点逆解→关节角校验→插补过程包络→自碰距离判定→风险分类关联图” 之间的断层。二、痛点分析映射到滨州职业学院《先进制造技术》课程模型《先进制造技术》课程模块 本篇痛点对应工业机器人技术基础DH参数、正逆运动学、关节限位、工作空间、轨迹插补 逆解插补限位校验先进制造技术基础空间几何、坐标变换、包络运动学 连杆距离角度包络FMS与先进生产管理工位快速换型、节拍保障 新点位预校验省示教时间智能制造与数字孪生机器人运动数字映射、关节看板 关节角碰撞挂数字孪生先进制造新模式工艺知识库点位→关节安全域 超限模型复用一句话总结我们需要一个“目标点位→逆运动学→关节插补→软限位校验自碰判定→风险分类关联图”程序实现从“示教拖拽试走”到“离线预校验防撞”的闭环。三、核心逻辑讲解大白话3.1 问题本质把机器人想成“几节胳膊”把六轴机器人想成人的胳膊接在转桌上* 目标点XYZ 手要够到的位置* 逆运动学 反推肩膀、胳膊肘、手腕各转多少度* 关节软限位 每个关节允许转的角度范围比如J2是±85°* 插补运动 从当前点走到目标点各关节按时间平滑转过去* 自碰 大臂和小臂离太近像胳膊肘蹭胸口* 瞬态越界 走的过程中某个中间姿态超了终点看着没事* 示教拖拽 人肉试撞了才知道* 离线校验 先把每个关节转角画成曲线贴着红线就报警3.2 业务逻辑 → 代码映射输入目标点位表│▼ TargetPointLoader (pandas)读取表:point_id, x, y, z, rx, ry, rz, 当前位姿各关节软限位 [min,max]│▼ InverseKinematics (numpy)逆解:简化6轴DH模型(平面俯仰解耦)给出各关节角度解(取主解)输出 J1~J6 角度│▼ JointInterpolator (numpy)插补:当前关节角 → 目标关节角按时间线性插补(可加梯形速度)采样N步, 得关节角时域序列│▼ LimitCollisionChecker (scipynumpy)校验:每步各关节是否超软限位 → 标红连杆间距(简化圆柱体距离) 安全距 → 自碰插补过程包络最大值记录瞬态越界单独标记│▼ RiskClassifier (sklearn)风险分级:特征: J2角, J3角, 插补峰值, 最小连杆距, 目标点高度标签: 安全/临界/超限RF分类 特征重要性 5折交叉验证│▼ RobotVisualizer (matplotlib networkx)可视化:1. 各关节角度插补曲线软限位红线2. 工作空间散点超界点标红3. 连杆最小距离随插补时间曲线4. 风险分级散点(红黄绿)5. 目标点→关节→风险关联网络6. 单点插补过程关节角热力│▼ SyntheticRobotTask (numpy)合成数据:多目标点×多姿态, 机制: 高位/深位易超J2, 折叠姿态易自碰3.3 为什么不能“示教拖拽”视角 问题看末端到位 关节可能已超界看终点角度 插补中间态越界漏检拖拽试走 撞本体/护栏才知逆解插补 每步角度都算出来软限位红线 贴线即标红连杆距离 自碰提前预警RF分类 新点位直接给红黄绿3.4 分析前后对比维度 传统方式 本程序超界发现 急停后看报警 插补曲线提前标红自碰 蹭了才知道 最小距20mm即报警新点位验证 示教拖半天 离线跑全组点位瞬态越界 不校验 每步包络校验知识沉淀 老师傅记忆 RF模型关联图四、OOP 代码实现4.1 项目结构robot_joint_sim/├── robot_joint_sim/│ ├── __init__.py│ ├── target_point_loader.py # 点位加载│ ├── inverse_kinematics.py # 逆解(numpy)│ ├── joint_interpolator.py # 插补(numpy)│ ├── limit_collision_checker.py # 限位自碰(scipy)│ ├── risk_classifier.py # 风险分类(sklearn)│ ├── robot_visualizer.py # 可视化│ └── synthetic_robot_task.py # 合成点位├── tests/│ ├── __init__.py│ └── test_robot.py├── results/│ ├── joint_angle_curves.png│ ├── workspace_scatter.png│ ├── link_distance_curve.png│ ├── risk_scatter.png│ ├── joint_risk_network.png│ ├── interp_heatmap.png│ ├── robot_detail.csv│ └── robot_report.txt└── run_robot.py4.2 核心源码detailssummary/summary目标点位加载器。import pandas as pdfrom pathlib import Pathclass TargetPointLoader:加载机器人目标点位与关节软限位。def __init__(self, filepath: str robot_targets.csv,encoding: str utf-8):self.filepath Path(filepath)self.encoding encodingdef load(self) - pd.DataFrame:if not self.filepath.exists():raise FileNotFoundError(self.filepath)df pd.read_csv(self.filepath, encodingself.encoding)req [point_id, x, y, z, j1_lim, j2_lim_min,j2_lim_max, j3_lim_min, j3_lim_max]miss [c for c in req if c not in df.columns]if miss:raise ValueError(f缺列: {miss})for c in [x, y, z, j1_lim, j2_lim_min, j2_lim_max,j3_lim_min, j3_lim_max]:df[c] pd.to_numeric(df[c], errorscoerce)return df.dropna(subsetreq).reset_indexTrue, dropTrue) \if False else df.dropna(subsetreq).reset_index(dropTrue)def summary(self, df: pd.DataFrame) - str:s f目标点数: {len(df)}\ns f工作空间X: {df[x].min():.0f}~{df[x].max():.0f}mm\ns fJ2软限位: {df[j2_lim_min].iloc[0]}~{df[j2_lim_max].iloc[0]}°\ns fJ3软限位: {df[j3_lim_min].iloc[0]}~{df[j3_lim_max].iloc[0]}°\ns f安全连杆距: 20mm(内置)return s.rstrip()注上面load 里有个防御性写法正式版简化为下句即可已按 PEP8 收敛。/detailsdetailssummary/summary六轴机器人简化逆运动学 (numpy)。import numpy as npfrom dataclasses import dataclassfrom typing import np as _np # 仅类型提示用, 运行用npdataclassclass JointAngles:j: np.ndarray # shape (6,)def degrees(self) - np.ndarray:return self.j.copy()class InverseKinematics:简化6轴模型(教学级解耦):J1 atan2(y, x)J2 俯仰主解(肩)J3 肘部补偿J4~J6 姿态解耦(简化给0主解)仅用于限位/碰撞预校验, 非产线标定级。def __init__(self, L1: float 320.0, L2: float 280.0):self.L1 L1 # 大臂长mmself.L2 L2 # 小臂长mmdef solve(self, x: float, y: float, z: float) - JointAngles:j1 np.degrees(np.arctan2(y, x))# 水平投影r np.sqrt(x*x y*y)# 肩到腕平面距离d np.sqrt(r*r z*z)d_clip min(d, self.L1 self.L2 - 1.0)cos3 (d_clip**2 - self.L1**2 - self.L2**2) / (2*self.L1*self.L2)cos3 np.clip(cos3, -1.0, 1.0)j3 180.0 - np.degrees(np.arccos(cos3))cos2 (self.L1**2 d_clip**2 - self.L2**2) / (2*self.L1*d_clip)cos2 np.clip(cos2, -1.0, 1.0)j2 np.degrees(np.arccos(cos2)) - np.degrees(np.arctan2(z, r))j4 0.0j5 np.degrees(np.arctan2(z, r)) * 0.5j6 0.0arr np.array([j1, j2, j3, j4, j5, j6])return JointAngles(arr)/detailsdetailssummary/summary关节空间插补 (numpy)。import numpy as npfrom typing import Dictfrom .inverse_kinematics import JointAnglesclass JointInterpolator:当前关节角 → 目标关节角, 时间线性插补。def __init__(self, steps: int 100, duration: float 2.0):self.steps stepsself.duration durationself.t np.linspace(0, duration, steps)def interpolate(self, start: JointAngles, goal: JointAngles) - Dict:s start.degrees()g goal.degrees()# 线性插补traj np.array([s (g - s) * k / (self.steps - 1)for k in range(self.steps)])# 角速度(°/s)dt self.duration / (self.steps - 1)vel np.gradient(traj, dt, axis0)return {t: self.t,traj: traj, # (steps,6)vel: vel, # (steps,6)peak_vel: float(np.max(np.abs(vel))),max_abs_angle: float(np.max(np.abs(traj))),}/detailsdetailssummary/summary限位校验 自碰判定 (scipy numpy)。import numpy as npfrom scipy.spatial.distance import cdistfrom typing import Dictfrom .joint_interpolator import JointInterpolatorclass LimitCollisionChecker:校验软限位 连杆最小距离。def __init__(self, safe_link_dist: float 20.0):self.safe_link_dist safe_link_distdef check(self, traj: np.ndarray, t: np.ndarray,lims: Dict) - Dict:traj: (steps,6) 角度序列lims: {关节名: (min,max)}steps traj.shape[0]report {per_step: [], overall: {}}min_link_all 1e9over_steps 0# 简化连杆位置: J2/J3角度映射为两连杆端点for k in range(steps):ja traj[k]flags {}for i, jn in enumerate([j1,j2,j3,j4,j5,j6]):if jn in lims:lo, hi lims[jn]flags[jn] (ja[i] lo) or (ja[i] hi)# 连杆距离简化模型: J2,J3决定两臂夹角ang np.radians(ja[1] - ja[2])# 夹角越小越近(折叠)link_dist 40.0 * abs(np.sin(ang)) 5.0min_link_all min(min_link_all, link_dist)collide link_dist self.safe_link_distif any(flags.values()) or collide:over_steps 1report[per_step].append({step: k, t: t[k], angles: ja.copy(),over_limit: any(flags.values()),limit_flags: flags,link_dist: link_dist,self_collision: collide,})# 汇总j2_peak float(np.max(np.abs(traj[:,1])))j3_peak float(np.max(np.abs(traj[:,2])))report[overall] {j2_peak_deg: j2_peak,j3_peak_deg: j3_peak,min_link_dist_mm: float(min_link_all),over_steps: over_steps,total_steps: steps,is_safe: (over_steps 0) and (min_link_all self.safe_link_dist),}return report/detailsdetailssummary/summary关节超限风险分类 (sklearn RF)。import numpy as npimport pandas as pdfrom typing import Dictfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.model_selection import cross_val_score, KFoldfrom sklearn.preprocessing import LabelEncoderclass RiskClassifier:安全/临界/超限 三分类。def __init__(self, random_state: int 42):self.random_state random_stateself.model_ Noneself.le_ LabelEncoder()def _features(self, df: pd.DataFrame):self.feat_cols [j2_peak_deg, j3_peak_deg,min_link_dist_mm, peak_vel, z]have [c for c in self.feat_cols if c in df.columns]self.feat_cols havereturn df[have].valuesdef fit(self, df: pd.DataFrame, y: np.ndarray):self.model_ RandomForestClassifier(n_estimators300, max_depth6, min_samples_leaf2,random_stateself.random_state, n_jobs-1)self.model_.fit(self._features(df), y)return selfdef feature_importance(self) - Dict:imp dict(zip(self.feat_cols, self.model_.feature_importances_))return dict(sorted(imp.items(), keylambda x: x[1], reverseTrue))def cross_validate(self, df: pd.DataFrame, y: np.ndarray) - Dict:X self._features(df)kf KFold(n_splits5, shuffleTrue, random_stateself.random_state)sc cross_val_score(self.model_, X, y, cvkf, scoringf1_macro)return {f1_macro_mean: float(sc.mean()), f1_macro_std: float(sc.std())}def predict(self, df: pd.DataFrame) - np.ndarray:return self.model_.predict(self._features(df))/detailsdetailssummary/summary机器人运动可视化 (matplotlib networkx)。import numpy as npimport pandas as pdimport matplotlib.pyplot as pltfrom pathlib import Pathimport networkx as nxplt.rcParams[font.sans-serif] [SimHei, WenQuanYi Micro Hei, DejaVu Sans]plt.rcParams[axes.unicode_minus] FalseRISK_COLOR {安全: #27AE60, 临界: #F39C12, 超限: #E74C3C}JOINT_NAMES [j1,j2,j3,j4,j5,j6]class RobotVisualizer:def __init__(self, results_dir: str results):self.results_dir Path(results_dir)self.results_dir.mkdir(exist_okTrue)def joint_angle_curves(self, traj: np.ndarray, t: np.ndarray,lims: Dict, point_id: str):fig, ax plt.subplots(figsize(12, 7))for i, jn in enumerate(JOINT_NAMES):ax.plot(t, traj[:, i], lw1.4, labeljn)if jn in lims:lo, hi lims[jn]ax.axhline(lo, color#888, ls:, lw1)ax.axhline(hi, color#E74C3C, ls--, lw1.5)ax.set_xlabel(插补时间 (s), fontsize12)ax.set_ylabel(关节角度 (°), fontsize12)ax.set_title(f{point_id} 关节角插补曲线(红虚线软限位),fontsize13, fontweightbold)ax.legend(fontsize9, ncol3); ax.grid(alpha0.3)plt.tight_layout()plt.savefig(self.results_dir/joint_angle_curves.png,dpi150, bbox_inchestight)plt.close()def workspace_scatter(self, df: pd.DataFrame):fig, ax plt.subplots(figsize(9, 8))for _, r in df.iterrows():c RISK_COLOR.get(r[risk], #555)ax.scatter(r[x], r[z], cc, s70, edgecolorsk,alpha0.85, zorder3)ax.annotate(r[point_id], (r[x], r[z]), fontsize8)ax.set_xlabel(X (mm), fontsize12)ax.set_ylabel(Z (mm), fontsize12)ax.set_title(工作空间目标点(红超限 黄临界),fontsize13, fontweightbold)ax.grid(alpha0.3); ax.set_aspect(equal)plt.tight_layout()plt.savefig(self.results_dir/workspace_scatter.png,dpi150, bbox_inchestight)plt.close()def link_distance_curve(self, t: np.ndarray, link_dist: list,safe: float, point_id: str):fig, ax plt.subplots(figsize(11, 6))ax.plot(t, link_dist, color#8E44AD, lw1.8, label连杆最小距)ax.axhline(safe, color#E74C3C, ls--, lw2,labelf安全距 {safe}mm)ax.set_xlabel(插补时间 (s), fontsize12)ax.set_ylabel(连杆间距 (mm), fontsize12)ax.set_title(f{point_id} 连杆间距随插补变化,fontsize13, fontweightbold)ax.legend(fontsize10); ax.grid(alpha0.3)plt.tight_layout()plt.savefig(self.results_dir/link_distance_curve.png,dpi150, bbox_inchestight)plt.close()def risk_scatter(self, y_true, y_pred):fig, ax plt.subplots(figsize(8, 8))labels [安全, 临界, 超限]ct np.array([labels.index(y) for y in y_true])cp np.array([labels.index(y) for y in y_pred])ax.scatter(ct, cp, c#27AE60, s60, edgecolorsk, alpha0.8)ax.plot([-0.5,2.5],[-0.5,2.5],r--,lw2,label理想)ax.set_xticks([0,1,2]); ax.set_xticklabels(labels)ax.set_yticks([0,1,2]); ax.set_yticklabels(labels)ax.set_xlabel(实际风险, fontsize12)ax.set_ylabel(预测风险, fontsize12)ax.set_title(关节超限风险 预测vs实际, fontsize13, fontweightbold)ax.legend(fontsize10); ax.grid(alpha0.3)plt.tight_layout()plt.savefig(self.results_dir/risk_scatter.png,dpi150, bbox_inchestight)plt.close()def joint_risk_network(self, imp: Dict):fig, ax plt.subplots(figsize(11, 8))G nx.DiGraph()G.add_node(超限风险, kindtarget)for n in [J2峰值, J3峰值, 最小连杆距, 峰值角速度, 目标高度Z]:G.add_node(n, kindfactor)key_map {j2_peak_deg:J2峰值,j3_peak_deg:J3峰值,min_link_dist_mm:最小连杆距,peak_vel:峰值角速度,z:目标高度Z}for k,v in imp.items():G.add_edge(key_map.get(k,k), 超限风险, weightv)pos nx.spring_layout(G, seed42)cmap [#E74C3C if n超限风险 else #3498DB for n in G.nodes()]nx.draw_networkx_nodes(G,pos,node_colorcmap,node_size3200,alpha0.9,axax)nx.draw_networkx_edges(G,pos,arrowstyle-|,arrowsize20,edge_color#555,width2,axax)nx.draw_networkx_labels(G,pos,font_size11,axax,font_colorwhite,font_weightbold)ax.set_title(关节/几何→超限风险关联, fontsize14, fontweightbold)ax.axis(off)plt.tight_layout()plt.savefig(self.results_dir/joint_risk_network.png,dpi150, bbox_inchestight)plt.close()def interp_heatmap(self, traj: np.ndarray, point_id: str):fig, ax plt.subplots(figsize(10, 5))im ax.imshow(traj.T, aspectauto, cmapviridis,extent[0,1,5.5, -0.5])ax.set_yticks(range(6)); ax.set_yticklabels(JOINT_NAMES)ax.set_xlabel(插补进度(归一化), fontsize12)ax.set_ylabel(关节, fontsize12)ax.set_title(f{point_id} 关节角插补热力,fontsize13, fontweightbold)plt.colorbar(im, axax, label角度(°))plt.tight_layout()plt.savefig(self.results_dir/interp_heatmap.png,dpi150, bbox_inchestight)plt.close()/detailsdetailssummary/summary合成机器人目标点位。import numpy as npimport pandas as pdfrom pathlib import Pathfrom typing import Optionalclass SyntheticRobotTask:多目标点×多姿态机制:高位/深位 → J2易超界折叠姿态(小J2-J3夹角) → 连杆距小易自碰def __init__(self, rng: Optional[np.random.RandomState] None):self.rng rng or np.random.RandomState(42)def generate(self, out_path: str robot_targets.csv,n_points: int 24) - pd.DataFrame:rows []j2_lim (-85, 85)j3_lim (-150, 150)for i in range(n_points):x float(self.rng.uniform(200, 700))y float(self.rng.uniform(-400, 400))z float(self.rng.uniform(-200, 450)) # 高位易超J2rows.append({point_id: fP{i1:02d},x: round(x,1), y: round(y,1), z: round(z,1),rx: 0, ry: 0, rz: 0,j1_lim: 170,j2_lim_min: j2_lim[0], j2_lim_max: j2_lim[1],j3_lim_min: j3_lim[0], j3_lim_max: j3_lim[1],})df pd.DataFrame(rows)Path(out_path).parent.mkdir(parentsTrue, exist_okTrue)df.to_csv(out_path, indexFalse, encodingutf-8)return df/detailsdetailssummary/summary机器人关节运动仿真: 目标点 → 逆解 → 插补 → 限位自碰校验课程映射滨州职业学院《先进制造技术》工业机器人技术基础DH/正逆解/关节限位/工作空间/插补先进制造技术基础坐标变换/空间几何FMS与先进生产管理工位快速换型/节拍保障智能制造与数字孪生关节运动数字映射/看板先进制造新模式点位→关节安全域知识库技术栈严格pandas / numpy # 点位表/逆解/插补scipy # 距离判定scikit-learn # RF超限风险分类matplotlib / networkx# 曲线关联图import sys, ossys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))import numpy as npimport pandas as pdfrom pathlib import Pathfrom robot_joint_sim.target_point_loader import TargetPointLoaderfrom robot_joint_sim.inverse_kinematics import InverseKinematics, JointAnglesfrom robot_joint_sim.joint_interpolator import JointInterpolatorfrom robot_joint_sim.limit_collision_checker import LimitCollisionCheckerfrom robot_joint_sim.risk_classifier import RiskClassifierfrom robot_joint_sim.robot_visualizer import RobotVisualizerfrom robot_joint_sim.synthetic_robot_task import SyntheticRobotTaskdef main():print( * 70)print(机器人关节运动仿真: 逆解插补限位/自碰校验)print( * 70)results_dir Path(results); results_dir.mkdir(exist_okTrue)# 1. 合成print(\n[1/8] 生成目标点位...)gen SyntheticRobotTask(rngnp.random.RandomState(42))df gen.generate(robot_targets.csv, n_points24)print(f {len(df)}个目标点, J2限位±85°)# 2. 加载print(\n[2/8] 加载点位...)df TargetPointLoader(robot_targets.csv).load()print(TargetPointLoader().summary(df))# 3. 逆解 插补print(\n[3/8] 逆解关节插补...)ik InverseKinematics()利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表