#!/usr/bin/env python3 """One-time generator: capture the hand-drawn geometry from route_power_stage.py and finalize_routing.py into hardware/power_routes.json and hardware/critical_routes.json, which the project-independent `pcbrouter fixed` loader consumes. This is a development tool, not part of the routing flow. It uses pcbnew only to resolve the pad anchors the scripts reference; the emitted JSON is plain data. The JSON files are already committed; you only need this to regenerate them after changing route_power_stage.py / finalize_routing.py. It reads the intermediate boards board_prerouted.kicad_pcb and board_power_local.kicad_pcb, which are git-ignored pipeline artifacts. If they are not present, regenerate them first by running steps 1-4 of scripts/README.md (preroute_bga -> route_power_stage -> stitch_plane_pads -> route_local_power). /usr/bin/python3 scripts/_extract_fixed_routes.py """ from __future__ import annotations import json from pathlib import Path import pcbnew ROOT = Path(__file__).resolve().parents[1] def mm(v: int) -> float: return round(pcbnew.ToMM(v), 4) class Capture: def __init__(self) -> None: self.tracks: list[dict] = [] self.vias: list[dict] = [] def track(self, net, a, b, width, layer_name): if a == b: return self.tracks.append({ "net": net, "layer": layer_name, "width": round(width, 4), "points": [[round(a[0], 4), round(a[1], 4)], [round(b[0], 4), round(b[1], 4)]], }) def via(self, net, x, y, dia=0.45, drill=0.225): self.vias.append({"net": net, "at": [round(x, 4), round(y, 4)], "diameter": round(dia, 4), "drill": round(drill, 4)}) def as_json(self) -> dict: # merge consecutive same-net/same-layer/same-width segments into polylines merged: list[dict] = [] for t in self.tracks: if (merged and merged[-1]["net"] == t["net"] and merged[-1]["layer"] == t["layer"] and abs(merged[-1]["width"] - t["width"]) < 1e-6 and merged[-1]["points"][-1] == t["points"][0]): merged[-1]["points"].append(t["points"][1]) else: merged.append({"net": t["net"], "layer": t["layer"], "width": t["width"], "points": list(t["points"])}) return {"tracks": merged, "vias": self.vias} def layer_name(board, layer_id): return board.GetLayerName(layer_id) def patch_power_stage(cap: Capture, board): import route_power_stage as rps def add_track(b, net, start, end, width, layer=pcbnew.F_Cu): cap.track(net, start, end, width, layer_name(board, layer)) def add_via(b, net, x, y, diameter=0.70, drill=0.35): cap.via(net, x, y, diameter, drill) rps.add_track = add_track rps.add_via = add_via rps.add_path = lambda b, net, pts, w, layer=pcbnew.F_Cu: [ add_track(b, net, s, e, w, layer) for s, e in zip(pts, pts[1:]) ] def patch_finalize(cap: Capture, board): import finalize_routing as fr def add_segment(b, net, start, end, width, layer=pcbnew.F_Cu, locked=True): cap.track(net, start, end, width, layer_name(board, layer)) def add_polyline(b, net, verts, width, layer=pcbnew.F_Cu): for s, e in zip(verts, verts[1:]): add_segment(b, net, s, e, width, layer) def add_via(b, net, position, diameter=0.45, drill=0.225): cap.via(net, position[0], position[1], diameter, drill) fr.add_segment = add_segment fr.add_polyline = add_polyline fr.add_via = add_via def main() -> None: for stem in ("board_prerouted", "board_power_local"): if not (ROOT / "hardware" / f"{stem}.kicad_pcb").exists(): raise SystemExit( f"hardware/{stem}.kicad_pcb missing - regenerate the pipeline " "intermediates first (steps 1-4 of scripts/README.md).") # -- power routes ------------------------------------------------------- board = pcbnew.LoadBoard(str(ROOT / "hardware" / "board_prerouted.kicad_pcb")) cap = Capture() patch_power_stage(cap, board) import route_power_stage as rps rps.route_core_buck(board) rps.route_small_buck(board, regulator="U14", inductor="L2", input_caps=("C26", "C27"), output_caps=("C28", "C29"), sw_net="BUCK1V8_SW", out_net="VCC1V8", input_x=112.98, plane_via_x=120.105) rps.route_small_buck(board, regulator="U15", inductor="L3", input_caps=("C30", "C31"), output_caps=("C32", "C33"), sw_net="BUCK3V3_SW", out_net="VCC3V3", input_x=127.98, plane_via_x=135.105) (ROOT / "hardware" / "power_routes.json").write_text( json.dumps(cap.as_json(), indent=1) + "\n") print(f"power_routes.json: {len(cap.as_json()['tracks'])} polylines, {len(cap.vias)} vias") # -- critical (RF / USB) routes + rule areas -------------------------- board = pcbnew.LoadBoard(str(ROOT / "hardware" / "board_power_local.kicad_pcb")) cap = Capture() patch_finalize(cap, board) import finalize_routing as fr fr.route_rf(board) fr.route_usb(board) data = cap.as_json() data["rule_areas"] = [ {"name": "USB_ESD_BREAKOUT", "layers": ["F.Cu"], "rect": [29.90, 50.70, 32.10, 53.10]}, {"name": "RF_NO_DIGITAL", "layers": ["B.Cu", "In2.Cu", "In4.Cu", "In5.Cu"], "rect": [152.00, 36.00, 174.50, 43.70]}, ] fence = [[x, 38.7] for x in range(153, 174, 2)] + [[x, 45.0] for x in range(161, 174, 2)] data["ground_zones"] = [{ "net": "GND", "layer": "F.Cu", "clearance": 0.20, "min_thickness": 0.10, "priority": 100, "outline": [[152.0, 36.0], [174.5, 36.0], [174.5, 49.5], [152.0, 49.5]], "via_fence": fence, }] (ROOT / "hardware" / "critical_routes.json").write_text(json.dumps(data, indent=1) + "\n") print(f"critical_routes.json: {len(data['tracks'])} polylines, {len(cap.vias)} vias, " f"{len(data['rule_areas'])} rule areas, fence {len(fence)}") if __name__ == "__main__": main()