﻿P10-08 刀路自检与预览（不依赖机床仿真）

## 用户需求（2026-10-20 09:25）
「这样直接出程序，我没法模拟验证刀路，再加工中没法看到实机的问题。」
用户已放弃机床仿真（无许可），需要**零成本的程序自检 + 肉眼可看的预览**。

## 一、机床行程基准（用户指定：850 型立加，最常见）
写入 `config\machine.json`（不存在则创建），默认值：
```json
{
  "name": "VMC850",
  "axis": {"X":[-420,420], "Y":[-260,260], "Z":[-500,100]},
  "travel": {"X":800, "Y":500, "Z":500},
  "tool_number_range": [1, 20],
  "note": "850 型立式加工中心常用行程 X800/Y500/Z500；坐标按工作台中心为原点估算，用户可改"
}
```
- 允许用户改这个文件；界面上「机床设置」可编辑行程与刀号范围。
- 刀号范围默认 T1–T20。

## 二、自检项（解析 NC 或 CLSF，纯 Python，不跑 NX）
输入：`A01_粗_*.nc` / `A02_半精_*.nc` / `A03_精_*.nc`（或 CLSF）。
输出：`状态\artifacts\check_<文件名>.json` + 控制台摘要。检查项：

1. **行程越界**：统计所有 X/Y/Z 最值，与 machine.json 比对；超出即红色告警，
   标出第一条越界的行号与坐标。
2. **空程占比**：G00 段长度 ÷ 总长度；同时用 F 值估算 G01 时间与 G00 时间。
   阈值：空程时间占比 >50% 告警（用户那个半精 308 分钟很可能就是这个问题）。
3. **刀号合法性**：程序头里的 T 号必须落在 T1–T20；出现 T0 或重复 T 号 → 致命告警。
4. **冷却与主轴**：每个刀段内必须出现 M08（或 M07）与 M03/M04；缺了告警。
5. **进给异常**：F 值 ≤0 或 > 10000 或单段切除量异常（用相邻段 Z 差与 XY 距离粗估）→ 告警。
6. **重复刀路检测**：把刀路按 5mm 网格离散，统计同一格点被切削的次数；
   被切 2 次以上的面积占比 >30% → 告警"疑似重复加工"。
7. **抬刀次数**：G00 Z 上升次数；相对加工面积明显过多 → 提示可能刀路碎。

## 三、预览图（PNG，肉眼可看）
用 matplotlib（本机已装）生成三联图，存 `状态\artifacts\preview_<阶段>.png`：
- 左：XY 俯视轨迹（G01 蓝色实线，G00 红色虚线）
- 中：XZ 侧视
- 右：Z 深度分布直方图（看层深是否均匀）
图上标注：行程边界框、总长、空程占比、最大 Z 深度。

## 四、接入工作台
`app\NXAIStart.cs` 新增按钮「⑩ 刀路自检」：
- 选中桌面 `NXAI产物` 里最新的 A01/A02/A03 三个 NC，
  逐个跑自检 → 日志区打印摘要表（阶段/行数/空程%/越界/告警数）
- 生成预览 PNG 后自动用默认看图程序打开
- 有致命告警（越界、T0、无冷却）时对话框明确列出，**不许只写"有警告"**

## 五、验收（必须实测）
1. 对现有三个 NC（A01/A02/A03）实跑，贴出 JSON 摘要与三个 PNG 路径
2. 贴出空程占比、时间估算、是否越界（用 850 行程）
3. 故意造一个越界程序（Z 改 -600）测试能告警，贴出告警原文
4. 故意把 T 号改成 T0 测试致命告警，贴出提示
5. 半精那 308 分钟：用自检给出"空程/切削"时间拆分，判断时间花在哪
6. 门禁全绿 + 新增 .py 含 `P2-02 控制台编码护栏`

## 约束
- 不改 NX 安装目录、cloud\、license.py、nx_post\
- 不伪造：所有统计来自真实解析
- 完成后写 `状态\P10-08.done.json`

## 追加：安全高度的准确定义（用户 2026-10-20 16:40）
「安全高度是，最高的平面再加 8mm」

