Skip to content

feat(kicad9): snap 2-pin parts onto IC pins, post-route visual optimization#302

Open
lachlanfysh wants to merge 7 commits into
devbisme:developmentfrom
lachlanfysh:pr/kicad9-snap-v2
Open

feat(kicad9): snap 2-pin parts onto IC pins, post-route visual optimization#302
lachlanfysh wants to merge 7 commits into
devbisme:developmentfrom
lachlanfysh:pr/kicad9-snap-v2

Conversation

@lachlanfysh
Copy link
Copy Markdown

Summary

Post-route visual optimization for KiCad 9 schematic generation that snaps 2-pin parts (resistors, caps, LEDs) onto their connected IC pins, producing cleaner schematics with fewer floating labels.

Rebased onto development branch per review feedback on #297. Adopts the upstream hierarchical UUID path threading and 4-tuple return pattern from node_to_sexp_schematic.

Features (gen_schematic.py)

  • Snap placement: Moves 2-pin parts onto connected IC/multi-pin part pins after routing succeeds
  • Chain snapping: Iteratively snaps 2-pin parts onto already-snapped parts (e.g. IC ← R ← LED)
  • Perpendicular stacking: Parts sharing an occupied IC pin get placed perpendicular
  • T-junction staggering: Repeated patterns (e.g. input protection networks) get staggered outward from IC with connecting wires
  • IC pre-shifting: When multiple ICs have stagger fans, ICs are shifted apart to prevent overlap
  • Power cap offset: Decoupling caps get offset from IC power pins with explicit wire connections

Features (sexp_schematic.py)

  • Label suppression: Pins physically connected by snap placement don't get redundant labels
  • Power bus wires: Co-linear power net pins (3+) connected with wire segments
  • No-connect flags: NCNet pins get proper no_connect markers
  • Power-pin force labels: 2-pin parts with unStubbed power pins get labels when snap is active
  • Overlap detection: Union-find clustering identifies overlapping pin positions

Safety

  • All snap features guarded behind snap_ran detection — only activates when _snap_two_pin_parts actually modified the node
  • Non-snap circuits produce identical output to upstream development
  • Snap is called post-route only (never between place and route)
  • 95 tests pass (63 auto_stub + 32 upstream schematic tests)

Test plan

  • All 63 auto_stub tests pass (snap, power, stub, ERC)
  • All 32 upstream schematic tests pass (no regressions)
  • test_and_gate_erc_clean passes (0-2 fixable ERC errors)
  • Non-auto_stub circuits produce identical output to upstream

🤖 Generated with Claude Code

lfyysh and others added 2 commits May 22, 2026 09:20
Post-route visual optimization that moves resistors, caps, and LEDs
onto their connected IC pins for cleaner schematics. Includes T-junction
staggering for repeated patterns, power cap offset wires, power bus
wire generation, no-connect flags, and label suppression for wired pins.

All snap features are guarded behind snap_ran detection so they only
activate when _snap_two_pin_parts actually modified the node, preserving
identical behavior for circuits that don't use auto_stub or don't have
snap-eligible parts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Snap geometry functions (snap_two_pin_parts, stagger, pre-shift) are
backend-agnostic — they operate on Point/Tx/orientation strings, not
KiCad S-expressions. Moved to schematics/ per maintainer feedback.

KiCad-specific sexp output (wireable nets, power bus wires, no-connect
flags) stays in tools/kicad9/sexp_schematic.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@lachlanfysh
Copy link
Copy Markdown
Author

Re: architecture feedback from #297 — on review the snap geometry was already backend-agnostic (operates on Point/Tx/orientation strings, no S-expression references), so I've moved it to schematics/snap.py as suggested.

The split is now:

  • schematics/snap.pysnap_two_pin_parts(), T-junction staggering, IC pre-shift (pure geometry, reusable across backends)
  • tools/kicad9/sexp_schematic.py — wireable-net detection, power bus wires, no-connect flags (produces KiCad S-expressions)
  • tools/kicad9/gen_schematic.py — orchestration only (calls snap after route, passes node to sexp writer)

@devbisme
Copy link
Copy Markdown
Owner

Thanks for making the changes and rebasing onto the development branch. I do appreciate it! I've merged your code into a new branch called lachlanfysh-pr/kicad9-snap-v2. Please make any further changes against that branch.

@devbisme
Copy link
Copy Markdown
Owner

I'm going to close this PR. Eventually we'll merge code from your lachlanfysh-pr/kicad9-snap-v2 branch into development and then master when there's a new release.

@devbisme devbisme closed this May 24, 2026
lfyysh and others added 5 commits May 26, 2026 18:37
The snap-v2 refactor lost two features from the original snap-placement
branch:

1. Connectivity-aware fallback: when routing fails, try placement with
   real connectivity at increasing expansion factors before falling back
   to full label-only output. Calls snap_two_pin_parts() to wire close
   2-pin parts directly.

2. Label deconfliction: post-pass that detects global_label overlap with
   component bounding boxes and nudges labels along their direction axis.

