# gmoccapy_XYZAB.ini 执行过程与界面通信分析

分析对象：

`/home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini`

结论先行：这个配置不是一个单独程序，而是一套 LinuxCNC 运行配置。`linuxcnc <ini>` 先启动 LinuxCNC 后台进程、实时运动模块和 HAL 网络，再启动 `gmoccapy` GTK 界面。界面与后台的主要通信不是 HTTP/TCP API，而是 LinuxCNC 的三类本地机制：

1. NML 命令/状态/错误通道：`linuxcnc.command()` 发命令，`linuxcnc.stat()` 读状态，`linuxcnc.error_channel()` 读错误。
2. HAL 共享内存引脚/信号网络：`gmoccapy` 自己注册 HAL component 和引脚，`POSTGUI_HALFILE` 把界面引脚接入实时/用户空间 HAL 网络。
3. 解释器 Python REMAP：`[PYTHON]` 和 `[RS274NGC] REMAP` 在 `milltask` 内部影响 G-code 解释/换刀流程，不是 GUI 主通信链路。

## 1. 配置角色

`gmoccapy_XYZAB.ini` 定义一台五轴仿真机：

- GUI：`[DISPLAY] DISPLAY = gmoccapy`。
- 轮询周期：`[DISPLAY] CYCLE_TIME = 100`，即界面约每 100 ms 轮询一次 LinuxCNC 状态。
- 任务进程：`[TASK] TASK = milltask`。
- 实时运动模块：`[EMCMOT] EMCMOT = motmod`。
- 运动周期：`BASE_PERIOD = 100000` ns，`SERVO_PERIOD = 1000000` ns。
- 坐标：`[TRAJ] COORDINATES = X Y Z A B`。
- 运动学：`[KINS] KINEMATICS = trivkins coordinates=xyzab`，`JOINTS = 5`。
- 主 HAL 文件：
  - `core_sim_XYZAB.hal`
  - `spindle_sim.hal`
  - `simulated_home.hal`
- GUI 创建完成后执行的 HAL 文件：
  - `gmoccapy_postgui.hal`
- HAL 用户接口：
  - `[HAL] HALUI = halui`

## 2. LinuxCNC 启动顺序

入口通常是：

```bash
linuxcnc /home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini
```

启动脚本路径：

`/home/meswork/cnc_wams/linuxcnc/scripts/linuxcnc`

关键过程如下。

### 2.1 解析 INI 和导出环境

启动脚本先检查 INI，然后设置：

- `CONFIG_DIR=<ini 所在目录>`
- `INI_FILE_NAME=<ini 完整路径>`
- `PATH=$CONFIG_DIR/bin:$PATH`

后续的 Python 模块、HAL 文件解析和 `gmoccapy.getiniinfo` 都依赖这些环境变量。

脚本从 INI 读取：

- `[EMCMOT] EMCMOT` -> `motmod`
- `[TASK] TASK` -> `milltask`
- `[HAL] HALUI` -> `halui`
- `[DISPLAY] DISPLAY` -> `gmoccapy`

### 2.2 启动后台和实时系统

脚本执行的后台启动顺序是：

1. 启动 `linuxcncsvr -ini <ini>`。脚本注释说明该进程默认持有/创建 NML buffers。
2. 启动 realtime/RTAPI/HAL。
3. 加载 `tpmod`、`homemod`。
4. 启动任务进程：

   ```bash
   halcmd loadusr -Wn inihal milltask -ini <ini>
   ```

5. 启动 HALUI：

   ```bash
   halcmd loadusr -Wn halui halui -ini <ini>
   ```

6. 依次执行 `[HAL] HALFILE`：

   ```bash
   halcmd -i <ini> -f core_sim_XYZAB.hal
   halcmd -i <ini> -f spindle_sim.hal
   halcmd -i <ini> -f simulated_home.hal
   ```

7. `halcmd start` 启动实时线程。
8. 最后以前台方式启动显示程序：

   ```bash
   gmoccapy -ini <ini>
   ```

实际运行文件是：

`/home/meswork/cnc_wams/linuxcnc/bin/gmoccapy`

它与源码副本 `/home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.py` 只有 shebang Python 版本不同，主体逻辑一致。

## 3. HAL 文件执行过程

### 3.1 core_sim_XYZAB.hal

该文件建立五轴仿真运动核心：

- `loadrt [KINS]KINEMATICS` 加载 `trivkins coordinates=xyzab`。
- `loadrt [EMCMOT]EMCMOT ... num_joints=[KINS]JOINTS unlock_joints_mask=16` 加载 `motmod`。
- 把 `motion-command-handler` 和 `motion-controller` 加入 `servo-thread`。
- 将仿真轴命令位置直接回环到反馈：

```hal
net Xpos joint.0.motor-pos-cmd => joint.0.motor-pos-fb ddt_x.in
net Ypos joint.1.motor-pos-cmd => joint.1.motor-pos-fb ddt_y.in
net Zpos joint.2.motor-pos-cmd => joint.2.motor-pos-fb ddt_z.in
net Apos joint.3.motor-pos-cmd => joint.3.motor-pos-fb
net Bpos joint.4.motor-pos-cmd => joint.4.motor-pos-fb
```

这说明该配置没有真实伺服驱动，运动命令立即变成反馈，用于仿真。

同时它还建立：

- 急停回环：

```hal
net estop-loop iocontrol.0.user-enable-out iocontrol.0.emc-enable-in
```

- 换刀准备/完成回环：

```hal
net tool-prep-loop iocontrol.0.tool-prepare iocontrol.0.tool-prepared
net tool-change-loop iocontrol.0.tool-change iocontrol.0.tool-changed
```

- 主轴方向、冷却液信号：

```hal
net spindle-fwd spindle.0.forward
net spindle-rev spindle.0.reverse
net flood iocontrol.0.coolant-flood
net mist iocontrol.0.coolant-mist
```

### 3.2 spindle_sim.hal

该文件模拟主轴：

- `spindle.0.speed-out` 进入 `limit2`，模拟转速斜坡。
- `sim_encoder` 生成 A/B/Z 相。
- `encoder` 将脉冲转为 `spindle.0.revs` 和 `spindle.0.speed-in`。
- `near` 比较命令转速和反馈转速，输出 `spindle.0.at-speed`。

核心链路：

```hal
spindle.0.speed-out -> limit2 -> sim_encoder -> encoder -> spindle.0.speed-in
near_speed.out -> spindle.0.at-speed
```

### 3.3 simulated_home.hal

该文件用 `comp` 比较轴位置，模拟 X/Y/Z 回零开关：

- X 回零位置设为 1。
- Y 回零位置设为 0.5。
- Z 回零位置设为 2。

当仿真位置达到比较点附近时，对应 `joint.N.home-sw-in` 被置位。

### 3.4 gmoccapy_postgui.hal

该文件必须等 GUI 启动并创建 `gmoccapy.*` HAL pins 后才能执行。`gmoccapy` 主程序在 `halcomp.ready()` 后读取 `[HAL] POSTGUI_HALFILE` 并执行：

```bash
halcmd -i <ini> -f gmoccapy_postgui.hal
```

本配置中的 postgui 连接：

```hal
net spindle-speed-limited => abs_spindle_feedback.in
net spindle-abs abs_spindle_feedback.out => gmoccapy.spindle_feedback_bar
net spindle-at-speed gmoccapy.spindle_at_speed_led
net tooloffset-x gmoccapy.tooloffset-x <= motion.tooloffset.x
net tooloffset-z gmoccapy.tooloffset-z <= motion.tooloffset.z
```

含义：

- 主轴模拟反馈值经过 `abs` 后驱动界面的主轴反馈条。
- `spindle-at-speed` 驱动界面的主轴到速 LED。
- `motion.tooloffset.x/z` 驱动界面的刀补显示引脚。

该文件中手动换刀 GUI 引脚连接被注释掉，所以这个仿真配置实际仍使用 `iocontrol.0.tool-change -> iocontrol.0.tool-changed` 的回环换刀，而不是弹出 gmoccapy 手动换刀确认流程。

## 4. gmoccapy GUI 初始化过程

`gmoccapy` 启动后主要执行：

1. 创建 HAL component：

```python
self.halcomp = hal.component("gmoccapy")
```

2. 创建 LinuxCNC NML 通信对象：

```python
self.command = linuxcnc.command()
self.stat = linuxcnc.stat()
self.error_channel = linuxcnc.error_channel()
self.stat.poll()
self.error_channel.poll()
```

3. 用 `Gtk.Builder` 加载界面：

```python
self.builder.add_from_file(XMLNAME)
```

其中 `XMLNAME` 指向：

`/home/meswork/cnc_wams/linuxcnc/share/gmoccapy/gmoccapy.glade`

4. 读取 INI：

```python
self._get_ini_data()
```

该逻辑由：

`/home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/gmoccapy/getiniinfo.py`

通过 `os.environ["INI_FILE_NAME"]` 和 `linuxcnc.ini()` 完成。

5. 根据 INI 动态生成 DRO、回零按钮、点动按钮、宏按钮等。
6. 调用 `_make_hal_pins()` 创建 gmoccapy 自己的 HAL pins。
7. 用 `gladevcp.makepins.GladePanel(...)` 为 glade 中的 HAL widget 创建/更新 HAL pins。
8. 调用：

```python
self.halcomp.ready()
```

此时 `gmoccapy.*` pins 对外可见。

9. 建立周期定时器：

```python
GLib.timeout_add(cycle_time, self._periodic)
GLib.timeout_add(1000, self._periodic_1s)
```

10. 构造完成后执行 `[HAL] POSTGUI_HALFILE`，然后进入：

```python
Gtk.main()
```

## 5. 界面与后台如何通信

### 5.1 NML：界面命令到 milltask

`linuxcnc.command()` 是 GUI 发控制命令的主入口。常见命令包括：

- 急停/复位/上电：

