Keepout exits and rule areas
A route may not leave a cell inside a wire keepout as a wire, nor reach a pin there by one: it changes layer instead, and the via goes in the pad. A pin whose every stub enters a keepout is reached by a via only. A structure keepout named rule_area... (mckicad names KiCad rule areas so) is checked for overlap only, as KiCad's DRC does, with the free ends of each wire tested for their round caps.
This commit is contained in:
@@ -33,6 +33,7 @@ import random
|
||||
from freeroute.board.board import BasicBoard
|
||||
from freeroute.board.items import ConductionArea, ObstacleArea, Pin
|
||||
from freeroute.geometry import IntBox, IntPoint
|
||||
from freeroute.geometry.polyline import corner_box
|
||||
|
||||
__all__ = ["GridRouter", "RouteResult", "route_board"]
|
||||
|
||||
@@ -298,7 +299,9 @@ class GridRouter:
|
||||
outline = self.board.outline
|
||||
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[IntBox]] = {}
|
||||
self._keepout_boxes: dict[int, list[tuple[IntBox, bool]]] = {}
|
||||
# pin end nodes no wire can reach without entering a keepout
|
||||
self._via_only: set[Node] = set()
|
||||
if outline is not None:
|
||||
# cells too close to the real outline (rounded corners included):
|
||||
# for a trace on every layer, and separately for a via's wider copper
|
||||
@@ -336,8 +339,12 @@ class GridRouter:
|
||||
continue
|
||||
# the same distance the exact engine verifies against
|
||||
if item.blocks_wires:
|
||||
self._keepout_boxes.setdefault(item.layer, []).append(box)
|
||||
for cell in self._cells_in_box(box, margin):
|
||||
self._keepout_boxes.setdefault(item.layer, []).append(
|
||||
(box, item.clearance)
|
||||
)
|
||||
# a rule area takes no clearance: a trace may touch it
|
||||
reach = margin if item.clearance else self.half_width
|
||||
for cell in self._cells_in_box(box, reach):
|
||||
if item.blocks_wires:
|
||||
self._keepout.add((*cell, item.layer))
|
||||
if item.blocks_vias:
|
||||
@@ -475,12 +482,19 @@ class GridRouter:
|
||||
return path
|
||||
cx, cy, cl = current
|
||||
base = g_score[current]
|
||||
for dx, dy in self._neighbours:
|
||||
# A cell inside a wire keepout (a pin there, or where a via
|
||||
# landed) may only be left by another via: a wire from it would
|
||||
# start inside the keepout.
|
||||
blocked = current in self._keepout or current in self._via_only
|
||||
neighbours = () if blocked else self._neighbours
|
||||
for dx, dy in neighbours:
|
||||
cell = (cx + dx, cy + dy)
|
||||
if not (0 <= cell[0] < self.cols and 0 <= cell[1] < self.rows):
|
||||
continue
|
||||
nxt = (*cell, cl)
|
||||
if nxt in goal_stub: # a pin end; a zone end is entered normally
|
||||
if nxt in self._keepout or nxt in self._via_only:
|
||||
continue # a pin inside a keepout is reached by a via only
|
||||
enter = goal_stub[nxt]
|
||||
else:
|
||||
enter = self._enter_cost(cell, cl, net_no, allow_ripup, conns)
|
||||
@@ -528,7 +542,9 @@ class GridRouter:
|
||||
if _point_segment(self._cell_center(c), pin_location, end) < reach
|
||||
]
|
||||
|
||||
def _stub_clear(self, pin: Pin, cell: Cell, layer: int, net_no: int, conns) -> bool:
|
||||
def _stub_clear(
|
||||
self, pin: Pin, cell: Cell, layer: int, net_no: int, conns, keepouts_only: bool = False
|
||||
) -> bool:
|
||||
keepouts = self._keepout_boxes.get(layer)
|
||||
if keepouts:
|
||||
# the stub is off the grid, so check it exactly, as the exact
|
||||
@@ -539,8 +555,14 @@ class GridRouter:
|
||||
if end != pin.location:
|
||||
for tile in PolylineShape(Polyline([pin.location, end]), self.half_width).tiles():
|
||||
grown = tile.offset(self.clearance)
|
||||
if any(k.overlaps(grown) for k in keepouts):
|
||||
if any(k.overlaps(grown if needs else tile) for k, needs in keepouts):
|
||||
return False
|
||||
# the track's round end at the pin, for a rule area
|
||||
cap = corner_box(pin.location, self.half_width)
|
||||
if any(not needs and k.overlaps(cap) for k, needs in keepouts):
|
||||
return False
|
||||
if keepouts_only:
|
||||
return True
|
||||
for c in self._stub_cells(pin.location, cell):
|
||||
owner = self._cell_owner.get((*c, layer))
|
||||
if owner is not None and conns[owner].net_no != net_no:
|
||||
@@ -646,6 +668,13 @@ class GridRouter:
|
||||
nodes |= self._escape_nodes(pin, net_no, conns, layers, (cx, cy))
|
||||
if not nodes: # nothing clear: fall back to the nearest cell, as before
|
||||
nodes = {(cx, cy, layer) for layer in layers}
|
||||
if self._keepout_boxes and not any(
|
||||
self._stub_clear(pin, (cx, cy), layer, net_no, conns, keepouts_only=True)
|
||||
for layer in layers
|
||||
):
|
||||
# no wire can reach the pin without entering a keepout: the
|
||||
# route has to arrive by a via, which goes in the pad
|
||||
self._via_only.update(nodes)
|
||||
return nodes
|
||||
|
||||
def _crowded(self, pin: Pin, net_no: int) -> bool:
|
||||
@@ -800,7 +829,9 @@ class GridRouter:
|
||||
if len(wire_segs) > 1:
|
||||
for idx, pin in ((0, conn.a_end), (len(wire_segs) - 1, conn.b_end)):
|
||||
layer, cells = wire_segs[idx]
|
||||
if isinstance(pin, Pin) and len(cells) == 1 and (*cells[0], layer) in self._keepout:
|
||||
node = (*cells[0], layer)
|
||||
walled = node in self._keepout or node in self._via_only
|
||||
if isinstance(pin, Pin) and len(cells) == 1 and walled:
|
||||
in_pad[idx] = pin.location
|
||||
conn.wires = []
|
||||
for idx, (layer, cells) in enumerate(wire_segs):
|
||||
|
||||
Reference in New Issue
Block a user