Skip to content

fix(routing): turn rotated footprints' Specctra pins once - #847

Open
pauliuszaleckas wants to merge 1 commit into
mixelpixx:mainfrom
pauliuszaleckas:fix/840-pad-rotation
Open

pauliuszaleckas wants to merge 1 commit into
mixelpixx:mainfrom
pauliuszaleckas:fix/840-pad-rotation

Conversation

@pauliuszaleckas

Copy link
Copy Markdown
Contributor

Summary

export_specctra_dsn's Rust exporter, the default disable path, turned the pads of every rotated footprint twice. In a .kicad_pcb, a pad's (at x y angle) angle already includes its footprint's rotation. The exporter wrote that angle as the pin's (rotate ...), and the component's (place ... <rotation>) turned it again. So Freerouting routed around a 90° footprint's 600×500 µm pads as 500×600. The same (rotate ...) also broke adopt_native_dsn, because dsn_identity read the pin number at a fixed index. Any KiCad DSN with a rotated pad was refused with DSN is missing pin number.

Closes #840

Approach

  • Exporter. footprints() now stores a pad's angle relative to its footprint (pin_rotation), like its x_um/y_um. It is normalized to [0, 360) and kept to six significant digits, which is how KiCad's exporter prints it (%.6g, measured below). The writer still omits (rotate ...) when it is 0.
  • Native adoption. dsn_identity reads (pin <padstack> [(rotate <deg>)] <number> <x> <y>). correlate_components compares the pin rotation next to the existing x/y check, and refuses a mismatch with native DSN changed pin rotation for component '<ref>' pin '<n>'.

Departure from the issue. The issue's suggested direction is "pad angle minus footprint angle, normalized". Code review then pointed out that KiCad prints a pin's rotate with %.6g. I measured it on KiCad 10.0.6: a pad 12.3456789° inside its footprint is written (rotate 12.3457). Rounding to six decimals would write 12.345679, and native adoption would refuse that board. So pin_rotation keeps six significant digits. The same probe showed that KiCad writes (place ...) at full precision (33.3333333), so the placement comparison is unchanged.

Architectural fit

This fixes the fail-closed Rust exporter and the native correlation that #670 set up in specctra.rs. It adds no new module and no workaround.

Out of scope:

Branch and dependencies

  • Base: main @ 9488e5f, one commit. Depends on nothing.
  • This PR and fix(routing): export rounded-rectangle pads to Specctra #843 (roundrect pads) both edit specctra.rs (correlate_components, footprints(), the test module), pcb_export.rs (each adds a served test module at the end of the file) and docs/API_MIGRATIONS.md. Whichever merges second needs a mechanical rebase. I'll do it once when asked.

Compatibility and safety

There are no argument, response-field, manifest or schema changes. The DSN content changes for any board with a rotated footprint or an in-footprint pad rotation; the API_MIGRATIONS.md entry describes it. export_specctra_dsn writes only the DSN and its manifest, so no board or schematic is mutated.

Validation

Changed tool behavior

Behavior Contract and evidence for this change
Accepted inputs and declared defaults Unchanged. board, output and native_bridge_mode (default disable) are the same.
Invalid/unsupported inputs and structured errors Unchanged for the Rust export. Under require, a native DSN whose pin rotation differs from the Rust export's is refused with the existing semantic-validation error, now naming pin rotation. Under prefer, it falls back to the Rust export and reports it in the bridge diagnostics. native_dsn_that_turns_a_pin_is_refused covers this.
Target, data source and prerequisite state Unchanged. The tool reads the live editor's SaveDocumentToString snapshot of the requested board. The served test goes through McpHandler::handle_message against a mock KiCad holding the fixture.
Observed changes and preserved unrelated objects Each pin's (rotate ...) now equals KiCad's own DSN for the same board. This is checked pin by pin on 0°, 45° and 90° footprints, including in-footprint turns of 12.3456789°, 90° and −90° (rotated_footprint_pins_carry_the_rotation_kicad_writes and the served test). Placements, pin positions, padstacks, nets and rules are unchanged. KiCad's DSN of a rotated board is now adopted byte-for-byte (native_dsn_of_rotated_footprints_is_adopted).
Failure before/after mutation, including applied work N/A. The tool writes the DSN and manifest output files as before, and nothing in the design is mutated.
Recovery from partial/uncertain results without repeating applied work N/A. The export is read-only toward the design, so rerunning it is safe.

