🤖 机器人手臂:运动学模拟与碰撞算法详解

基于 2026-10-03 的代码现状逐文件核对写成——所有数字(关节轴坐标、行程、胶囊数、自检误差)均取自当前仓库的真实代码与数据文件,不是设计目标值。

正运动学:枢轴链IK:闭式支 + 冗余扫描 + 牛顿收口 碰撞 I:OCCT 精确布尔(离线)碰撞 II:实时胶囊 + 动态限位 11 张算法图 · 5 张实机渲染

算法图(SVG)已内联本页,可单文件分享;实机渲染图引用 docs/shots/ 相对路径。文字版同步于 docs/运动学与碰撞算法详解.md,由 scratch/build_algo_doc_html.py 生成。

第一篇系统总览:一个事实源、两个碰撞模型

整台手臂的"运动学模拟"分布在两个运行环境里,靠数据文件(而不是人工抄写)保持一致。

环境载体算法
离线(Python/CadQuery)human_layout.py → arm_model.py → sweep_v2.py参数化 CAD 建模、OCCT 精确布尔碰撞、行程扫描
在线(浏览器)arm-ik-solver.js + ik-glue.js + three.js 场景图闭式 IK、实时胶囊碰撞、动态限位
系统总览:一个事实源、两个碰撞模型、一条数据链 ① 建模侧(Python · CadQuery) ② 数据契约(docs/*.json) ③ 网页查看器(单文件 HTML) human_layout.py 唯一事实源:6 根关节轴 rear/d、 连杆高度链、台架/机身界面 arm_model.py · arm_build.py 参数化 CAD:9 打印件 + 6 电机模组 装配体 / STEP / STL / 质量属性 strength_check.py · torque_balance.py 同一关节表:截面刚度 / 重力力矩网格搜索 sweep_v2.py(离线权威碰撞) 多进程 + GJK 预筛 + OCCT 精确布尔 scratch/fit_capsules.py CAD 网格 → 实时胶囊模型拟合 travel.json / travel_detail.json 单关节硬限位(滑块)+ 组合折叠明细 collide.json 1995 胶囊 + adj 机械区间 + 硬边标记 torque_balance.json · strength.json 逐关节力矩负载率 / 截面安全系数 mesh_arm.json(网格) AB.export → build_viewer.py 打包 写出 写出 arm-ik-solver.js(ArmIK) 闭式支 + (θ4,θ5) 扫描 + 牛顿收口 DLS 兜底 · 实时胶囊碰撞 · 动态限位 纯 JS 无依赖 · 主线程 / Worker 同源 ik-glue.js(交互层) 拖拽 / 箭头姿态 / 实时追踪 / Worker 滑块动态限位夹取(边界缓存) three.js 场景图 6 级嵌套 pivot(与 FK 链逐位一致) FK 渲染 + 残差复核(双通道对账) 限位 / 碰撞注入求解器与 UI 几何网格 关节 rear/d 常量经 build_viewer 注入两处(CAD 与网页同源) 改几何的同步纪律:arm_model.py 重建 → AB.export 写 mesh_arm.json → build_viewer.py 重打包 → 渲染出图确认 → 几何变更必须重跑 sweep_v2.py(行程/碰撞权威);collide.json 由 fit_capsules.py 重新拟合。
图 1 | 系统总览:一个事实源、两个碰撞模型、一条数据链。几何变更必须沿底部同步链走完整,缺一步网页就是旧的、行程就是错的。

关键纪律是单一事实源:6 根关节轴的位置 rear 与方向 d 只在 human_layout.py 里定义一次,CAD 装配、离线碰撞扫描、网页求解器三处都从这里取值——网页端由 build_viewer.py 打包时把同一组常量注入 HTML,因此"CAD 里看到的"和"网页里算的"逐位一致,不存在第二套会漂移的关节表。

两个碰撞模型各司其职:离线精确模型(sweep_v2.py)对真实 CAD 实体做布尔求交,慢(每姿态秒级)但精确,负责产出权威行程数据;在线近似模型(collide.json 胶囊体)把每件零件拟合成一组胶囊,快(每次 ~5ms)但近似,负责求解过程中的实时避碰。几何变更时的同步链:

arm_model.py 重建 → AB.export 写 mesh_arm.json → build_viewer.py 重打包
→ 渲染出图确认 → sweep_v2.py 重扫(travel.json)→ fit_capsules.py 重拟合(collide.json)
实机等轴测渲染
图 2 | 实机全貌(查看器渲染,零位姿态):6 电机人形构型,上臂 158mm / 前臂 110mm,13 件打印件 + 6 台 60mm 电机模组。

第二篇运动学建模与正运动学(FK)

2.1关节拓扑

对标智元灵犀 X1 的人形构型:肩部两轴(抬举+俯仰)→ 大臂自转 → 肘俯仰 → 腕俯仰 → 腕自转。所有关节轴都是坐标轴方向的(±X/±Y/±Z),这是后面一切降阶解法的前提:

关节名称轴方向轴位置(CAD 系)驱动
J1肩抬举+X(滚转)x=−51, z=610M1 → P04 肩叉
J2肩俯仰+Y(俯仰)x=19.5, z=490M2 → P05 大臂旋转座
J3大臂自转−Z(滚转)x=19.5, z=445M3 → P06 大臂骨架
J4肘部俯仰+Y(俯仰)x=19.5, z=332M4 → P07 小臂骨架
J5腕部俯仰+Y(俯仰)x=19.5, z=222M5 → P08 腕偏转座
J6腕自转−Z(滚转)x=19.5, z=176M6 → P09 工具法兰
运动链与关节轴(正视简化示意) 沿 −Y 看:X 向右、Z 向上、Y 指向纸内(⊗)。零位 θ=0,臂竖直下垂。 立柱 P01(台架) 底座 P02 J1 肩抬举 轴 +X(滚转,前后方向) J2 肩俯仰 轴 +Y(⊗,与 J4/J5 平行同向) J3 大臂自转 轴 −Z(臂自身轴线) J4 肘部俯仰 轴 +Y(⊗) J5 腕部俯仰 轴 +Y(⊗,与 J4 同向 ⇒ R4·R5 可合并) J6 腕自转 轴 −Z(滚转,TCP 在轴线上 ⇒ 不动位置) TCP 工具法兰安装面中心(J6 组打印件最低面) C0 = 120 45 113 110(前臂) 46 z=610z=490z=445 z=332z=222z=176 肩关节组 大臂 158 前臂 110 腕组 46 + 法兰 图例 X 轴(水平,前后) Y 轴(指向纸内) Z 轴(竖直,J3/J6 实为 −Z) 结构前提(求解器启动即断言):J2/J4/J5 平行同向且同一 x 平面(X_ARM≈19.5)、J1 ∥ X、J3/J6 ∥ Z、TCP 落在 J6 轴线上。 这五条性质让六轴问题可以降阶为「二维冗余扫描 + 闭式支」,任何一条不成立时求解器会打出 WARN 并回落通用路径。
图 3 | 运动链与关节轴(正视简化示意)。求解器启动即断言五条结构前提:J2/J4/J5 平行同向且同一 x 平面、J1∥X、J3/J6∥Z、TCP 在 J6 轴线上——任何一条被改型破坏都会显式报警并回落通用路径,而不是悄悄算错。
实机侧视
图 4 | 实机侧视(零位):立柱 P01 在左,臂竖直下垂——与图 3 的简化示意逐点对应。

2.2正运动学:枢轴链

每个关节是"绕一根不经过原点的轴"的旋转,写成枢轴变换;整条链就是六次左乘:

Ai(θi) = T(Pi) · R(di, θi) · T(−Pi)      对任意点:x' = R·(x−Pi) + Pi
World = A1(θ1)·A2(θ2)·A3(θ3)·A4(θ4)·A5(θ5)·A6(θ6)·TCP
正运动学:枢轴变换与链式累积 单关节:绕“不在原点的轴”旋转 枢轴点 P + 轴方向 d θ=0 位形(虚线)→ θ 位形(蓝) 关节角 θi = 绕轴 d 的有符号转角 A(θ) = T(P) · R(d,θ) · T(−P) x' = R·(x − P) + P 先平移枢轴点到原点 → 绕轴转 θ → 平移回去。关节表只存 P(rear)与 d(单位轴向量),六个关节共 12 个常量。 六链累积:World = A₁·A₂·A₃·A₄·A₅·A₆·TCP A1 A2 A3 A4 A5 A6 θ1θ2θ3 θ4θ5θ6 World TCP 每级只读父级位姿与自己 —— 与 three.js 场景图 6 级嵌套 pivot 逐位一致 两套实现,互为对账: · fkChain():严格 4×4 同质矩阵链 —— 对照基准,慢而直白 · fk():按结构常量展开三角函数的闭式快路径 —— 求解器 热路径专用,每姿态仅 ~20 次乘加 · 自检:5000 个随机位形,两实现的 TCP 差 ≤ 5.7e-13 mm · 工具长度 tool 沿 J6 轴外伸:TCP_z 随 tool 平移(ax=J6 轴) 指纹校验:预置自检值绑定(限位表+常量+源码)的 FNV-1a 指纹, 模型或求解器一变即提示重算。
图 5 | 枢轴变换与链式累积:两套实现互为对账——fkChain() 严格 4×4 矩阵链做基准,fk() 展开三角函数做热路径;5000 随机位形最大差 5.7e-13 mm。

三个对算法影响深远的结构性质:

  • J4 ∥ J5 同向 ⇒ R4·R5 = R_y(θ4+θ5),腕部两个俯仰在"工具轴指向"上合并成一个未知数 s = θ4+θ5;
  • TCP 在 J6 轴线上 ⇒ θ6 只提供绕工具轴的滚转,完全不改变 TCP 位置——位置任务与姿态任务由此解耦;
  • 工具轴(法兰 +Z)= (R1…R5)·ẑ,与 θ6 无关——姿态任务只剩 2 个方程。

第三篇逆运动学:闭式支 + 二维冗余扫描 + 牛顿收口

3.1问题形式与降阶

6 自由度机械臂,任务是给定 TCP 目标点(位置任务,3 个方程)或"目标点 + 工具姿态 {方位角 az, 仰角 el, 滚转 roll}"(位姿任务,再加 3 个方程)。臂本身有 6 个未知数,位置任务下存在 2 维冗余。

核心思路(branch()):把 (θ4, θ5) 当作冗余参数。一旦给定这两个角,腕心相对量 V 完全确定(u=−L·sinθ5, w=A54−L·cosθ5, dx=u·c4+w·s4, dz=A43−u·s4+w·c4),剩下的 θ1..θ3 可以用平面三角学一步闭式解出(每格至多 4 支:θ3 的 ±σ 与 ±(π−σ)),位置精确到浮点误差,不需要任何迭代。

闭式支 branch():给定 (θ4,θ5),θ1..θ3 一步解出(简化示意) 第 1 步:(θ4,θ5) ⇒ 腕心平面坐标 V J4 轴(+Y,⊗) J5 轴 腕心方向(L = L5+tool) θ4 θ5 肘(J4) u = −L·sinθ5 w = A54 − L·cosθ5 dx = u·cosθ4 + w·sinθ4 dz = A43 − u·sinθ4 + w·cosθ4 V = (dx, dz + A32) J4∥J5 同向 ⇒ R4·R5 = R(θ4+θ5):腕心只由这两个角决定, 姿态任务里工具轴对齐也因此只差 2 个未知数。 第 2 步:肩部圆交 + 肘部四支 ⇒ θ1/θ2/θ3 J1 轴(+X,⊙) 目标(柱坐标 R, φ) φ θ1 = φ − atan2(cc, b) J1(正视) J2 C0=120 腕心(由 V 决定) σ 肘部三角:σ=acos(√m2) θ3 四支:±σ 与 ±(π−σ) (肘上/肘下 × 同号/反号镜像族) n = (R²+A²−Vx²−Vz²−C0²) / (2·C0),cc = C0+n θ2 = atan2((λA−κn)/det, (κA+λn)/det) κ=Vx·c3, λ=Vz b = −Vx·sinθ3 (θ1 由柱角 φ 反推) 退化防线:|Vx|<1e-7、det<1e-12、m2 越界 → 该支无解,直接落选 示意省略了 X0/Y0 基准平移与 tool 常量;四支解的位置全部精确命中目标 ——「扫描代替数值搜索」的根基。
图 6 | 闭式支 branch():给定 (θ4,θ5),θ1..θ3 一步解出。四支解的位置全部精确命中目标——「扫描代替数值搜索」的根基。

于是"求 IK"变成了"在 (θ4,θ5) 二维平面上挑最好的格点"——扫描代替数值搜索。每个格点的解天然精确,扫描的全部意义在于挑出代价(关节行程)最小的那支构型。

3.2姿态参数化:方位角 / 仰角 / 滚转

姿态任务用三个角描述工具姿态(poseMat() 构造工具系旋转矩阵,poseAng() 逆分解)。三个关键性质撑起了姿态算法:

  • θ6 只提供绕工具轴的滚转,工具轴 = (R1…R5)·ẑ 与 θ6 无关——姿态任务因此只剩 2 个方程;
  • 滚转由 rollClose() 闭式补出:θ6 = −atan2(N[1,0], N[0,0]),其中 N = R5ᵀ·R_target;
  • 推论:全姿态偏差 oerr ≥ 工具轴夹角(20 万随机样本验证零违反)——粗扫用一次点积预筛(1−cos2°)就挡掉所有必被拒的候选,免掉矩阵链 + acos。
工具姿态参数化:方位角 az · 仰角 el · 滚转 roll CAD +Z 方位基线 工具轴 (法兰 +Z 外伸) el(仰角,自 +Z 抬起) 0…180° roll(绕轴滚转) = θ6,绕工具轴 az(绕 Z 方位角,−180…180°) 法兰中心 poseMat(az, el, roll) → 工具系旋转矩阵(列 = x/y/z) z = (sin el·cos az, sin el·sin az, cos el) x0 = (−sin az, cos az, 0) (子午面基,恒⊥z) x = R(z, roll)·x0  y = z × x poseAng() 为其逆分解;el ∈ [0,180],az/roll ∈ (−180,180]。 界面以「工具朝外」描述;求解器内部法兰法向朝臂内, 两者相差绕工具 x 轴 180°(RX180),入栈/出栈各翻一次。 为什么姿态任务只剩 2 个未知数 · θ6 只提供绕工具轴的滚转,不改变工具轴 ⇒ 工具轴 = (R1…R5)·ẑ · 滚转由 rollClose 闭式补出:θ6 = −atan2(N[1,0], N[0,0]),N = R5ᵀ·Rtar · 推论:全姿态偏差 oerr ≥ 工具轴夹角(20 万随机样本验证零违反)—— 粗扫用一次点积预筛(1−cos2°)挡掉必被拒的候选,省掉矩阵链+acos
图 7 | 工具姿态参数化 az/el/roll:界面以"工具朝外"描述,求解器内部法兰法向朝臂内,相差绕工具 x 轴 180°(RX180),入栈/出栈各翻一次。

3.3求解流水线

逆运动学求解流水线(solve → solveDyn) ① 输入 目标 TCP (x,y,z) · 工具长 tool · 可选姿态 R(az,el,roll) ref = 当前姿态:取「本步行程最小」的构型 ② 冗余维准备:(θ4,θ5) 网格 + 逐行可行段探测 步长 h=0.25°;先按探测步 hp 找每行有解的连续段(位置 2° / 姿态 0.5°) 段端外扩 PAD=2° 兜住段界不确定性;段内仍按 h 精扫,密度不降 ③ 全域粗扫:每格 branch() 闭式解 θ1..θ3(≤4 支) 位置由闭式支精确命中(零残差),扫描只优化冗余维;候选有界保留 4000 ④ [姿态任务] 孤立根枚举:残差变号括号 → 阻尼高斯-牛顿收根 切向相切的根不变号,按每支 ‖e‖ 最小的节点补种;精确根直接进候选池 ⑤ 局部加密 + 模式搜索:32 种子 × 窗口 ±2h(步长 h/8) 8 邻域模式搜索步长逐轮减半至 2e-4°,把每条谷底走到最低 ⑥ [姿态任务] 二维牛顿收口 + escalating 加压 对工具轴切向偏差做 (θ4,θ5) 牛顿迭代,轴残差 →1e-12、位置保持精确 仍无 oerr≤0.05° 达标解则姿态权重 ×100 重扫(最多 3 轮) ⑦ 选解:cost = Σ w·|wrap180(θ−ref)| + 1e-6·Σ|θ| 姿态任务先取 oerr≤0.05° 达标集,集内再比代价 精确根只在代价 ≤ 达标最优+2° 时接管(防漏检多付几十度) ⑧ solveDyn 碰撞后处理(装了胶囊模型时) 解先过全姿态碰撞校验:不安全 → 候选表找 ≤1.5×cost+20° 的安全解 贴边轴二分动态边界放宽(≥5° 才值得重解,重解 cost 不升才采用) 输出:q[6] · 残差 err / oerr · 本步行程 moveSum · 候选表 list 徽章分级:已到达 / 姿态到达·位置最近 / 混合折中 / 不可达·最近位姿 solveWarm 实时快路 拖拽中:只在上帧解附近的 (θ4,θ5) 递进窗口 ±4→14→40° 同支跟随(Σ|Δθ|≤25°)+ 残差 ≤1e-6 + 姿态牛顿收口 任一校验不过 → null 自动回落全量扫描(③) 快路只负责「能快时快」, 松手后全量求解才是终值 校验不过回落 兜底:目标不可达时 closest() 位置 DLS:160 迭代 × 160 次 LCG 确定性重启(同输入 逐位一致)+ anchor 回拉 0.03 压退化漂移 + 零空间抛光收敛 closest6() 位置+姿态 6 维联合 DLS(姿态按 20mm/rad 折算) closestOri() 姿态优先:闭式轴 对齐 + (θ3,s,θ4) 坐标下降, 姿态精确、位置取最近 距离差 ≤0.5mm 时优先锚定解 0 采样点 / 不可达 返回最近位姿 热路径性能手段:轴对齐特化 rotAxis(免 Rodrigues 通用式)· sin/cos 单槽缓存 · 关节限位数组预取 · 残差 fk 复核延后到入选候选统一补算 全量扫描 40~430 ms(后台 Worker);实时快路毫秒级 —— 这是拖拽能跟手的原因
图 8 | 逆运动学求解流水线:主流程七步 + solveWarm 实时快路 + 不可达兜底。全量扫描 40~430ms(后台 Worker),快路毫秒级——这是拖拽能跟手的原因。
  1. 逐行可行段探测:位置闭式支只在平面三角有解时存在,限位矩形里只有 7~27% 的格点能过判据。先用探测步 hp 找每行的连续可行段(位置任务 2°、姿态任务 0.5°——姿态任务的可行带实测最窄 0.98°,2° 探测会整条漏掉造成"误报不可达"),段端外扩 PAD=2°,段内仍按原步长 h(默认 0.25°)精扫:密度不降、成本大降。
  2. 全域粗扫:每格 branch() 出 ≤4 支 → 限位筛(NaN 候选用否定式写法保证落选)→ 打分,候选有界保留 4000。
  3. 孤立根枚举(姿态任务):不再靠网格密度去撞窄带——逐行对姿态残差两个分量找变号括号,从括号中点沿该支跑阻尼高斯-牛顿收根;切向相切的根不变号,按每支 ‖e‖ 最小的节点补种。
  4. 局部加密 + 模式搜索:32 个种子各开 ±2h 窗口(步长 h/8,小步长档 h/4),再对最优的 24 个候选做 8 邻域模式搜索(步长逐轮减半至 2e-4°)。
  5. 姿态收口:姿态达标带是 (θ4,θ5) 平面上的斜窄谷(局部灵敏度可达 ~10°/°),模式搜索只能停在谷壁(实测最好 0.106°)。直接对"工具轴在目标轴法平面上的切向偏差"做 (θ4,θ5) 二维牛顿迭代(每步重解闭式位置支、按 θ1..θ3 最近匹配保持同支),轴残差压到 1e-12 量级、位置全程保持精确;尚无 oerr≤0.05° 的解时姿态权重 ×100 重扫(最多 3 轮)。
  6. 选解:cost = Σ w·|wrap180(θ−ref)| + 1e-6·Σ|θ|。ref 是当前姿态——判据是「所有电机需要转过的角度之和最小」(这一步行程最短),连续轴取等价角防绕远。姿态任务先取 oerr≤0.05° 的达标集、集内再比代价,"省几度关节角、丢 0.2° 姿态"的交易被显式禁止。
  7. solveDyn 碰撞后处理:见第六篇。
冗余维 (θ4,θ5) 平面:可行带、加密窗口与牛顿收口 可行带:branch 判据筛掉 73~93% 的格点 ××× ××× ××× ××× ××× 逐行可行段 rowSpans(探测步 hp,段端外扩 PAD=2°): 每行通常是单一连续段,段内仍按 h 精扫 —— 密度不降、成本大降 加密窗口 ±2h(步长 h/8, 小步长档 h/4)罩住种子 8 邻域模式搜索: 步长减半,停在窄谷谷壁 牛顿收口点 轴残差 →1e-12(位置保持精确) −111°−90°−60° −30°0°+16° −93°−60°−30° 0°+34° θ4(J4 肘部俯仰) θ5(J5 腕俯仰) 每个格点的位置解由闭式支精确给出 —— 扫描不是在「逼近」目标,而是在冗余维里「挑构型」; 姿态任务的达标带实测最窄 0.98°,2° 探测步会整条漏掉(误报不可达),故姿态任务探测步降到 0.5°。
图 9 | 冗余维 (θ4,θ5) 平面:可行带、逐行可行段、加密窗口与牛顿收口点。每个格点的位置解由闭式支精确给出——扫描不是在「逼近」目标,而是在冗余维里「挑构型」。

3.4兜底:最近可达位姿

目标不可达时不报错了事,而是返回最近可达位姿,并按任务语义分三种:

  • closest():位置 DLS(阻尼最小二乘)——160 迭代 × 160 次 LCG 确定性重启(固定种子,同一目标逐位可复现,消灭"随机倒摆")+ anchor 回拉 0.03(压准退化构型下 J3 的无用漂移,实测 82°)+ 零空间抛光(到目标距离不增的前提下把 Σ|θ| 滑到最小);
  • closest6():位置 + 姿态 6 维联合 DLS(姿态旋转矢量按 20 mm/rad 折算进误差向量);
  • closestOri():姿态优先——利用 J4∥J5 的降阶结构(工具轴 = Rx(θ1)·Ry(θ2)·Rz(−θ3)·Ry(s)·ẑ),(θ3, s, θ4) 三维粗扫 + 坐标下降,姿态精确、位置取最近。

3.5混合优先(位置/姿态滑杆)

位置与姿态在远目标上不可兼得(实测同一目标:位置 0mm 时姿态差 44°,姿态 0° 时位置差 116mm)。mix 模式用两端锚点(位置优先解 / 姿态优先解)的误差各自归一,按 J(q) = w·(e/e_ref)² + (1−w)·(o/o_ref)² 加权折中,种子取两锚点 + 关节空间插值 + ref,6 维阻尼高斯-牛顿 + 单轴抛光;J 同量级带内取行程更小者防横跳。滑杆 0%/100% 直接走两条专用算法,结果逐位一致;拖动中走 mixWarm 快路,位移超限(疑换盆地)回落全量。

3.6实时续解(warm 路径)

拖拽时目标每帧只挪几毫米,全域粗扫(0.25° 下 6~30 万点,40~430ms)是 100ms 级延迟的唯一来源。solveWarm 只在上一帧解附近的 (θ4,θ5) 递进窗口(±4°→14°→40°)里走同一闭式支:位置残差 ≤1e-6、关节跳 ≤25°(防换支/倒摆)、姿态牛顿收口——任一校验不过就返回 null 交回全量。快路只负责"能快的时候快",松手后的全量求解才是最终结果。

3.7工作空间剖面

工作空间由纯 FK 采样刻画(scratch/gen_workspace_svg.cjs,与查看器「可达空间」同口径:travel.json + adj 机械区间内随机位形、J6=0、未计自碰撞)。J1 整周连续,可达集对 J1 轴旋转对称,所以 (x, r) 二维剖面(r = 离 J1 轴的距离)就是完整工作空间:rmax = 517mm 恰好是零位臂竖直下垂的 TCP,前伸边界 x≈+416mm。

运动学工作空间剖面((x, r) 平面,真实 FK 采样 60000 位形) r = 离 J1 轴的距离;J1 整周连续 ⇒ 可达集对该轴旋转对称,本剖面即完整工作空间(未计自碰撞与胶囊干涉) -400 -300 -200 -100 0 100 200 300 400 100 200 300 400 500 x:CAD 前后方向 (mm) r:离 J1 轴距离 (mm) J2 r=120J3 r=165J4 r=278J5 r=388J6 r=434TCP r=517 rmax = 517(离 J1 轴最远,与预置自检一致) 采样口径与查看器「可达空间」一致:限位内随机位形(travel.json + adj 机械区间),J6=0;边界 x ∈ [-377, 416]
图 10 | 工作空间 (x, r) 剖面:真实 FK 采样 60000 位形(确定性 LCG,可复现)。紫色点为零位各关节轴到 J1 轴的距离;采样结果与查看器预置自检(400000 位形、rmax=517、x −378…415)一致。

自检成绩(预置值,指纹校验):

项目样本结果
FK 双实现对账5000最大差 5.7e-13 mm
位置 IK 重构200/200 命中最大残差 9.9e-13 mm
姿态 IK 重构400 组398 命中,最大残差 1.79°,平均 77.8 ms/次
运动学可达空间400 000 位形x −378…415 / y −388…642 / z 94…1125 mm
姿态 IK 的 2 组未达 0.05° 达标线,以近似解如实上报 poseTier=approx 并给出真实残差,不虚报精确。
IK 求解姿态
图 11 | 一次 IK 求解后的实机姿态:所有滑块、残差徽章与候选构型表均由上述流水线驱动。

第四篇交互层:把求解器接进 30fps

ik-glue.js 负责三件事:调度、拖拽语义、动画与对账。

调度。全量求解(40~430ms)放进 Blob Worker 后台跑,主线程只收结果渲染。每个请求按 id 记账,"是否最新一帧"用第二参交给回调——被插队作废的请求也会被放行,保证实时追踪的门闩(liveBusy/livePending)不因一次竞态永久停摆。Worker 不可用时自动回落主线程同步求解,行为逐位一致。

拖拽语义。黄球 = 移动目标(Shift 只上下);姿态控制开启后出现工具箭头:拖箭头头部 = 弧球式瞄准(指针射线与"以目标为球心、箭长为半径"的球求交,方向即工具轴指向),拖箭杆/捏环/粉色侧球 = 绕轴滚转(指针在垂直于工具轴的平面上的转角)。"实时追踪"模式下拖动即算即动,松手全量精修。

动画与对账。解算结果经 animTo 动画写入:连续轴(J1/J3/J6)按 wrap180 走短弧(+151.9°→−167° 若按数值插值会反向扫 318.9°,走短弧只有 41.1°),末帧落回求解器给的代表角——与插值终点恰差 360° 整数倍,同一物理角、视觉零跳变;直接写入会取消在飞动画,实时解不会被入场动画覆写。每帧渲染后还用场景图真实矩阵链复算一次 TCP 位置与姿态偏差(sceneOrientErr),和求解器的闭式残差双通道对账——任何"求解器以为对了、场景画错了"的不同步都会立刻暴露在界面上。

第五篇碰撞检测 I:离线权威扫描(sweep_v2.py)

离线扫描回答的问题是:"每个关节到底能转到多少度?"判定必须精确——它直接对真实 CAD 实体做 OCCT 布尔求交,一切预筛都设计成保守证书:只能"确证分离"才允许跳过精确布尔。

离线碰撞检测(sweep_v2.py):逐层过滤漏斗 目标:把「每姿态都做 NURBS 布尔」压到只剩真正贴 face 的零件对 —— 精确性一分不减,调用量掉几个数量级 1 全对全候选对 19 件 ≈ 171 对(9 打印件 + 6 电机 + 台架 P01/P02),按姿态刚体变换 2 跳过刚性安装对 MOUNT_PAIRS 12 对:电机 ↔ 被驱动件由螺钉锁死,无相对运动 3 组跨度规则 该姿态中没有活动关节跨越的对跳过(零位时全部必查——静止不代表不干涉) 4 轴区间预剪枝(单关节扫描专用,精确安全) 沿关节轴的轴向/径向区间永不相交的对,任何角度都不可能碰撞 5 AABB 分离 8 角点变换后取包围盒,0.1mm 余量;分离即跳过 6 包围球分离 球心随组变换、半径旋转不变;球分离 ⇒ 零件必分离 7 GJK 凸包预筛(保守证书) 0.5mm 凸包 ⊕ 0.8mm 球:确证分离才允许跳过 可能干涉/数值退化一律回落精确布尔——结果与纯布尔一致 8 顶点侵入实锤(反方向 shortcut) 一方网格顶点(≤240 采样)落入对方实体 >1mm ⇒ 直接判干涉,免 4~12s 布尔;只证「撞」不证「自由」 9 OCCT 精确布尔 Common 实体交体积 > 2mm³ 记干涉(附干涉质心) SetRunParallel(True) · 多进程分发姿态 布尔每对 0.5~12s 前面每层都在为它省调用 扫描策略:单关节双向粗扫 2°(小批量、首个卡死角即停)+ 二分逼近 0.2° → travel.json(滑块硬限位) 组合姿态收敛 6°(一个关节保持极限、扫其余,两轮迭代)→ 只写 travel_detail.json,不压缩滑块 | workers = min(12, CPU−2, 内存/2.5GB)
图 12 | 离线碰撞检测的九层过滤漏斗:目标是把「每姿态都做 NURBS 布尔」压到只剩真正贴 face 的零件对——精确性一分不减,调用量掉几个数量级。

逐层拆解(每姿态的判定路径 test_posture()):

  1. 全对全:19 件(9 打印件 + 6 电机 + 台架 P01/P02)≈ 171 对。台架默认参与碰撞——臂撞立柱就是臂撞躯干,是真约束(--no-stand 仅用于诊断某个限位是臂自身折叠还是躯干挡的)。
  2. 跳过刚性安装对:12 对电机↔被驱动件由螺钉锁死(MOUNT_PAIRS),无相对运动。
  3. 组跨度规则:某姿态下没有活动关节跨越的零件对相对静止,跳过;但零位(无活动关节)时必须全查——相对位姿不变不代表不干涉。
  4. 轴区间预剪枝(单关节扫描专用):按关节轴算每对零件的轴向/径向区间,永不相交的对在该关节任何角度都不可能碰撞——对单关节扫描是精确安全的。
  5. AABB:8 角点变换后取包围盒,0.1mm 余量。
  6. 包围球:球心随组刚体变换、半径旋转不变,仍精确保守。
  7. GJK 凸包预筛:零件按 0.5mm 容差网格化取顶点云 → scipy 凸包精简(电机 15 万顶点 → 千余点),GJK 带 0.8mm 安全外扩(> 网格内缩 0.5mm,保证"确证分离"判据保守)。"可能干涉/数值退化/迭代超限"一律回落精确布尔——结果与纯布尔完全一致,--no-gjk 可关闭做对比校验。
  8. 顶点侵入实锤(反方向捷径):一方的网格顶点(≤240 采样点)落入对方实体内部 >1mm 即实锤干涉,免掉 4~12s 的 NURBS 布尔。注意方向性:它能证"撞",不能证"自由"——无命中则照旧走布尔。
  9. OCCT 精确布尔 BRepAlgoAPI_Common:交体积 > 2mm³ 记干涉并附干涉质心;布尔失败记入 errors 不装作没看见。

扫描策略:

  • 单关节硬限位:2° 粗扫(小批量并行、首个卡死角即停)+ 二分逼近到 0.2°,正反两向——travel.json 的来源。
  • 组合姿态收敛:一个关节保持在极限、扫其余关节(6° 步长),两轮迭代收敛出"组合折叠约束"。这些约束只写 travel_detail.json、不压缩滑块——否则手臂会被静态限位锁死;查看器改用动态模型按当前姿态实时处理(第六篇)。
  • 并行:mp.Pool 每个工作进程独立建模(杜绝 STEP/网格过期),workers = min(12, CPU−2, 可用内存/2.5GB)。

当前权威数据(travel.json / travel_detail.json,2026-10-03 扫描;collide.json 胶囊拟合 2026-09-29):

轴单关节硬限位滑块范围备注
J1−180…+180±180 连续整周无干涉
J2−106.25…+10.38−105…+9含 1° 安全余量
J3−180…+180±180 连续整周无干涉
J4−112.62…+17.88−111…+16
J5−94.75…+35.75−93…+34
J6−180…+180±180 连续电机行程 ±360°

组合折叠明细共 99 条(典型:工具法兰 P09 撞立柱 P01_A 限制了 J3/J4/J5 的组合极限)。

零件爆炸图
图 13 | 全零件爆炸图:离线扫描的"全对全"就是在这些零件(13 打印件 + 6 电机 + 台架)之间两两做布尔。

第六篇碰撞检测 II:实时胶囊模型与动态限位

6.1模型:CAD → 胶囊

离线布尔每姿态秒级,放进 30fps 的交互里不可能。scratch/fit_capsules.py 把每件零件的 CAD 网格拟合为一组胶囊体(线段端点 a,b + 半径 r),写进 docs/collide.json:共 1995 枚胶囊,归属 7 个运动组(g0=台架固定 … g6=J6 组),端点是零位 CAD 世界坐标,组 g 的胶囊随 A1…Ag 嵌套旋转——与 FK 链同一累积变换,与场景图逐位一致。胶囊是外凸近似,判"干涉"略偏保守(含 margin=0.3mm 外扩),判"自由"需离线布尔背书;换来的是每次 collideFree() 只需 ~5ms(零位最坏情形)。

实时碰撞模型:CAD 网格 → 胶囊体 + 两级宽相位 手臂按运动组胶囊化(fit_capsules.py 拟合) 台架 g0(root 固定) g1 · J1 组(肩吊臂) g2 · J2 组(肩叉) g3 · J3 组(大臂骨架) g4 · J4 组(小臂骨架) g5 · J5 组(腕偏转座) g6 · J6 组(工具法兰) 胶囊 = 线段端点 a,b + 半径 r,端点是零位 CAD 世界坐标; 组 g 的胶囊随 A1..Ag 嵌套旋转(与 FK 链同一累积变换)。 两级宽相位 → 胶囊精确距 第 1 级 · 零件包围球(19 球) 球心距 > rad_A+rad_B+rmax_A+rmax_B+margin 才筛除(阈值推导防漏判) 第 2 级 · 簇球(每零件胶囊按主轴分桶,每簇 ≤4 枚) 主轴 = 胶囊中点的幂迭代方向(5 轮);再细分实测得不偿失 第 3 级 · 胶囊对:线段-线段距离(Ericson 标准式) d(轴_a, 轴_b) < r_i + r_j + margin(0.3mm) ⇒ 干涉;无 visit 回调时首对即短路 d(轴间距) 自由 ⇔ d ≥ r_i + r_j + margin 胶囊并集 ⊇ 零件本体 ⇒ 「自由」可信、「干涉」需离线布尔确认 三条关键规则 · 只检 |Δg|≥2 的零件对(同组相对静止、零位已保证不干涉); 相邻对 P04×P05/P06×P07/P07×P08 同心跨轴,塞进模型零位必误报 ⇒ 改用 adj 一维自由区间表(CAD 实测、与姿态无关) · adj:J2 [−107°,+128°] · J4 [−112°,+112°] · J5 [−96°,+96°] · 硬边(lo 侧相邻件自碰撞)静态值即真值;可放宽侧(撞立柱)按姿态实时二分 性能:1995 胶囊 · collideFree ≈ 5ms/次(零位最坏) 预分配缓冲 + 代际戳缓存(免 GC)· 轴对齐特化旋转 · 热循环标量直传免建数组
图 14 | 实时胶囊模型与两级宽相位:只检 |Δg|≥2 的零件对,相邻打印件对改用 adj 一维自由区间表(CAD 实测,由 scratch/probe_adj.py 扫描生成;collide.json 缺 adj 时查看器显式告警并退回静态限位)。

6.2宽相位与热路径

  • 只检 |Δg|≥2:同组相对静止(零位单实体检查已保证不干涉);相邻组的对(P04×P05 / P06×P07 / P07×P08)同心跨在同一关节轴上,胶囊轴距≈0,塞进模型会在零位误报(实测 P04×P03 超报 15mm)——改用 adj 一维自由区间表:J2 [−107°,+128°]、J4 [−112°,+112°]、J5 [−96°,+96°]。
  • 两级宽相位:零件包围球(19 球)→ 簇球(每零件的胶囊按主轴投影分桶、每簇 ≤4 枚,主轴由幂迭代 5 轮定向)→ 只有邻近簇才逐对变换胶囊端点做线段-线段距离(Ericson《Real-Time Collision Detection》标准式,手写标量直传版,免建数组)。宽相位阈值带 rmax+margin 是有推导的:早期版本漏了 rmax,把 P01_C×P06 在 J1=20°/J2=49° 的 1618mm³ 深撞整对筛掉。
  • 性能工程:预分配缓冲 + 代际戳缓存(一次判定 ~2500 个小数组的 GC 开销占 13% → 归零)、轴对齐特化 rotAxis(六个关节轴都是 ±X/±Y/±Z,Rodrigues 通用式退化为每轴 4 个非零元素)、簇球再细分实测得不偿失(62 万次球对测试 27ms/check)。

6.3动态限位:静态值只是零位快照

travel.json 的单关节限位是"其它轴全零"时的一次性扫描,对"撞固定立柱"这类跨 ≥2 组的约束系统性过紧:实测 J2 正向静态 +9.75°(垂臂零位),J1=+90° 时真边界放到 +108.75°。查看器的滑块边界因此分三层:

动态限位:静态值只是零位快照,真边界由碰撞模型按姿态二分 以 J5 腕俯仰 + 向为例(数值取自实机数据) −360°0°+360°(电机限位) adj 机械区间(相邻件 CAD 实测):J5 = [−96°, +96°] 零位静态扫描 +34°(撞立柱 P01_A) 当前姿态动态边界 +96° 当前 θ dynLimit:从当前角向限位方向二分「collideFree」翻转点(tol 0.25°) 为什么静态限位不可信:它来自「其它轴全零」的一次性扫描。实测 J2 正向静态 +9.75°(垂臂零位撞立柱), J1=+90° 时动态边界放到 +108.75° —— 撞立柱这类「跨 ≥2 组」的约束必须按姿态重算,滑块才不锁死。 solveDyn:静态解 → 碰撞校验 → 贴边轴放宽重解(最多 2 轮) 静态限位求解 solveTo(全流水线) 全姿态碰撞校验 不安全→候选表选安全解 贴边轴检测(0.5°内) 非连续轴 · 非硬边 dynLimit 二分边界 放宽 ≥5° 才值得重解 放宽后重解(粗档) cost 不升才采用 滑块夹取 clampAxis:缓存 [轴, 其余轴 1° 量化姿态] → {lo, hi};拖同一轴直接取缓存, 拖过边界才重算一次二分(~40ms)。当前姿已干涉时退回机械区间 —— 否则 dynLimit 会把界外值当边界,滑块再也回不来。
图 15 | 动态限位:adj 机械区间 → dynLimit 二分(tol 0.25°)→ solveDyn 放宽重解;滑块夹取带边界缓存,当前姿已干涉时退回机械区间。
  1. 机械区间 = adj 表(相邻件实测,必然无自碰撞);
  2. 动态边界 = dynLimit() 从当前角向限位方向二分"collideFree"翻转点(tol 0.25°),上限受电机行程 ±360° 与 adj 封顶;"硬边"(lo 侧相邻件自碰撞,配置无关)直接取静态值不放宽;
  3. 滑块夹取 clampAxis() 带边界缓存:[轴, 其余轴 1° 量化姿态] → {lo,hi},拖同一轴直接取缓存,拖过边界才重算一次二分(~40ms);当前姿已干涉时退回机械区间——否则 dynLimit 会把界外值当边界,滑块再也回不来。

求解侧对应的是 solveDyn:静态解先过全姿态碰撞校验(不安全→按代价升序在候选表里找 ≤1.5×cost+20° 的安全解);解贴在限位边界(0.5° 内)时对非连续、非硬边的轴二分动态边界,放宽 ≥5° 才值得重解(粗档步长 ×2,省 3~4 倍),新解 cost 不升才采用——最坏情况就是静态行为,无回退风险。这就是"动态限位放宽重解"徽章的来源。

腕部特写
图 16 | 腕部特写:P08 腕偏转座 + P09 工具法兰 + J6 电机——P07×P08 这类相邻件对的对位精度由 adj 表与布尔验证共同背书。

第七篇力矩与强度校核(简述)

运动学之外,同一张关节表还驱动两份物理校核,保证"能转到位"之外还"转得动、扛得住"。

  • 力矩(docs/torque_balance.json + torque_report.md):零件质量与质心全部取自 CAD(bom.json),在可达姿态网格上搜索每个关节的最大重力力矩与 Σm·d² 惯量——不做任何硬编码常数。电机参数为工程假定(连续 2.5 / 峰值 8.0 / 热衰减许用 2.0 N·m,可用环境变量覆盖)。当前结论:J1 重力力矩 5.97 N·m、峰值需求 7.36 N·m(峰值裕度 1.09×),连续负载率超许用(J1 238.8% / J2 171.6%)——即持臂静态工况超出热衰减后的连续额定,这是后续减速比/选型迭代的输入。
  • 强度(docs/strength.json):对 15 个关键截面(吊臂根座、吊臂颊板、肩叉腹板等)按梁理论算弯曲应力与挠度,输出安全系数 SF(当前最小截面:吊臂根座 @z=480,SF≈9.1)。

第八篇自检体系与复现命令

这套系统最重要的工程习惯是:每个算法都带数值自检,且自检值绑定代码指纹(FNV-1a over 限位表+运动学常量+求解器源码),改模型或改求解器后指纹失配、页面自动标红要求重算。

自检工具通过线
FK 双实现对账ArmIK.selfTest()≤1e-9 mm(实测 5.7e-13)
位置 IK 重构同上逐支精确命中(实测 9.9e-13)
姿态 IK 重构poseSelfTest()oerr<0.05° 且位置<1e-3
线段距离解析式暴力密集采样对比(scratch 自检脚本)逐位一致
螺钉孔对位scratch/fit_check.py 布尔干涉0 干涉(轴线取样法会误报,弃用)
全行程零干涉sweep_v2.py 全对全扫描单关节推满 0 碰撞
python src/arm_model.py        # 重建模型 + mesh_arm.json + STEP/STL + bom
python src/build_viewer.py     # 重打包单文件网页查看器
python src/sweep_v2.py         # 离线行程/碰撞权威扫描(几何变更后必跑)
node scratch/compute_precomputed.cjs   # 重算预置自检值与指纹
node scratch/gen_workspace_svg.cjs     # 重新生成工作空间剖面图(本文图 10)
python src/torque_balance.py   # 力矩校核报告
python src/strength_check.py   # 强度校核
python scratch/build_algo_doc_html.py  # 重新生成本页(SVG 变更后)

附文件索引

文件角色
src/human_layout.py关节轴/拓扑单一事实源
src/arm_model.py · arm_build.py参数化 CAD 与导出
src/sweep_v2.py离线碰撞与行程权威工具
src/shot.py渲染出图(docs/shots/)
src/finalize_travel.py从 sweep 明细重建 travel.json 的维护工具
web/arm-ik-solver.js六轴 IK 求解器(纯 JS,浏览器/Node 双端)
web/ik-glue.js交互层:拖拽/Worker/动态限位/动画
scratch/fit_capsules.pyCAD 网格 → 胶囊模型拟合(collide.json)
scratch/probe_adj.py相邻打印件 1D 自由区间实测(adj 表)
scratch/compute_precomputed.cjs重算预置自检值与代码指纹
scratch/gen_workspace_svg.cjs工作空间剖面图生成(本文图 10)
docs/travel.json · travel_detail.json滑块硬限位 / 组合折叠明细
docs/collide.json1995 胶囊 + adj 机械区间 + 硬边表
docs/algo_svg/本文全部算法示意图源文件