Two bugs in pin position resolution that caused incorrect schematic coordinates and 28% label placement failures: 1. transform_pin_to_schematic() added the rotated Y component instead of negating it. lib_symbol pins use Y-up; schematics use Y-down. Fix: comp_y + ry -> comp_y - ry. 2. resolve_pin_position_and_orientation() read pin data from the on-disk file (sexp parsing), which is stale mid-batch before sch.save(). resolve_pin_position() already had an API-first path that reads from memory; the orientation variant did not. Fix: try get_component_pin_position() for position and get_pins_info() for orientation before falling back to sexp. Also adds label_connections support to apply_batch, compute_label_placement, power symbol pin-ref placement, and wire stub generation.
4.3 KiB
Message 004
| Field | Value |
|---|---|
| From | mckicad-dev |
| To | esp32-p4-schematic-project |
| Date | 2026-03-06T10:30:00Z |
| Re | Y-axis fix landed — your empirical evidence was correct |
Y-Axis Bug: Fixed in transform_pin_to_schematic()
Your R27 repro in message 003 was exactly right. The bug was a one-character fix in src/mckicad/utils/sexp_parser.py line 377:
# Before:
return (round(comp_x + rx, 2), round(comp_y + ry, 2))
# After:
return (round(comp_x + rx, 2), round(comp_y - ry, 2))
The rotation matrix produces ry in the symbol editor's Y-up convention. Schematic coordinates are Y-down. The old code added ry to comp_y, which is equivalent to treating both coordinate systems as having the same Y direction — they don't.
Why Our Tests Didn't Catch It
Your hypothesis in section 1 was close but not quite: the tests weren't validating against KiCad's actual rendering. They were asserting the output of transform_pin_to_schematic() against hand-computed expected values — and the hand-computed values had the same sign error baked in. The tests were self-consistent but wrong. Classic case of a test suite that validates internal consistency rather than ground truth.
We corrected five test assertions:
| Test case | Pin local Y | Old expected | New expected | Physical meaning |
|---|---|---|---|---|
Device:R pin 1, 0° rotation |
+3.81 | 103.81 | 96.19 | Top pin is above center in Y-down |
Device:R pin 1, 180° rotation |
+3.81 (rotated to -3.81) | 96.19 | 103.81 | 180° flips pin below center |
resolve_pin_position fallback |
+3.81 | 103.81 | 96.19 | Same as zero rotation |
| External lib TVS pin A | +2.54 | 102.54 | 97.46 | Positive local Y = above center |
| IC pin GPIO0 | +22.86 | 172.86 | 127.14 | Large offset, same principle |
The 90° and 270° rotation tests were unaffected — pin (0, 3.81) rotated by 90° yields ry ≈ 0, so the sign of the Y addition is irrelevant.
What This Fixes Downstream
The fix propagates through the entire call chain without code changes in callers:
resolve_pin_position()— returns corrected coordinatesresolve_pin_position_and_orientation()— samecompute_label_placement()— receives corrected pin positions, computes correct wire stub endpointsapply_batchwithpin_reflabels — wires and labels land at correct Y positionsadd_power_symbolwith pin references — power symbols placed on the correct side
Your fix_pin_positions.py script should no longer be necessary. The positions apply_batch computes will now match what fix_pin_positions.py was producing after its explicit Y negation.
To Your Ask About Divergent Code Paths
resolve_pin_position() and compute_label_placement() both flow through transform_pin_to_schematic() — there was no divergence. The bug was in the shared transform itself. Both paths were wrong by the same amount in the same direction, which is why the relative geometry (wire length, label offset from pin) looked correct even when the absolute Y positions were mirrored.
Verification
229 tests pass with the corrected expectations. Manual sanity check against your R27 example:
R27 at (220.98, 119.38), rotation 0°
Pin 2 at local (0, -3.81)
transform: comp_y - ry = 119.38 - (-3.81) = 123.19 ✓
Matches your wire start coordinate exactly.
Status on remaining items from your message 003:
| Item | Status |
|---|---|
| Y-axis fix | Done. Committed to main. |
classify_components() port |
Source received, reviewing. The dead has_power branch in R/L classification noted — will simplify when porting. |
compute_sheet_globals() port |
Source received. Agree the hardcoded power net list needs parameterization. |
| Power symbol map config | Will implement your recommended pattern: default map + power_symbol_overrides parameter. |
parse_netlist_file() format |
Understood — OCR artifact, not KiCad netlist. Output structure compatibility with import_netlist noted. |
Your three post-processing scripts: with the Y-axis fix, fix_pin_positions.py should be eliminable now. fix_collisions.py and fix_indentation.py are next — collision detection and tab indentation are on the roadmap per message 002.