"""Routing behaviours found on the TerraHawk rc4 boards (2026-09-27).""" from __future__ import annotations import math import pytest from freeroute.board import build_board from freeroute.dsn import parse_dsn from freeroute.route.pipeline import route_dsn_board_exact # 20 x 20 mm board whose corners are rounded with a 3 mm radius, as polylines _R = 3000.0 _arc = [(_R - _R * math.cos(math.radians(a)), _R - _R * math.sin(math.radians(a))) for a in range(0, 91, 15)] def _outline_coords(): pts = [] for cx, cy, sx, sy in ((0, 0, 1, 1), (20000, 0, -1, 1), (20000, -20000, -1, -1), (0, -20000, 1, -1)): corner = [(cx + sx * x, cy - sy * y) for x, y in _arc] pts.extend(corner if sx * sy > 0 else corner[::-1]) return " ".join(f"{x:.1f} {y:.1f}" for x, y in pts) def _dsn(placement, network, extra=""): return f"""(pcb t (parser) (resolution um 10) (unit um) (structure (layer F.Cu (type signal)) (layer B.Cu (type signal)) (boundary (path pcb 0 {_outline_coords()})) (via "Via[0-1]_600:300_um") (rule (width 250) (clearance 150)) {extra}) (placement {placement}) (library (image P (pin pad 1 0 0)) (padstack pad (shape (rect F.Cu -150 -500 150 500)) (attach off)) (padstack "Via[0-1]_600:300_um" (shape (circle F.Cu 600)) (shape (circle B.Cu 600)) (attach off))) (network {network}))""" def test_routes_keep_edge_clearance_at_a_rounded_corner(): # two pads near the top-left corner: the direct path cuts the corner arc text = _dsn("(component P (place A 1800 -6000 front 0) (place B 6000 -1800 front 90))", "(net N (pins A-1 B-1))") dsn = parse_dsn(text) result, scale, _ = route_dsn_board_exact(dsn, edge_clearance_mm=0.5) assert result.routed_net_numbers outline = build_board(dsn).outline half = 125 * scale for segs in result.result.wires.values(): for _layer, pts in segs: for p in pts: # every trace point, plus half the trace, keeps 0.5 mm to the edge assert outline.edge_distance(p) >= half + 500 * scale - 1 @pytest.mark.xfail( strict=True, reason="known gap: the grid step (trace + clearance, 0.4 mm here) does not line up " "with a 0.5 mm pin row, so only the outer pins get a column; the FreeRouting JAR, " "being gridless, routes all five. This is most of the gap on an LQFP.", ) def test_every_pin_of_a_half_millimetre_pitch_row_escapes(): # five pads at 0.5 mm pitch, each wired to its own pad 8 mm away places = " ".join(f"(place R{i} {6000 + 500 * i} -6000 front 0)" for i in range(5)) places += " " + " ".join(f"(place F{i} {6000 + 500 * i} -14000 front 0)" for i in range(5)) nets = " ".join(f"(net N{i} (pins R{i}-1 F{i}-1))" for i in range(5)) result, _, _ = route_dsn_board_exact(parse_dsn(_dsn(f"(component P {places})", nets))) assert len(result.routed_net_numbers) == 5