```python
self.command.state(linuxcnc.STATE_ESTOP)
self.command.state(linuxcnc.STATE_ESTOP_RESET)
self.command.state(linuxcnc.STATE_ON)
self.command.state(linuxcnc.STATE_OFF)
```

- 模式切换：

```python
self.command.mode(linuxcnc.MODE_MANUAL)
self.command.mode(linuxcnc.MODE_MDI)
self.command.mode(linuxcnc.MODE_AUTO)
```

- 点动：

```python
self.command.jog(linuxcnc.JOG_CONTINUOUS, ...)
self.command.jog(linuxcnc.JOG_INCREMENT, ...)
self.command.jog(linuxcnc.JOG_STOP, ...)
```

- 回零：

```python
self.command.home(-1)
self.command.home(joint_or_axis)
self.command.unhome(joint)
```

- 主轴/冷却/倍率：

```python
self.command.spindle(...)
self.command.spindleoverride(...)
self.command.flood(...)
self.command.mist(...)
self.command.feedrate(...)
self.command.rapidrate(...)
```

- MDI 和自动运行：

```python
self.command.mdi("...")
self.command.auto(linuxcnc.AUTO_RUN, self.start_line)
self.command.auto(linuxcnc.AUTO_PAUSE)
self.command.auto(linuxcnc.AUTO_RESUME)
self.command.abort()
```

这些命令写入 NML command buffer。`milltask` 后台循环中读取 command buffer，然后执行规划、解释器、IO 和 motion 同步。

`milltask` 初始化时打开三类 NML channel：

- `emcCommand`：GUI/halui 等写命令，`milltask` 读。
- `emcStatus`：`milltask` 写综合状态，GUI 读。
- `emcError`：后台写错误/提示，GUI 读。

`milltask` 主循环的关键节奏是：

1. `emcCommandBuffer->read()` 读取新命令。
2. `emcTaskPlan()` 做解释/规划。
3. `emcTaskExecute()` 执行任务。
4. `emcMotionUpdate(&emcStatus->motion)` 更新运动状态。
5. `emcTaskUpdate(&emcStatus->task)` 更新任务状态。
6. `emcStatusBuffer->write(emcStatus)` 写回状态。

所以 GUI 的控制命令不是直接调用 `motmod`，而是经 NML 发给 `milltask`，再由 `milltask` 协调解释器、IO、motion 和 HAL。

### 5.2 NML：后台状态到界面

`gmoccapy` 通过 100 ms 定时器调用 `_periodic()`：

```python
self.stat.poll()
error = self.error_channel.poll()
```

然后根据 `self.stat` 更新：

- 当前 G-code/M-code。
- 速度、进给、主轴、冷却状态。
- HALUI 相关 pin 状态。
- 主轴 Vc。
- UI 时间、进度等。

如果 `error_channel.poll()` 读到错误或提示，`gmoccapy` 会显示通知，并通过自己的 HAL pin `gmoccapy.error` 输出错误状态。

### 5.3 HAL：界面引脚与实时/用户空间 HAL 网络

`gmoccapy` 自己注册了一个 HAL component，名字固定为：

```text
gmoccapy
```

动态创建的典型 HAL pins 有：

- 硬按钮输入：
  - `gmoccapy.h-button.button-0` 到 `button-9`
  - `gmoccapy.v-button.button-0` 到 `button-6`
- 轴点动输入：
  - `gmoccapy.jog.axis.jog-x-plus`
  - `gmoccapy.jog.axis.jog-x-minus`
  - 对 Y/Z/A/B 等轴同理
- 点动增量：
  - `gmoccapy.jog.jog-inc-N`
  - `gmoccapy.jog.jog-increment`
- 倍率/速度编码器输入：
  - `gmoccapy.feed.feed-override.counts`
  - `gmoccapy.spindle.spindle-override.counts`
  - `gmoccapy.jog.jog-velocity.counts`
  - `gmoccapy.rapid.rapid-override.counts`
- 模拟直控输入：
  - `gmoccapy.feed.feed-override.direct-value`
  - `gmoccapy.spindle.spindle-override.direct-value`
  - `gmoccapy.jog.jog-velocity.direct-value`
  - `gmoccapy.rapid.rapid-override.direct-value`
- 工具/刀补相关：
  - `gmoccapy.tooloffset-x`
  - `gmoccapy.tooloffset-z`
  - `gmoccapy.tool-diameter`
  - `gmoccapy.toolchange-change`
  - `gmoccapy.toolchange-changed`
  - `gmoccapy.toolchange-number`
- 程序进度：
  - `gmoccapy.program.length`
  - `gmoccapy.program.current-line`
  - `gmoccapy.program.progress`
- 错误和消息：
  - `gmoccapy.error`
  - `gmoccapy.delete-message`

这些 pins 的变化由 `hal_glib.GPin` 包装。`GPin` 每 100 ms 轮询一次 HAL pin，如果值变化，就发出 `value_changed` 信号，触发 gmoccapy 回调。例如：

- `gmoccapy.jog.axis.jog-x-plus` 变为 true：
  1. `GPin` 检测到变化。
  2. 调用 `_on_pin_jog_changed(...)`。
  3. 再调用 `_on_btn_jog_pressed(...)`。
  4. 最终调用 `self.command.jog(...)` 发 NML 点动命令。

因此 HAL 输入通常不是直接驱动 motion，而是先进入 GUI 回调，再由 GUI 通过 NML command 发给 `milltask`。也可以有纯 HAL 路径，例如 postgui 中的主轴反馈条和 LED 是 HAL 信号直接驱动 gmoccapy HAL widget。

### 5.4 HALUI 的角色

本配置启用了：

```ini
[HAL]
HALUI = halui
```

`halui` 是另一个用户空间 HAL component，它把 LinuxCNC 常用命令/状态暴露为 HAL pins。它自己也使用 LinuxCNC command/stat 通道与 `milltask` 交互。

本 INI 的 `[HALUI]` section 没有定义 MDI_COMMAND，因此这里的 `halui` 主要提供默认 HALUI pins，便于外部硬件按钮或 HAL 逻辑直接控制 LinuxCNC。`gmoccapy` 的主控制链路仍是自己的 `linuxcnc.command/stat/error_channel`，不是通过 `halui` 间接通信。

## 6. 典型操作链路

### 6.1 界面上电

用户点击界面上电按钮：

```text
GTK toggle button
  -> gmoccapy.on_tbtn_on_toggled()
  -> linuxcnc.command().state(STATE_ON)
  -> NML emcCommand
  -> milltask
  -> iocontrol/motion 状态改变
  -> emcStatus
  -> gmoccapy self.stat.poll()
  -> 界面按钮、DRO、状态区刷新
```

本仿真中急停链路由 HAL 回环：

```hal
iocontrol.0.user-enable-out -> iocontrol.0.emc-enable-in
```

所以没有真实外部急停硬件。

### 6.2 界面点动 X+

用户按住 X+：

```text
GTK jog button pressed
  -> _on_btn_jog_pressed("x+")
  -> self.command.jog(JOG_CONTINUOUS, ...)
  -> NML emcCommand
  -> milltask/motion
  -> motmod 输出 joint.0.motor-pos-cmd
  -> core_sim_XYZAB.hal 回环到 joint.0.motor-pos-fb
  -> emcStatus 位置更新
  -> gmoccapy stat.poll()
  -> DRO/预览刷新
```

松开按钮时：

```text
GTK jog button released
  -> _on_btn_jog_released("x+")
  -> self.command.jog(JOG_STOP, ...)
```

如果来自外部 HAL 按钮：

```text
HAL signal -> gmoccapy.jog.axis.jog-x-plus
  -> GPin value_changed
  -> _on_pin_jog_changed()
  -> 同上 NML 点动链路
```

### 6.3 主轴启动和反馈

用户点击主轴正转：

```text
GTK spindle forward
  -> _set_spindle("forward")
  -> self.command.spindle(1, rpm)
  -> NML emcCommand
  -> milltask/motion
  -> spindle.0.forward / spindle.0.speed-out HAL pins
  -> spindle_sim.hal 模拟编码器和速度反馈
  -> spindle.0.speed-in / spindle.0.at-speed
  -> gmoccapy_postgui.hal
  -> gmoccapy.spindle_feedback_bar / gmoccapy.spindle_at_speed_led
  -> 界面显示主轴反馈和到速状态
```

同时 gmoccapy 的 `_periodic()` 也会从 `stat.spindle` 和 HAL 的 `spindle.0.speed-out` 读取状态刷新按钮/文字。

### 6.4 冷却液

用户点击 flood/mist：

```text
GTK coolant button
  -> self.command.flood(FLOOD_ON/OFF) 或 self.command.mist(MIST_ON/OFF)
  -> NML emcCommand
  -> milltask/iocontrol
  -> iocontrol.0.coolant-flood / iocontrol.0.coolant-mist HAL pins
  -> core_sim_XYZAB.hal 中 flood/mist 信号
  -> stat.poll() 回读状态并刷新按钮
```

### 6.5 MDI、自动运行和 REMAP

MDI 和自动运行也走 NML：

```text
GUI
  -> command.mode(MODE_MDI) / command.mdi(...)
  -> 或 command.mode(MODE_AUTO) / command.auto(AUTO_RUN, line)
  -> milltask 解释器/任务队列
  -> motion + io + status
```

INI 中的：

```ini
REMAP=M6  modalgroup=6 prolog=change_prolog ngc=change_g43 epilog=change_epilog
REMAP=M61 modalgroup=6 prolog=settool_prolog ngc=settool_g43 epilog=settool_epilog

[PYTHON]
PATH_PREPEND = ./python
TOPLEVEL = ./python/toplevel.py
```

表示 `milltask` 的解释器在执行 M6/M61 时加载 `./python/toplevel.py`，再导入 `remap.py` 和 `stdglue.py`。这些逻辑运行在解释器/任务侧，用于换刀 prolog/epilog 和工具参数处理，不是 gmoccapy 界面轮询的一部分。

## 7. 通信结构图