MR-1 test: switch sheet wires 0→36 (was 63 on v1 branch — remaining
gap is power symbol rendering, not yet ported to v2).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…sing

Two fixes to match v1 branch output quality:

1. Power symbol rotation: rotate GND/supply symbols based on
   calc_pin_dir() so they point away from the connected component,
   matching KiCad convention. Previously all power symbols were at
   angle=0 regardless of pin direction.

2. Remove snap_ran guard: _find_wireable_nets, _gen_power_bus_wires,
   and _gen_no_connect_flags now run unconditionally instead of only
   when snap marker attributes are present. This matches v1 behavior
   where these always ran. Also removes snap_ran from the force-emit
   condition for power labels on 2-pin parts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…l wires

When a hierarchical subcircuit's routing fails (e.g. daisy_seed with 27
parts), the RoutingFailure propagated to the root and triggered the
full-circuit fallback — stubbing ALL nets across ALL subcircuits and
destroying wires in successfully-routed sheets like switch_input and
led_drivers.

Now each child's routing failure is caught independently: the failed
child's nets are stubbed and re-routed with labels, while all other
children keep their wired connections. This is the correct behavior —
one complex subcircuit should degrade gracefully, not poison the whole
schematic.

MR-1 result: switch_input wires 36→45, routing succeeds on normal path
instead of falling through to fallback. Only daisy_seed (the actual
failure) gets labels-only output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
High-fanout nets are now marked _deferred_stub instead of immediately
stubbed, preserving connectivity for placement grouping. After placement,
_apply_deferred_stubs() classifies each net per-subcircuit: short-distance
nets with few pins stay as wires, others get stubbed. A rescue pass
un-stubs auto-stubbed nets that ended up close together.

Also adds SIGALRM-based routing timeouts (child + top-level), progressive
stubbing fallback when routing fails, and KeyError handling for switchbox
creation edge cases.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reverting the removal of net.stub checks in get_internal_nets() and
get_internal_pins(). Without these checks, fully-stubbed nets (power,
classified-as-too-far) leak into the routing graph, causing the router
to attempt wires instead of clean labels — producing broken connections
and displaced labels.

Deferred-stub nets don't need this removal since _stub stays False
during placement; only _deferred_stub is set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@lachlanfysh
Copy link
Copy Markdown
Author

Note: I'm currently debugging regressions in the v2 branch (pr/kicad9-snap-v2) that weren't present in the original #297 implementation. The deferred stubbing and rescue mechanisms I added for better wire/label balance are causing issues with label placement and broken wire connections in the generated schematics. Working to understand the root cause and get the output quality back to the #297 baseline before resubmitting.

@lachlanfysh
Copy link
Copy Markdown
Author

Update: regressions resolved on pr/kicad9-snap-v3

Root cause identified: the sexp_schematic.py rewrite on the development branch (between #297 and #302) introduced two bugs:

  1. Label orientation swaporient_map had D↔U swapped ("D": 270, "U": 90 → should be "D": 90, "U": 270), displacing labels 17.78 units from pin endpoints
  2. write_top_schematic restructure — broke UUID path threading and label positioning pipeline

Fix approach on pr/kicad9-snap-v3:

  • Restored feat(kicad9): snap 2-pin parts onto IC pins, power bus wires, label deconfliction #297's working sexp_schematic.py output pipeline (correct orientations, label positioning)
  • Cherry-picked the UUID path threading fix from development (needed for hierarchical sheet references)
  • Added deferred stubbing for connectivity-aware placement (high-fanout nets stay connected during placement, then get stubbed as labels)
  • Added child-node net classification — only large chain structures (switch→R→IC grids, LED→driver chains) get router wires; smaller subcircuits use clean labels at pins

Output now exactly matches #297 quality across all 14 hierarchical sheets (verified wire/label counts per sheet). 63 tests pass.

Branch: pr/kicad9-snap-v3 — ready for review when convenient.

@devbisme devbisme reopened this May 28, 2026
@devbisme
Copy link
Copy Markdown
Owner

Which of my branches should I merge pr/kicad9-snap-v3 into?
master, development or lachlanfysh-pr/kicad9-snap-v2?

@lachlanfysh
Copy link
Copy Markdown
Author

`lachlanfysh-pr/kicad9-snap-v2` would be cleanest — v3 is based on `development` (same ancestor as v2) so it should merge without conflicts, and it supersedes everything on v2.

v3 is 2 commits on `development`:

  1. Snap placement + label suppression + UUID paths — the core feature from feat(kicad9): snap 2-pin parts onto IC pins, power bus wires, label deconfliction #297, fixed for the label orientation issue on `development`
  2. Deferred stubbing + chain-aware classification — additive improvement for smarter wire/label decisions (can be dropped independently if you'd prefer to review it separately)

Both are self-contained against `development`, so merging into `development` directly would also work if you'd prefer to skip the intermediate branch.

@devbisme
Copy link
Copy Markdown
Owner

I've merged your v3 into development and created a new branch called lachlanfysh. I'll test it soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants