diff --git a/src/freeroute/route/grid_router.py b/src/freeroute/route/grid_router.py index 283258d..e99429f 100644 --- a/src/freeroute/route/grid_router.py +++ b/src/freeroute/route/grid_router.py @@ -311,6 +311,9 @@ class GridRouter: self._via_edge_block: set[Cell] = set() # wire keepouts as boxes, to check a pin stub the way the exact engine will self._keepout_boxes: dict[int, list[tuple[IntBox, bool]]] = {} + # via keepouts as boxes, and other copper a via in a pad must clear + self._via_keepout_boxes: list[tuple[IntBox, bool]] = [] + self._static_copper: list[tuple[IntBox, int]] | None = None # pin end nodes no wire can reach without entering a keepout self._via_only: set[Node] = set() if outline is not None: @@ -360,6 +363,8 @@ class GridRouter: self._keepout.add((*cell, item.layer)) if item.blocks_vias: self._via_keepout.add((*cell, item.layer)) + if item.blocks_vias: + self._via_keepout_boxes.append((box, item.clearance)) def _rasterize_fixed_copper(self) -> None: """Tracks and vias already on the board: copper of their net, which @@ -452,7 +457,14 @@ class GridRouter: goals = self._group_ends(conn.b_group, conn.net_no, conns) if not starts or not goals: return None - common = sorted(set(starts) & set(goals)) + # A start that is already an end joins at once, except that ending on + # a plane there needs a via: one that cannot be placed (a via keepout, + # another net's pad) is no end, and the search goes on from there. + common = [ + n for n in sorted(set(starts) & set(goals)) + if not isinstance(goals[n], ConductionArea) + or self._via_placeable((n[0], n[1]), conn.net_no, conns) + ] if common: conn.a_end, conn.b_end = starts[common[0]], goals[common[0]] return [common[0]] @@ -485,6 +497,12 @@ class GridRouter: ok = via_ok.get(cell) if ok is None: ok = via_ok[cell] = self._via_placeable(cell, net_no, conns) + # a via leaving or reaching a walled-in pin goes in its pad + # (see _commit), so it has to fit there exactly + for node, ends in ((source, starts), (target, goals)): + pin = ends.get(node) + if ok and isinstance(pin, Pin) and self._walled(node): + ok = self._via_fits_at(pin.location, net_no) if ok: self._relax(source, target, g_via, goal_cell, g_score, came_from, open_heap) continue @@ -548,6 +566,29 @@ class GridRouter: # copper too, or a stub ends up closer than clearance to a trace in the # cell beside it. + def _walled(self, node: Node) -> bool: + """A pin end no wire may leave or reach: only a via in its pad will do.""" + return node in self._keepout or node in self._via_only + + def _via_fits_at(self, point: IntPoint, net_no: int) -> bool: + """A via centred exactly on ``point`` clears every via keepout and all + other nets' pads and fixed copper (what a via in a pad must meet).""" + via = IntBox(point.x - self.via_half, point.y - self.via_half, + point.x + self.via_half, point.y + self.via_half) + grown = via.offset(self.clearance) + for box, needs in self._via_keepout_boxes: + if box.overlaps(grown if needs else via): + return False + if self._static_copper is None: + copper = [ + (p.shape.bounding_box(), p.net_nos[0] if p.net_nos else -1) + for p in self.board.get_pins() + if p.shape is not None and any(layer in self.layers for layer in p.layers) + ] + copper += [(box, net) for net, _, box in self.board.get_fixed_copper()] + self._static_copper = copper + return not any(net != net_no and box.overlaps(grown) for box, net in self._static_copper) + def _stub_cells(self, pin_location: IntPoint, cell: Cell) -> list[Cell]: """Cells where another net's trace would come too close to the stub.""" from freeroute.board.outline import _point_segment