```text
                         +---------------------+
                         |      gmoccapy       |
                         | GTK UI + HAL comp   |
                         +----------+----------+
                                    |
                    +---------------+----------------+
                    |                                |
                    v                                v
        linuxcnc.command/stat/error_channel       HAL pins
                    |                                |
                    v                                v
              NML buffers                    HAL shared memory
     emcCommand / emcStatus / emcError              |
                    |                                |
                    v                                v
              linuxcncsvr                     halui / iocontrol /
                    |                          motmod / sim_encoder /
                    v                          gmoccapy_postgui.hal
                milltask                            |
        interpreter + task + IO                     |
                    |                                |
                    v                                |
                 motmod <----------------------------+
              realtime motion
```

更精确地说：

- GUI 控制类动作：`gmoccapy -> NML command -> milltask -> motion/io/HAL`。
- GUI 状态显示：`milltask -> NML status/error -> gmoccapy stat/error poll`。
- GUI HAL 控件：`HAL network <-> gmoccapy.* pins`。
- postgui 连接：`HAL network -> gmoccapy.* pins`，必须在 `gmoccapy.halcomp.ready()` 后执行。

## 8. 需要注意的细节

1. `gmoccapy_postgui.hal` 不能放到普通 `HALFILE` 中执行，因为普通 HALFILE 执行时 `gmoccapy.*` pins 还不存在。
2. 本配置是仿真机，轴反馈是命令位置回环，不代表真实驱动闭环。
3. `core_sim_XYZAB.hal` 和 `gmoccapy_postgui.hal` 都处理了 tool-change loop。postgui 中先 `unlinkp`，再重新建立 `iocontrol.0.tool-change -> iocontrol.0.tool-changed`，目的是避免仿真环境中的连接冲突并继续自动完成换刀。
4. `HALUI = halui` 提供额外 HAL 控制入口，但 gmoccapy 的主要 GUI 交互直接使用 `linuxcnc.command/stat/error_channel`。
5. `gmoccapy` 源码中非 identity kinematics 的 joint minus pin 回调参数看起来写成了 `"{0}+"`，即 plus/minus 都传 plus。对本 `trivkins` 仿真配置通常不影响主轴向点动，但如果改成非平凡运动学并启用 joint jogging，需要复查这一处。

## 9. G 代码执行条件和状态互锁

本节分析的是 `gmoccapy_XYZAB.ini` 当前配置的实际逻辑。关键前提是：

```ini
[TRAJ]
NO_FORCE_HOMING = 0
```

因此本配置要求先完成全轴回零，才允许进入 MDI/Auto 等会执行解释器命令的模式。如果改成 `NO_FORCE_HOMING = 1`，gmoccapy 会绕过这部分回零互锁。

### 9.1 三层保护

G 代码是否能执行，不只由一个按钮决定，而是三层共同约束：

1. GUI 页面和按钮敏感状态：gmoccapy 根据 `state-*`、`mode-*`、`interp-*`、`all-homed` 等状态事件启停按钮。
2. gladevcp Action 对象：例如暂停、单步、重载文件按钮绑定到 `EMC_ToggleAction_Pause`、`EMC_Action_Step`、`EMC_Action_Reload`，这些对象也会检查 machine on、解释器状态和模式。
3. LinuxCNC 后台校验：最终的 `command.mode()`、`command.mdi()`、`command.auto()`、`command.jog()` 等命令会进入 NML，`milltask`/motion 会再次按任务状态、模式、限位、回零、解释器状态拒绝非法命令。

所以界面按钮不可点是第一道互锁，后台拒绝非法命令是最后一道互锁。

### 9.2 自动运行 G 代码的正常链路

从冷启动到自动运行一个文件，正常条件链如下：

```text
启动 linuxcnc <ini>
  -> linuxcncsvr / milltask / motmod / HAL / halui 启动
  -> gmoccapy 创建 linuxcnc.command/stat/error_channel 和 gmoccapy HAL component
  -> gmoccapy_postgui.hal 连接界面 HAL pins
  -> F1 急停状态或默认急停状态
  -> 解除急停：command.state(STATE_ESTOP_RESET)
  -> 上电：command.state(STATE_ON)
  -> 回零：command.home(-1) 或逐轴 command.home(joint)
  -> 收到 all-homed 状态，MDI/Auto/Touch/Tool 相关按钮变为可用
  -> 加载程序：EMC_Action_Open.load_file(path) 或 reload
  -> 切换 Auto：command.mode(MODE_AUTO)
  -> Cycle Start：command.auto(AUTO_RUN, start_line)
  -> milltask 解释 RS274NGC，必要时执行 REMAP，例如 M6/M61
  -> motion/io/HAL 运动和 IO 状态变化
  -> gmoccapy stat.poll()/GStat 信号刷新界面
```

从界面角度，自动运行前应满足：

- LinuxCNC 后台进程和 HAL 网络已启动，gmoccapy 已完成 `halcomp.ready()`。
- 急停已复位，即不在 `STATE_ESTOP`。
- 机床已上电，即 `task_state == STATE_ON`。
- 全轴已回零，即收到 `all-homed`；本配置 `NO_FORCE_HOMING = 0`，不能跳过。
- 解释器空闲，即 `INTERP_IDLE`。运行中不能切换 Manual/MDI/Auto，也不能加载/编辑程序。
- 已处于 Auto 模式，或按钮/Action 会先 `ensure_mode(... MODE_AUTO)`。
- 应加载了有效程序文件。gmoccapy 的快捷键逻辑会用 `btn_use_current` 是否敏感判断文件已加载；`btn_run` 回调本身直接发送 `AUTO_RUN`，如果无文件或状态不合法，最终由 LinuxCNC 后台报错。
- 未处于设置页、编辑页、文件选择页、偏置编辑页等会临时禁止模式切换的页面。
- 硬限位没有阻止运动；若限位触发，只能在允许的状态下通过 `override_limits()` 临时忽略限位后退出限位。

### 9.3 MDI 和宏的执行条件

MDI 的入口包括 MDI 历史输入框、宏按钮、Touch Off、工具补偿、坐标系切换等内部 MDI 命令。

```text
MDI 输入框 Enter
  -> EMC_MDIHistory.submit()
  -> ensure_mode(MODE_MDI)
  -> command.mdi(cmd)
  -> milltask interpreter
```

宏按钮来自 INI `[MACROS]`：

```ini
MACRO = i_am_lost
MACRO = halo_world
MACRO = jog_around
MACRO = increment xinc yinc
MACRO = go_to_position X-pos Y-pos Z-pos
```

点击宏按钮后，gmoccapy 生成：

```text
O<macro_name> call [param1] [param2] ...
```

然后调用 `command.mdi(command)`。宏运行期间会禁用宏按钮，键盘按钮在非 onboard 模式下变成中断宏的按钮，点击会 `command.abort()`。

MDI 的互锁要点：

- `rbt_mdi` 只有在上电且全轴回零后才被启用。
- 进入 MDI 前如果按钮不敏感，gmoccapy 会立即 `command.abort()`，切回 Manual，并提示当前不能进入 MDI。
- MDI 运行中 gmoccapy 允许继续添加 MDI 命令，但会禁用 Manual/Auto/Setup/Load/Edit 等容易破坏解释器状态的入口。
- Touch Off、工具 Touch Off、坐标系切换等内部命令会临时切到 `MODE_MDI`，发送 G/M 代码，再切回 `MODE_MANUAL`。

### 9.4 任务状态互锁

LinuxCNC 这里最重要的任务状态是 `STATE_ESTOP`、`STATE_ESTOP_RESET`、`STATE_OFF`、`STATE_ON`。

| 状态 | 触发/来源 | gmoccapy 界面动作 | 允许的主要操作 | 禁止或限制 |
| --- | --- | --- | --- | --- |
| `STATE_ESTOP` 急停 | F1、急停按钮置位、退出时最终置位 | 急停按钮显示按下，上电按钮不敏感且关闭，强制 `MODE_MANUAL` | 只能复位急停；Esc 仍可 abort | 上电、回零、点动、MDI、Auto、主轴、冷却、进给覆盖等全部禁用 |
| `STATE_ESTOP_RESET` 急停复位 | 急停按钮释放后 `command.state(STATE_ESTOP_RESET)` | 上电按钮变为可用；点动页外框可见，但具体点动/速度控件仍受上电限制 | 可以点击上电；如果有限位，可显示 ignore limits | 机床未上电，仍不能运动、不能执行 MDI/Auto |
| `STATE_OFF` 下电 | 上电按钮释放或启动/异常进入 OFF | 关闭 Manual/MDI/Auto、回零、Touch、Tool、Jog、Spindle、Coolant、Feed/Rapid 控件；退出按钮可用 | 可以退出或重新上电 | 不能回零、点动、执行程序、主轴冷却 |
| `STATE_ON` 上电 | 上电按钮置位后 `command.state(STATE_ON)` 成功 | Manual、回零、Jog、Feed/Rapid、Spindle、Coolant 基本控件启用；退出按钮禁用 | 可回零、点动、主轴冷却、调覆盖率 | 未回零时 MDI/Auto/Touch/Tool 仍被禁用 |

上电按钮的处理有额外保护：

- 如果当前仍是 `STATE_ESTOP`，点击上电会立即撤销按钮状态，不发送上电。
- 发送 `STATE_ON` 后会 `wait_complete()` 并 `stat.poll()`；如果后台没有进入 `STATE_ON`，界面会撤销上电按钮并报错。
- 下电时发送 `STATE_OFF`，并统一 `_update_widgets(False)` 禁用可运动控件。

### 9.5 模式互锁

任务模式是 `MODE_MANUAL`、`MODE_MDI`、`MODE_AUTO`。