### 定义（以此为准，覆盖之前模糊表述）
```
安全高度 Z = 毛坯/工件最高面的 Z 值 + 8.0 mm
（不是绝对 Z=8，也不是工件坐标系原点 +8）
```
- 毛坯最高面取值优先级：
  1. 若定义了毛坯几何（BLANK）→ 取毛坯包围盒最大 Z
  2. 否则取工序所选区域/体的最大 Z
  3. 都没有 → 报错"无法确定最高面，请先定义毛坯或选区域"，**不许默认用 8**
- 偏移量默认 8.0mm，可在模板/配置里改（1~30 范围，越界拒绝）
- 多工序同零件时：每道工序都按"该工序加工区域的最高面 + 8"，不是整个零件最高面
  （避免深腔工序从不必要的高处开始空跑）
- 日志与 template_check 输出必须写明实际计算值，例如：
  `安全高度 = 毛坯顶 Z=0.0 + 8.0 = Z 8.0`
  或 `安全高度 = 区域最高面 Z=-12.5 + 8.0 = Z -4.5`

### 校验（template_check.py）
- 不得只打印"8.0mm"这种孤立数字，必须打印"基准 Z + 偏移 = 结果"三段式
- 若模板没有几何无法算基准 → 明确输出 `安全高度 = 基准(未定义) + 8.0`，并在 --check 里判为"待使用时计算"（不算失败，但不能伪装成已算好）

### 与既有模板的关系
- 现有 11 个模板里"安全高度=8.0mm"的记录，一律改为"偏移 8.0mm（使用时按最高面计算）"
- 重新生成模板 .prt 时，把 clearance 设为"相对毛坯顶"模式（NX 里 Clearance → Plane → 选择"自动/从毛坯"）

### 验收
1. 贴出 template_check --list 里安全高度的新表述（三段式或"待使用时计算"）
2. 在一个有毛坯的零件上实测：贴出"毛坯顶 Z=X + 8.0 = Z Y"的计算日志
3. 构造"无毛坯且无区域"场景，贴出正确报错（不许默认 8）
4. 门禁全绿

## 硬性修正（用户 2026-10-20 16:45，覆盖上文所有"例外"条款）
**用户原话：「必须采用螺旋进刀。」**

### 规则（不允许任何例外）
- 所有铣类模板（TPL_FACE / TPL_SIDE / TPL_POCKET_R / TPL_POCKET_S / TPL_POCKET_F /
  TPL_ZLEVEL / TPL_HOLEMILL / TPL_SURFACE）的**进刀方式必须是 Helical（螺旋）**。
- 之前写的"面铣/侧面可从外部下刀用直线"的例外条款**作废**，不许再引用。
- `engage_linear_reason` 这个例外字段**删除**，不许用它绕过。

### 如果某工序类型不支持 Helical（例如 PLANAR_MILL / FACE_MILLING）
**不许**退化成 linear，正确做法是**换工种**：
```
平面铣(FACE/PLANAR) 不支持 Helical
  → 改用 CAVITY_MILL（型腔铣）处理同一平面区域，它支持螺旋进刀
  → 或用 ZLEVEL_PROFILE（深度轮廓）按 Z 层走
```
即：**为了满足"必须螺旋"，允许调整工序类型，不允许放弃螺旋。**

### 校验（判定标准，达不到即失败）
```
template_check --check <TPL>：
  从 .prt 读回 engage 类型
  出现 helical 字样 → 通过
  出现 linear/ramp/arc 且无螺旋 → 失败，exit 非 0，并打印实际读到的类型
```
`--check all` 必须**8 个铣类模板全部 helical**，否则 exit 非 0。

### 验收
1. `template_check --check all` → 8 个铣类模板逐个打印 `进刀=helical...`，exit=0
2. 独立进程读回 .prt 的实际 engage 类型（贴原文，不许只看配置）
3. 若某个模板因换工种导致参数变化（如 ap/ae），说明变化原因

## 追加：层间采用斜进刀（用户 2026-10-20 17:10）
「为了保证可以一刀流，每层之间采用斜进刀，减少跳刀。」

