1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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()
|