Files
pcb/scripts/prepare_freerouting_dsn.py

53 lines
1.9 KiB
Python

#!/usr/bin/env python3
"""Compensate Freerouting 2.3.0's KiCad-10 DSN clearance scale mismatch.
KiCad exports widths and clearances in micrometres. Freerouting 2.3.0 keeps
the widths but internally imports DSN clearance values at one quarter of the
intended physical value. Scaling only ``(clearance N)`` entries by four makes
the router's physical rules match the source KiCad board. Coordinates, track
widths, vias, drills, padstacks, and keepouts are deliberately untouched.
"""
from __future__ import annotations
import argparse
import re
from pathlib import Path
CLEARANCE = re.compile(r"(\(clearance\s+)(\d+(?:\.\d+)?)")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("input", type=Path)
parser.add_argument("output", type=Path)
parser.add_argument("--factor", type=float, default=4.0)
args = parser.parse_args()
source = args.input.read_text()
original = [float(match.group(2)) for match in CLEARANCE.finditer(source)]
if not original:
raise RuntimeError(f"No clearance rules found in {args.input}")
def scale(match: re.Match[str]) -> str:
value = float(match.group(2)) * args.factor
rendered = str(int(value)) if value.is_integer() else f"{value:g}"
return match.group(1) + rendered
converted, count = CLEARANCE.subn(scale, source)
scaled = [float(match.group(2)) for match in CLEARANCE.finditer(converted)]
assert count == len(original) == len(scaled)
assert all(after == before * args.factor
for before, after in zip(original, scaled, strict=True))
# A conversion must never touch the board geometry or trace-width rules.
assert source.count("(width ") == converted.count("(width ")
assert source.count("(polygon ") == converted.count("(polygon ")
args.output.write_text(converted)
print(f"Scaled {count} clearance rules by {args.factor:g}x")
print(f"Saved {args.output}")
if __name__ == "__main__":
main()