Rip every user of a shared via, and judge vias round at the outline

Two connections of a net can share a via. The grid kept one owner per via
cell next to the claim list, and the search and rip-up read only that owner:
a route of another net through the via ripped the first user and left the
second's via under the new trace. The exact check then threw one of them
out. The search and rip-up now read the claim list, and the single owner is
gone.

The exact check also tested a via against the outline as its square box,
whose corner reaches past a 45-degree chamfer where the round via does not.
The grid keeps the via's centre its radius plus the edge clearance from the
outline, as KiCad measures it; the exact check now does the same.

kitspace_minisumo_v3 seed 4: 4 rejected connections to 0, 11 failed to 7.
This commit is contained in:
2026-10-03 11:03:38 -06:00
parent ad10113b8b
commit 8f006fec25
4 changed files with 87 additions and 20 deletions
+58 -1
View File
@@ -143,11 +143,48 @@ def test_a_shared_via_keeps_its_footprint_when_one_user_is_ripped():
router._rip(first)
assert second.routed and via in second.via_cells
assert all(router._via_owner.get(c) == second.index for c in footprint)
assert all(router._via_claims.get(c) == [second.index] for c in footprint)
# another net still keeps its distance from the via that is left
assert all(router._enter_cost_base(c, 0, sig.net_no, False, conns) is None for c in footprint)
def test_a_route_through_a_shared_via_rips_every_user():
"""Two connections of a net share a via. Another net routed through it
in rip-up mode has to rip both, or the one left keeps its via under the
new trace and the exact check throws one of them out."""
dsn = parse_dsn(_SHARED_VIA)
s = dsn.resolution.value
router = GridRouter(
build_board(dsn),
trace_width=round(_rule_width_dsn(dsn) * s),
clearance=round(_rule_clearance_dsn(dsn) * s),
via_half=_via_half_board(dsn, s),
)
conns = router._conns = router._build_connections()
gnd = [c for c in conns if len({c.a.component_ref, c.b.component_ref} & {"G1", "G2", "G3"})]
sig = next(c for c in conns if c not in gnd)
first, second = gnd
g3 = router._cell_of(next(p for p in router.board.get_pins() if p.component_ref == "G3").location)
via = (g3[0] - 10, g3[1])
def through_via(conn):
a, b = router._cell_of(conn.a.location), router._cell_of(conn.b.location)
return _line(a, via, 0) + _line(via, b, 1)
router._commit(first, through_via(first))
router._commit(second, through_via(second))
# the via's copper alone: footprint cells no trace or trace halo covers
crossing = [
node
for c in router._via_cells(via)
for node in ((*c, 0), (*c, 1))
if node not in router._cell_owner and node not in router._halo
]
assert crossing
assert router._crossed_owners(crossing, sig.net_no, conns) == {first.index, second.index}
# --- a wider net class --------------------------------------------------------
_WIDE = """(pcb wide (parser) (resolution um 10) (unit um)
@@ -225,3 +262,23 @@ def test_a_pad_cell_check_uses_the_rotated_outline():
c = router._cell_center(cell)
square = IntBox(c.x - margin, c.y - margin, c.x + margin, c.y + margin)
assert oval.shape.intersection(square).dimension() != 2
def test_a_via_by_a_chamfered_corner_is_judged_round():
"""The grid keeps a via's centre its radius plus the edge clearance from
the outline. The exact check used the via's square, whose corner reaches
past a 45-degree chamfer where the round via does not."""
from freeroute.board.outline import BoardOutline
from freeroute.geometry import IntBox, IntPoint
from freeroute.route.exact_router import _inside_outline
# a 10 mm square with its lower-left corner cut at 45 degrees
outline = BoardOutline([IntPoint(*p) for p in
[(30, 0), (100, 0), (100, 100), (0, 100), (0, 30)]])
half, edge = 4, 1
via = IntPoint(19, 20) # its centre 6.4 from the chamfer x + y = 30
square = IntBox(via.x - half, via.y - half, via.x + half, via.y + half)
assert not outline.box_clear(square, edge) # the square's corner: 0.7 away
assert _inside_outline(outline, [], [via], half + edge, edge)
# 4.2 from the chamfer: the round via itself is too close
assert not _inside_outline(outline, [], [IntPoint(18, 18)], half + edge, edge)
+1 -1
View File
@@ -31,7 +31,7 @@ def test_occupancy_only_holds_routed_connections(name):
router, _ = _routed_router(name)
conns = router._conns
assert all(conns[i].routed for i in router._cell_owner.values())
assert all(conns[i].routed for i in router._via_owner.values())
assert all(conns[i].routed for claims in router._via_claims.values() for i in claims)
assert all(conns[i].routed for claims in router._halo.values() for i in claims)