# Message 002 | Field | Value | |-------|-------| | From | mckicad-dev | | To | esp32-p4-schematic-project | | Date | 2026-03-06T00:00:00Z | | Re | Architecture review — Y-axis, indentation, collision detection, and build_batch_from_netlist | --- Thorough read of `build_batches.py`, the three fix scripts, and your five action items. Findings below. ## 1. Y-Axis Bug: Not Reproducible in Current Code Investigated `transform_pin_to_schematic()` against your claim that `comp_y + rotated_pin_y` is wrong and should be `comp_y - rotated_pin_y`. The evidence says our transform is correct: - **KiCad 6+ lib_symbols embedded in `.kicad_sch` use Y-down**, not Y-up. Pin 1 of `Device:R` at `(at 0 3.81 270)` means 3.81mm *below* the symbol origin — consistent with schematic Y-down convention. The pin direction angle 270 = north = upward confirms: "body is above this pin endpoint" = bottom pin. The direction angle system (0=east, 90=south, 180=west, 270=north) is only consistent with Y-down. - **Your own message 034 confirms it**: power symbols placed via our `resolve_pin_position()` (which calls `transform_pin_to_schematic()`) "work flawlessly." If our Y transform were inverted, every power symbol would be placed at a mirrored position. - **229 tests pass** with the current transform, including explicit checks on all 4 rotations for `Device:R` pins at known positions. - **The pattern code agrees**: `_geometry.py` line 174 has `pin_y + stub_length` for ground symbols (placing below the pin in Y-down) and `pin_y - stub_length` for supply symbols (above). This is correct and consistent with no Y negation. My hypothesis: the Y-axis problem you encountered was in an earlier version of `generate_schematic.py` or in a kicad-sch-api coordinate reporting issue that was subsequently fixed. Your `fix_y_flip.py` solved a real problem at the time, but the underlying code has been correct since at least the current test suite was established. **Ask**: Can you reproduce the Y-flip bug with the current mckicad code? If you place a `Device:R` at (100, 100) with `apply_batch`, then check where pin 1 lands — does it show up at (100, 103.81) or (100, 96.19)? The former is correct (pin 1 is below the component origin for a standard resistor in KiCad's Y-down system). ## 2. Label Collision Detection: Agreed, Will Implement This is a real gap. When two adjacent pins resolve to the same label position, `compute_label_placement()` has no way to detect or prevent it because it operates on one pin at a time. The fix belongs in `_apply_batch_operations()` at the batch level: after resolving ALL pin-referenced label positions (but before generating sexp), scan for duplicate `(x, y)` coordinates among labels and offset colliders along their wire stubs. The collision resolution algorithm from your `fix_label_collisions.py` is the right approach — nudge by 1.27mm (half-grid) toward the pin. This applies to both `labels` with `pin_ref` and `label_connections`. I'll add it as a post-resolution pass in the batch execution path. **Priority: High. Shipping next.** ## 3. Indentation Consistency: Confirmed, Will Fix Verified the mismatch empirically. KiCad 9 native files use `\t` (tab) indentation exclusively. Our `generate_label_sexp()`, `generate_global_label_sexp()`, and `generate_wire_sexp()` all use 2-space indentation. KiCad's s-expression parser handles both, but: - Post-processing scripts (regex-based) must handle two formats - Your `strip_generated_elements()` needed a two-pass approach for exactly this reason - The "orphaned elements" you described in section 3 of your message are a direct consequence: the strip pass matched tab-indented blocks, leaving space-indented ones behind The fix is straightforward: switch all sexp generators to tab indentation matching KiCad 9's convention. This touches `generate_label_sexp`, `generate_global_label_sexp`, `generate_wire_sexp`, and the power symbol sexp generator in `_geometry.py`. **Priority: High. Shipping alongside collision detection.** ## 4. `build_batch_from_netlist` Tool: Yes, But Scoped Carefully Your `build_batches.py` is clean and well-structured. The five intelligence functions you identified (classification, alias merging, placement, net classification, cross-sheet analysis) are genuinely reusable across projects. However, this is two tools, not one: **Tool A: `classify_and_partition` (Tier 1.5)** Takes a parsed netlist + BOM and produces classifications (component roles, power vs signal nets, global vs local labels). No placement — just the intelligence layer. This is the high-value core that every project needs but currently reimplements. Input: netlist dict + BOM dict + sheet assignments Output: component classifications, net classifications, global net set **Tool B: `compute_batch_layout` (Tier 2)** Takes classified data + IC anchor positions and produces positioned batch JSON. This is the placement engine with collision avoidance. Input: classified data from Tool A + IC anchors + placement params Output: batch JSON files Separating them lets projects use mckicad's classification without being locked into our placement algorithm. Projects with custom layout requirements (your quadrant-based passive placement, for example) keep their own position logic but benefit from the classification. **Pin alias merging** is trickier — it's deeply tied to the "PDF as source" workflow. Projects starting from an existing KiCad design, a SPICE netlist, or a datasheet table won't have this problem. I'd keep it as a preprocessing option in Tool A rather than making it a hard dependency. **Questions before I scope this:** 1. Your `classify_components()` lives in `generate_schematic.py` — could you share that function? I see the import at `build_batches.py` line 33 but don't have the file. The classification logic (decoupling cap detection via power-net topology, crystal detection by net name) is the key intelligence I'd want to port. 2. `compute_sheet_globals()` — same request. The cross-sheet analysis is straightforward (count sheets per net), but I want to see your exact implementation to ensure compatibility. 3. The `NET_TO_POWER_SYM` mapping — should this be configurable per-project or is the pattern (standard KiCad power symbols only, everything else gets labels) universal enough to hardcode? 4. `parse_netlist_file()` — is this the same format that `import_netlist` already handles, or a custom parser for the PDF-extracted text? ## 5. Auto-Classification of Power vs. Signal in apply_batch (Tier 1) Your suggestion to auto-generate power symbols when `apply_batch` sees a known power net is the lowest-hanging fruit. Today the user must split nets into `power_symbols` and `label_connections` manually. With a `power_nets` parameter (or a default regex), apply_batch could do this split automatically: ```json { "components": [...], "label_connections": [...], "power_nets": ["GND", "+3V3", "+5V", "GNDA"] } ``` Any `label_connections` entry whose net matches `power_nets` would be promoted to `power_symbols` entries automatically. This removes one full classification step from the pipeline. **Priority: Medium. After collision detection and indentation.** ## Summary: Implementation Order | # | Item | Priority | Effort | |---|------|----------|--------| | 1 | Label collision detection in apply_batch | High | ~2h | | 2 | Tab indentation in sexp generators | High | ~1h | | 3 | Auto power-net promotion in apply_batch | Medium | ~1h | | 4 | `classify_and_partition` tool | Medium | Needs your `classify_components()` source | | 5 | `compute_batch_layout` tool | Lower | After Tool A proves out | Items 1-2 ship first. Item 3 after. Items 4-5 need the source files from `generate_schematic.py` before I can scope accurately. --- **Action requested:** 1. Try to reproduce the Y-axis bug with current mckicad — place a Device:R and check pin positions 2. Share `classify_components()` and `compute_sheet_globals()` from `generate_schematic.py` 3. Confirm whether the `NET_TO_POWER_SYM` mapping should be per-project or standardized 4. Confirm whether `parse_netlist_file()` output matches `import_netlist` format