tools/epro2/kicad: hierarchical export + global_label + 5-Voltage power ports

Three coupled changes so kicad-cli sch erc runs at the project level
(across all sheets of one schematic) instead of single-sheet:

1. (label) → (global_label (shape passive)). EPRO2 nets are
   project-global by construction (named rails span every page in the
   SCH and physically wire across PCBs); KiCad's local label is sheet-
   scoped and triggers `label_dangling` for any name not duplicated on
   the same page.

2. New root_sch_writer that groups SCH_PAGE docs by their parent SCH
   (META.schematic), emits one root .kicad_sch per group with one
   (sheet ...) entry per child, and threads the root-assigned uuid back
   into each child's (sheet_instances) so KiCad can bind them.
   --all-sch now defaults to this; --flat falls back to one-file-per-page.

3. EPRO2's "5-Voltage" placeholder COMPONENT (partId
   pid8a0e77bacb214e, 365 instances on ESP-VoCat) is the editor's power
   port. The rail name lives in the placement's `Global Net Name` ATTR,
   not in the PART. We now emit a (global_label "<rail>") at the
   placement coords whenever that attr is set (101/365 of them on
   ESP-VoCat — the rest are unconfigured drafts).

ESP-VoCat 5 hierarchical roots: 2325 → 2265 violations. Modest because
5 of 6 SCHs are single-page (no cross-sheet nets to resolve), and the
one 4-page schematic (CoreBoard) shares only a handful of names across
sheets — most net names are de-facto sheet-local. The remaining ~190
pin_not_connected are dominated by 0402-style passives whose pin tip
lies on a wire's interior, not at an endpoint; KiCad needs an explicit
(junction) at those points and we don't yet emit one. Marked as the
next follow-up in log.md.

47 → 52 unit tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-29 00:05:47 +08:00
parent 54f0173947
commit ff5553fb06
6 changed files with 479 additions and 33 deletions

57
log.md
View File

@@ -4,6 +4,63 @@
---
## 2026-04-29 00:30 KiCad 导出 Phase 3 hierarchicalroot + global_label + 5-Voltage 电源端口
**Claude 会话**
`54f0173`。Handoff #3 多 sheet hierarchical`kicad-cli sch erc` 在 project 视角而不是单 sheet 视角下校验,理论上能把 single-sheet ERC 看不见跨 sheet 的 248+111 残留压下去。
### 三件事一起做
1. **`(label)``(global_label)`**`tools/epro2/kicad/sch_writer.py`
EPRO2 的 NET 是项目全局——一个 GND 名字横跨整个 schematic 而且通过 PCB 走线到隔壁板子。KiCad 的 `(label)` 是 sheet-scoped单 sheet ERC 看到一个名字只出现一次就报 dangling`(global_label)` 才是项目级hierarchical ERC 在 root 上能跨 sheet 解。
2. **`tools/epro2/kicad/root_sch_writer.py` 新模块**
按 EPRO2 的 `SCH_PAGE.META.schematic` 把页分组,每组 emit 一个 root `.kicad_sch`,里面 N 个 `(sheet ...)` 引子页。子页的 `(sheet_instances (path "/<assigned_uuid>" ...))` 必须回引 root 给它分配的 uuid——少了这一步 ERC 把子页当孤岛走。
3. **5-Voltage 电源端口识别**`sch_writer.py` COMPONENT 循环里加判断)
实测 ESP-VoCat 有 365 个 partId=`pid8a0e77bacb214e` 的 COMPONENT——挖了下发现这是 EPRO2 内部的 "Voltage" 占位符号,对应 KiCad 的 `(power_symbol)`。**网络名不在 PART 里,而是 placement 自己的 `Global Net Name` ATTR**101 个有名字、264 个还是空的草稿态)。每次有名字就在 placement 位置 emit 一个 `(global_label)`
### CLI 改造
`uv run python -m tools.epro2.kicad <project> --all-sch` 现在默认 hierarchical 输出:每个 SCH 一个目录,里面 root + 子页。`--flat` 兜回老行为(一张图一个文件)。`DELETE_DOC.isDelete=True` 的 SCH 直接跳——LCD-BD 那个被删了的没生成。
### ESP-VoCat 实测
`kicad-cli sch erc <each-root>` 跨 5 个 root 累加:
| Type | flat baseline | hier 后 | Δ |
|---|---:|---:|---:|
| wire_dangling | 52 | 52 | 0 |
| pin_not_connected | 196 | 190 | -6 |
| label_dangling → global_label_dangling | 111 | 105 | -6 |
| pin_not_driven | 23 | 21 | -2 |
| endpoint_off_grid | 1372 | 1340 | -32 (LCD-BD 移除带走的) |
| lib_symbol_issues | 571 | 557 | -14 (同上) |
| **TOTAL** | **2325** | **2265** | **-60** |
**修得不够多——为什么**EPRO2 的 6 个 SCH 里 5 个只有 1 pagehierarchical 对它们没用;只有 CoreBoard 是真 4-page 多 sheet。CoreBoard 自己里面也只有 GND / MCU_3V3 / VCC_3V3 是真跨 sheet 共享的网其它GPIO4, CHIP_PU, AUDIO_I2C_*, I2S_*)都是 sheet-local——名字虽然 unique 但只在一个 sheet 上有 wire 引用hierarchical ERC 也救不了,依然 dangling。
**剩下 190 PNC 的真原因**:抽样发现 C44/C45/R19/R20 这类 0402 元件wire 从一个 pin 出来直直穿过另一个 pin但 pin 落在 wire **中段**而不是端点。KiCad ERC 要求 pin 落在 wire 端点或 explicit junction 处才认连接wire 中段穿过的 pin 不自动连。EPRO2 源里这种连接合法但导出时丢了——要修需要做 wire-pin 几何相交,在中段 pin 位置 split wire 或 emit `(junction)`。下一轮再做。
### 决策Why
- **不走单 root 包全部 9 page**6 块 PCB 是物理独立板子merge 进一个 root 会把 BaseBoard 的 GND 和 CoreBoard 的 GND 误判成同一个网。EPRO2 已经按 SCH 分好组,按 SCH 拆 root 是结构对齐做法。
- **`(global_label (shape passive))`**不知道方向input/output/bidirectional 都会触发更多 ERC 检查(如 pin_not_drivenpassive 最保守。power 网用 power_in 才理想,但需要 driver 元件,超出本轮范围。
- **保留占位符号 placement 同时再 emit global_label**5-Voltage 占位符在 KiCad 里画出来虽然冗余但不影响 ERC删了反而丢视觉信息。
- **不实现自动 junction**geometry 计算成本明显高于本轮收益(<10 PNC 的预期降幅),做成下一轮独立改动更清晰。
### 测试
47 → 52 单测全过root_sch_writer 3 个 + power-port label 2 个 + sheet_path/page_num propagate 1 个 - test_named_wire 改 global_label 重写 1 个。
### 下一步建议
- **wire-pin junction emission**(中等工作量):算每个 COMPONENT 的 abs pin 位置,对每条 wire 查"非端点 pin 命中",命中就 split wire 或 emit junction。预期把 PNC 从 190 再砍一半左右。
- 或者直接进 **Phase 3 真正的 .kicad_pcb 导出**——schematic 这边已经够用PCB 才是 Forge 投影最后一块。
---
## 2026-04-28 23:55 KiCad 导出修真实连接错wire_dangling -88%, pin_not_connected -52%
**Claude 会话**