起因

机床界面篇定了编程屏和仿真屏,两块屏吃的是同一样东西:段表——直线和圆弧排成的几何清单,仿真渲染它,插补执行它,运行屏显示"当前执行第几段"查的还是它。段表从哪儿来,就是这篇的事:把程序文本变成段表。

程序文本有两代,都得吃。老厂里流通的存量几乎全是 3B——HF 时代三十年攒下来的程序,老师傅图纸柜里的 U 盘拷出来的全是它;新 CAD/CAM 后处理吐出来的又是 G 代码。只吃一样,机器要么接不上行业存量,要么对不上新工具。这两代语言各自的来历——3B 为什么五个字段全无符号、G 代码的模态为什么存在——单独开了一篇:G代码与3B代码详解。解析器还是应用篇里少数不依赖运动控制卡手册就能动工的模块:输入文本、输出段表,纯函数,没有硬件依赖——顺手把它做成全系统第一个能在开发机上跑 cargo test 的模块,测试向量当回归资产攒起来。

一、先定段表:解析器的输出契约

先把输出定死,再写解析。段表是编程、仿真、插补三方的契约,它的形态比任何一方的代码都稳定:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/// 一段。坐标全部绝对值、单位 µm、类型 i64——解析器内部就把方言差异消化干净
pub enum Seg {
    Line  { xe: i64, ye: i64, ue: Option<i64>, ve: Option<i64> }, // XYUV 四轴直线
    Arc   { xe: i64, ye: i64, cx: i64, cy: i64, ccw: bool },     // 圆心绝对坐标
    Rapid { xe: i64, ye: i64 },
    Dwell { ms: u32 },
    Comp  { side: Side, d_no: u8 },          // G41/G42 + D 号(补偿值在参数区查)
    Taper { side: Side, millideg: i32 },     // G51/G52 + A 角度(毫度)
    CompOff, TaperOff, Stop, OptStop, End,
}

pub struct Row {              // 段表实际是一行行来的,每行带出处
    pub src_line: u32,        // 源程序行号——运行屏"当前执行哪一行"、报错跳行都靠它
    pub seg: Seg,
}

三条设计决定:

  • 绝对 µm 一口径。G 代码的 I J 是相对起点、3B 的坐标是"相对起点或圆心"还无符号——这些方言差异全部在解析器内部归一成绝对坐标。下游的仿真和插补不应该知道程序原来是什么格式
  • 补偿和锥度只记录、不展开。G41/G42 的转接拐角、G51 的 UV 联动量是几何变换,归插补和补偿篇做。解析器忠实记录"从这段起补偿左、D03"就够——层次混了,解析器会变成半个插补器,两头都不好改
  • ue/ve 做成 Option。AutoCut 系的方言用 XYUV 四坐标直接编锥度,普通程序没有 UV,Option 表达"这一段带不带锥度联动",不带就是平面切割

二、G 代码:词法、模态与我们的方言

先说丑话:G 代码没有强制标准,每家控制器的方言都不一样。所以第一件事是把我们自己的子集定义清楚,子集之外的东西一律显式报"不支持的代码",不静默忽略——静默忽略等于给碰撞和废件埋雷。

词法简单:一行一个块,块里是"字母+有符号数"的词序列。(括号注释);行注释、行首 N 号(解析后丢弃,只保留行号)、/ 块删除开关(界面上一键切换跳过)。词法之前先过一遍全角字符检查——中文输入法敲出来的G和X是全角,肉眼分不出来,词法直接报"全角字符",错误信息里把行列出来。

真正要当心的是模态(续效)。G01 之后连续十行只有坐标,运动方式还是 G01——解析器是个状态机,每个模态组各自记住当前值:

代码 语义
运动 G00/G01/G02/G03 快速定位/直线/顺圆/逆圆,续效
距离 G90/G91 绝对/增量,续效
平面 G17 只支持 XY 平面;G18/G19 报不支持
补偿 G40/G41/G42 丝径补偿关/左/右 + D 号
锥度 G50/G51/G52 锥度关/左/右 + A 角度(毫度存)
坐标系 G54–G59 工件坐标系偏置,值在参数区
暂停 G04 X/P 毫秒 非续效,只在本块有效
辅助 M00/M01/M02 程序停/选择停/结束

