53 lines
1.9 KiB
Python
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()
|