| 模式 | 进入方式 | 页面切换 | 允许的典型动作 | 互锁 |
| --- | --- | --- | --- | --- |
| Manual | `rbt_manual` 或状态回调强制 | 主页面、Manual 按钮页、Jog 页 | 回零、点动、Touch Off、Tool 页面、主轴冷却、覆盖率 | 运行中不能切换；设置页/编辑页会临时禁用模式按钮 |
| MDI | `rbt_mdi`、MDI 输入框、内部 Touch/Tool 命令 | MDI 按钮页，输入框获得焦点 | 单条 MDI、宏、内部 `G10/G43/M61/Tn M6` 等 | 未上电/未回零不能进入；按钮不敏感时会 abort 并切回 Manual |
| Auto | `rbt_auto`、Run/Step Action 可确保 Auto | Auto 按钮页，G-code 预览获得焦点 | `AUTO_RUN`、`AUTO_STEP`、Pause/Resume、Stop、Reload、From Line | 未上电/未回零不能进入；解释器运行中禁止切模式 |

键盘快捷键也遵守互锁：

- F1：任何时候发送 `STATE_ESTOP`。
- Esc：任何时候 `command.abort()`。
- F2：不在急停时切换上电。
- F3/F5：只有解释器空闲时才允许切 Manual/MDI。
- Auto 模式下，`R` 运行、`Ctrl+R` 重载、`P` 暂停、`S` 继续；但要求当前不是编辑页，并且已有程序加载。
- 键盘点动只在 Manual 且主页面时处理。

### 9.6 回零与运动模式互锁

本配置 `NO_FORCE_HOMING = 0`，回零状态直接控制解释器相关入口：

```text
not-all-homed
  -> all_homed = False
  -> 禁用 rbt_mdi / rbt_auto / btn_tool / btn_touch / tbtn_switch_mode
  -> teleop_enable(0)，进入 joint mode

all-homed
  -> all_homed = True
  -> 启用 rbt_mdi / rbt_auto / btn_tool / btn_touch / tbtn_switch_mode
  -> teleop_enable(1)，进入 world/teleop mode
```

回零按钮逻辑：

- `btn_homing` 只切到回零按钮页。
- `ref_all` 调用 `command.home(-1)`。
- 单轴/单关节回零先检查是否已有 joint 正在 homing；若有则拒绝新回零命令。
- 回零前会 `teleop_enable(0)` 进入 joint mode。
- `unref_all` 或 unhome 信号会 `command.unhome(-1/joint)`，并把 `all_homed` 置 False。

非 identity kinematics 时，joint/world 模式还会影响 MDI/Auto 的敏感状态：joint mode 下 `rbt_mdi`、`rbt_auto` 会被禁用，防止在关节模式下执行笛卡尔程序。本配置使用 `trivkins coordinates=xyzab`，轴和 joint 一一对应，但 gmoccapy 仍保留这套通用逻辑。

### 9.7 解释器状态互锁

解释器状态主要由 `interp-idle` 和 `interp-run` 信号驱动界面：

| 解释器状态 | gmoccapy 处理 | 结果 |
| --- | --- | --- |
| `INTERP_IDLE` | 重新启用 Jog、G-code view、冷却、主轴、Load、Edit、Optional Blocks、Reload；若已上电且已回零，启用 MDI/Auto/Tool/Touch；`btn_run=True`、`btn_stop=False` | 空闲，可准备运行或编辑 |
| `INTERP_READING` / `INTERP_WAITING` / 运行中 | 禁用 Manual/MDI/Auto、Setup、Tool、Load、Edit、Optional Blocks、部分主轴按钮、Touch、Reload；`btn_run=False`、`btn_stop=True` | 正在执行，防止模式/文件/工具状态被改坏 |
| Pause | gladevcp pause toggle 会保持暂停按钮激活；gmoccapy 额外允许主轴方向按钮和 G-code view 敏感 | 暂停时可查看代码并调整部分主轴控制 |

停止按钮有两条链路：

```text
btn_stop related-action hal_action_stop -> command.abort()
btn_stop clicked handler                -> command.abort(), start_line=0, gcode line=0, pause=False
```

因此 Stop 既让 LinuxCNC abort，也清理 gmoccapy 的从行运行和暂停显示状态。

### 9.8 限位与忽略限位

限位状态通过 `hard-limits-tripped` 信号进入界面：

- 有限位触发时，`chk_ignore_limits` 变为可用，`ntb_jog` 也会保持可用，便于退出限位。
- 点击 `chk_ignore_limits` 时调用 `command.override_limits()`。
- 外部 HAL pin `gmoccapy.ignore-limits` 也可驱动这个复选框。
- 限位解除后，ignore limits 自动取消选中并变为不敏感。

是否允许上电、是否允许运动最终仍由 motion/task 判断。gmoccapy 的职责是暴露 override 控件并避免在明显非法状态下发运动命令。

### 9.9 特殊页面互锁

gmoccapy 有几个“临时工作页”，它们会有意禁用模式切换：

- 设置页 `tbtn_setup`：进入时强制 `MODE_MANUAL`，记录原模式，禁用 Manual/MDI/Auto/User Tabs。是否允许进入由偏好设置决定：不加锁、输入解锁码、或 HAL pin `unlock-settings`。退出时按急停/上电/回零状态恢复模式按钮，并尝试恢复原模式。
- 文件选择页 `btn_load`：切到文件页和全屏预览，刷新文件列表，禁用 Manual/MDI/Setup。选择文件后调用 `hal_action_open.load_file(path)`，回到 Auto 页并恢复模式按钮。
- 编辑页 `btn_edit`：使 G-code view 可编辑，隐藏/调整预览和 DRO，禁用 Manual/MDI/Setup。退出时如有未保存修改会询问是否丢弃，并触发 reload 以恢复解释器看到的文件。
- 偏置编辑 `tbtn_edit_offsets`：编辑坐标偏置时禁用 Jog、MDI、Auto、Setup 和 User Tabs；退出编辑时强制回 Manual。
- Tool 编辑页：如果工具表有未保存修改，离开或换刀前会询问是否丢弃；不能删除当前主轴内的工具。

这些页面互锁的共同目的，是避免一边编辑文件/设置/偏置/工具表，一边让解释器执行旧文件或半修改状态。

## 10. 界面按钮和控制逻辑

### 10.1 顶部电源和模式按钮

| 控件 | 回调/绑定 | 实际命令 | 控制逻辑 |
| --- | --- | --- | --- |
| `tbtn_estop` | `on_tbtn_estop_toggled` | `command.state(STATE_ESTOP)` 或 `STATE_ESTOP_RESET` | 按下进入急停；释放复位急停。复位失败会重新置位急停按钮并提示。 |
| `tbtn_on` | `on_tbtn_on_toggled` | `command.state(STATE_ON/OFF)` | 急停中不能上电；上电失败会撤销按钮；下电后禁用运动和执行控件。 |
| `rbt_manual` | pressed | `command.mode(MODE_MANUAL)` | 只发模式命令；具体页面由 `mode-manual` 状态回调切换。 |
| `rbt_mdi` | pressed | `command.mode(MODE_MDI)` | 未满足条件时状态回调会 abort 并切回 Manual。 |
| `rbt_auto` | pressed | `command.mode(MODE_AUTO)` | 未满足条件时状态回调会 abort 并切回 Manual。 |
| `tbtn_setup` | `on_tbtn_setup_toggled` | 必要时 `MODE_MANUAL` | 解锁后进入设置页，禁用模式切换；退出后恢复。 |
| `tbtn_user_tabs` | `on_tbtn_user_tabs_toggled` | 无 NML 命令 | 仅切换用户页显示；设置页激活时不允许自由切换。 |

### 10.2 手动页、回零和点动

| 控件/入口 | 实际命令 | 控制逻辑 |
| --- | --- | --- |
| `btn_homing` | 无，切页面 | 显示动态生成的回零按钮。 |
| `ref_all` | `command.home(-1)` | 全轴回零，先切 joint mode。 |
| 单轴/单 joint 回零 | `command.home(joint)` | 如果已有 joint 在 homing，则拒绝。 |
| `unref_all` | `command.unhome(-1)` | 取消回零状态，切 joint mode，Auto/MDI 随后被禁用。 |
| `tbtn_switch_mode` | `command.teleop_enable(0/1)` | 在 joint/world mode 间切换；非 identity kinematics 下 joint mode 禁用 Auto/MDI。 |
| 屏幕点动按钮 | `command.jog(...)` | 只在 `stat.enabled` 且 `MODE_MANUAL` 时有效；连续点动松开时 `JOG_STOP`，增量点动松开不停止。 |
| 点动增量按钮 | `command.jog(JOG_STOP, ...)` 后更新距离 | 切换增量前先停止当前点动，避免速度/距离状态混乱。 |
| 键盘点动 | `command.jog(...)` | 只在 Manual 主页面，偏置页/工具页/编辑输入框不抢键。 |
| 窗口失焦 | `command.jog(JOG_STOP, ...)` | 如果 Manual 中仍有速度，停止所有非 homing joint 的点动。 |

外部 HAL 点动 pin 走同一套按钮函数：

```text
gmoccapy.jog.axis.x-plus / x-minus / ...
  -> _on_pin_jog_changed()
  -> _on_btn_jog_pressed/released()
  -> command.jog(...)
```

### 10.3 Touch Off 和偏置按钮

| 控件 | 实际命令 | 控制逻辑 |
| --- | --- | --- |
| `btn_touch` | 无，切页面 | 显示坐标偏置/Touch Off 页面。 |
| 轴 Touch Off 动态按钮 | `G10 L20 P0 <axis><value>` | 必须 `STATE_ON` 且已回零；临时切 MDI，执行后 reload，再回 Manual。 |
| DRO 轴点击 | 同轴 Touch Off | 点击 DRO 也会进入 `_on_btn_set_value_clicked()`。 |
| `btn_zero_g92` | `G92.1` | 清 G92，之后回 Manual 并刷新 Touch 页。 |
| `btn_set_selected` | `G54` 到 `G59.3` | 只能选择有效坐标系；临时 MDI 后回 Manual。 |
| `tbtn_edit_offsets` | offsetpage 内部编辑 | 编辑时禁用 Jog、MDI、Auto、Setup、User Tabs；退出回 Manual。 |
| Block Height | `G10 L2 P0 Z<origin>` | 结合探针设置写入 blockheight/probeheight HAL pins，并设置 Z 原点。 |

