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:
2026-09-27 06:40:34 -06:00
parent 3ad5c983f5
commit f1df60fc31
7 changed files with 245 additions and 34 deletions
+22
View File
@@ -45,6 +45,25 @@ def _via_padstack(dsn: DsnBoard) -> str:
return dsn.via_padstack_names[0] if dsn.via_padstack_names else _DEFAULT_VIA
def _via_half_board(dsn: DsnBoard, scale: int) -> int | None:
"""Half the via pad's size in board units, from the DSN's via padstack.
None when the DSN names no via padstack with a shape, so the engines fall
back to their old estimate.
"""
padstack = dsn.padstack(_via_padstack(dsn))
if padstack is None or not padstack.shapes:
return None
half = 0.0
for shape in padstack.shapes:
if hasattr(shape, "diameter"):
half = max(half, shape.diameter / 2)
elif len(getattr(shape, "coords", [])) >= 4:
xs, ys = shape.coords[0::2], shape.coords[1::2]
half = max(half, (max(xs) - min(xs)) / 2, (max(ys) - min(ys)) / 2)
return round(half * scale) or None
def route_dsn_board(
dsn: DsnBoard,
*,
@@ -62,6 +81,7 @@ def route_dsn_board(
clearance_board = round(_rule_clearance_dsn(dsn) * scale)
result = route_board(
board,
via_half=_via_half_board(dsn, scale),
trace_width=width_board,
clearance=clearance_board,
layers=layers,
@@ -130,6 +150,7 @@ def route_dsn_board_exact(
clearance_board = round(_rule_clearance_dsn(dsn) * scale)
exact = route_board_exact(
board,
via_half=_via_half_board(dsn, scale),
trace_width=width_board,
clearance=clearance_board,
layers=layers,
@@ -175,6 +196,7 @@ def route_dsn_board_rooms(
clearance_board = round(_rule_clearance_dsn(dsn) * scale)
rooms = route_board_rooms(
board,
via_half=_via_half_board(dsn, scale),
trace_width=width_board,
clearance=clearance_board,
layers=layers,