坑:数值不用 f64,手工解析成定点 µm。 0.1 在二进制里是无穷小数,f64 累加几千段能漂出 1µm——对 0.005mm 精度目标的机器这是白送的系统误差。手工解析:整数部分 ×1000,小数逐位 ×10^(2−i),一步到位是 i64。配套两条纪律:小数超过 3 位(mm 的 µm 分辨率)报错不截断——静默吃掉 0.0004 的差值,闭环里永远查不出来;遇到 , 报"疑似德语区小数点"的专用错误码——德语区 CAM 导出的 X12,345 真实存在,解析成 12 和 345 两个字就是废件(CSV 分隔符同一个坑,国际化篇)。

圆弧两种写法都收,但偏爱 I J:G02 X Y I J 圆心是起点相对增量,几何无歧义;G02 X Y R 半径格式一个 R 对应两个圆心,按惯例正 R 取小圆弧(≤180°)、负 R 取大圆弧,弦长大于 2|R| 报错。R 格式解析完同样折算成绝对圆心进段表。

I J 格式自带一个白捡的校验——起点半径和终点半径必须一致

1
2
3
4
5
6
// 抄错 X Y 或 I J,最先暴露在这里
let rs2 = dist2(s, c);           // (sx-cx)² + (sy-cy)²,i64 装得下(±2m 即 ±2e6 µm)
let re2 = dist2(e, c);
if (rs2 - re2).abs() > TOL2 {
    return Err(ArcRadiusMismatch { line, rs2, re2 });  // 两边半径差多少,错误信息里直接给数
}

整圆是 I J 的专属能力:X Y 省略(等于起点)加 I J 就是一个 360°——3B 表达不了整圆,这是两代格式的能力差,导入 3B 时整圆必然是四段,段数对不上要心里有数。

F 字段解析了、存了,但它在线切割里的含义和普通 CNC 不一样:实际进给速度由放电状态反馈调节——放电 ISR 统计的平均电流和短路率实时调插补步频(HF 时代叫"变频"),F 只是初值和上限。把 F 当恒速用,切割效率直接掉一半,这是线切割和铣削 CNC 的思维方式差异,调度篇的放电反馈环就是为它建的。

三、3B:五字段、无符号与象限

3B 一行一段,格式是 B X B Y B J G Z,五个字段全是没有符号的整数(µm):

字段 含义
X Y 直线:终点相对起点的坐标绝对值;圆弧:起点相对圆心的坐标绝对值
J 计数长度:该段在计数方向轴上投影的总和,µm
G 计数方向:GX / GY
Z 加工指令:直线 L1–L4(终点所在象限);圆弧 SR1–4(顺)、NR1–4(逆),数字是起点所在象限

符号信息全藏在 Z 里,这是 3B 时代给硬件省掉的麻烦,现在全成了解析器的活。

直线的重建最省事:L1–L4 给出终点相对起点的象限,符号一挂,终点 = 起点 + (±X, ±Y)。计数方向对直线的规则是终点坐标绝对值大的轴(|X|>|Y| 取 GX),J 等于那个大数——解析器重算一遍和纸面对数,抄错的 X Y 直接现形。

圆弧是 3B 解析的硬骨头,一步一步来。关键是先接受一个事实:3B 的圆弧不直接给终点——逐点比较法的机器拿圆心定位、拿计数器判终,终点是插补走出来的。解析器要把这个过程用几何重建出来:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
已知:S(绝对,上一段终点)、Z、G、J、X、Y(均无符号)
1. 起点相对圆心的向量 r_s:象限符号由 Z 的数字定(NR2 → 第二象限 → r_s = (−X, +Y))
2. 圆心 C = S − r_s;半径 R = √(X²+Y²)
3. 起点在计数轴上的步进方向 σ,由(顺/逆, 起点象限, G)查表——NR1 逆圆起点在 Q1,
   x 递减 y 递增:G=GX 时 σ=−1,GY 时 σ=+1
4. 单象限段(不跨轴):终点在计数轴上 e_G = s_G + σ·J;另一轴由圆方程解出,
   两个根按扫过方向所在象限取一个
5. 跨象限段:按坐标轴逐象限累计投影(教材的 J 累加规则),整象限的跨度先扣掉,
   余量在末象限内按第 4 步收尾
6. 复核:按重建出的圆弧重算 G 轴总投影,与纸面 J 对不上 → 报错,X/Y/J 至少一个抄错

第 6 步是 3B 的 J 天生该当的角色:校验和。纸面 J 是人抄的,几何是机器算的,两边对不上必有一边错。老程序库里 J 抄错一位数是真实发生率最高的错误,靠这一步在导入时就抓住,而不是切到一半轨迹合不拢。

