54f0173947
tools/epro2/kicad: fix two structural ERC bugs — wire_dangling -88%, pin_not_connected -52%
...
Bisect found two semantics mismatches between EPRO2 and KiCad that cause
the 850 real-connectivity ERC violations on the ESP-VoCat ref project:
1. sym_writer was emitting lib coords without negating Y, but KiCad lib
uses Y-up and re-flips Y on placement (Y-down schematic). So vertically
arranged pins ended up at Y-mirrored absolute positions and wires that
reach the geometric pin tip in EPRO2 missed the rendered pin tip in
KiCad. Fix: lib_y = -epro2_y, lib_rot = (360 - rot) % 360 for pin/text.
2. sch_writer was treating each LINE as an isolated wire — but EPRO2
binds segments into nets by NAME (WIRE.NET attr), not just geometry.
Multi-segment nets like GND/VBUS show up as N disconnected stubs to
KiCad. Fix: per-LINE, look up lineGroup → WIRE → NET attr and emit a
`(label "<NET>")` at the LINE's start. Same-named labels on distinct
physical wires is how KiCad's ERC recognizes a multi-segment net.
ESP-VoCat 9 sheets:
wire_dangling 444 → 52 (-88%)
pin_not_connected 406 → 196 (-52%)
real connectivity total 850 → 248 (-71%)
Why we did NOT round to grid (the obvious-looking fix): EPRO2 places
some pins on a 10-mil pitch (e.g. magnetic socket); rounding to KiCad's
default 50-mil ERC grid would collapse those pins. The 248 residual is
fundamentally cross-sheet — single-sheet ERC can't see a net's other
endpoints on sibling sheets — and is a Phase-3 (hierarchical sheet)
problem, not a per-sheet one.
41 → 46 unit tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-28 23:43:11 +08:00
fb577cc89f
tools/epro2/kicad: fix two KiCad 8 parse blockers (newline + pin_numbers)
...
装 kicad 8.0.9 (apt PPA) 后跑 kicad-cli sch erc 校验我们 emit 的
.kicad_sch 文件,发现 9/9 sheets 一开始全部报 "Failed to load schematic
file" — 父节点解析就挂掉。Bisect 找到两个语法 bug:
1. **(pin_numbers (hide no)) 不被 KiCad 8 接受**
KiCad 8 lib_symbols 里 `pin_numbers` 是 token-form,不接受 (hide
yes/no) 子块。要么省略整个 block 默认 visible,要么 `(pin_numbers
hide)` 表示隐藏。原来的 `(hide no)` 风格是 KiCad 7 旧语法。
Fix: tools/epro2/kicad/sym_writer.py 删掉 (pin_numbers (hide no))
行;KiCad 默认 visible 行为正是我们想要的。
2. **String 里的字面 \n / \r / \t 让 KiCad 解析器中止**
ESP-VoCat 的 Overview sheet 有 TEXT "Battary\n3.7V 700mAH"(多行
电池标签),EPRO2 里以**字面 0x0a 字符**存储。我们把它原样 emit
成 "..." 包住的字符串 → KiCad reader 在 quoted string 内遇到 \n
就报 parse error 不给 message。
Fix: tools/epro2/kicad/sexpr.py 在 str escape 路径加 \n / \r / \t
转义;reader 加 \r 解码(roundtrip 用)。
修完后:
9/9 sheets parse OK in KiCad 8.0.9
ERC 跑通,9 个 sheet 共 2793 violations,分布:
1372 endpoint_off_grid (49%, cosmetic — 30-mil EPRO2 grid 不
snap KiCad 默认 50-mil grid)
571 lib_symbol_issues (20%, cosmetic — facere 库未注册到
user library table;库已 embed 在
.kicad_sch 内联可用)
444 wire_dangling (16%, real — wire 端点没精确对齐 pin)
406 pin_not_connected (15%, 同上的另一面)
Cosmetic 占 70%,real connectivity 30%,下个 phase 处理:
- grid 校准(把 coord 精确 round 到统一 grid 上)
- pin tip 端点匹配(KiCad 需要 wire 端点 == pin (at) 字段对应的
绝对坐标,浮点必须精确相等)
- 生成 sym-lib-table 注册 facere 库(消 lib_symbol_issues)
测试:
+ test_string_escapes_newlines_and_tabs
+ test_lib_symbol_omits_pin_numbers_block
reader 加 \r 解码
41/41 通过(39 旧 + 2 新)。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-28 23:04:58 +08:00
8a91ce43f4
tools/epro2/kicad: Phase-2 lib_symbols — render symbol bodies from SYMBOL docs
...
Phase 1 emit的 .kicad_sch 里组件位置 + 属性都对,但 lib_symbols 是空
stub —— KiCad 渲染时每个组件显示成红色 "?"。Phase 2 把 SYMBOL 文档
里的 PART + RECT/POLY/CIRCLE/TEXT/PIN primitives 翻成 KiCad lib symbol
块,填到 lib_symbols 里,让 KiCad 显示真正的原理图符号。
新增 tools/epro2/kicad/sym_writer.py:
write_lib_symbol(symbol_doc) → S-expr list 形如:
(symbol "facere:<partId>"
(pin_numbers (hide no))
(pin_names (offset 1.016))
(in_bom yes) (on_board yes)
(property "Reference" "U" ...)
(property "Value" "<title>" ...)
(property "Footprint" "" hide)
(property "Datasheet" "" hide)
(symbol "<partId>_1_1"
(rectangle ...) ← from RECT.dotX1/Y1/dotX2/Y2
(polyline (pts ...)) ← from POLY.points + closed → fill
(circle ...) ← from CIRCLE.center/radius
(text "..." ...) ← from TEXT.value/x/y/rotation
(pin <type> line (at ...) (length ...) (name ...) (number ...))
← from PIN + sibling ATTR ops
))
PIN 名字/编号/电气类型解析(这是关键数据探测点):
EPRO2 PIN 不直接带 number/name/type 字段;这些信息存为独立 ATTR 操作
(parentId=<pin_id>, key="Pin Name"/"Pin Number"/"Pin Type")
Pin Type 取值映射:IN→input, OUT→output, BIDIR→bidirectional,
POWER_IN→power_in, POWER_OUT→power_out, NC→no_connect, ...
默认 passive(保守)
sch_writer 集成(lib_symbols 自动填):
write_sch_page(doc, project_relations=pr) — 增 pr 可选参数
内部 _build_lib_symbols(): 收集本 sheet 用到的 partIds → 通过
ProjectRelations.parts_by_id 解析到 SYMBOL 文档 → write_lib_symbol →
组装 (lib_symbols ...) 块;同 partId 多 SYMBOL 候选取第一个,去重
WriteStats 增 lib_symbols_embedded / lib_symbols_missing
CLI 加 --no-lib-symbols 用于回到 Phase-1 行为(占位符调试用)。
ESP-VoCat 重导出验证:9/9 SCH_PAGE 全部 0 lib_miss
P1_45092758.kicad_sch wires=187 symbols=138 lib_emb=29
codec_0b0163fa.kicad_sch wires=190 symbols=112 lib_emb=20
Interface_b336a7c7.kicad_sch symbols=95 lib_emb=13
...
P1_408c9f4f.kicad_sch wires= 6 symbols= 10 lib_emb= 3
测试:6 个新单测覆盖 outer wrapper / pin ATTR pull / 多形状 primitives /
sch_writer 集成路径 / 缺失 lib 计数 / no-pr 回退到 Phase 1。
合计 **39/39 通过**(parser 6 + relations 9 + project_relations 6 +
sexpr 6 + sch_writer 6 + sym_writer 6)。
下一步 Phase 3:footprint library + .kicad_pcb 导出。
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-28 22:45:19 +08:00
9213429a57
tools/epro2/kicad: Phase-1 EPRO2 → KiCad schematic exporter
...
写第一版 EPRO2 → .kicad_sch 转换:把 SCH_PAGE Document 的 wires +
COMPONENT placements + TEXT 输出到一个可被 KiCad 7+ 打开的 sch 文件。
不含 symbol 主体(lib_symbols 留空 stub),所以 KiCad 里组件会渲染
成红色 "?" 占位,但布线 + 位置 + Designator/Value 属性都正确。完整
symbol 库导出留 Phase 2。
模块结构:
tools/epro2/kicad/sexpr.py 手写 S-expr emitter,Sym 标记裸符号,
str 自动加引号 + 转义;float 去尾零;
bool→yes/no;NaN/Inf 主动报错
tools/epro2/kicad/_sexpr_reader.py 极简 S-expr parser,仅给 round-trip
测试用(非完整 KiCad reader)
tools/epro2/kicad/sch_writer.py write_sch_page(doc) → str;处理:
LINE → (wire (pts ...) ...)
COMPONENT → (symbol (lib_id facere:<partId>)
(at x y rot) (property Reference ...) ...)
TEXT → (text "..." (at ...))
单位 mil → mm × 0.0254;零长 wire 跳过
tools/epro2/kicad/__main__.py CLI: --doc <uuid> | --all-sch
ESP-VoCat 验证(python -m tools.epro2.kicad <project> --all-sch):
9 SCH_PAGE 全部转换成功
P1_408c9f4f.kicad_sch wires= 6 symbols= 10 text= 0 skipped= 2 (370 lines)
P1_ee409917.kicad_sch wires= 20 symbols= 14 text= 0 skipped= 3
P1_54743d77.kicad_sch wires= 42 symbols= 30 text= 3
Overview_dc13d6d2.kicad_sch wires= 0 symbols= 1 text= 34 (说明页)
MCU_510cff33.kicad_sch wires= 91 symbols= 86 text= 9
Interface_b336a7c7.kicad_sch wires= 99 symbols= 95 text= 6
P1_5c38f45b.kicad_sch wires=179 symbols= 86 text= 9
P1_45092758.kicad_sch wires=187 symbols=138 text= 10 (主图)
codec_0b0163fa.kicad_sch wires=190 symbols=112 text= 10
输出落在 data/processed/kicad_sch/<filename>.kicad_sch(gitignore 内,
可重新生成;不入库)。
测试:6 个 sexpr 测 + 6 个 sch_writer 测,含 round-trip parse 验证。
parser/relations/project_relations 的旧 21 个不动,合计 **33/33 通过**。
下一步:
1. Phase 2 — symbol library 导出 (.kicad_sym),把 SYMBOL doc 的 PIN/RECT/
TEXT primitives 转 KiCad symbol 主体;填 lib_symbols 块让组件渲染
出真正的 schematic 符号
2. footprint library + .kicad_pcb 导出
3. 用 KiCad CLI (kicad-cli sch erc) 跑 ERC 校验
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com >
2026-04-28 22:29:15 +08:00