Window boards: never search a connection ending outside the window

With items away from the window left off its board, a stub from the window
edge out to a rerouted net's far pin crossed copper the exact check never
saw: 38 shorts on th-eda's flight controller. A connection with an end
outside the bounds is now left unrouted, and a window whose new copper
leaves the window is refused.
This commit is contained in:
2026-10-06 23:07:09 -06:00
parent 28523bcbac
commit d1def2c4b9
3 changed files with 45 additions and 0 deletions
+24
View File
@@ -211,6 +211,10 @@ class GridRouter:
# these nets get connections, and the grid covers only ``bounds``
# (cells outside it do not exist, so nothing is routed there).
self.only_nets = frozenset(only_nets) if only_nets is not None else None
self._bounds = bounds
# connections with an end outside ``bounds``: never searched, since
# the grid has no cells to reach that end by
self._outside: set[int] = set()
# Multi-pin nets: pair a net's pin groups by a minimum spanning tree,
# and let a connection start or end on copper its net already routed
# (a T into the tree), not only on one chosen pin.
@@ -717,6 +721,8 @@ class GridRouter:
def _search(
self, conn: _Connection, allow_ripup: bool, conns: list[_Connection]
) -> list[Node] | None:
if conn.index in self._outside:
return None
half = self._conn_half(conn)
self._cur_half, self._spread = half, self._spread_for(half)
try:
@@ -1872,6 +1878,11 @@ class GridRouter:
self._net_conns: dict[int, list[_Connection]] = {}
for conn in conns:
self._net_conns.setdefault(conn.net_no, []).append(conn)
if self._bounds is not None:
self._outside = {
c.index for c in conns
if not (_group_inside(c.a_group, self._bounds) and _group_inside(c.b_group, self._bounds))
}
return conns
def _fixed_pieces(self, net_no: int) -> list[_FixedPiece]:
@@ -2017,6 +2028,19 @@ def _group_label(group) -> str:
return "/".join(parts) or "?"
def _group_inside(group, bounds: IntBox) -> bool:
"""A group has a pin inside ``bounds``, or a zone reaching into it."""
pins, zones = group
if any(bounds.ll.x <= p.location.x <= bounds.ur.x and bounds.ll.y <= p.location.y <= bounds.ur.y
for p in pins):
return True
for zone in zones:
box = zone.bounding_box()
if box.ll.x <= bounds.ur.x and bounds.ll.x <= box.ur.x and box.ll.y <= bounds.ur.y and bounds.ll.y <= box.ur.y:
return True
return False
def _ends_of(conn) -> tuple[str, str]:
"""A readable description of what a connection joins."""
return _group_label(conn.a_group), _group_label(conn.b_group)
+5
View File
@@ -206,6 +206,11 @@ def window_reroute(
after = _open(f for f in sub.failed if f[0] in affected)
if len(after) >= len(before):
continue
# the window board left out what lies away from it, so copper
# reaching out of the window was never checked against it
if any(not _inside((sub.wires.get(n, []), sub.vias.get(n, [])), window)
for n in affected if sub.wires.get(n) or sub.vias.get(n)):
continue
# a net keeping some of its copper is written at one width: the
# window's new copper has to have gone in at that width too
if any(
+16
View File
@@ -110,3 +110,19 @@ def test_window_board_keeps_far_items_of_the_rerouted_nets_only():
refs = {p.component_ref for p in sub.get_pins()}
assert {"P1", "P2"} <= refs # net N's pins, though outside the view
assert not refs & {"Q1", "Q2"} # net M's, far away, left out
def test_a_connection_ending_outside_the_window_gets_no_copper():
"""Its far end has no grid cells; a stub out to it would cross items the
window board never saw."""
from freeroute.route.exact_router import route_board_exact
dsn = parse_dsn(_slot(far=True))
s = dsn.resolution.value
window = IntBox(70000, -70000, 130000, -30000) # the slot only; net M's pins are far off
result = route_board_exact(
build_board(dsn), trace_width=round(_rule_width_dsn(dsn) * s),
clearance=round(_rule_clearance_dsn(dsn) * s), only_nets=frozenset({2}), bounds=window,
).result
assert [f[0] for f in result.failed] == [2]
assert not result.wires.get(2) and not result.vias.get(2)