Honour KiCad's keepouts: keyhole rings, wire_keepout, per-tile rasterizing
On the TerraHawk rc4 power stage every engine misread the board's rule areas: - A no-track ring along the board edge arrives as one keyhole polygon, and keepout polygons were wrapped in a single convex tile, so the ring became the whole board. Split keepout polygons into convex pieces instead. - The grid engine rasterized an area by its overall bounding box, which for that ring is again the whole board; it routed nothing. Rasterize per tile. - "Keep router off" rule areas arrive as wire_keepout, which the reader did not know, so tracks ran through them (172 DRC items_not_allowed). Read it at board and image level. For now it blocks vias too, which is stricter than KiCad and cannot create a violation. - place_keepout only restricts part placement; stop treating it as copper. The oracle now runs the JAR headless; it was opening FreeRouting's GUI on the desktop for every oracle test.
This commit is contained in:
@@ -17,6 +17,7 @@ import pytest
|
||||
|
||||
from freeroute.board import BasicBoard, ObstacleArea, Pin, build_board
|
||||
from freeroute.dsn import parse_dsn
|
||||
from freeroute.geometry import IntPoint
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
@@ -129,3 +130,60 @@ def test_board_net_set_covers_oracle_routed_nets(board):
|
||||
assert routed, "oracle produced no routed nets"
|
||||
# every net the reference router routed exists on our constructed board
|
||||
assert routed <= board.nets.names()
|
||||
|
||||
|
||||
# --- keepouts as KiCad exports them ------------------------------------------
|
||||
# Found on the TerraHawk charge-node boards (2026-09-27): a no-track ring along
|
||||
# the board edge arrives as one keyhole polygon, "keep router off" rule areas
|
||||
# arrive as wire_keepout, and neither may block the board's interior.
|
||||
|
||||
_RING_DSN = """(pcb ring (parser) (resolution um 10) (unit um)
|
||||
(structure
|
||||
(layer F.Cu (type signal)) (layer B.Cu (type signal))
|
||||
(boundary (path pcb 0 0 0 40000 0 40000 -30000 0 -30000 0 0))
|
||||
(keepout "" (polygon F.Cu 0 0 0 40000 0 40000 -30000 0 -30000 0 0
|
||||
500 -500 500 -29500 39500 -29500 39500 -500 500 -500 0 0))
|
||||
(wire_keepout "pour" (polygon F.Cu 0 10000 -10000 20000 -10000 20000 -20000 10000 -20000))
|
||||
(place_keepout "courtyard" (polygon F.Cu 0 30000 -10000 35000 -10000 35000 -20000 30000 -20000))
|
||||
(rule (width 250) (clearance 150)))
|
||||
(placement) (library) (network))"""
|
||||
|
||||
|
||||
def _areas(dsn_text):
|
||||
return build_board(parse_dsn(dsn_text)).get_obstacle_areas()
|
||||
|
||||
|
||||
def _blocked(areas, x_um, y_um):
|
||||
p = IntPoint(x_um * 10, y_um * 10)
|
||||
return any(t.contains(p) for a in areas for t in a.tiles)
|
||||
|
||||
|
||||
def test_keyhole_ring_keepout_blocks_only_the_border():
|
||||
areas = _areas(_RING_DSN)
|
||||
assert _blocked(areas, 200, -15000) # inside the 0.5 mm edge strip
|
||||
assert not _blocked(areas, 5000, -5000) # open board interior
|
||||
|
||||
|
||||
def test_wire_keepout_is_read_and_blocks_its_area():
|
||||
dsn = parse_dsn(_RING_DSN)
|
||||
assert [k.kind for k in dsn.keepouts].count("wire_keepout") == 1
|
||||
assert _blocked(_areas(_RING_DSN), 15000, -15000)
|
||||
|
||||
|
||||
def test_place_keepout_does_not_block_routing():
|
||||
assert not _blocked(_areas(_RING_DSN), 32500, -15000)
|
||||
|
||||
|
||||
def test_grid_router_leaves_the_interior_of_a_ring_keepout_open():
|
||||
from freeroute.route.pipeline import route_dsn_board
|
||||
|
||||
ring_only = "\n".join(
|
||||
line for line in _RING_DSN.splitlines() if "wire_keepout" not in line
|
||||
).replace(
|
||||
"(placement) (library) (network)",
|
||||
"(placement (component P (place P1 5000 -15000 front 0) (place P2 35000 -15000 front 0)))\n"
|
||||
" (library (image P (pin pad 1 0 0)) (padstack pad (shape (circle F.Cu 600)) (attach off)))\n"
|
||||
" (network (net N (pins P1-1 P2-1)))",
|
||||
)
|
||||
result, _, _ = route_dsn_board(parse_dsn(ring_only), max_passes=2)
|
||||
assert result.routed_net_numbers
|
||||
|
||||
Reference in New Issue
Block a user