### 10.4 自动页和文件执行按钮

| 控件 | 回调/绑定 | 实际命令 | 控制逻辑 |
| --- | --- | --- | --- |
| `btn_load` | `on_btn_load_clicked` | 无，进入文件页 | 刷新 `IconFileSelection1`，禁用 Manual/MDI/Setup。 |
| 文件选择确认 | `on_IconFileSelection1_selected` | `hal_action_open.load_file(path)` | 加载文件，返回 Auto 页，恢复模式按钮。 |
| `btn_reload` | `hal_action_reload` | `_load_file(stat.file)` | 解释器运行中 Action 会禁用；空闲时重载当前文件。 |
| `btn_run` | `on_btn_run_clicked` | `command.auto(AUTO_RUN, start_line)` | 直接按 `start_line` 启动。状态和文件合法性主要靠按钮敏感状态、快捷键判断和 LinuxCNC 后台校验。 |
| `btn_stop` | `hal_action_stop` + `on_btn_stop_clicked` | `command.abort()` | 同时清 `start_line`、G-code 行号和暂停按钮。 |
| `tbtn_pause` | `hal_tgl_pause` + `on_tbtn_pause_toggled` | 激活 `AUTO_PAUSE`，取消激活 `AUTO_RESUME` | 只有 Auto 运行/等待状态有效；gmoccapy 额外切换图标并允许部分主轴/G-code 控件。 |
| `btn_step` | `hal_action_step` | `ensure_mode(MODE_AUTO)` 后 `AUTO_STEP` | 上电且解释器空闲时可用。 |
| `btn_from_line` | `restart_dialog` | 只设置 `start_line` | 选择行号后不立即运行；下一次 `btn_run` 从该行启动。该功能不会检查前序 G 代码上下文，界面 tooltip 已标为危险。 |
| `tbtn_optional_blocks` | `on_tbtn_optional_blocks_toggled` | `command.set_block_delete(state)` 后 reload | 控制跳过 `/` 可选段；状态写入偏好设置。 |
| `btn_edit` | `on_btn_edit_clicked` | 无，进入编辑页 | 使 G-code view 可编辑，禁用 Manual/MDI/Setup。 |

### 10.5 MDI、宏和虚拟键盘按钮

| 控件 | 实际命令 | 控制逻辑 |
| --- | --- | --- |
| `hal_mdihistory` 输入框 | `ensure_mode(MODE_MDI)` 后 `command.mdi(cmd)` | Enter 或图标提交；`HALMETER`、`STATUS`、`HALSHOW` 是特殊工具命令，不作为 MDI 发给解释器。 |
| MDI 删除历史 | 无 NML 命令 | 只清当前会话列表，不删除历史文件。 |
| 宏按钮 | `O<name> call [params...]` | 参数由对话框输入；发送后清 live plotter，禁用宏按钮直到空闲。 |
| 计算器按钮 | 无 NML 命令 | 将计算值插入 MDI 输入框或编辑器。 |
| 键盘按钮 | 可能 `command.abort()` | MDI 宏运行时可作为中断按钮；其他时候切换虚拟键盘显示。 |

### 10.6 工具和换刀按钮

| 控件 | 实际命令 | 控制逻辑 |
| --- | --- | --- |
| `btn_tool` | 无，切页面 | 显示工具表编辑页。 |
| 添加/删除工具 | tool editor 本地表操作 | 删除当前主轴工具被禁止；修改后标记 `edited=True`。 |
| 重载工具表 | `tooledit.reload()` | 有未保存修改时先询问是否丢弃。 |
| 保存工具表 | `tooledit.save()` | 保存后清 edited 标记。 |
| `btn_select_tool_by_no` | `Tn M6` | 输入工具号；若等于当前主轴工具则拒绝；发送前切 MDI。 |
| `btn_change_tool` | `Tn M6` | 对工具表选中工具执行换刀；发送后再发一个短 `G4 P...`，避免状态信号丢失。 |
| `btn_index_tool` | `M61 Qn` | 只改变当前工具号，不执行 M6 换刀流程；同样用 MDI 发送。 |
| 工具 Touch Off X/Z | `G10 L10 P<tool> X/Z<value>` | 必须选中工具就是主轴工具；G41/G42 刀补开启时拒绝；如果原来有 G43，写入后重新 `G43`。 |
| HAL tool-change | 对话框确认后置 `toolchange-changed` | `toolchange-change` pin 触发手动换刀对话框；确认 pin 可由外部 HAL 按钮关闭对话框；取消时 abort 并保留旧工具。 |

本仿真配置中 `gmoccapy_postgui.hal` 把 `iocontrol.0.tool-change` 和 `iocontrol.0.tool-changed` 重新接成自动完成回路，所以多数 M6 在仿真里不会真正等待人工换刀。

### 10.7 主轴、冷却和覆盖率

| 控件/HAL pin | 实际命令 | 控制逻辑 |
| --- | --- | --- |
| `rbt_forward` | `command.spindle(1, rpm)` | 急停中直接返回；Auto/MDI 解释器 reading/waiting 时不允许手动改主轴，避免覆盖 G-code 主轴命令。 |
| `rbt_reverse` | `command.spindle(-1, rpm)` | 同上，且按 min/max spindle rpm 修正输出。 |
| `rbt_stop` | `command.spindle(0)` | 停主轴，显示转速清 0。 |
| `spc_spindle` | `command.spindleoverride(value)` | 按实际 speed-out、min/max spindle rpm 反算覆盖率，避免超限。 |
| `btn_spindle_100` | 设置 `spc_spindle=100` | 通过 slider 回调发送 override。 |
| `tbtn_flood` | `command.flood(FLOOD_ON/OFF)` | 如果界面状态已等于 stat 状态，不重复发送。 |
| `tbtn_mist` | `command.mist(MIST_ON/OFF)` | 同 flood。 |
| `spc_feed` | `command.feedrate(value)` | 百分比转 0..n 倍率后发送。 |
| `btn_feed_100` | 设置 `spc_feed=100` | 通过 slider 回调发送 feed override。 |
| `spc_rapid` | `command.rapidrate(value)` | 设置快速倍率。 |
| reset override HAL pins | `rapidrate(1.0)` 或点击 100% 按钮 | 外部硬件可复位 feed/spindle/rapid override。 |

覆盖率和点动速度也可以由 HAL counts 或 analog direct-value 驱动。`*_count-enable` 或 `*_analog-enable` 置位后，gmoccapy 会隐藏对应屏幕按钮或按外部输入更新 slider。

### 10.8 硬件按钮和 HAL 输入

gmoccapy 创建了一组硬件按钮输入 pin：

```text
gmoccapy.h-button.button-0 ... button-9
gmoccapy.v-button.button-0 ... button-6
```

输入 pin 变高时：

```text
_button_pin_changed()
  -> 判断 h-button 还是 v-button
  -> 找当前页面对应位置的可见按钮
  -> 如果按钮不敏感则忽略
  -> ToggleButton 反转 active
  -> RadioButton set_active(True) 并 emit("pressed")
  -> 普通 Button emit("clicked")
```

这意味着硬件按钮不是固定绑定某个功能，而是绑定“当前界面底部/右侧第 N 个按钮”。同一个硬件按钮在 Manual、MDI、Auto、Tool、File、Edit 页会触发不同软件按钮。它继承软件按钮的敏感状态，因此同样受急停、上电、回零、解释器运行等互锁限制。

其他关键 HAL 输入/输出：

- `gmoccapy.jog.axis.*`：外部点动输入，走同一套 jog 逻辑。
- `gmoccapy.jog.jog-increment.*`：外部选择点动增量。
- `gmoccapy.unlock-settings`：设置页 HAL 解锁。
- `gmoccapy.ignore-limits`：外部驱动 ignore limits。
- `gmoccapy.optional-stop`、`gmoccapy.blockdelete`：源码注释写作 optional stop 和 block delete，但当前回调实际是交叉的：`optional-stop` pin 会驱动界面的 `tbtn_optional_blocks`，也就是 `set_block_delete()`；`blockdelete` pin 会调用 `set_optional_stop()`。
- `gmoccapy.delete-message`、`gmoccapy.warning-confirm`：外部清消息或确认弹窗。
- `gmoccapy.toolchange-number/change/changed/confirm`：手动换刀对话框和 iocontrol/remap 之间的握手。
- `gmoccapy.program.length/current-line/progress`：把程序长度、当前行、进度输出给 HAL。
- `gmoccapy.error`：存在报警通知时输出 True。

### 10.9 设置页和数据类按钮

设置页多数按钮不直接控制运动，而是改 gmoccapy 偏好设置或 HAL 参数：

- DRO 位数、DRO 显示切换、G-code 字体、消息弹窗位置和字体：写入 prefs，刷新界面属性。
- 运行从某行开始设置：写入 `run_from_line` prefs，决定 Auto 页是否显示/启用 `btn_from_line`。
- Reload tool、tool measurement、probe height/search velocity/probe velocity/block height：写入 prefs 和 `gmoccapy.probeheight/searchvel/probevel/blockheight/toolmeasurement` HAL pins。
- Turtle jog：改变 jog slider 的 min/max 和当前值，也可由 `gmoccapy.jog.turtle-jog` HAL pin 驱动。
- 音频、虚拟键盘、主题、图标、屏幕布局：只影响界面行为或视觉，不直接向 LinuxCNC 后台发运动命令。

设置页入口本身有锁。进入后会禁用模式按钮，退出时根据 `STATE_ESTOP`、`STATE_ON`、`all_homed`、`NO_FORCE_HOMING` 恢复 Manual/MDI/Auto 的可用状态。

### 10.10 控制逻辑总图

