后处理改造 · 修订 14 —— 坐标里加变量号(X[1.2+#646])防忘文章
对象:
mill_intergrex_300_2022后处理(.tcl/.def/.cdl/.pui)
机床:Mazak INTEGREX 300(车铣复合 · 5 轴)· 系统 FANUC · NX 2606
需求编号:需求③(坐标宏 / Add # Variable)
本文用途:防忘。忘了从头看第 0 章;要改代码看第 3、4 章;要查现场问题看第 7 章。
行号约定:文中行号一律指明文母本mill_intergrex_300_2022_bf.tcl(559,160 字节,
即"已降级ni、加密前的最后一份明文")。加密后的.tcl是字节码,里面没有行号。
目录
| 章 | 内容 |
|---|---|
| 0 | 结论速查(30 秒回忆版) |
| 1 | 为什么要加这个变量号 |
| 2 | 加变量号的方法是什么 |
| 3 | 具体代码(4 段,含位置) |
| 4 | 主要代码逐行解释(起什么作用、有什么用) |
| 5 | 这个实现的逻辑是什么 |
| 6 | 真 Tcl 8.6 实测数据 |
| 7 | 维护、排错、跨机纪律 |
| 8 | 附录:术语与变量速查 |
第 0 章 · 结论速查
一句话:给某道工序在 NX 里加一个 UDE 开关(add_variable = Active),
这道工序输出的每个 X / Y 就从 X1.2 变成 X[1.2+#646];
工序一结束开关自动关掉,后面没加事件的工序逐字不变。
| 问题 | 答案 |
|---|---|
| 加在哪 | 工序的开始事件里加 UDE add_variable,状态 Active |
| 变成什么样 | X1.2 → X[1.2+#646];Y3.4 → Y[3.4+#647](Z、I/J/K、R、F 一律不动) |
| 变量号怎么来 | 默认 X = 600+刀号*5-4、Y = 600+刀号*5-3(T10 → #646/#647) |
| 代码在哪 | .tcl 里 4 段:事件入口 / 开关函数 / 输出前改写 / 工序结束复位 |
要不要动 .def |
不要。本方案一个字节都不碰 .def |
| 什么时候失效 | 每道工序结束时由 PB_CMD_collect_cutting_time 自动复位 |
| 怎么验收 | 加事件的工序有方括号,没加的工序与改动前逐字一致 |
四处改动位置一览:
| # | 文件 | proc | 明文行号 | 作用 |
|---|---|---|---|---|
| 1 | .tcl |
MOM_add_variable |
782–785 | 事件入口(NX 一遇到这个 UDE 就调它) |
| 2 | .tcl |
PB_CMD_add_variable_flag |
5802–5840 | 读事件状态 → 打开本工序的开关 |
| 3 | .tcl |
PB_CMD_before_output 的 B 段 |
5973–6008 | 每行落盘前改写 X/Y |
| 4 | .tcl |
PB_CMD_collect_cutting_time 末尾 |
6382–6384 | 工序结束 → 关开关 |
| 附 | .pui |
Control Functions 表 |
777 | 把 add_variable 事件登记给 proc(不做 = 事件被静默丢弃) |
第 1 章 · 为什么要加这个变量号
1.1 问题的起点:坐标是"死数"
后处理吐出来的坐标,原本都是写死的数值:
G0 X111.5 Y0.0 Z62.
G1 X84.9138 Y15.7205 F141.48这个 111.5 是出程序那一刻从模型上算出来的。以后只要工件在机床上的位置和出程序时不一样
(夹具换了一套、工件在转台上重新装夹、找正后差了几丝、托盘/工位换了),
坐标就得重新算、程序就得重新出、工件号就得重新对。
1.2 加变量号之后:数值 + 宏变量
FANUC 允许在地址后面直接用表达式([...] 是宏程序的表达式括号),于是可以写成:
G0 X[111.5+#646] Y[0.0+#647] Z62.
G1 X[84.9138+#646] Y[15.7205+#647] F141.48机床执行时,实际落点 = 后处理算出来的基准值 + #646 里当时的值。
#646 是公共宏变量(#500 以上断电保持),可以在机床侧随时改:
- 在 MDI / 程序开头用
#646=-0.03写进去; - 由机床的自动测量 / 找正程序写进去;
- 由上料工在操作面板上手动改。
结果:偏移量变了,NC 程序一个字都不用改、不用重出。
这是这套写法唯一的目的 —— 把"必须重新出程序才能改的量",变成"机床上改一个变量就能改的量"。
1.3 为什么是"每把刀一组"
这台后处理的 #6xx 区早就按"每把刀占 5 格"分配好了,不是为需求③新开的区:
寿命变量编号 =
600 + 刀号*5(.tcl第 9194 行注释,寿命段代码)
以 T10 为例,5 格是 #646 ~ #650:
| 槽位 | T10 的变量 | 用途 | 谁写的 |
|---|---|---|---|
5N-4 |
#646 |
X 坐标偏移 | 需求③(本文) |
5N-3 |
#647 |
Y 坐标偏移 | 需求③(本文) |
5N-2 |
#648 |
H 调正量(长度方向) | PB_CMD_custom_command_10,注释原文 (H TIAO ZHENG BIAN LIANG:#648) |
5N-1 |
#649 |
D 调正量(半径方向) | 同上,(D TIAO ZHENG BIAN LIANG:#649) |
5N |
#650 |
寿命计数(GOTO 判断用) |
寿命段 #650=#650+1 |
也就是说:5N-2 / 5N-1 原来是"换刀临时变量",已经被挪走了,5N-4 / 5N-3 是空出来的两格,
坐标偏移正好落在这一组的头上。所以公式才是 600 + 刀号*5 - 4(X)和 - 3(Y)。
一句话:不是"随便找个变量",而是"塞进这套后处理已经约定好的、每刀一组的槽位里"。
好处是:每把刀可以有自己的 X/Y 偏移(不同刀具装夹位置/主轴不同,偏差本来就不一样),
而且和 H/D 调正量、寿命计数互不打架。
1.4 为什么要"逐工序开关"
同样的 X 字,在这个后处理里还兼职干别的活(.def 里 90 个块含 X/Y):
| 行 | X/Y 是 | 能不能加变量号 |
|---|---|---|
G53 G0 X0. Y0. |
机床坐标 | ❌ 加了就撞 |
G28 / G30 |
回参考点中间点 | ❌ |
G52 / G92 / G10 |
局部坐标 / 坐标设定 / 数据输入 | ❌ |
G4 X1.0 |
X 是时间(秒) | ❌ |
G68 / G68.2 |
旋转中心 / 倾斜面 | ❌ |
G0/G1/G2/G3、G8x |
工件坐标 | ✅ 只有这些该加 |
所以做法必须是"按需开":
需要偏移的工序加事件,其它工序一个字不动,改造零回归风险。
1.5 适用 / 不适用
| 适用 | 不适用(本方案的跳过名单,实测均不被改写) |
|---|---|
直线/快速/圆弧:G0 G1 G2 G3 |
G53 / G53.1 机床坐标 |
全部循环:G8x(钻/镗/攻/铰) |
G28 / G30 回参考点 |
| 只有 X / 只有 Y 的行 | G52 / G92 / G10 坐标类 |
模态省略 G 的裸坐标行(X1.2 Y3.4) |
G4 / G04 延时(X 是时间) |
G68 / G68.2 / G68.5 旋转/倾斜 |
|
含 ( ) 的注释行、操作者信息行 |
|
#650=#650+1、IF [#650 GT 37] GOTO 101 等宏变量行(本来就不含裸 X/Y,天然不动) |
已知未拦:G65 P.. X.. Y..(宏调用传参)不在跳过名单里 → 实测会被改写。
本机程序若用到 G65 传参,需要在第 3.3 节的跳过规则里补 |65。目前没用,先记着。
第 2 章 · 加变量号的方法是什么
2.1 整体手法:两件事咬合
① UDE 事件 → 打开本工序的"开关"(一个全局变量)
② 输出钩子 → 开关开着,就把即将落盘的那一行文本改写
关键在于改哪一层。PB 在每一行 NC 落盘之前留了一个官方钩子:
MOM_before_output(.tcl第 123 行的注释原文):This command is executed just before every NC block is to be output to a file.
- Never overload this command! - Any customization should be done inPB_CMD_before_output!PB_CMD_before_output(第 5931 行)的职责:allows users to massage the NC data (
mom_o_buffer) before it finally gets output- No need to attach this command to any event marker.
也就是说:每一个 NC 块都会经过它,不用挂任何事件,拿到的就是"已经拼好的整行文本"
(G0 X1.2 Y3.4 的字面内容,小数点、前导零都是后处理算好的写法)。
于是做法变成:不动 .def 的块定义,只改这一行的文本。
2.2 数据流(一次工序的完整时序)
工序开始序列
└─ UDE: add_variable = Active
└─ MOM_add_variable (.tcl 782)
└─ PB_CMD_add_variable_flag (.tcl 5802)
├─ 读 mom_command_status → "ACTIVE"
├─ set ::nc_addvar_on 1 ← 开关打开
├─ 读 mom_x_var / mom_y_var → ::nc_x_var / ::nc_y_var(可选)
└─ 只影响本道工序
工序内每一个 NC 块(快速/直线/圆弧/循环…)
└─ 拼好一行 → MOM_before_output → PB_CMD_before_output(.tcl 5931)
├─ A 段:循环 R 值跟踪(需求②,与本需求无关)
└─ B 段:开关 == 1 ? 改写 X/Y : return ← 本文主角
X1.2 → X[1.2+#646]
工序结束序列(Operation End Sequence)
└─ {End of Path} 的第一项 = PB_CMD_collect_cutting_time(.tcl 6334)
└─ 末尾:set ::nc_addvar_on 0 ← 开关关闭
下一道工序(若没加事件)→ 开关是 0 → 输出逐字不变
2.3 要动哪些文件(清单)
| 文件 | 动不动 | 动什么 |
|---|---|---|
.tcl |
要动 | 4 段代码(第 3 章全文)。这是唯一必须改的文件 |
.pui |
已登记 | 777 行:{add_variable} {PB_CMD_add_variable_flag} {add_variable} {UDE}。不登记,事件会被静默丢弃 |
.cdl(后处理自己的) |
现状够用 | EVENT add_variable(第 3–15 行)只有 command_status。想让事件里能手填变量号,才需要加 x_var / y_var 两个 PARAM |
NX 全局 ude.cdl |
现状够用 | 同上(.../MACH/resource/user_def_event/ude.cdl 第 53–65 行)。改完必须重启 NX,且出程序那台机器也要一致 |
.def |
不动 | 本方案一个字节都不碰。桌面的 mill_intergrex_300_2022_EXPR.def 是旧方案(修订4)的产物,别用 |
⚠ 当前状态(文章落笔时):
.cdl与 NX 全局ude.cdl里的add_variable都只有command_status,
所以事件框里看不到"变量号"输入格 —— 现在走的是默认公式。
要启用手填,按第 4.5 节/第 7.4 节补两个PARAM并重启 NX。
2.4 操作步骤(NX 里怎么用)
- 打开工序(操作导航器 → 双击工序);
- 找到开始事件那一栏 —— 与加
set_new_tool、set_tool_life的位置完全一样; - 添加 UDE → 选
add_variable→ 状态选Active→ 确定; - 出程序,检查这道工序的
G0/G1/G2/G3和G8x是不是X[..+#646] Y[..+#647]; - 换一道没加事件的工序,检查它的输出必须与改动前逐字一致。
2.5 为什么不采用另外两种做法(都试过)
| 做法 | 结果 |
|---|---|
A. .def 里每条坐标拆成两个字(X_wrap 表达式 + X 数值),用 \opt 互斥 |
能用,但要在 .def 里改 140 行、新增 2 个地址;且"模态省略重复坐标"会变成按字符串比较,容易出新问题。→ 保留作参考,不采用(这就是修订 4 + _EXPR.def) |
B. 块里同放两个 X,用 MOM_suppress once X 屏蔽一个 |
不成立。① once 只管一个块,循环事件一次出好几个块 → 会出两份坐标;② MOM_disable_address X 是全局开关,会把 G53/G28/G30/G4 里的 X 一起干掉(回零段变 G28 G91)。 |
| C. 改即将落盘的那一行文本(本文采用) | 只改1 个 proc;循环多块天然覆盖;不株连;数字格式沿用后处理已算好的写法,逐字一致;最坏失败模式也只是"没改写",程序照样能跑 |
一句话对比:
| 对比项 | A/B | C(本方案) |
|---|---|---|
| 要改的地方 | .def 140 行 + 多个事件表 |
1 个 proc 的 B 段 |
| 循环多块 | 覆盖不到 / 出两份 | 天然覆盖 |
G53/G28 株连 |
会 | 按 G 代码规则跳过 |
| 数字格式 | 要自己 format,容易不一致 |
沿用原文本,零差异 |
| 最坏结果 | X 整字消失 |
最多"没改写" |
第 3 章 · 具体代码
以下 4 段就是全部改动。位置见每段标题。改的是明文母本,改完再加密(见修订13)。
3.1 事件入口(.tcl 第 782–785 行)
proc MOM_add_variable { } {
#=============================================================
PB_CMD_add_variable_flag
}说明:
MOM_<事件名>是 PB 的约定 ——.pui里登记了add_variable → PB_CMD_add_variable_flag,
PB 保存时就会把MOM_add_variable生成为"调用该命令"。这段其实不用手写,
在 PB 里登记好自己就会出现;手改.pui的话才需要补。
3.2 开关函数(.tcl 第 5802–5840 行,函数名 PB_CMD_add_variable_flag)
proc PB_CMD_add_variable_flag { } {
#=============================================================
#=============================================================
# 【需求③】工序"开始事件"里加了 UDE "add_variable" 且状态为 Active 时,
# 只对【本道工序】打开坐标宏开关;工序结束时由
# PB_CMD_collect_cutting_time 自动关闭。
# 注意:NX 送进来的状态值是全大写 ACTIVE,所以做大小写无关判断。
# 事件里可填 x_var / y_var 指定宏变量号;填 0 或不填则用默认公式:
# X -> #600 + 刀号*5 - 4 Y -> #600 + 刀号*5 - 3
#=============================================================
global mom_command_status
global mom_x_var mom_y_var
set st ""
if { [info exists mom_command_status] } {
set st [string tolower [string trim $mom_command_status]]
}
set ::nc_addvar_on 0
set ::nc_x_var 0
set ::nc_y_var 0
if { [lsearch -exact {"" inactive off no false disable disabled none} $st] < 0 } {
set ::nc_addvar_on 1
set v 0
if { [info exists mom_x_var] } { regexp {[0-9]+} $mom_x_var v }
if { $v > 0 } { set ::nc_x_var $v }
set v 0
if { [info exists mom_y_var] } { regexp {[0-9]+} $mom_y_var v }
if { $v > 0 } { set ::nc_y_var $v }
}
if { [info exists ::nc_dbg] && $::nc_dbg == 1 } {
MOM_output_literal "(DBGADD on=$::nc_addvar_on x=$::nc_x_var y=$::nc_y_var)"
}
}3.3 输出前改写 —— PB_CMD_before_output 的 B 段(.tcl 第 5973–6008 行)
该 proc 从第 5931 行开始,A 段(5940–5971)是循环 R 值跟踪(需求②),一字未动。
下面是 B 段,紧跟在 A 段之后、}之前。
# ============ B. coord macro (UNCHANGED - rev6) ============
if { ![info exists ::nc_addvar_on] || $::nc_addvar_on != 1 } { return }
if { ![info exists mom_o_buffer] || [string length $mom_o_buffer] == 0 } { return }
set buf $mom_o_buffer
if { [string first $mom_sys_control_out $buf] >= 0 } { return }
if { [string first ")" $buf] >= 0 } { return }
if { [regexp {G(53|28|30|52|92|10|04|4|68)\M} $buf] } { return }
set tn 0
if { [info exists mom_tool_number] } {
set tn $mom_tool_number
if { $tn > 100 } { set tn [expr {int(double($tn) / 100.0 + 0.5)}] }
}
set xn [expr {600 + $tn * 5 - 4}]
set yn [expr {600 + $tn * 5 - 3}]
if { [info exists ::nc_x_var] && $::nc_x_var > 0 } { set xn $::nc_x_var }
if { [info exists ::nc_y_var] && $::nc_y_var > 0 } { set yn $::nc_y_var }
set lx $mom_sys_leader(X)
set ly $mom_sys_leader(Y)
set n 0
if { [string length $lx] > 0 } {
set pat [format {%s(-?[0-9.]+)} $lx]
set rep [format {%s[%s+#%s]} $lx "\\1" $xn]
incr n [regsub -all $pat $buf $rep buf]
}
if { [string length $ly] > 0 } {
set pat [format {%s(-?[0-9.]+)} $ly]
set rep [format {%s[%s+#%s]} $ly "\\1" $yn]
incr n [regsub -all $pat $buf $rep buf]
}
if { $n > 0 } { set mom_o_buffer $buf }同时,proc 开头的 global 清单(第 5933–5936 行)需要这几项:
global mom_o_buffer
global mom_sys_leader
global mom_sys_control_out
global mom_tool_number3.4 工序结束复位 —— PB_CMD_collect_cutting_time 末尾(.tcl 第 6382–6384 行)
# 【需求③】本道工序结束,关闭坐标宏开关,
# 防止污染后面没加 add_variable 事件的工序。
if { [info exists ::nc_addvar_on] } { set ::nc_addvar_on 0 }⚠ 这里有一个真实缺陷,见第 5.7 节 —— 该函数第 6347 行有一句
if { ![info exists mom_tool_number] } { return },在复位之前。
一旦某道工序没有mom_tool_number,就会提前return,复位被跳过、开关残留。
建议把复位语句挪到第 6345 行(那句::nc_new_tool_request清标记)旁边,即移到return之前。
第 4 章 · 主要代码逐行解释
4.1 MOM_add_variable(事件入口)
proc MOM_add_variable { } { ;# NX 在工序里遇到 add_variable 这个 UDE 时调用的入口
PB_CMD_add_variable_flag ;# 唯一动作:转交给开关函数
}| 行 | 起什么作用 | 有什么用 |
|---|---|---|
proc MOM_add_variable { } |
按 PB 命名规则生成的事件入口(MOM_ + 事件名) |
NX 处理到"工序开始序列里的这个 UDE"时,会自动调到它。入口不写,事件就是空转 |
PB_CMD_add_variable_flag |
转调开关函数 | 把"事件识别"和"具体逻辑"分开:入口永远只有一行,逻辑全在 PB_CMD_* 里,好维护 |
4.2 PB_CMD_add_variable_flag(读状态 → 开开关)
| 行号 | 代码 | 起什么作用 | 有什么用 / 为什么这么写 |
|---|---|---|---|
| 5812 | global mom_command_status |
声明要读 NX 送进来的事件状态 | 这是 UDE 里那个下拉框(Active/Inactive)的值 |
| 5813 | global mom_x_var mom_y_var |
声明可选的手填变量号 | 事件里填了才会有;没填这两个变量根本不存在,后面靠 info exists 兜 |
| 5815–5818 | set st "" / if {[info exists mom_command_status]} / set st [string tolower [string trim $mom_command_status]] |
把状态取成小写、去空格的字符串 | ① 必须先判存在:没加事件时这个变量不存在,直接读会报错;② string tolower 是因为 NX 真实送进来的是全大写 ACTIVE(这是当初踩过的坑,写 == "Active" 会永远判错);③ string trim 防手改 .cdl 时带进空格 |
| 5820–5822 | set ::nc_addvar_on 0 / set ::nc_x_var 0 / set ::nc_y_var 0 |
先无条件清零(默认不生效) | 关键设计:每次事件触发都先复位,避免上一次的残留(例如上一道工序手填过 656)漏到这一次 |
| 5824 | if { [lsearch -exact {"" inactive off no false disable disabled none} $st] < 0 } { |
判断状态是否**"不是关闭类"** | 用"黑名单"而不是"白名单 == "active"",好处:将来 NX 的取值多一个变体(例如 on、true)也不会误判成关闭。-exact 必须加:否则 -gl 模糊匹配会把 off 当成……更重要的原因是精确匹配才可靠(lsearch 默认允许通配符) |
| 5826 | set ::nc_addvar_on 1 |
打开本工序的开关 | 用一个全局(:: 前缀)变量跨 proc 传递状态:事件函数设、输出钩子读、结束函数清。用 :: 前缀避免和 proc 内的同名局部变量撞车 |
| 5828–5830 | set v 0 / if { [info exists mom_x_var] } { regexp {[0-9]+} $mom_x_var v } / if { $v > 0 } { set ::nc_x_var $v } |
从事件里抠出 X 的变量号 | ① info exists 兜住"事件里没这一格";② 用 regexp {[0-9]+} 只取数字——用户可能填 #656、656、656,regexp 都能抠出 656(填 #656 直接拿字符串会出错);③ > 0 才算数:填 0 / 空 = "走默认公式"的约定 |
| 5832–5834 | 同样处理 mom_y_var |
抠出 Y 的变量号 | 与 X 对称,独立判定(可以只填 X 不填 Y) |
| 5837–5839 | if { [info exists ::nc_dbg] && $::nc_dbg == 1 } { MOM_output_literal "(DBGADD on=... )" } |
调试开关 | 打开总开关 ::nc_dbg(.tcl 第 6880 行)后,NC 里会多一行 (DBGADD on=1 x=0 y=0),用来确认"事件到底有没有送达"。确认完记得把 ::nc_dbg 改回 0 |
这一段解决了什么问题:把"NX 里点了个下拉框"翻译成"本工序的一个 Tcl 开关",
并且把"变量号"这个可选项也一并准备好(填了用手填值,没填用 0 表示"走默认")。
4.3 PB_CMD_before_output 的 B 段(逐行)
PB_CMD_before_output由MOM_before_output对每一个 NC 块自动调用,
参数就是"已经拼好、等着落盘的那一行文本",放在全局变量mom_o_buffer里。
本段只读它、必要时改写它,绝不调用任何MOM_输出命令(否则递归)。
| 行号 | 代码 | 起什么作用 | 有什么用 / 为什么这么写 |
|---|---|---|---|
| 5973 | # ============ B. coord macro (UNCHANGED - rev6) ============ |
段落标记 | 说明这段与"修订6 版"一致,方便以后比对 |
| 5974 | `if { ![info exists ::nc_addvar_on] | $::nc_addvar_on != 1 } { return }` | |
| 5975 | if { ![info exists mom_o_buffer] || [string length $mom_o_buffer] == 0 } { return } |
空行/没有缓冲时不处理 | 有些内部调用可能没有缓冲内容,防 regsub 拿空串做出意外结果 |
| 5977 | set buf $mom_o_buffer |
把待处理文本取到局部变量 | 后面要对 buf 做多次 regsub(改写两遍:先 X 后 Y),用局部变量更清晰;最后再一次性写回 |
| 5979 | if { [string first $mom_sys_control_out $buf] >= 0 } { return } |
注释/控制行不碰 | mom_sys_control_out 在本后处理里 = "("(.tcl 第 160 行)。(T10 X Y) 这类行里的 X/Y 是文字,不是坐标 |
| 5980 | if { [string first ")" $buf] >= 0 } { return } |
含右括号的行也不碰 | 双保险:MOM_output_literal "(...)" 出来的消息行、操作者信息行 |
| 5981 | if { [regexp {G(53|28|30|52|92|10|04|4|68)\M} $buf] } { return } |
不是工件坐标的行不碰 | 这是最重要的一条安全规则。\M 是 Tcl 正则的"词尾"锚:G53、G28、G30、G52、G92、G10、G04/G4、G68(含 G68.2/G68.5)都会命中。这些行里的 X/Y 分别是机床坐标、回零中间点、局部坐标、时间、旋转中心 —— 加了变量号就撞机。用 \M 而不是 \> 或 $,是因为 G 码后面可能跟 G53.1 之类的小数 |
| 5983–5987 | set tn 0 / if { [info exists mom_tool_number] } { set tn $mom_tool_number; if { $tn > 100 } { set tn [expr {int(double($tn) / 100.0 + 0.5)}] } } |
取本行所属刀具的刀号 | ① info exists 兜底:取不到就用 0(对应 #596/#597,不报错);② > 100 时是"刀号×100+补偿号"的编码(例如 104 表示 1 号刀+4 号补偿),必须还原成整数刀号,否则变量号会飞到 #1116 那种非法区。这条规则与 PB_CMD_custom_command_10 完全一致(那里也做同样的还原),必须一致,否则同一把刀在换刀段和坐标宏里会对不上号 |
| 5988–5989 | set xn [expr {600 + $tn * 5 - 4}] / set yn [expr {600 + $tn * 5 - 3}] |
默认变量号公式 | 这就是"每刀 5 格"的槽位公式:5N-4 给 X、5N-3 给 Y(T10 → #646/#647)。和 #(600+5N) 的寿命计数、5N-2/5N-1 的 H/D 调正量共用同一套分配,互不冲突 |
| 5990–5991 | if { [info exists ::nc_x_var] && $::nc_x_var > 0 } { set xn $::nc_x_var }(Y 同理) |
手填优先 | 事件里填了变量号就用填的;填 0 / 没填(::nc_x_var = 0)就走默认公式。覆盖方向是"手填 > 默认" |
| 5993–5994 | set lx $mom_sys_leader(X) / set ly $mom_sys_leader(Y) |
取 X/Y 的字头 | 不写死 "X",而是问后处理自己:.def 里地址的 LEADER 值就是 X。这样以后有人改了字头(例如车床上加前导字符),这段代码不用改 |
| 5995 | set n 0 |
计数器 | 累计"这一行总共被替换了几处",用来判断要不要把改过的文本写回 |
| 5997–6001 | if { [string length $lx] > 0 } { ... } 里三行:set pat [format {%s(-?[0-9.]+)} $lx]set rep [format {%s[%s+#%s]} $lx "\\1" $xn]incr n [regsub -all $pat $buf $rep buf] |
把这一行里的 X 数字套上 [..+#..] |
三行的分工: ① pat = 搜索模式 X(-?[0-9.]+):找字头 X,后面接可选负号 + 一串数字/小数点,并用 ( ) 捕获这段数字。-? 是为了 X-1.25 这种负值;[0-9.]+ 里的 . 是"小数点也是数字的一部分"② rep = 替换串 X[\1+#646]:用 format 拼出来,%s 依次塞进字头、\1(regsub 的第 1 个捕获组)、变量号。为什么用 format 而不用双引号直写:双引号里 \1、\[、\] 的转义规则容易写错,format 把"格式"和"数据"分开,所见即所得③ regsub -all ... buf ... buf = 全局替换,结果写回 buf;regsub 的返回值是"替换了几处",所以 incr n [...] 能顺带计数。一行的 X 只会有一处(模态也只会写一次),但这个写法对"意外多写"也安全④ 数字本体被原样搬进 \1 —— 小数点位数、前导零、正负号全都是后处理算好的原样,这是本方案最大的优点(对比"自己 format 一个数字"会有格式差异) |
| 6002–6006 | Y 的同样三行($ly / $yn) |
同上,处理 Y | 与 X 完全对称。只处理 X、Y 两个轴 —— Z / I / J / K / R / F 一律不动(这是需求口径) |
| 6008 | if { $n > 0 } { set mom_o_buffer $buf } |
只在真改过时才写回 | 没改过就不动 mom_o_buffer —— 形式上也是"零差异"的保证;同时避免无谓赋值 |
这段解决了什么问题:在不碰 .def、不碰任何事件表的前提下,
把"即将落盘的那一行"里的工件坐标改写成"基准值 + 宏变量"的形式,
并且靠三条跳过规则(注释行 / 右括号行 / G 码黑名单)做到只改该改的。
4.4 复位那两行
if { [info exists ::nc_addvar_on] } { set ::nc_addvar_on 0 }| 行 | 起什么作用 | 有什么用 |
|---|---|---|
if { [info exists ::nc_addvar_on] } |
判存在 | 万一这个 proc 在别的场合被调用(例如程序里没有坐标宏相关代码),也不会报错 |
set ::nc_addvar_on 0 |
关掉开关 | ::nc_addvar_on 是全局的,不复位就会污染下一道工序:下一道没加事件的工序也会被套上 [..+#..]。所以"开"和"关"必须成对 —— 开在事件函数里,关在工序结束 |
4.5 关于"手填变量号"(可选功能)
代码里已经支持 ::nc_x_var / ::nc_y_var,但要让 NX 界面上出现输入格,
需要在两个 .cdl 里各加两个 PARAM(两个文件必须一致):
(a) 后处理自己的 mill_intergrex_300_2022.cdl,EVENT add_variable(第 3–15 行)整段换成:
EVENT add_variable
{
POST_EVENT "add_variable"
UI_LABEL "add_variable"
CATEGORY MILL DRILL
PARAM command_status
{
TYPE o
DEFVAL "Active"
OPTIONS "Active","Inactive"
UI_LABEL "Add # Variable"
}
PARAM x_var
{
TYPE i
DEFVAL "0"
UI_LABEL "X Variable No"
}
PARAM y_var
{
TYPE i
DEFVAL "0"
UI_LABEL "Y Variable No"
}
}
(b) NX 全局 ...\Designcenter2606\MACH\resource\user_def_event\ude.cdl(第 53–65 行那一段,
在 PARAM command_status 的 } 之后、最外层 } 之前)插入同样的两个 PARAM。
改完重启 NX(UDE 库是启动时读的)。已经加进工序的旧事件不会自动长出输入格,
要在工序里删掉重新加一次。
用法:两个格都填 0 或不填 → 走默认公式;填 656 / 657 → 就用 #656 / #657。
未做这一步时,功能照常工作(走默认公式),只是界面上看不见那两个格。
第 5 章 · 这个实现的逻辑是什么
5.1 状态机:只有三个状态
┌──────────────────────────────┐
│ 其它工序:::nc_addvar_on = 0 │ ← 什么也不做(输出逐字不变)
└──────────────────────────────┘
工序开始事件(UDE add_variable = Active)
↓ PB_CMD_add_variable_flag
┌──────────────────────────────┐
│ 本工序:::nc_addvar_on = 1 │ ← 每个 NC 块的 X/Y 被改写
└──────────────────────────────┘
工序结束({End of Path} → PB_CMD_collect_cutting_time 末尾)
↓ set ::nc_addvar_on 0
└──────── 回到状态 0 ──────────┘
为什么用"全局变量 + 两道保险":
Tcl 里 proc 之间不共享局部变量,而"事件"和"每一行输出"分别在两个 proc 里,
只能用全局变量(:: 前缀)传状态;工序结束再复位,形成闭环。
5.2 判断链:为什么是"黑名单"而不是"白名单"
改写发生在文本层,看到的只是"一行字符串",所以只能靠行内容特征判断该不该改:
要不要改这一行?
开关没开? → 不改(return)
行是空的? → 不改
含 "(" 或 ")"(注释/消息)?→ 不改
含 G53/G28/G30/G52/G92/G10/G4/G04/G68? → 不改
否则 → 改(把 X/Y 套上 [..+#..])
用黑名单的理由:
- 白名单要枚举:
.def里有 90 个含 X/Y 字的块(直线/圆弧/快速/各种循环的各个子块…),
漏一个就少改一处;而且以后改.def又要同步维护。 - 黑名单只列"X/Y 不是工件坐标"的少数几种情况,稳定得多。
- 最坏情况是"多改了一行"(例如
G65),而不是"漏改",且数控程序照样能跑
—— 失败模式温和。
5.3 改写算法:为什么用 regsub 而不是拆字符串
一行 NC 里,"X" 可能出现多次且含义不同(G53 G0 X0. 里的 X 是机床坐标),
用 split 按空格拆再拼回去,会破坏原有的空格/前导零格式。
regsub 是原地正则替换:
- 只动匹配到的那一小段(
X后面的数字),其它字符逐字节保留; - 用捕获组
\1把原数字原样搬进去,所以1.2还是1.2、-0.03还是-0.03; - 返回值天然就是"替换了几处",顺手当计数器。
而"哪些行不处理"的问题,用前置的 G 码黑名单解决,
不在正则里堆条件 —— 这样规则一目了然,也好维护。
5.4 变量号算法:两级(默认公式 + 手填覆盖)
变量号 = 默认公式(按刀号定位到"每刀 5 格"里的那一格)
↓ 若事件里手填了 (>0)
手填值
- 默认公式:
X = 600 + 刀号*5 - 4、Y = 600 + 刀号*5 - 3。
这里的刀号是还原后的整数刀号(>100的刀号*100+补偿号编码要先还原因)。 - 之所以"默认就用公式",是因为这样用户只要加一个事件、什么都不用填就能用;
手填只是给特殊场合留的口子。
5.5 为什么必须"只改文本、不调输出命令"
PB_CMD_before_output 是被 MOM_before_output 调的 —— 也就是正处在
"输出一个 NC 块"的过程当中。如果在这里再调 MOM_output_literal 之类,
就会递归地再触发一次输出流程,轻则多出行、重则死循环。
所以这一段只做两件事:读 mom_o_buffer、写 mom_o_buffer。
(这也是为什么调试信息放在 A 段之外的事件函数里打印,而不是在这里打。)
5.6 与"需求②(循环 R 值)"共存
同一个 PB_CMD_before_output 里,A 段负责循环 R 值跟踪(需求②),
B 段负责坐标宏(需求③)。两段的先后是确定的:
- A 段先跑:它读
mom_o_buffer里原始的 Z 值来跟踪刀位
(如果 B 段先改写了 X/Y,虽然不影响 Z,但保持"先跟踪、后改写"的顺序更清晰); - B 段后跑:改写 X/Y。
A、B 两段互不引用对方的变量(A 用 ::nc_cycR*,B 用 ::nc_addvar_on),所以顺序其实不敏感,
但不要重新排列它们,也不要往 B 段里塞 A 段的逻辑。
5.7 ⚠ 已知缺陷:复位语句可能被跳过(建议修)
PB_CMD_collect_cutting_time 的结构是:
proc PB_CMD_collect_cutting_time { } {
...
if { [info exists ::nc_new_tool_request] } { set ::nc_new_tool_request 0 } ;# ← 6345
if { ![info exists mom_tool_number] } { return } ;# ← 6347 ★ 提前返回
... 累加切削时间 / 记刀号 / 记文本 ...
if { [info exists ::nc_addvar_on] } { set ::nc_addvar_on 0 } ;# ← 6384 复位
}问题:一旦"本道工序没有 mom_tool_number",第 6347 行就 return,
第 6384 行的复位永远不会执行 → 开关残留 → 下一道没加事件的工序也会被套上方括号。
风险高不高:实际出程序时 End of Path 几乎总是有刀号(工序都用刀),所以平时看不出来;
但这是一个"迟早会咬人"的隐患。
建议改法(把复位提到 return 之前):
# 【需求③】工序结束先关坐标宏开关(必须放在下面那句 return 之前!)
if { [info exists ::nc_addvar_on] } { set ::nc_addvar_on 0 }
# 需求①保险:本道工序结束,把"本工序的 set_new_tool 标记"清掉
if { [info exists ::nc_new_tool_request] } { set ::nc_new_tool_request 0 }
if { ![info exists mom_tool_number] } { return }需要我改的话说一声 —— 改完要重新走一遍"降 ni → 加密 → Deploy-TBC"的流程。
5.8 其它已知边界
| 边界 | 说明 | 处理 |
|---|---|---|
G65 P.. X.. Y.. |
宏调用传参也会被改写 | 用到就在第 3.3 节跳过规则里补 |65 |
增量输出 G91 |
G91 G1 X.. Y.. 会被改写;增量模式下加宏偏移的物理含义与绝对模式不同 |
目前后处理按绝对输出用;若某道工序会出 G91,在跳过规则里补 |91(注意 G91 G30 现已被 G30 规则拦下) |
G43/G44(刀长补偿)行 |
不在黑名单里,如果这一行同时带 X/Y 会被改写 | 本后处理里 G43 行只带 Z/H,实测无坐标;若以后出现带 X/Y 的补偿行,补规则 |
| 只有 X 没有 Y 的行 | 正常处理(实测 G0 X0. → G0 X[0.+#646]) |
无需处理 |
| 0 值坐标 | 正常处理:Y0. → Y[0.+#647](数字原样保留) |
无需处理 |
加密后的 .tcl |
字节码,行号和文本都不可读 | 只能改明文母本再加密(修订13) |
第 6 章 · 真 Tcl 8.6 实测数据
方法:用 NX 自带的真解释器 ...\Designcenter2606\NXBIN\python\tcl86t.dll(ctypes 调 Tcl_Eval),
把桌面 mill_intergrex_300_2022_bf.tcl 里的 PB_CMD_add_variable_flag 和
PB_CMD_before_output 原文按大括号配对抽出来,摆好 mom_o_buffer / mom_sys_leader /
mom_tool_number 后逐条跑。
复现命令:
python Test-CoordMacro.py "C:\Users\Administrator\Desktop\mill_intergrex_300_2022_bf.tcl"
(脚本:桌面 Test-CoordMacro.py,也有一份在工作区 tclenc_verify\coordmacro_test.py)
6.1 事件状态 → 开关(7 种取值)
事件状态 mom_command_status |
::nc_addvar_on |
::nc_x_var |
::nc_y_var |
说明 |
|---|---|---|---|---|
ACTIVE |
1 | 0 | 0 | NX 真实送进来的值(全大写) |
Active |
1 | 0 | 0 | 大小写变体也认 |
Inactive |
0 | 0 | 0 | 不开 |
Off |
0 | 0 | 0 | 不开 |
ACTIVE + 填 656/657 |
1 | 656 | 657 | 手填生效 |
ACTIVE + 填 0/0 |
1 | 0 | 0 | 填 0 = 走默认公式 |
| 变量根本不存在 | 0 | 0 | 0 | 没加事件 |
6.2 逐行改写对照(20 条,全部 OK)
| 输入行 | 刀号 | 开关 | 输出行 | 结果 |
|---|---|---|---|---|
G0 X1.2 Y3.4 |
10 | 开 | G0 X[1.2+#646] Y[3.4+#647] |
✅ |
N100 G1 X-1.25 Y0. Z-5. F200. |
10 | 开 | N100 G1 X[-1.25+#646] Y[0.+#647] Z-5. F200. |
✅ 负值/零/其它字不动 |
G81 X10. Y20. Z-3. R2. F100. |
10 | 开 | G81 X[10.+#646] Y[20.+#647] Z-3. R2. F100. |
✅ 循环也套 |
G2 X10. Y20. I5. J0. |
7 | 开 | G2 X[10.+#631] Y[20.+#632] I5. J0. |
✅ I/J 不动 |
G0 X1.2 Y3.4 |
104 | 开 | G0 X[1.2+#601] Y[3.4+#602] |
✅ 按 T1 还原 |
G0 X1.2 Y3.4 |
10 | 开 | G0 X[1.2+#656] Y[3.4+#657] |
✅ 手填 656/657 |
G0 X0. |
10 | 开 | G0 X[0.+#646] |
✅ 只有 X 的行 |
G0 X1.2 Y3.4 |
10 | 关 | G0 X1.2 Y3.4 |
✅ 逐字不变 |
G53 G0 X0. Y0. |
10 | 开 | G53 G0 X0. Y0. |
✅ 跳过 |
G28 G91 X0 |
10 | 开 | G28 G91 X0 |
✅ 跳过 |
G91 G30 X0 |
10 | 开 | G91 G30 X0 |
✅ 跳过 |
G52 X10. Y10. |
10 | 开 | G52 X10. Y10. |
✅ 跳过 |
G68.2 X1. Y2. I0. J0. K0. |
10 | 开 | G68.2 X1. Y2. I0. J0. K0. |
✅ 跳过 |
G4 X1.0 |
10 | 开 | G4 X1.0 |
✅ 跳过(X 是时间) |
(T10 X Y) |
10 | 开 | (T10 X Y) |
✅ 跳过(注释) |
T01.00T02.00M06 |
10 | 开 | T01.00T02.00M06 |
✅ 换刀行不动 |
#650=#650+1 |
10 | 开 | #650=#650+1 |
✅ 宏变量行不动 |
IF [#650 GT 37] GOTO 101 |
10 | 开 | IF [#650 GT 37] GOTO 101 |
✅ 不动 |
X1.2 Y3.4 Z5. |
10 | 开 | X[1.2+#646] Y[3.4+#647] Z5. |
✅ 裸坐标行(模态省略 G) |
G65 P9001 X1. Y2. |
10 | 开 | G65 P9001 X[1.+#646] Y[2.+#647] |
⚠ 已知边界(会被改) |
异常项:0。
6.3 复位
::nc_addvar_on 从 1 → 执行 if { [info exists ::nc_addvar_on] } { set ::nc_addvar_on 0 } → 0 ✅
第 7 章 · 维护、排错、跨机纪律
7.1 三条纪律(老规矩)
- 明文母本必须留着(
_bf.tcl/.bak84)。加密版.tcl绝不能再让 Post Builder 打开并保存
—— PB 会按.pui重写.tcl,加密当场作废。 - 流程固定:PB 里改完 →(如需)降
ni→ 加密 → 双击Deploy-TBC.cmd→ 出程序核对 NC。 - 跨机四件套一致:
.tcl/.def/.cdl/.pui,以及 NX 全局ude.cdl
(.cdl决定"事件有哪些参数",.pui决定"事件挂给哪个 proc")。
7.2 PB 保存后被重写怎么办
在 Post Builder 里改过东西并保存了 → .tcl 里这 4 段会被 PB 重写掉 →
直接再跑一次 Apply-CoordMacro.ps1(桌面,幂等,自动备份)。
powershell -ExecutionPolicy Bypass -File "%USERPROFILE%\Desktop\Apply-CoordMacro.ps1"
跑完看同目录 Apply-CoordMacro.log,最后一行 === DONE === 即成功。
7.3 现场排错表
| 现象 | 原因 / 处理 |
|---|---|
| 该出方括号的工序,一个都没有 | ① 事件没挂上 → 查 .pui 第 777 行登记;② 状态不是 Active → 打开 ::nc_dbg 看 (DBGADD on=..);③ .cdl 里没有 add_variable 事件定义;④ 没重启 NX 就加事件 |
方括号有,但号不对(例如出现 +#596) |
刀号没取到 → mom_tool_number 在该块不存在,走了 tn=0 的兜底。把那段 NC 发出来核对;或直接在该工序事件里手填变量号 |
| 本该没有方括号的工序出现了方括号 | 复位没执行 → 见第 5.7 节(return 提前返回的缺陷);或 PB 保存后代码被重写,需重灌 |
某行 G28/G53 被套了方括号 |
跳过规则被改动 → 核对第 3.3 节那三行是否与原版一致(尤其 \M 不能丢) |
| 坐标整字消失 | 说明改写发生在"块定义层"而不是"文本层" → 请确认没有把 _EXPR.def 混进来用 |
改了 .cdl 后事件框里还是没有变量号格 |
① 只改了一个 .cdl(后处理 + NX 全局两个都要改,且要一致);② 没重启 NX;③ 旧事件要删掉重加 |
7.4 验收判据(每次改完必查)
① 加事件的工序:G0/G1/G2/G3 + G8x 的 X/Y 都带 [..+#..],Z/I/J/K/R/F 原样
② 没加事件的工序:与改动前【逐字一致】(这一条是硬指标)
③ G53/G28/G30/G52/G92/G10/G4/G68 行:一个方括号都没有
④ .def 文件的字节数不变(本方案不碰 .def)
⑤ 变量号与刀号对得上:T10 → #646/#647;T7 → #631/#632;T1 → #601/#602
第 8 章 · 附录:术语与变量速查
8.1 用到的全局变量
| 变量 | 谁设 | 谁读 | 含义 |
|---|---|---|---|
::nc_addvar_on |
PB_CMD_add_variable_flag / PB_CMD_collect_cutting_time |
PB_CMD_before_output B 段 |
本工序的坐标宏开关(0/1) |
::nc_x_var |
同上(来自事件手填) | B 段 | 手填的 X 变量号;0 = 用默认公式 |
::nc_y_var |
同上 | B 段 | 手填的 Y 变量号;0 = 用默认公式 |
::nc_dbg |
.tcl 第 6880 行(手工开关) |
各处调试分支 | 1 = 在 NC 里打印诊断行 |
8.2 NX 送进来的变量
| 变量 | 含义 | 注意 |
|---|---|---|
mom_command_status |
UDE 的状态 | NX 送进来是全大写 ACTIVE |
mom_x_var / mom_y_var |
UDE 里手填的变量号 | 需要在两个 .cdl 里加 PARAM 才会有 |
mom_tool_number |
当前刀号 | >100 = 刀号*100+补偿号,要还原 |
mom_o_buffer |
即将落盘的那一行 NC 文本 | 只读/改写,别在这里调 MOM_ 输出命令 |
mom_sys_leader(X/Y) |
X/Y 的字头(值就是 X/Y) |
别写死 |
mom_sys_control_out |
注释起始符(本后处理 = () |
用于识别注释行 |
8.3 关键常量与公式
| 项 | 值 |
|---|---|
| 默认变量号 | X = 600 + 刀号*5 - 4;Y = 600 + 刀号*5 - 3 |
| 每刀 5 格 | 5N-4 X偏移 / 5N-3 Y偏移 / 5N-2 H调正 / 5N-1 D调正 / 5N 寿命计数 |
| 跳过名单(G 码) | 53 / 28 / 30 / 52 / 92 / 10 / 04 / 4 / 68 |
| 输出形态 | X[基准值+#变量号](FANUC 宏表达式;[ ] 是表达式括号) |
8.4 相关文件
| 文件 | 说明 |
|---|---|
mill_intergrex_300_2022_bf.tcl |
明文母本(本文行号基准,559,160 字节) |
mill_intergrex_300_2022.tcl |
加密后的字节码(NX 实际加载的就是它) |
Apply-CoordMacro.ps1 |
本需求的 .tcl 重灌脚本(PB 保存后跑一次) |
Test-CoordMacro.py |
本需求的真 Tcl 8.6 离线测试脚本(第 6 章数据就是它跑的) |
后处理改造_修订4_坐标宏表达式.md |
旧方案(.def 双地址版)实现与取舍,保留参考 |
后处理改造_修订6_坐标宏_缓冲改写版.md |
本方案(缓冲行改写)的完整说明 |
后处理改造_修订12_循环R值_防忘总纲.md |
需求②(同一个 proc 的 A 段) |
后处理改造_修订13_Tcl官方加密后处理_方案与落地.md |
加密与部署(Deploy-TBC.cmd) |
本文基于 2026-10-16 的桌面文件实测撰写。第 3 章代码与 mill_intergrex_300_2022_bf.tcl 逐字一致;
第 6 章数据由真 Tcl 8.6.15(NX 自带 tcl86t.dll)跑出。