diff --git a/generate_scaled_footprints.py b/generate_scaled_footprints.py new file mode 100644 index 0000000..8ef95e7 --- /dev/null +++ b/generate_scaled_footprints.py @@ -0,0 +1,115 @@ +#/usr/bin/env python3 + +import re +import tempfile +import os +import subprocess +from pathlib import Path + +from bs4 import BeautifulSoup +import click + +default_widths = '3mm,5mm,8mm,10mm,12mm,15mm,18mm,20mm,25mm,30mm,35mm,40mm,45mm,50mm,60mm,70mm,80mm,90mm,100mm,120mm,150mm' + + +# Mostly from https://www.w3.org/TR/css-values/#absolute-lengths +UNIT_FACTORS = { + 'm': 1000, + 'cm': 10, + 'mm': 1, + 'Q': 1/4, + 'in': 25.4, + 'mil': 25.4/1000, + 'pc': 25.4/6, + 'pt': 25.4/72, + 'px': 25.4/96, + } + +def parse_length(foo, default_unit=None): + ''' Parse given physical length, and return result converted to mm. ''' + + match = re.fullmatch(r'(.*?)(m|cm|mm|Q|in|mil|pc|pt|px|)', foo.strip().lower()) + if not match: + raise ValueError(f'Invalid length "{foo}"') + num, unit = match.groups() + + if not unit: + if default_unit: + unit = default_unit + else: + raise ValueError(f'Unit missing from length "{foo}"') + + return float(num) * UNIT_FACTORS[unit] + + +@click.command() +@click.option('--width') +@click.option('--height') +@click.option('--sexp-layer', default='F.SilkS') +@click.option('--basename', help='Base name for generated symbols and library') +@click.argument('input_svg') +def export(width, height, basename, sexp_layer, input_svg): + svg_flatten = str(Path(os.environ.get('SVG_FLATTEN', 'svg-flatten')).expanduser()) + usvg = str(Path(os.environ.get('USVG', 'usvg')).expanduser()) + + if not basename: + match = re.fullmatch(r'(.*?)(([-_.][0-9.,]+)(m|cm|mm|Q|in|mil|pc|pt|px|))?', Path(input_svg).stem) + basename, *rest = match.groups() + print(f'No --basename given. Using "{basename}"') + + export_width, export_height = width, height + if not export_width or export_height: + export_width = default_widths + + elif export_width and export_height: + raise click.ClickException('Only one of --width or --height must be given.') + + if export_width: + targets = export_width + axis = 'width' + else: + targets = export_height + axis = 'height' + + # Determine input document size + with tempfile.NamedTemporaryFile() as f: + try: + subprocess.run([usvg, input_svg, f.name], check=True) + except FileNotFoundError: + raise click.ClickException('Cannot find usvg binary in PATH. You can give a custom path to the usvg binary by setting the USVG environment variable.') + + soup = BeautifulSoup(f.read(), features='xml') + svg = soup.find('svg') + doc_w_mm, doc_h_mm = parse_length(svg['width'], default_unit='px'), parse_length(svg['height'], default_unit='px') + + print(f'Input file has dimensions width {doc_w_mm:.1f} mm by height {doc_h_mm:.1f} mm') + + outdir = Path(f'{basename}.pretty') + outdir.mkdir(exist_ok=True) + + for target_length in targets.split(','): + target_length = parse_length(target_length, default_unit='mm') + + if axis == 'width': + scaling_factor = target_length / doc_w_mm + else: + scaling_factor = target_length / doc_h_mm + + instance_name = f'{basename}_{target_length:.1f}mm' + outfile = outdir / f'{instance_name}.kicad_mod' + print(f'{outfile}: Scaling to target {axis} {target_length:.1f} mm using scaling factor {scaling_factor:.3f}') + + try: + proc = subprocess.run([svg_flatten, + '-o', 'sexp', + '--sexp-layer', sexp_layer, + '--sexp-mod-name', instance_name, + '--scale', str(scaling_factor), + input_svg], check=True, capture_output=True) + outfile.write_bytes(proc.stdout) + except FileNotFoundError: + raise click.ClickException('Cannot find svg-flatten binary in PATH. You can give a custom path to the svg-flatten binary by setting the SVG_FLATTEN environment variable.') + + +if __name__ == '__main__': + export()