--smooth: turn grid staircases into octilinear runs after routing
A last stage on the finished result, wire by wire: a run of legs between two points becomes the octilinear shape joining them when its copper is clear, the furthest point first; right-angle corners left are chamfered; straight runs are merged. Ends, vias, points where another wire of the net meets the wire, and legs across own-net pads stay. Every new leg and corner is checked on the exact shapes against other nets, pads, keepouts and the board edge, slanted legs both ways round as the verifier counts a pair. A net whose pads end up joined differently is put back, and the whole smoothing is dropped if the finished check finds more violations than before. --smooth-report adds track length per layer.
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
"""Smoothing: staircases become octilinear runs, corners are chamfered, and
|
||||
nothing it keeps (ends, vias, joints, pads it crosses) moves."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from rpmroute.dsn import parse_dsn
|
||||
from rpmroute.route.smooth import smooth
|
||||
from rpmroute.route.verify import verify_result
|
||||
from rpmroute.ses.model import RoutedVia, RoutedWire, RoutingResult
|
||||
|
||||
VIA = "Via[0-1]_600:300_um"
|
||||
|
||||
|
||||
def _board(extra_pads: str = "", extra_places: str = "", nets: str = "") -> str:
|
||||
"""A 30 x 30 mm two-layer board, units um, 0.2 mm track and clearance.
|
||||
A1 at (2, -2) mm and A2 at (22, -22) mm are net SIG."""
|
||||
return f"""(pcb "smooth.dsn"
|
||||
(parser (string_quote ") (space_in_quoted_tokens on) (host_cad "t") (host_version "1"))
|
||||
(resolution um 10)
|
||||
(unit um)
|
||||
(structure
|
||||
(layer F.Cu (type signal) (property (index 0)))
|
||||
(layer B.Cu (type signal) (property (index 1)))
|
||||
(boundary (path pcb 0 0 0 30000 0 30000 -30000 0 -30000 0 0))
|
||||
(via "{VIA}")
|
||||
(rule (width 200) (clearance 200))
|
||||
)
|
||||
(library
|
||||
(padstack P (shape (rect F.Cu -300 -300 300 300)) (attach off))
|
||||
(padstack "{VIA}" (shape (circle F.Cu 600)) (shape (circle B.Cu 600)) (attach off))
|
||||
(image PAD (pin P 1 0 0))
|
||||
{extra_pads}
|
||||
)
|
||||
(placement
|
||||
(component PAD (place A1 2000 -2000 front 0) (place A2 22000 -22000 front 0) {extra_places})
|
||||
)
|
||||
(network
|
||||
(net SIG (pins A1-1 A2-1))
|
||||
{nets}
|
||||
(class default (rule (width 200) (clearance 200)))
|
||||
)
|
||||
(wiring)
|
||||
)
|
||||
"""
|
||||
|
||||
|
||||
def _staircase(a, b, step):
|
||||
"""Points from ``a`` to ``b`` (DSN um) in alternating x and y steps."""
|
||||
(x, y), (bx, by) = a, b
|
||||
pts = [(x, y)]
|
||||
sx = step if bx > x else -step
|
||||
sy = step if by > y else -step
|
||||
while (x, y) != (bx, by):
|
||||
if x != bx:
|
||||
x = x + sx if abs(bx - x) > step else bx
|
||||
pts.append((x, y))
|
||||
if y != by:
|
||||
y = y + sy if abs(by - y) > step else by
|
||||
pts.append((x, y))
|
||||
return pts
|
||||
|
||||
|
||||
def _wire(pts, layer="F.Cu", fixed=False):
|
||||
return RoutedWire(layer=layer, width=200, coords=[v for p in pts for v in p], fixed=fixed)
|
||||
|
||||
|
||||
def _points(wire):
|
||||
c = wire.coords
|
||||
return [(round(c[i]), round(c[i + 1])) for i in range(0, len(c), 2)]
|
||||
|
||||
|
||||
def _result(*wires, vias=(), net="SIG"):
|
||||
result = RoutingResult()
|
||||
for w in wires:
|
||||
result.add_wire(net, w)
|
||||
for x, y in vias:
|
||||
result.net(net, create=True).vias.append(RoutedVia(padstack=VIA, x=x, y=y))
|
||||
return result
|
||||
|
||||
|
||||
def test_a_staircase_becomes_one_diagonal():
|
||||
dsn = parse_dsn(_board())
|
||||
stair = _staircase((2000, -2000), (22000, -22000), 1000)
|
||||
result = _result(_wire(stair))
|
||||
report = smooth(dsn, result)
|
||||
pts = _points(result.nets[0].wires[0])
|
||||
assert pts == [(2000, -2000), (22000, -22000)]
|
||||
assert report.wires_changed == 1 and report.after.right_angles == 0
|
||||
assert report.after.total_mm < report.before.total_mm
|
||||
assert verify_result(dsn, result).clean
|
||||
|
||||
|
||||
def test_a_pad_in_the_way_is_gone_round():
|
||||
# another net's pad sits on the diagonal shortcut of an L-shaped route
|
||||
dsn = parse_dsn(_board(extra_places="(place B1 12000 -12000 front 0) (place B2 28000 -28000 front 0)",
|
||||
nets="(net OTHER (pins B1-1 B2-1))"))
|
||||
result = _result(_wire([(2000, -2000), (22000, -2000), (22000, -22000)]))
|
||||
report = smooth(dsn, result)
|
||||
pts = _points(result.nets[0].wires[0])
|
||||
assert pts[0] == (2000, -2000) and pts[-1] == (22000, -22000)
|
||||
assert report.wires_changed == 1 # the corner is chamfered, short of the pad
|
||||
assert verify_result(dsn, result).clean
|
||||
|
||||
|
||||
def test_a_joint_and_a_via_stay():
|
||||
dsn = parse_dsn(_board(extra_places="(place A3 22000 -2000 front 0)",
|
||||
nets="").replace("(pins A1-1 A2-1)", "(pins A1-1 A2-1 A3-1)"))
|
||||
stair = _staircase((2000, -2000), (22000, -22000), 1000)
|
||||
joint = stair[11] # A3's leg ends here, on the staircase
|
||||
leg = _wire([(22000, -2000), (joint[0], -2000), joint])
|
||||
via = stair[25]
|
||||
result = _result(_wire(stair), leg, vias=[via])
|
||||
smooth(dsn, result)
|
||||
pts = _points(result.nets[0].wires[0])
|
||||
assert joint in pts and via in pts
|
||||
assert pts[0] == (2000, -2000) and pts[-1] == (22000, -22000)
|
||||
assert verify_result(dsn, result).clean
|
||||
|
||||
|
||||
def test_the_board_edge_is_kept_clear_of():
|
||||
# a staircase hugging the top edge: a chamfer may not push copper over it
|
||||
dsn = parse_dsn(_board(extra_places="(place A3 22000 -500 front 0)").replace(
|
||||
"(pins A1-1 A2-1)", "(pins A1-1 A3-1)"))
|
||||
wire = _wire([(2000, -2000), (2000, -500), (22000, -500)])
|
||||
result = _result(wire)
|
||||
smooth(dsn, result, edge_clearance_mm=0.3)
|
||||
for _x, y in _points(result.nets[0].wires[0]):
|
||||
assert -y >= 300 + 100 # half width + edge clearance from y = 0
|
||||
|
||||
|
||||
def test_fixed_wiring_is_left_alone():
|
||||
dsn = parse_dsn(_board())
|
||||
stair = _staircase((2000, -2000), (22000, -22000), 1000)
|
||||
result = _result(_wire(stair, fixed=True))
|
||||
report = smooth(dsn, result)
|
||||
assert _points(result.nets[0].wires[0]) == stair
|
||||
assert report.wires == 0
|
||||
|
||||
|
||||
def test_a_detour_with_nothing_in_its_way_is_straightened():
|
||||
dsn = parse_dsn(_board(extra_places="(place A3 22000 -2000 front 0)").replace(
|
||||
"(pins A1-1 A2-1)", "(pins A3-1 A2-1)"))
|
||||
# a U out to x = 12 mm and back, with nothing between its ends
|
||||
result = _result(_wire([(22000, -2000), (12000, -2000), (12000, -22000), (22000, -22000)]))
|
||||
report = smooth(dsn, result)
|
||||
assert _points(result.nets[0].wires[0]) == [(22000, -2000), (22000, -22000)]
|
||||
assert (report.before.right_angles, report.after.right_angles) == (2, 0)
|
||||
assert verify_result(dsn, result).clean
|
||||
@@ -190,6 +190,7 @@ def test_cli_supported_combinations_are_accepted(tmp_path):
|
||||
"window": ["--window-reroute", "1"],
|
||||
"vias": ["--reduce-vias"],
|
||||
"via_cost": ["--via-cost", "40"],
|
||||
"smooth": ["--smooth"],
|
||||
}
|
||||
for engine, options in ENGINE_OPTIONS.items():
|
||||
for option in options:
|
||||
|
||||
Reference in New Issue
Block a user