Fixtures and oracle. specctra_rotated_footprints{,_45}.kicad_pcb were built from specctra_two_resistors.kicad_pcb with KiCad 10.0.6 pcbnew Python and saved through pcbnew.SaveBoard. The oracle is KiCad's pcbnew.ExportSpecctraDSN of each saved board; only the root path atom was normalized. The script and the per-pin table are in specctra_rotated_footprints.README.md:

Ref Footprint Pad angles in the file KiCad's pin (rotate ...) Before this PR
R1 90° 102.3456789, 102.3456789 12.3457, 12.3457 102.3456789, 102.3456789
R2 0° 0, 0 none none
R3 (_45) 45° 45, 45 none 45, 45
R4 90° 180, 0 90, 270 180, none

Live KiCad. I ran pcbnew 10.0.6 under a headless Weston with IPC, opened a copy of specctra_rotated_footprints_45.kicad_pcb, and called export_specctra_dsn over stdio:

Server (rotate ...) written
Installed build (before) R1 102.3456789 ×2, R3 45 ×2, R4-1 180
This branch (target/debug/konnect) R1 12.3457 ×2, R4-1 90, R4-2 270, which is KiCad's DSN

The installed plugin has no native bridge, so I didn't run prefer/require live. Adoption is covered by the unit tests against KiCad's real DSN.

Negative controls. Each guard was neutered in turn, cargo test -p konnect-core --lib --tests --no-fail-fast was run, and the file was restored. A diff hash confirmed the tree was unchanged.

Guard neutered Failing tests
pin_rotation subtracts the footprint angle (- 0.0 * footprint_degrees) 4: rotated_footprint_pins_carry_the_rotation_kicad_writes, native_dsn_of_rotated_footprints_is_adopted, pin_rotation_drops_float_noise, served_export_turns_only_pins_turned_inside_their_footprint
Normalization into [0, 360) (rem_euclid removed) 3: rotated_footprint_pins_carry_the_rotation_kicad_writes, native_dsn_of_rotated_footprints_is_adopted, served_export_…
Six significant digits ({turn:.5e} → {turn}) 4: rotated_footprint_pins_carry_the_rotation_kicad_writes, native_dsn_of_rotated_footprints_is_adopted, pin_rotation_drops_float_noise, served_export_…
dsn_identity skips an optional (rotate ...) (false &&) 2: native_dsn_of_rotated_footprints_is_adopted, native_dsn_that_turns_a_pin_is_refused
correlate_components compares pin rotation (false &&) 1: native_dsn_that_turns_a_pin_is_refused

Local gate on head 2db04ed:

  • cargo fmt --all -- --check: clean
  • cargo clippy --workspace --locked --all-targets -- -D warnings: clean
  • cargo test -p konnect-core --locked reliability_contract: 2 passed
  • cargo test --workspace --locked --lib --tests: 2394 passed, 0 failed, 45 ignored
  • cargo test --workspace --locked --doc: 9 passed
  • cargo xtask fix-doc-counts --check: current
  • Live KiCad 10.0.6 export, as above. The schematic viewer, plugin and packaging are untouched.
  • Not run: the #[ignore]d Freerouting tests, which need Java and FREEROUTING_JAR.

Code review findings I did not apply:

Risk and rollback

The risk is low. The change is confined to Rust Specctra export and native correlation. Boards whose footprints are all at 0° with pad angles of at most six significant digits export byte-for-byte as before. Rolling back means reverting this commit, which restores the double rotation.

🤖 Generated with Claude Code

A .kicad_pcb pad angle already includes the footprint's, and the DSN
place turns the image again. Write a pin's rotate relative to its
footprint, as KiCad does (%.6g, [0, 360)). Read an optional rotate when
correlating a native DSN, and compare it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@neusse neusse added bug Something isn't working P1 High-value workflow reliability area:routing Traces, autorouting, Specctra/Freerouting status:waiting-on-review Next actor: maintainer status:waiting-on-dependency Next actor: the dependency owner — see linked blocking issue and removed status:waiting-on-review Next actor: maintainer labels Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:routing Traces, autorouting, Specctra/Freerouting bug Something isn't working P1 High-value workflow reliability status:waiting-on-dependency Next actor: the dependency owner — see linked blocking issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

export_specctra_dsn turns rotated footprints' pads twice: the pad's absolute angle is written as the pin's relative (rotate)

2 participants