### 规则（覆盖之前"层间=直接"的表述）
```
步距间转移（Transfer between passes） : 直接（不抬刀）
层间转移（Transfer between levels）   : 斜进刀（Ramp / 螺旋过渡），**禁止垂直直接下切**
  斜坡角 Ramp Angle   = 1° ~ 3°（默认 2°）
  层间重叠 Overlap    = 0.5 ~ 1.0 mm（避免接刀台阶）
  斜坡最大长度        = 不超过 2×D
  抬刀                = 关闭（层间不抬刀）
```

### 为什么
- 垂直直接下切（Direct/Plunge）在封闭腔里等于"每层扎一刀"，伤刀、易崩刃、有撞刀风险
- 抬刀过渡 = 每层一次空程，刀路碎、效率低（用户要避免的就是这个）
- 斜进刀 = 沿着上一层已加工面斜向下切，连续、稳定、无空程 → 这才是一刀流

### 与进刀的关系（区分开，别混）
```
进刀 (Engage，切入工件的第一刀)      = 螺旋 Helical（P10-07G 要求，无例外）
层间过渡 (Between Levels，层与层之间) = 斜进刀 Ramp（本要求）
退刀 (Retract)                       = 最小 / 与进刀相同
```

### 校验（template_check.py，从 .prt 读回）
每个铣类模板必须能读出：
```
层间转移 = ramp（或 helical）
斜坡角   = 1~3°
层间重叠 = 0.5~1.0 mm
抬刀     = false
```
任一项不符 → exit 非 0，打印实际读到的值（禁止只打印配置值）。

### 插铣等特殊工序
TPL_PLUNGE（若启用）本身就是 Z 向直插，属于工艺需要，需在模板里显式标注
`plunge_mode=true`，但**其他所有铣类禁止直插**。

### 验收
1. `template_check --check all` → 每个铣类打印 `层间=ramp 角=2.0° 重叠=0.8mm 抬刀=false`
2. 独立进程从 .prt 读回上述四项（贴原文）
3. 与 P10-07G 的进刀螺旋一起，输出一张"进刀+层间"总表（8 个铣类模板）
4. 门禁全绿

## 追加：自检发现的真实缺陷（用户 2026-10-20 20:45 实测）
自检工具算出的时间暴露了严重问题：

```
A01_粗   F3055.8  G01段1127  切削 12.9 分钟   ← 铝件参数，正常
A02_半精 F299.2   G01段1796  切削 306 分钟    ← 进给只有粗的 1/10.2
A03_精   F299.2   G01段1801  切削 303 分钟    ← 同上
```

同一个零件、同一把 D12，**粗加工用铝件进给（Vc325→F3055），
半精/精却用了钢件进给（Vc90-130→F299）**。
后果：半精/精各要 5 小时，实际不可用。

### 根因
模板/三阶段生成时，半精与精的 S/F **没有按"当前零件材料"计算**，
而是取了模板里固化的钢件档参数。

### 要做的
1. 半精/精的 S/F 必须**按工序单里的材料族**算（与粗加工同一材料来源），
   不许用模板里的固定值。
2. 生成后**必须自检并断言**：
   - 同一零件的粗/半精/精，材料族必须一致
   - 若 F 值相差 >5 倍，判定为"参数错配"，自检要报 fatal（不是 warning）
3. `nc_check.py` 增加一项检查：`feed_consistency`
   ```
   读同一零件的多个 NC，比较 F 值中位数
   最大/最小 > 5 → fatal: "进给跨材料族错配（粗 F=x / 精 F=y）"
   ```
4. 重跑三阶段后，半精/精的 F 应落在铝件精加工档（约 F1000~2000），
   时间应从 306 分钟降到 30~60 分钟量级

### 验收
1. 贴出修复后三个 NC 的 F 值与切削时间（半精/精应显著下降）
2. `nc_check.py` 的 feed_consistency 检查：修复前报 fatal、修复后通过（贴两次输出）
3. 证明三阶段用同一材料族（贴工序单里的材料与三道工序的 Vc）