三个坑,都真踩过的人才知道疼:

  • 圆弧 X Y 的口径分歧:主流教材和 HF 系机器是"起点对圆心",个别老教材写成终点对圆心。机器逻辑站在起点这边——逐点比较法要的是圆心加判终计数,终点本来就是走出来的。拿不准的历史程序,两种解释都按上面流程跑一遍,能通过 J 复核且轮廓闭合的那种解释是对的
  • 起点恰在坐标轴上的象限归属:约定俗成是"算即将进入的象限",但各家老控制器的习惯不统一。导入器留一个开关(轴上起点按进入象限/按离开象限),默认前者,配合闭合校验用
  • 4B/5B 别当 3B 硬啃:4B 在 3B 尾上多了补偿符号和半径,直接按 3B 解析会在第 6 步 J 复核大面积报错——报错信息里提示"疑似 4B 格式",导入器单独走 4B 路径

四、两种文本一个骨架:全局校验

单段级的检查(J 复核、半径一致、弦长 2R)抓的是抄写错误。还有一类错误单段看不出来,得整条轨迹一起看:

  • 轮廓闭合:线切割程序绝大多数是封闭轮廓(凹模型腔、外形),末段终点必须回到首段起点,容差给 2µm。3B 圆弧任何一个字段抄错,最后多半在闭合这一步现形——上一节两个"拿不准"的开关,最终裁决也是它
  • 软限位:段表整体对轴行程包络检查一遍,越界段标出(机床界面篇仿真屏的三项预检之一,数据在这一步备好)
  • 补偿/锥度状态配对:G41 之后没有 G40 就 M02、G51 挂着没 G52——状态机的收尾检查,漏关的补偿会在下一次加工时带着上一次的偏移量,这是废件制造机
  • 首段防呆:程序一上来就 G02——没有前一段建立位置上下文,模态坐标机状态没初始化,直接报"程序必须以定位或直线段开始"

错误报告的形态照编程界面(机床界面篇)的闭环设计:错误码用报警码体系的程序段,信息带行号 + 字段 + 两个数(纸面值 vs 重算值)。“第 47 行 J 与几何不符:纸面 3400,重算 3410"比"解析失败"值钱一百倍——前者老师傅自己就能把程序改了,后者只能打电话。

五、容量、归属与测试

工程账三笔:

  • 内存归属:段表是变长的、程序有多大有多大,只能上堆——堆只有 Core1 合法(架构红线),解析跑在协作层 Core1,正好。每行 Row 加 Seg 按 48B 算,十万段约 5MB,给段表设显式上限并在编程界面显示余量,别让一个异常程序把服务核的堆吃穿
  • 性能不用操心:词法是逐字符状态机,十万段毫秒量级,“保存即查"毫无压力。真正慢的是人找错,所以第 4 节的错误信息质量比解析速度重要一个数量级
  • 测试是这个模块最大的红利:解析器纯函数、无硬件依赖,是全系统第一个能在开发机上 cargo test 的模块。golden 文件攒起来——矩形件、带圆角的凸模、锥度口沿、老师傅的历史程序各存一份,连同期望段表一起进仓库。以后改解析器,先跑测试再上机,半夜不用蹲车间

总结

实战清单:绝对 µm 段表契约(Seg/Row、src_line 溯源、Option 承载 XYUV);G 代码模态状态机 + 方言子集表(不支持显式报错)+ 定点解析(拒 f64、3 位小数封顶、德语逗号专用码)+ I J/R 双格式与半径一致性校验 + 整圆;3B 五字段与象限指令、圆心重建六步(含 J 累加规则与复核)+ 三个坑(口径分歧用 J 与闭合裁决、轴上起点开关、4B 识别);全局校验四件套(闭合 2µm、软限位、状态配对、首段防呆)+ 错误信息带行号字段双数值;Core1 堆归属 + 48B/段容量账 + host 侧 cargo test 与 golden 集。跑通标志:老师傅的历史 3B 程序库成批导入,J 复核和闭合校验双零误报;故意篡改一个 J、一个 I、删一个 G40,三种坏程序都被对应检查抓住且错误信息能直接指导改程序;仿真屏把解析出的段表画出完整轨迹;G 和 3B 两份等价程序解析出的段表逐字节一致。

下一篇先补两代语言的来龙去脉:G代码与3B代码详解——两代线切割程序语言的来历、格式与方言——格式为什么长这样,线切割的编程和铣床差在哪。执行侧的插补紧随其后。

参考链接