Route to the real board outline, per connection, and out of fine-pitch pins
- The exact engine verified copper per net and dropped a whole net on any conflict: a 25-pin +3V3 net lost every connection to one bad segment. Verify and accept per ratsnest connection instead. - Pad and keepout cells were rounded outward, blocking cells up to half a step clear of the copper; 62 of the 64 pins of a 0.5 mm pitch LQFP could not start a route. Block only cells whose centre is inside the clearance zone, and let a net walk along inside its own pad, which adds no copper. - Routing was bounded by the outline's bounding box, so tracks cut across rounded corners. Keep the DSN boundary as a polygon; the grid blocks cells too close to it (scanline plus per-edge, precomputed for vias) and the exact and room engines reject copper that is. - --edge-clearance MM sets copper-to-edge clearance, e.g. KiCad's min_copper_edge_clearance, which the DSN does not carry. Default 0. On the TerraHawk rc4 power stage the exact engine now matches the FreeRouting JAR: same open count, no DRC errors, with a 0.5 mm edge rule.
This commit is contained in:
@@ -45,6 +45,14 @@ def _via_padstack(dsn: DsnBoard) -> str:
|
||||
return dsn.via_padstack_names[0] if dsn.via_padstack_names else _DEFAULT_VIA
|
||||
|
||||
|
||||
def _mm_to_board(dsn: DsnBoard, scale: int, value_mm: float | None) -> int | None:
|
||||
"""A length in millimetres as board units, or None."""
|
||||
if value_mm is None:
|
||||
return None
|
||||
unit_mm = {"inch": 25.4, "mil": 0.0254, "mm": 1.0, "cm": 10.0, "um": 0.001}[dsn.unit]
|
||||
return round(value_mm / unit_mm * scale)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@@ -67,6 +75,7 @@ def _via_half_board(dsn: DsnBoard, scale: int) -> int | None:
|
||||
def route_dsn_board(
|
||||
dsn: DsnBoard,
|
||||
*,
|
||||
edge_clearance_mm: float | None = None,
|
||||
layers: list[int] | None = None,
|
||||
rip_up: bool = True,
|
||||
max_passes: int = 10,
|
||||
@@ -77,11 +86,13 @@ def route_dsn_board(
|
||||
toggles rip-up-and-retry (``False`` = greedy single pass)."""
|
||||
board = build_board(dsn)
|
||||
scale = max(dsn.resolution.value, 1)
|
||||
edge_clearance_board = _mm_to_board(dsn, scale, edge_clearance_mm)
|
||||
width_board = round(_rule_width_dsn(dsn) * scale)
|
||||
clearance_board = round(_rule_clearance_dsn(dsn) * scale)
|
||||
result = route_board(
|
||||
board,
|
||||
via_half=_via_half_board(dsn, scale),
|
||||
edge_clearance=edge_clearance_board,
|
||||
trace_width=width_board,
|
||||
clearance=clearance_board,
|
||||
layers=layers,
|
||||
@@ -122,10 +133,12 @@ def build_routing_result(
|
||||
layers: list[int] | None = None,
|
||||
rip_up: bool = True,
|
||||
max_passes: int = 10,
|
||||
edge_clearance_mm: float | None = None,
|
||||
) -> RoutingResult:
|
||||
"""Route ``dsn`` (grid track) and convert to a DSN-unit RoutingResult."""
|
||||
route, scale, layer_names = route_dsn_board(
|
||||
dsn, layers=layers, rip_up=rip_up, max_passes=max_passes
|
||||
dsn, layers=layers, rip_up=rip_up, max_passes=max_passes,
|
||||
edge_clearance_mm=edge_clearance_mm,
|
||||
)
|
||||
return _to_routing_result(route, dsn, scale, layer_names)
|
||||
|
||||
@@ -133,6 +146,7 @@ def build_routing_result(
|
||||
def route_dsn_board_exact(
|
||||
dsn: DsnBoard,
|
||||
*,
|
||||
edge_clearance_mm: float | None = None,
|
||||
layers: list[int] | None = None,
|
||||
max_passes: int = 10,
|
||||
rip_up: bool = True,
|
||||
@@ -146,11 +160,13 @@ def route_dsn_board_exact(
|
||||
the 45-degree recovery pass for still-dropped 2-pin nets."""
|
||||
board = build_board(dsn)
|
||||
scale = max(dsn.resolution.value, 1)
|
||||
edge_clearance_board = _mm_to_board(dsn, scale, edge_clearance_mm)
|
||||
width_board = round(_rule_width_dsn(dsn) * scale)
|
||||
clearance_board = round(_rule_clearance_dsn(dsn) * scale)
|
||||
exact = route_board_exact(
|
||||
board,
|
||||
via_half=_via_half_board(dsn, scale),
|
||||
edge_clearance=edge_clearance_board,
|
||||
trace_width=width_board,
|
||||
clearance=clearance_board,
|
||||
layers=layers,
|
||||
@@ -170,6 +186,7 @@ def build_exact_routing_result(
|
||||
diagonal: bool = False,
|
||||
rip_up: bool = True,
|
||||
max_passes: int = 10,
|
||||
edge_clearance_mm: float | None = None,
|
||||
) -> RoutingResult:
|
||||
"""Route ``dsn`` (exact track) and convert to a DSN-unit RoutingResult."""
|
||||
exact, scale, layer_names = route_dsn_board_exact(
|
||||
@@ -179,12 +196,18 @@ def build_exact_routing_result(
|
||||
diagonal=diagonal,
|
||||
rip_up=rip_up,
|
||||
max_passes=max_passes,
|
||||
edge_clearance_mm=edge_clearance_mm,
|
||||
)
|
||||
return _to_routing_result(exact.result, dsn, scale, layer_names)
|
||||
|
||||
|
||||
def route_dsn_board_rooms(
|
||||
dsn: DsnBoard, *, layers: list[int] | None = None, shove: bool = False, pack: bool = False
|
||||
dsn: DsnBoard,
|
||||
*,
|
||||
layers: list[int] | None = None,
|
||||
shove: bool = False,
|
||||
pack: bool = False,
|
||||
edge_clearance_mm: float | None = None,
|
||||
) -> tuple[ExactRouteResult, int, list[str]]:
|
||||
"""Route a parsed :class:`DsnBoard` with the continuous expansion-room track;
|
||||
return the result, the scale, and layer names. ``shove`` enables occupancy-
|
||||
@@ -192,11 +215,13 @@ def route_dsn_board_rooms(
|
||||
``pack`` enables the coordinated multi-trace channel packer."""
|
||||
board = build_board(dsn)
|
||||
scale = max(dsn.resolution.value, 1)
|
||||
edge_clearance_board = _mm_to_board(dsn, scale, edge_clearance_mm)
|
||||
width_board = round(_rule_width_dsn(dsn) * scale)
|
||||
clearance_board = round(_rule_clearance_dsn(dsn) * scale)
|
||||
rooms = route_board_rooms(
|
||||
board,
|
||||
via_half=_via_half_board(dsn, scale),
|
||||
edge_clearance=edge_clearance_board,
|
||||
trace_width=width_board,
|
||||
clearance=clearance_board,
|
||||
layers=layers,
|
||||
@@ -207,10 +232,17 @@ def route_dsn_board_rooms(
|
||||
|
||||
|
||||
def build_rooms_routing_result(
|
||||
dsn: DsnBoard, *, layers: list[int] | None = None, shove: bool = False, pack: bool = False
|
||||
dsn: DsnBoard,
|
||||
*,
|
||||
layers: list[int] | None = None,
|
||||
shove: bool = False,
|
||||
pack: bool = False,
|
||||
edge_clearance_mm: float | None = None,
|
||||
) -> RoutingResult:
|
||||
"""Route ``dsn`` (room track) and convert to a DSN-unit RoutingResult."""
|
||||
rooms, scale, layer_names = route_dsn_board_rooms(dsn, layers=layers, shove=shove, pack=pack)
|
||||
rooms, scale, layer_names = route_dsn_board_rooms(
|
||||
dsn, layers=layers, shove=shove, pack=pack, edge_clearance_mm=edge_clearance_mm
|
||||
)
|
||||
return _to_routing_result(rooms.result, dsn, scale, layer_names)
|
||||
|
||||
|
||||
@@ -222,6 +254,7 @@ def route(
|
||||
shove: bool = False,
|
||||
pack: bool = False,
|
||||
diagonal: bool = False,
|
||||
edge_clearance_mm: float | None = None,
|
||||
) -> str:
|
||||
"""Route a Specctra DSN string and return the routed SES string.
|
||||
|
||||
@@ -235,9 +268,13 @@ def route(
|
||||
"""
|
||||
dsn = parse_dsn(dsn_text)
|
||||
if engine == "exact":
|
||||
result = build_exact_routing_result(dsn, layers=layers, shove=shove, diagonal=diagonal)
|
||||
result = build_exact_routing_result(
|
||||
dsn, layers=layers, shove=shove, diagonal=diagonal, edge_clearance_mm=edge_clearance_mm
|
||||
)
|
||||
elif engine == "room":
|
||||
result = build_rooms_routing_result(dsn, layers=layers, shove=shove, pack=pack)
|
||||
result = build_rooms_routing_result(
|
||||
dsn, layers=layers, shove=shove, pack=pack, edge_clearance_mm=edge_clearance_mm
|
||||
)
|
||||
else:
|
||||
result = build_routing_result(dsn, layers=layers)
|
||||
result = build_routing_result(dsn, layers=layers, edge_clearance_mm=edge_clearance_mm)
|
||||
return write_ses(dsn, result)
|
||||
|
||||
Reference in New Issue
Block a user