```text
用户点击/键盘/HAL button
  -> GTK signal 或 HAL pin callback
  -> gmoccapy 回调检查当前 state/mode/interp/all_homed/page
  -> 需要时切换 LinuxCNC mode
  -> linuxcnc.command 发 state/mode/jog/mdi/auto/spindle/coolant/override
  -> NML emcCommand
  -> linuxcncsvr / milltask / motion / iocontrol
  -> HAL pins 和 realtime motion 变化
  -> emcStatus/emcError
  -> linuxcnc.stat/error_channel + GStat 信号
  -> gmoccapy 刷新按钮敏感状态、DRO、G-code 行、进度、报警
```

最重要的互锁可以压缩为一句话：

```text
急停复位 + 机床上电 + 全轴回零 + 解释器空闲 + 合法模式 + 非特殊编辑/设置页
  -> 才应该允许执行 MDI 或 Auto G 代码
```

其中 `btn_run` 是一个需要特别注意的例外：它的回调本身只做 `AUTO_RUN`，不重复检查所有前置条件。因此若要在二次开发中增加外部 Cycle Start、远程运行、或新的运行按钮，应复用上面的完整条件，而不能只调用 `command.auto(AUTO_RUN, 0)`。

## 11. 按钮逐项核对清单

本节按源码重新核对按钮。范围包括：

- `gmoccapy.glade` 中的 7 个 gladevcp Action 对象和 126 个静态可点击控件：`GtkButton`、`GtkToggleButton`、`GtkRadioButton`、`GtkCheckButton`、`GtkFileChooserButton`、`GtkColorButton`、`GtkFontButton`。
- `gmoccapy` 运行时按 INI 动态生成的按钮：回零、Touch Off、点动、MDI 宏、点动增量、OffsetPage/ToolEdit 内嵌按钮。
- 数值滑块、SpinButton、ComboBox 不是“按钮”，前文已按功能说明；本节只在它们由按钮/复选框联动时提到。

本 XYZAB 配置的实际动态条件：

- 轴列表为 `X Y Z A B`，joint 数为 5。
- `[TRAJ] NO_FORCE_HOMING = 0`，所以回零互锁启用。
- 未定义 `[TOOLSENSOR]`，所以 Touch Off 页不会出现 `Block Height` 按钮，自动测刀复选框默认不可用。
- `[MACROS]` 定义 5 个宏按钮：`i_am_lost`、`halo_world`、`jog_around`、`increment xinc yinc`、`go_to_position X-pos Y-pos Z-pos`。

### 11.1 gladevcp Action 对象

这些不是直接显示的按钮，而是 Glade 中供按钮 `related-action` 绑定或由 gmoccapy 代码调用的动作对象。

| 对象 | 被谁使用 | 功能 |
| --- | --- | --- |
| `hal_action_open` | 文件选择确认、启动自动加载文件 | 通过 FileChooser/`load_file(path)` 打开 G-code 文件，解释器运行中禁用。 |
| `hal_action_reload` | `btn_reload`、`btn_reload_edit`、Touch/Offset 后刷新 | 重载 `stat.file` 当前文件。 |
| `hal_action_step` | `btn_step` | 确保 Auto 模式后发送 `AUTO_STEP`。 |
| `hal_action_stop` | `btn_stop` | 发送 `command.abort()` 并等待完成。 |
| `hal_tgl_pause` | `tbtn_pause` | Toggle 激活时 `AUTO_PAUSE`，取消激活时 `AUTO_RESUME`。 |
| `hal_action_save` | `btn_save` | 保存当前 G-code 文件。 |
| `hal_action_saveas` | `btn_save_as` | 另存当前 G-code 文件。 |

### 11.2 顶部状态和模式按钮

| 按钮 | 回调 | 功能 | 后台命令/影响 |
| --- | --- | --- | --- |
| `tbtn_estop` | `on_tbtn_estop_toggled` | 急停/复位急停 | 按下发 `STATE_ESTOP`；释放发 `STATE_ESTOP_RESET`，失败时按钮回到急停状态。 |
| `tbtn_on` | `on_tbtn_on_toggled` | 机床上电/下电 | 上电发 `STATE_ON`，下电发 `STATE_OFF`；急停未复位时不能上电。 |
| `rbt_manual` | `on_rbt_manual_pressed`、`on_rbt_manual_toggled` | 切 Manual 模式，图标亮灭 | 发 `MODE_MANUAL`；页面切换由状态回调完成。 |
| `rbt_mdi` | `on_rbt_mdi_pressed`、`on_rbt_mdi_toggled` | 切 MDI 模式，图标亮灭 | 发 `MODE_MDI`；未上电/未回零时会被禁用或切回 Manual。 |
| `rbt_auto` | `on_rbt_auto_pressed`、`on_rbt_auto_toggled` | 切 Auto 模式，图标亮灭 | 发 `MODE_AUTO`；未上电/未回零时会被禁用或切回 Manual。 |
| `tbtn_user_tabs` | `on_tbtn_user_tabs_toggled` | 显示/隐藏用户页 | 只切 UI 页，不发 NML 运动命令。 |
| `tbtn_setup` | `on_tbtn_setup_toggled` | 进入/退出设置页 | 进入时强制 Manual、禁用模式按钮；需按设置的解锁方式通过。 |

### 11.3 预览和视图按钮

| 按钮 | 回调 | 功能 |
| --- | --- | --- |
| `rbt_view_p` | `on_rbt_view_p_toggled` | Gremlin 预览切透视视图 `p`。 |
| `rbt_view_x` | `on_rbt_view_x_toggled` | 预览切 X 方向视图。 |
| `rbt_view_y` | `on_rbt_view_y_toggled` | 预览切 Y 方向视图。 |
| `rbt_view_y2` | `on_rbt_view_y2_toggled` | 预览切 Y2 视图，主要用于后置刀架车床显示。 |
| `rbt_view_z` | `on_rbt_view_z_toggled` | 预览切 Z 方向视图。 |
| `btn_zoom_in` | `on_btn_zoom_in_clicked` | 放大 G-code 预览。 |
| `btn_zoom_out` | `on_btn_zoom_out_clicked` | 缩小 G-code 预览。 |
| `tbtn_view_dimension` | `on_tbtn_view_dimension_toggled` | 显示/隐藏程序外形尺寸。 |
| `tbtn_view_tool_path` | `on_tbtn_view_tool_path_toggled` | 显示/隐藏实时刀路。 |
| `btn_delete_view` | `on_btn_delete_view_clicked` | 清空实时刀路轨迹。 |
| `tbtn_fullsize_preview0` | `on_tbtn_fullsize_preview_toggled` | Manual 页预览最大化/恢复。 |
| `tbtn_fullsize_preview1` | `on_tbtn_fullsize_preview_toggled` | Auto 页预览最大化/恢复。 |
| `chk_show_dro` | `on_chk_show_dro_toggled` | 设置预览区是否显示 DRO。 |
| `chk_show_offsets` | `on_chk_show_offsets_toggled` | 设置预览区是否显示偏置。 |
| `chk_show_dtg` | `on_chk_show_dtg_toggled` | 设置预览区是否显示 DTG。 |

### 11.4 手动页、回零、点动按钮

| 按钮 | 回调 | 功能 | 后台命令/影响 |
| --- | --- | --- | --- |
| `btn_homing` | `on_btn_homing_clicked` | 打开回零按钮页 | 只切按钮页。 |
| `btn_touch` | `on_btn_touch_clicked` | 打开 Touch Off/偏置页 | 显示 OffsetPage，是否显示预览由设置决定。 |
| `btn_tool` | `on_btn_tool_clicked` | 打开工具表页 | 显示 ToolEdit。 |
| `tbtn_switch_mode` | `on_tbtn_switch_mode_toggled` | Joint/World motion mode 切换 | `teleop_enable(0/1)`；非 identity kinematics 下影响 MDI/Auto 可用性。 |
| `btn_exit` | `on_btn_exit_clicked` | 退出 gmoccapy | 销毁窗口；销毁回调会 `STATE_OFF` 后 `STATE_ESTOP`。 |
| `tbtn_turtle_jog` | `on_tbtn_turtle_jog_toggled` | 低速/普通点动速度档切换 | 改变 jog velocity slider 范围和值。 |
| `chk_ignore_limits` | `on_chk_ignore_limits_toggled` | 忽略限位以便退限位 | 选中时调用 `command.override_limits()`。 |

动态回零按钮：

| 动态按钮 | 本 XYZAB 是否出现 | 功能 |
| --- | --- | --- |
| `ref_all` | 出现 | 全部回零，调用 `command.home(-1)`。 |
| `home_axis_x/y/z/a/b` | 出现 | 对应轴回零；先检查是否已有 joint 正在 homing。 |
| `home_joint_0..4` | 在 joint 显示模式下可能使用 | 对应 joint 回零。 |
| `unref_all` | 出现 | 全部 unhome，调用 `command.unhome(-1)`。 |
| `home_back` | 出现 | 返回 Manual 主按钮页。 |
| `previous_button` / `next_button` | 本配置不需要 | 轴数超过 7 时分页显示回零按钮。 |

动态点动按钮：

| 动态按钮 | 本 XYZAB 是否出现 | 功能 |
| --- | --- | --- |
| `x+ x- y+ y- z+ z- a+ a- b+ b-` | 出现 | 轴点动；Manual 且 `stat.enabled` 时才发 `command.jog()`。 |
| `0+ 0- ... 4+ 4-` | 非全回零或 joint mode 时使用 | joint 点动；同样走 `command.jog()`。 |
| `rbt_0` | 出现 | 连续点动。 |
| `rbt_1..5` | 出现 | 对应 INI `INCREMENTS`：`1.000 mm`、`0.100 mm`、`0.010 mm`、`0.001 mm`、`1.2345 in`。 |

### 11.5 Touch Off 和 OffsetPage 内部按钮

