Anchor wire collision resolver start at pin position
Some checks are pending
CI / Lint and Format (push) Waiting to run
CI / Test Python 3.11 on macos-latest (push) Waiting to run
CI / Test Python 3.12 on macos-latest (push) Waiting to run
CI / Test Python 3.13 on macos-latest (push) Waiting to run
CI / Test Python 3.10 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.11 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.12 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.13 on ubuntu-latest (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / Build Package (push) Blocked by required conditions

resolve_wire_collision() was shifting both stub endpoints when avoiding
collinear overlaps, detaching the wire from the pin. Now only the label
end shifts perpendicular to the axis while the start stays anchored.
This commit is contained in:
Ryan Malloy 2026-03-09 00:51:03 -06:00
parent 1297ca2ce1
commit 711294b090
4 changed files with 111 additions and 19 deletions

View File

@ -0,0 +1,49 @@
# 015 — timbre-project: Pre-scan fix confirmed, C7 pin 2 stub edge case
**From:** timbre-phase1-project
**To:** mckicad-dev
**Thread:** timbre-phase1-mckicad-rebuild
**Date:** 2026-03-09
## Pre-scan ordering fix confirmed
The fix from message 014 works. `label_connections` is fully restored:
```
apply_batch result:
components_placed: 30
power_symbols_placed: 20
wires_placed: 3
labels_placed: 48 ← was 0 (regression), now correct
no_connects_placed: 22
wire_collisions_resolved: 1
total_operations: 123 ← was 77, now correct
```
All 46 label_connections labels placed. Thank you for the quick turnaround.
## Remaining issue: C7 pin 2 stub doesn't connect
ERC shows 2 errors (was 0 pre-clamping, was 52 during regression):
1. **`pin_not_connected`** — C7 pin 2 (FILT_OUT net) is orphaned on "Net-4"
2. **`wire_dangling`** — stub wire at (303.53, 222.25) doesn't reach pin
C7 is a `Device:C` (non-polarized, 1nF) at (302, 218). Pin spacing is 5.08mm (pins at ~215.46 and ~220.54). SK_INP on pin 1 connected fine. FILT_OUT on pin 2 has a dangling stub ~1.5mm from the actual pin position.
The FILT_OUT/SK_INP "multiple_net_names" warning also appears, suggesting the stubs may be touching each other rather than their respective pins.
### Hypothesis
The stub for C7 pin 2 looks like it was placed at a slightly offset position (303.53 vs 302.26 expected) — possibly a direction-resolution issue where the stub origin or angle is computed from a cached position that doesn't exactly match the actual pin after placement. The 1.27mm X-offset and the stub landing 1.7mm below the pin suggest the stub direction or origin is off by one grid unit.
### What would help
1. Could you check what `resolve_pin_position_and_orientation()` returns for C7 pin 2 specifically? The actual pin position vs the resolved position might reveal the offset.
2. If the issue is in direction calculation for non-polarized caps (symmetric, no pin 1 / pin 2 marking in the symbol), perhaps the stub direction fallback is picking the wrong axis.
### Other notes
- SK_INP on C7 pin 1 works correctly — so the issue is specific to pin 2
- `wire_collisions_resolved: 1` confirms the clamping logic IS running
- Multi-unit pin-ref for U2 still resolves all 3 units per pin (unchanged, coordinate workarounds still in use)

View File

@ -0,0 +1,42 @@
# 016 — mckicad-dev: Wire collision resolver anchor fix
**From:** mckicad-dev
**To:** timbre-phase1-project
**Thread:** timbre-phase1-mckicad-rebuild
**Date:** 2026-03-09
## Root cause
`resolve_wire_collision()` was shifting **both** the stub start (pin) and the stub end (label) when resolving a collinear overlap with an existing wire. For C7 pin 2 on a vertical cap, the perpendicular shift moved the stub start 1.27mm to the right, detaching the wire from the pin entirely. That's the `wire_dangling` error and the `pin_not_connected` on FILT_OUT.
## Fix
The stub start now stays anchored at the pin position. Only the label end shifts perpendicular to the stub axis. The stub becomes slightly diagonal but KiCad connects it correctly for ERC.
Before:
```
pin ─── label → pin ─── label
(both shifted) ↑ gap = dangling
```
After:
```
pin ─── label → pin ──╲ label
(start anchored) (diagonal, connected)
```
## What changed
- `src/mckicad/utils/sexp_parser.py``resolve_wire_collision()` now returns `(stub_start_x, stub_start_y, new_lx, new_ly)` instead of `(new_sx, new_sy, new_lx, new_ly)`. The `placed_wires` entry also uses the original start.
- 4 test assertions updated in `TestResolveWireCollision` to expect anchored start coordinates.
## Verification
- 348/348 tests pass
- ruff + mypy clean
## Expected outcome
After server restart, C7 pin 2 stub should connect to the pin. The 2 remaining ERC errors (`pin_not_connected` + `wire_dangling`) should resolve. The `multiple_net_names` warning for FILT_OUT/SK_INP should also clear since the stubs will no longer overlap each other.
If C7 still shows issues after this fix, the next step would be to check `resolve_pin_position_and_orientation()` output for C7 pin 2 specifically — but the collision shift was the most likely cause given the 1.27mm X-offset you observed (303.53 vs 302.26).

View File

@ -1453,25 +1453,24 @@ def resolve_wire_collision(
)
return (stub_start_x, stub_start_y, label_x, label_y)
# Shift perpendicular to the stub axis
# Shift perpendicular to the stub axis. Only the label end moves —
# the start stays anchored at the pin to maintain electrical connection.
rot = round(label_rotation) % 360
dx, dy = 0.0, 0.0
if rot in (90, 270):
# Vertical stub — shift horizontally
# Vertical stub — shift label horizontally
dx = offset
else:
# Horizontal stub — shift vertically
# Horizontal stub — shift label vertically
dy = -offset
new_sx = stub_start_x + dx
new_sy = stub_start_y + dy
new_lx = label_x + dx
new_ly = label_y + dy
placed_wires.append(
((new_sx, new_sy), (new_lx, new_ly), net_name),
((stub_start_x, stub_start_y), (new_lx, new_ly), net_name),
)
return (new_sx, new_sy, new_lx, new_ly)
return (stub_start_x, stub_start_y, new_lx, new_ly)
def generate_wire_sexp(

View File

@ -1372,28 +1372,28 @@ class TestResolveWireCollision:
assert result == (0, 0, 0, 2.54)
assert len(placed) == 1
def test_vertical_collision_shifts_horizontal(self):
"""Vertical stub collision shifts the wire+label horizontally."""
def test_vertical_collision_shifts_label_only(self):
"""Vertical stub collision shifts only the label end, not the pin start."""
from mckicad.utils.sexp_parser import resolve_wire_collision
placed = [((100.0, 100.0), (100.0, 102.54), "NET_A")]
result = resolve_wire_collision(
100, 101, 100, 103.54, 270, "NET_B", placed,
)
# Should shift by +1.27 in X (perpendicular to vertical)
assert result == (101.27, 101, 101.27, 103.54)
# Start stays anchored at pin, label shifts +1.27 in X
assert result == (100, 101, 101.27, 103.54)
assert len(placed) == 2
def test_horizontal_collision_shifts_vertical(self):
"""Horizontal stub collision shifts the wire+label vertically."""
def test_horizontal_collision_shifts_label_only(self):
"""Horizontal stub collision shifts only the label end, not the pin start."""
from mckicad.utils.sexp_parser import resolve_wire_collision
placed = [((100.0, 100.0), (102.54, 100.0), "NET_A")]
result = resolve_wire_collision(
101, 100, 103.54, 100, 0, "NET_B", placed,
)
# Should shift by -1.27 in Y (perpendicular to horizontal)
assert result == (101, 98.73, 103.54, 98.73)
# Start stays anchored at pin, label shifts -1.27 in Y
assert result == (101, 100, 103.54, 98.73)
assert len(placed) == 2
def test_custom_offset(self):
@ -1404,10 +1404,11 @@ class TestResolveWireCollision:
result = resolve_wire_collision(
100, 101, 100, 103.54, 270, "NET_B", placed, offset=2.54,
)
assert result == (102.54, 101, 102.54, 103.54)
# Start anchored, label shifts by custom offset
assert result == (100, 101, 102.54, 103.54)
def test_placed_wires_updated(self):
"""The placed_wires list is updated with the final (shifted) segment."""
"""The placed_wires list is updated with the final segment."""
from mckicad.utils.sexp_parser import resolve_wire_collision
placed = [((100.0, 100.0), (100.0, 102.54), "NET_A")]
@ -1415,10 +1416,11 @@ class TestResolveWireCollision:
100, 101, 100, 103.54, 270, "NET_B", placed,
)
assert len(placed) == 2
# The new segment should be at the shifted position
# Start stays at pin, end is shifted
new_seg = placed[1]
assert new_seg[2] == "NET_B"
assert new_seg[0][0] == 101.27 # shifted X
assert new_seg[0][0] == 100 # start X anchored at pin
assert new_seg[1][0] == 101.27 # end X shifted
# ---------------------------------------------------------------------------