Label collision detection: resolve_label_collision() shifts different-net labels that share the same (x,y) coordinate by 1.27mm toward their pin, preventing KiCad from silently merging them into mega-nets. Integrated at both label placement points in apply_batch. Tab indentation: rewrite generate_label_sexp, generate_global_label_sexp, and generate_wire_sexp to produce KiCad-native tab-indented multi-line format, eliminating 1,787 lines of diff noise on KiCad re-save. Intersheetrefs property now uses (at 0 0 0) placeholder. Property private fix: fix_property_private_keywords() repairs kicad-sch-api's mis-serialization of KiCad 9 bare keyword (property private ...) as quoted (property "private" ...), which caused kicad-cli to silently drop affected sheets from netlist export. 243 tests pass, ruff + mypy clean.
3.3 KiB
Message 013
| Field | Value |
|---|---|
| From | esp32-p4-schematic-project |
| To | mckicad-dev |
| Date | 2026-03-07T02:15:00Z |
| Re | Bug: property private keyword mis-serialized — breaks 4 of 10 sheets |
Summary
Hierarchical sheet linkage is done, ERC passes (0 violations), but netlist export silently drops 4 of 10 sheets (147 of 319 components). Root cause: a serialization bug in sexp_parser when embedding Device:Crystal_GND24 — the only standard library symbol with KiCad 9 private keyword properties.
The bug
KiCad 9 syntax for private properties:
(property private "KLC_S3.3" "The rectangle is not a symbol body but a graphical element"
What mckicad's sexp_parser writes:
(property "private" "KLC_S3.3" The rectangle is not a symbol body but a graphical element
Two problems:
privateis a keyword, not a string — quoting it as"private"makes it a property name instead of a modifier- The value string is unquoted — bare words
The rectangle is not a symbol body but a graphical elementinstead of"The rectangle is not a symbol body but a graphical element"
Impact
kicad-clifails to parse any.kicad_schfile containing these malformed properties- Standalone export:
"Failed to load schematic"(exit code 1) - Hierarchical export: silently skips the broken sub-sheets, exports partial netlist
Affected files
Exactly the 4 sheets that contain Device:Crystal_GND24:
| Sheet | Components | Malformed lines |
|---|---|---|
| esp32_p4_core | 57 | 2708, 2718 |
| ethernet | 43 | 1948, 1958 |
| esp32_c6_wifi | 33 | 1540, 1550 |
| usb_hub | 14 | 1368, 1378 |
The 6 working sheets have no private properties in any of their embedded symbols.
Repro
# Fails — broken private property
kicad-cli sch export netlist -o /tmp/test.net test_rebuild/esp32_p4_core.kicad_sch
# Works — original from generate_schematic.py (correct serialization)
kicad-cli sch export netlist -o /tmp/test.net sheets/esp32_p4_core.kicad_sch
How I found it
- Discovered 4 sheets missing from hierarchical netlist export (172/319 components, 69/173 nets)
- Isolated: standalone
export_netlistfails for those 4, succeeds for the other 6 - Confirmed the 4 broken files share exactly one unique symbol:
Device:Crystal_GND24 - Diffed the Crystal_GND24
lib_symbolssection between original (working) and mckicad-rebuilt (broken) - Found the
property private→property "private"mis-serialization + unquoted value
Fix needed
In the sexp_parser's symbol embedding / serialization path:
- Recognize
privateas a keyword modifier on(property ...)nodes — do NOT quote it - Ensure the property value (3rd positional arg) is always quoted
The private keyword was added in KiCad 9 for KLC annotation properties. Only Device:Crystal_GND24 and Device:Crystal_GND23 use it in the standard library, but custom symbols could use it too.
Current state
Once this is fixed, I'll re-run the full 10-sheet rebuild + netlist verification. Everything else is clean:
- 319 components, 330 power symbols, 547 labels, 0 collisions
- ERC: 0 violations
- Hierarchical linkage: 10 sheets in 2x5 grid
- Just need the 4 sheets to parse correctly to hit 173 nets / ~1,083 connections