| 按钮 | 来源 | 功能 | 后台命令/影响 |
| --- | --- | --- | --- |
| `touch_x/y/z/a/b` | 动态生成 | 对对应轴 Touch Off | 要求 `STATE_ON` 且已回零；执行 `G10 L20 P0 <axis><value>`。 |
| `touch_back` | 动态生成 | 返回 Manual 主按钮页 | 不发运动命令。 |
| `block_height` | 动态生成 | 设置测刀块高度 | 本 XYZAB 未定义 `[TOOLSENSOR]`，实际不出现；出现时会设置 blockheight 并用 `G10 L2 P0 Z...`。 |
| `previous_button` / `next_button` | 动态生成 | Touch Off 轴按钮分页 | 本配置 5 轴，不需要分页。 |
| `Edit Offsets` | OffsetPage 动态改造 | 进入/退出偏置表编辑 | 编辑时禁用 Jog/MDI/Auto/Setup；编辑单元格时可能发 `G10/G92/G43.1` 等 MDI。 |
| `Zero G92/G52` | OffsetPage 动态改造 | 清 G92/G52 偏置 | 发 `G92.1`，回 Manual。 |
| `Set Selected` | OffsetPage 动态改造 | 把选中的坐标系设为当前坐标系 | 对有效 `G54` 到 `G59.3` 发对应 MDI。 |
| OffsetPage `Calculator` | OffsetPage 动态改造 | 编辑偏置数值时弹计算器 | 只辅助输入；最终由编辑逻辑发 MDI。 |
| `rbtn_show_preview` | 设置页 | Touch 页默认显示预览 | 写 prefs `show_preview_on_offset=True`。 |
| `rbtn_show_offsets` | 设置页 | Touch 页默认显示偏置表 | 与 `rbtn_show_preview` 成组，无独立回调；通过另一按钮状态体现。 |

### 11.6 Auto 页和程序执行按钮

| 按钮 | 回调/Action | 功能 | 后台命令/影响 |
| --- | --- | --- | --- |
| `btn_load` | `on_btn_load_clicked` | 进入文件选择页 | 刷新目录，禁用 Manual/MDI/Setup。 |
| `btn_reload` | `hal_action_reload` | 重载当前程序 | 调 gladevcp reload action。 |
| `btn_run` | `on_btn_run_clicked` | 运行已加载程序 | `command.auto(AUTO_RUN, start_line)`。 |
| `btn_stop` | `hal_action_stop` + `on_btn_stop_clicked` | 停止运行程序 | `command.abort()`，清 `start_line`、行号、暂停状态。 |
| `tbtn_pause` | `hal_tgl_pause` + `on_tbtn_pause_toggled` | 暂停/继续程序 | Action 发 `AUTO_PAUSE/AUTO_RESUME`；回调更新图标和部分控件敏感状态。 |
| `btn_step` | `hal_action_step` | 单步执行程序 | `AUTO_STEP`。 |
| `btn_from_line` | `on_btn_from_line_clicked` | 设置从某行开始运行 | 只打开 restart dialog 并设置 `start_line`；后续 Run 才执行。 |
| `tbtn_optional_blocks` | `on_tbtn_optional_blocks_toggled` | 跳过/不跳过 `/` 可选段 | `command.set_block_delete(state)`，保存 prefs 并 reload。 |
| `btn_edit` | `on_btn_edit_clicked` | 进入 G-code 编辑页 | 使 G-code view 可编辑，禁用 Manual/MDI/Setup。 |

### 11.7 MDI 页和宏按钮

| 按钮 | 来源 | 功能 | 后台命令/影响 |
| --- | --- | --- | --- |
| `btn_delete` | Glade | 清空当前会话 MDI 历史列表 | 不删除历史文件，不发 LinuxCNC 命令。 |
| `macro_0` | 动态生成 | 运行 `i_am_lost` | 发 `O<i_am_lost> call`。 |
| `macro_1` | 动态生成 | 运行 `halo_world` | 发 `O<halo_world> call`。 |
| `macro_2` | 动态生成 | 运行 `jog_around` | 发 `O<jog_around> call`。 |
| `macro_3` | 动态生成 | 运行 `increment xinc yinc` | 先弹参数输入，再发 `O<increment> call [xinc] [yinc]`。 |
| `macro_4` | 动态生成 | 运行 `go_to_position X-pos Y-pos Z-pos` | 先弹 3 个参数，再发 `O<go_to_position> call [...]`。 |
| Macro `previous_button` / `next_button` | 动态生成 | 宏按钮分页 | 本配置 5 个宏，不需要分页显示。 |
| Macro `calculator` | 动态生成 | 显示计算器 | 向 MDI 输入框插入计算值。 |
| Macro `keyboard` | 动态生成 | 显示键盘或中断运行宏 | 宏运行中可 `command.abort()`。 |
| MDI 输入框 Enter | `EMC_MDIHistory.submit` | 提交手写 MDI | `ensure_mode(MODE_MDI)` 后 `command.mdi(cmd)`。 |

### 11.8 编辑页按钮

| 按钮 | 回调/Action | 功能 |
| --- | --- | --- |
| `btn_search_forward` | `on_btn_search_forward_clicked` | 在 G-code 编辑器中向下搜索。 |
| `btn_search_back` | `on_btn_search_back_clicked` | 在 G-code 编辑器中向上搜索。 |
| `btn_undo` | `on_btn_undo_clicked` | 撤销编辑。 |
| `btn_redo` | `on_btn_redo_clicked` | 重做编辑。 |
| `chk_ignore_case` | 无独立回调 | 搜索/替换时忽略大小写。 |
| `chk_replace_all` | 无独立回调 | 替换时执行全部替换。 |
| `btn_replace` | `on_btn_replace_clicked` | 执行搜索替换。 |
| `btn_comment` | `on_btn_toggle_comment_clicked` | 对选中 G-code 行切换注释。 |
| `tbtn_split_view` | `on_tbtn_split_view_toggled` | 编辑时显示/隐藏分屏预览。 |
| `btn_reload_edit` | `hal_action_reload` | 重载当前 G-code 文件。 |
| `btn_save` | `hal_action_save` | 保存当前文件。 |
| `btn_save_as` | `hal_action_saveas` | 当前文件另存为。 |
| `btn_new` | `on_btn_new_clicked` | 创建临时新文件，内容为 startup code + `M2`。 |
| `btn_calc` | `on_btn_show_calc_clicked` | 在编辑器插入计算值。 |
| `btn_keyb` | `on_btn_show_kbd_clicked` | 显示/隐藏虚拟键盘。 |
| `btn_back_edit` | `on_btn_back_clicked` | 退出编辑页；有未保存修改时询问是否丢弃。 |

### 11.9 工具页和换刀按钮

| 按钮 | 来源 | 功能 | 后台命令/影响 |
| --- | --- | --- | --- |
| `btn_select_tool_by_no` | Glade | 输入工具号并换刀 | 发 `Tn M6`。 |
| `btn_index_tool` | Glade | 只登记当前工具号 | 发 `M61 Qn`，不做机械换刀移动。 |
| `btn_change_tool` | Glade | 对工具表选中工具执行换刀 | 发 `Tn M6`。 |
| `btn_tool_touchoff_x` | Glade | 当前主轴工具 X 方向 Touch Off | 发 `G10 L10 P<tool> X<value>`，必要时恢复 `G43`。 |
| `btn_tool_touchoff_z` | Glade | 当前主轴工具 Z 方向 Touch Off | 发 `G10 L10 P<tool> Z<value>`，必要时恢复 `G43`。 |
| `btn_back_tool` | Glade | 退出工具页 | 如工具表未保存会询问。 |
| ToolEdit `delete` | ToolEdit 内部按钮，gmoccapy 重连回调 | 删除选中工具 | 当前主轴工具不能删除；修改后标记工具表已编辑。 |
| ToolEdit `add` | ToolEdit 内部按钮，gmoccapy 重连回调 | 添加新工具行 | 标记工具表已编辑。 |
| ToolEdit `reload` | ToolEdit 内部按钮，gmoccapy 重连回调 | 从文件重载工具表 | 有未保存修改时先询问。 |
| ToolEdit `apply` | ToolEdit 内部按钮，gmoccapy 重连回调 | 保存工具表到文件 | 清除 edited 标记。 |
| ToolEdit `Calculator` | ToolEdit 动态追加 | 用计算器编辑工具表数值 | 只辅助输入，编辑后标记工具表已编辑。 |

### 11.10 文件选择页按钮

| 按钮 | 回调 | 功能 |
| --- | --- | --- |
| `btn_home` | `on_btn_home_clicked` | 让 `IconFileSelection1` 跳到 home 目录。 |
| `btn_jump_to` | `on_btn_jump_to_clicked` | 跳到设置里保存的用户目录。 |
| `btn_dir_up` | `on_btn_dir_up_clicked` | 上一级目录。 |
| `btn_reload_dir` | `on_btn_refresh_dir_clicked` | 刷新当前目录文件列表。 |
| `btn_sel_prev` | `on_btn_sel_prev_clicked` | 选择上一个文件。 |
| `btn_sel_next` | `on_btn_sel_next_clicked` | 选择下一个文件。 |
| `tbtn_sort` | `on_tbtn_sort_toggled` | 按日期排序/恢复普通排序。 |
| `btn_select` | `on_btn_select_clicked` | 选择当前高亮文件；后续 `on_IconFileSelection1_selected` 加载。 |
| `btn_back_file_load` | `on_btn_back_clicked` | 退出文件选择页，不返回文件。 |

### 11.11 主轴、冷却、覆盖率按钮

| 按钮 | 回调 | 功能 | 后台命令/影响 |
| --- | --- | --- | --- |
| `btn_feed_100` | `on_btn_feed_100_clicked` | 进给倍率回 100% | 设置 feed slider，随后 `command.feedrate(1.0)`。 |
| `tbtn_flood` | `on_tbtn_flood_toggled` | 冷却液 flood 开/关 | `command.flood(FLOOD_ON/OFF)`。 |
| `tbtn_mist` | `on_tbtn_mist_toggled` | 雾冷 mist 开/关 | `command.mist(MIST_ON/OFF)`。 |
| `btn_spindle_100` | `on_btn_spindle_100_clicked` | 主轴倍率回 100% | 设置 spindle override slider。 |
| `rbt_reverse` | `on_rbt_reverse_clicked/released` | 主轴反转 | `_set_spindle("reverse")` 后 `command.spindle(-1, rpm)`。 |
| `rbt_stop` | `on_rbt_stop_clicked` | 主轴停止 | `command.spindle(0)`。 |
| `rbt_forward` | `on_rbt_forward_clicked/released` | 主轴正转 | `_set_spindle("forward")` 后 `command.spindle(1, rpm)`。 |

