"""Via reduction: each net with vias is routed again with vias made dear, kept only when it fails nothing new and has fewer vias.""" from __future__ import annotations from rpmroute.board import build_board from rpmroute.dsn import parse_dsn from rpmroute.geometry import IntPoint from rpmroute.route.grid_router import RouteResult from rpmroute.route.pipeline import ( _rule_clearance_dsn, _rule_width_dsn, _via_half_board, route_dsn_board_exact, ) from rpmroute.route.vias import reduce_vias VIA = "Via[0-1]_600:300_um" def _board(second_on_bottom: bool = False) -> str: """Two pads 14 mm apart on an empty two-layer board; the second on the bottom layer when ``second_on_bottom``.""" p2_stack = "Bot_Pad" if second_on_bottom else "Top_Pad" return f"""(pcb "vias.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 20000 0 20000 -10000 0 -10000 0 0)) (via "{VIA}") (rule (width 200) (clearance 200)) ) (library (padstack Top_Pad (shape (rect F.Cu -300 -300 300 300)) (attach off)) (padstack Bot_Pad (shape (rect B.Cu -300 -300 300 300)) (attach off)) (padstack "{VIA}" (shape (circle F.Cu 600)) (shape (circle B.Cu 600)) (attach off)) (image TOP (pin Top_Pad 1 0 0)) (image SECOND (pin {p2_stack} 1 0 0)) ) (placement (component TOP (place P1 3000 -5000 front 0)) (component SECOND (place P2 17000 -5000 front 0)) ) (network (net N (pins P1-1 P2-1)) (class default (rule (width 200) (clearance 200))) ) (wiring) ) """ def _route_args(dsn) -> dict: scale = max(dsn.resolution.value, 1) return { "heuristic_weight": 1.0, "via_half": _via_half_board(dsn, scale), "edge_clearance": None, "trace_width": round(_rule_width_dsn(dsn) * scale), "clearance": round(_rule_clearance_dsn(dsn) * scale), "layers": None, "max_passes": 10, "rip_up": True, "shove": False, "diagonal": False, "rollback": True, "tree_nets": True, "net_widths": {}, "via_drill": None, "hole_to_hole": 0, "via_cost": 10.0, } def _reduce(text: str, result: RouteResult) -> RouteResult: dsn = parse_dsn(text) args = _route_args(dsn) return reduce_vias( dsn, result, args, resolution=1, margin=20000, via_half=args["via_half"], via_drill=2 * args["via_half"], via_name=VIA, ) def _pins(text: str) -> tuple[IntPoint, IntPoint]: pins = {p.component_ref: p.location for p in build_board(parse_dsn(text)).get_pins()} return pins["P1"], pins["P2"] def test_a_needless_via_pair_is_routed_out(): text = _board() a, b = _pins(text) # a detour: down to the bottom layer and back up, for no reason v1, v2 = IntPoint(a.x + 40000, a.y), IntPoint(b.x - 40000, b.y) detour = RouteResult(1000) detour.add_wire(1, 0, [a, v1]) detour.add_via(1, v1) detour.add_wire(1, 1, [v1, v2]) detour.add_via(1, v2) detour.add_wire(1, 0, [v2, b]) out = _reduce(text, detour) assert out.failed == [] assert sum(len(v) for v in out.vias.values()) == 0 assert out.wires.get(1) def test_a_needed_via_stays(): text = _board(second_on_bottom=True) exact, _scale, _layers = route_dsn_board_exact(parse_dsn(text), resolution=1) routed = exact.result assert routed.failed == [] vias_before = sum(len(set(v)) for v in routed.vias.values()) assert vias_before >= 1 out = _reduce(text, routed) assert out.failed == [] assert sum(len(set(v)) for v in out.vias.values()) == vias_before def test_the_flag_runs_the_stage_end_to_end(): exact, _scale, _layers = route_dsn_board_exact(parse_dsn(_board()), resolution=1, reduce_vias=True) assert exact.result.failed == []