Model pads and vias as KiCad places them
Measured against KiCad's own pads on the TerraHawk rc4 power stage: - Back-side parts kept their pads on F.Cu (32 pads). Mirror the copper layer for a back-side part. - Pad outlines were copied unrotated (29 pads on rotated parts). Place the outline through the same chain as the pin: pin rotation, back-side mirror, part rotation. Oval pads, a path with a width, came out as a zero-height sliver; pad them out by the aperture. - Multi-shape padstacks used the first shape; use the largest. - Footprint-level keepouts were parsed but never placed. KiCad exports a mounting hole's plated ring that way, so the router could not see it. - Vias were sized like a trace (0.25 mm square for a 0.6 mm via), and every remaining short and clearance error was a via. Read the size from the DSN's via padstack and use it in all three engines; the grid engine now reserves the cells a via's copper reaches. With these the exact and room engines route that board with no shorts, clearance or other DRC errors. Pins now carry their component reference.
This commit is contained in:
@@ -98,6 +98,7 @@ class _Connection:
|
||||
"vias",
|
||||
"trace_cells",
|
||||
"via_cells",
|
||||
"via_halo",
|
||||
)
|
||||
|
||||
def __init__(self, index: int, net_no: int, a: Pin, b: Pin) -> None:
|
||||
@@ -111,6 +112,7 @@ class _Connection:
|
||||
self.vias: list[IntPoint] = []
|
||||
self.trace_cells: list[tuple[Cell, int]] = []
|
||||
self.via_cells: list[Cell] = []
|
||||
self.via_halo: list[Cell] = []
|
||||
|
||||
|
||||
class GridRouter:
|
||||
@@ -129,6 +131,7 @@ class GridRouter:
|
||||
rip_cap: int = 4,
|
||||
rip_penalty: float = 30.0,
|
||||
orthogonal: bool = False,
|
||||
via_half: int | None = None,
|
||||
) -> None:
|
||||
self.board = board
|
||||
self.trace_width = max(trace_width, 1)
|
||||
@@ -144,6 +147,12 @@ class GridRouter:
|
||||
self._neighbours = _NEIGHBOURS[:4] if orthogonal else _NEIGHBOURS
|
||||
# cell size must fit a trace plus its clearance to a neighbouring trace
|
||||
self.step = max(self.trace_width + self.clearance, 1)
|
||||
# A via's copper is wider than a trace. Another net's trace needs its
|
||||
# centre via_half + clearance + half_width from the via's centre, so a
|
||||
# via also claims every cell within via_reach of its own.
|
||||
self.via_half = via_half or self.half_width
|
||||
need = self.via_half + self.clearance + self.half_width
|
||||
self.via_reach = max(0, math.ceil(need / self.step) - 1)
|
||||
|
||||
if layers is None:
|
||||
layers = [i for i, layer in enumerate(board.layer_structure.arr) if layer.is_signal]
|
||||
@@ -243,10 +252,22 @@ class GridRouter:
|
||||
return None
|
||||
return self.rip_penalty * (1 + rip_count)
|
||||
|
||||
def _via_cells(self, cell: Cell) -> list[Cell]:
|
||||
"""The cell a via sits in plus the neighbours its copper reaches."""
|
||||
r = self.via_reach
|
||||
cx, cy = cell
|
||||
return [
|
||||
(x, y)
|
||||
for x in range(max(cx - r, 0), min(cx + r, self.cols - 1) + 1)
|
||||
for y in range(max(cy - r, 0), min(cy + r, self.rows - 1) + 1)
|
||||
]
|
||||
|
||||
def _via_placeable(self, cell: Cell, net_no: int, conns: list[_Connection]) -> bool:
|
||||
"""A through via needs every signal layer strictly free of other nets."""
|
||||
"""A through via needs its whole footprint free of other nets on every layer."""
|
||||
return all(
|
||||
self._enter_cost(cell, layer, net_no, False, conns) == 0.0 for layer in self.layers
|
||||
self._enter_cost(c, layer, net_no, False, conns) == 0.0
|
||||
for c in self._via_cells(cell)
|
||||
for layer in self.layers
|
||||
)
|
||||
|
||||
# --- A* search ----------------------------------------------------------
|
||||
@@ -321,8 +342,12 @@ class GridRouter:
|
||||
conn.via_cells = list(via_cells)
|
||||
for cell, layer in conn.trace_cells:
|
||||
self._cell_owner[(*cell, layer)] = conn.index
|
||||
conn.via_halo = []
|
||||
for cell in conn.via_cells:
|
||||
self._via_owner[cell] = conn.index
|
||||
for c in self._via_cells(cell):
|
||||
if c not in self._via_owner:
|
||||
self._via_owner[c] = conn.index
|
||||
conn.via_halo.append(c)
|
||||
conn.wires = []
|
||||
for idx, (layer, cells) in enumerate(wire_segs):
|
||||
points = [self._cell_center(c) for c in cells]
|
||||
@@ -340,9 +365,10 @@ class GridRouter:
|
||||
for cell, layer in conn.trace_cells:
|
||||
if self._cell_owner.get((*cell, layer)) == conn.index:
|
||||
del self._cell_owner[(*cell, layer)]
|
||||
for cell in conn.via_cells:
|
||||
for cell in conn.via_halo:
|
||||
if self._via_owner.get(cell) == conn.index:
|
||||
del self._via_owner[cell]
|
||||
conn.via_halo = []
|
||||
conn.trace_cells = []
|
||||
conn.via_cells = []
|
||||
conn.wires = []
|
||||
@@ -493,6 +519,7 @@ def route_board(
|
||||
rip_up: bool = True,
|
||||
max_passes: int = 10,
|
||||
orthogonal: bool = False,
|
||||
via_half: int | None = None,
|
||||
) -> RouteResult:
|
||||
"""Convenience wrapper: build a :class:`GridRouter` and route the board."""
|
||||
router = GridRouter(
|
||||
@@ -504,5 +531,6 @@ def route_board(
|
||||
rip_up=rip_up,
|
||||
max_passes=max_passes,
|
||||
orthogonal=orthogonal,
|
||||
via_half=via_half,
|
||||
)
|
||||
return router.route()
|
||||
|
||||
Reference in New Issue
Block a user