### 11.12 系统/诊断按钮

| 按钮 | 回调 | 功能 |
| --- | --- | --- |
| `btn_classicladder` | `on_btn_classicladder_clicked` | 如果 `classicladder_rt` 存在，启动 ClassicLadder；否则弹提示。 |
| `btn_hal_scope` | `on_btn_hal_scope_clicked` | 启动 `halscope`。 |
| `btn_status` | `on_btn_status_clicked` | 启动 `linuxcnctop`。 |
| `btn_hal_meter` | `on_btn_hal_meter_clicked` | 启动 `halmeter`。 |
| `btn_calibration` | `on_btn_calibration_clicked` | 启动 `emccalib.tcl -- -ini <ini>`。 |
| `btn_show_hal` | `on_btn_show_hal_clicked` | 启动 `halshow.tcl`。 |

这些按钮通过 `os.popen(...)` 启动外部诊断/调试工具，不直接发运动命令。

### 11.13 设置页按钮和可点控件

| 控件 | 回调 | 功能 |
| --- | --- | --- |
| `rbtn_fullscreen` | `on_rbtn_fullscreen_toggled` | 设置启动/当前窗口为全屏。 |
| `rbtn_maximized` | `on_rbtn_maximized_toggled` | 设置启动/当前窗口为最大化，并联动标题栏选项。 |
| `rbtn_window` | `on_rbtn_window_toggled` | 设置窗口模式，允许调整位置和大小。 |
| `chkbtn_hide_titlebar` | `on_chkbtn_hide_titlebar_toggled` | 隐藏/显示窗口标题栏。 |
| `chk_hide_cursor` | `on_chk_hide_cursor_toggled` | 隐藏/显示鼠标光标。 |
| `chk_hide_tooltips` | `on_chk_hide_tooltips_toggled` | 全局隐藏/显示工具提示。 |
| `chk_turtle_jog` | `on_chk_turtle_jog_toggled` | 隐藏/显示 turtle jog 按钮。 |
| `rel_colorbutton` | `on_rel_colorbutton_color_set` | 设置 DRO 相对坐标颜色。 |
| `abs_colorbutton` | `on_abs_colorbutton_color_set` | 设置 DRO 绝对坐标颜色。 |
| `dtg_colorbutton` | `on_dtg_colorbutton_color_set` | 设置 DRO DTG 颜色。 |
| `homed_colorbtn` | `on_homed_colorbtn_color_set` | 设置已回零轴显示颜色。 |
| `unhomed_colorbtn` | `on_unhomed_colorbtn_color_set` | 设置未回零轴显示颜色。 |
| `chk_toggle_readout` | `on_chk_toggle_readout_toggled` | 允许点击 DRO 切换读数模式。 |
| `fontbutton_popup` | `on_fontbutton_popup_font_set` | 设置消息弹窗字体。 |
| `chk_use_frames` | `on_chk_use_frames_toggled` | 设置消息是否带边框。 |
| `btn_launch_test_message` | `on_btn_launch_test_message_pressed` | 生成一条测试消息。 |
| `fontbutton_gcodeview` | `on_fontbutton_gcodeview_font_set` | 设置 G-code 视图字体。 |
| `chk_font_monospace` | `on_chk_font_monospace_toggled` | 过滤/选择等宽字体。 |
| `chk_font_regular` | `on_chk_font_regular_toggled` | 过滤/选择 Regular/Medium 字体。 |
| `chk_en_audio` | `on_chk_en_audio_toggled` | 启用/关闭提示音。 |
| `audio_alert_chooser` | `on_change_sound` | 选择提示音文件。 |
| `audio_error_chooser` | `on_change_sound` | 选择错误提示音文件。 |
| `chk_use_kb_on_offset` | `on_chk_use_kb_on_offset_toggled` | Offset 页自动显示键盘。 |
| `chk_use_kb_on_tooledit` | `on_chk_use_kb_on_tooledit_toggled` | ToolEdit 页自动显示键盘。 |
| `chk_use_kb_on_mdi` | `on_chk_use_kb_on_mdi_toggled` | MDI 页自动显示键盘。 |
| `chk_use_kb_on_edit` | `on_chk_use_kb_on_edit_toggled` | 编辑页自动显示键盘。 |
| `chk_use_kb_on_file_selection` | `on_chk_use_kb_on_file_selection_toggled` | 文件选择页自动显示键盘。 |
| `chk_kbd_set_height` | `on_chk_kb_set_height_toggled` | 固定/取消固定虚拟键盘高度。 |
| `chk_kbd_set_width` | `on_chk_kb_set_width_toggled` | 固定/取消固定虚拟键盘宽度。 |
| `rbtn_no_run_from_line` | `on_rbtn_run_from_line_toggled` | 禁用 Auto 页从行运行按钮。 |
| `rbtn_run_from_line` | `on_rbtn_run_from_line_toggled` | 启用 Auto 页从行运行按钮。 |
| `chk_use_kb_shortcuts` | `on_chk_use_kb_shortcuts_toggled` | 启用/禁用 F1/F2/F3/F5/R/P/S 等键盘快捷键。 |
| `file_to_load_chooser` | `on_file_to_load_chooser_file_set` | 设置启动时自动加载的文件。 |
| `btn_use_current` | `on_btn_use_current_clicked` | 把当前已加载程序保存为启动自动加载文件。 |
| `btn_none` | `on_btn_none_clicked` | 清空启动自动加载文件。 |
| `jump_to_dir_chooser` | `on_jump_to_dir_chooser_file_set` | 设置文件选择页 Jump To 目录。 |
| `rbt_use_unlock` | `on_rbt_unlock_toggled` | 设置设置页使用密码解锁。 |
| `rbt_no_unlock` | `on_rbt_unlock_toggled` | 设置设置页不需要解锁。 |
| `rbt_hal_unlock` | `on_rbt_unlock_toggled` | 设置设置页由 HAL pin `unlock-settings` 解锁。 |
| `chk_use_tool_measurement` | `on_chk_use_tool_measurement_toggled` | 启用自动测刀，并设置相关 HAL pins；本配置缺少 `[TOOLSENSOR]`，默认不可用。 |
| `chk_reload_tool` | `on_chk_reload_tool_toggled` | 启动 GUI 时重新加载上次主轴工具和长度补偿。 |

### 11.14 硬件按钮映射

| HAL 输入 | 功能 |
| --- | --- |
| `gmoccapy.h-button.button-0..9` | 触发当前底部按钮栏对应位置的软件按钮。 |
| `gmoccapy.v-button.button-0..6` | 触发当前右侧按钮栏对应位置的软件按钮。 |

硬件按钮不固定绑定某个功能，而是按当前页面位置映射。若对应软件按钮不敏感，硬件输入会被忽略；若对应软件按钮是 ToggleButton/RadioButton/Button，会分别执行切换、选中并 pressed、或 clicked。

## 12. 关键源文件索引

- INI：
  - `/home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini`
- 启动脚本：
  - `/home/meswork/cnc_wams/linuxcnc/scripts/linuxcnc`
- 实际 gmoccapy 可执行文件：
  - `/home/meswork/cnc_wams/linuxcnc/bin/gmoccapy`
- gmoccapy Glade 界面定义：
  - `/home/meswork/cnc_wams/linuxcnc/share/gmoccapy/gmoccapy.glade`
- gmoccapy INI 读取：
  - `/home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/gmoccapy/getiniinfo.py`
- gmoccapy 对话框：
  - `/home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/gmoccapy/dialogs.py`
- gladevcp HAL pin 生成：
  - `/home/meswork/cnc_wams/linuxcnc/lib/python/gladevcp/makepins.py`
  - `/home/meswork/cnc_wams/linuxcnc/lib/python/common/hal_glib.py`
- gladevcp LinuxCNC Action 和 MDI/File 控件：
  - `/home/meswork/cnc_wams/linuxcnc/lib/python/gladevcp/hal_actions.py`
  - `/home/meswork/cnc_wams/linuxcnc/lib/python/gladevcp/hal_filechooser.py`
  - `/home/meswork/cnc_wams/linuxcnc/lib/python/gladevcp/hal_mdihistory.py`
- LinuxCNC task/NML 主循环：
  - `/home/meswork/cnc_wams/linuxcnc/src/emc/task/emctaskmain.cc`
- HAL：
  - `/home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/core_sim_XYZAB.hal`
  - `/home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/spindle_sim.hal`
  - `/home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/simulated_home.hal`
  - `/home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/gmoccapy_postgui.hal`

## 13. 按钮图标整理索引

本配置的 gmoccapy 图标主题来自
`/home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.pref`
中的 `icon_theme = classic`。界面按钮图标不是全部直接写在
`gmoccapy.glade` 中，运行时还会由
`/home/meswork/cnc_wams/linuxcnc/bin/gmoccapy` 的 `_set_icon_theme()`
按 `Gtk.IconTheme` 重新绑定到 `classic` 主题图标。

图标整理结果已单独保存到：

- `/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.md`
- `/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/button_icon_inventory.csv`
- `/home/meswork/cnc_wams/work/working3/gmoccapy_button_icons/files/`

清单覆盖 Glade 静态按钮、Python 动态生成按钮、运行时 active/inactive 状态图标、
ToolEdit/OffsetPage 内嵌按钮，以及 `[MACROS]` 中宏按钮的自定义图片。当前整理结果
共 211 行，复制实际图标文件 112 个；其中 `i_am_lost` 和 `go_to_position`
宏按钮使用 NGC 文件内 `(IMAGE, ...)` 声明的 PNG 图片，其余无图标宏按钮使用文字标签。
