Don't apply dilation scripts in convert, add paste test

wip
jaseg 2023-04-02 23:18:03 +02:00
rodzic e1c40e8c80
commit be24d0368f
16 zmienionych plików z 33884 dodań i 68 usunięć

Wyświetl plik

@ -16,7 +16,6 @@ from zipfile import ZipFile, is_zipfile
from pathlib import Path
from bs4 import BeautifulSoup
from lxml import etree
import numpy as np
import click
@ -71,7 +70,7 @@ def paste(input_gerbers, input_svg, output_gerbers, is_zip,
run_cargo_command('usvg', *shlex.split(os.environ.get('USVG_OPTIONS', '')), input_svg, processed_svg.name)
with open(processed_svg.name) as f:
soup = BeautifulSoup(f.read(), features='lxml')
soup = BeautifulSoup(f.read(), features='xml')
for (side, use), layer in [
*stack.graphic_layers.items(),
@ -245,8 +244,7 @@ def empty_template(output_svg, size, force, copper_layers, no_default_layers, la
@click.option('--composite-drill-file/--separate-drill-file', 'composite_drill', help='Use Altium composite Excellon drill file format (default)')
@click.option('--dilate', default=0.1, type=float, help='Default dilation for subtraction operations in mm')
@click.option('--curve-tolerance', type=float, help='Tolerance for curve flattening in mm')
@click.option('--no-subtract', 'no_subtract', flag_value=True, help='Disable subtraction')
@click.option('--subtract', help='Use user subtraction script from argument (see description above)')
@click.option('--subtract', help='Use user subtraction script from argument (default for "convert": none)')
@click.option('--trace-space', type=float, default=0.1, help='passed through to svg-flatten')
@click.option('--vectorizer', help='passed through to svg-flatten')
@click.option('--vectorizer-map', help='passed through to svg-flatten')
@ -254,76 +252,89 @@ def empty_template(output_svg, size, force, copper_layers, no_default_layers, la
@click.option('--circle-test-tolerance', help='passed through to svg-flatten')
@click.option('--pattern-complete-tiles-only', is_flag=True, help='passed through to svg-flatten')
@click.option('--use-apertures-for-patterns', is_flag=True, help='passed through to svg-flatten')
def convert(input_svg, output_gerbers, is_zip, dilate, curve_tolerance, no_subtract, subtract, trace_space, vectorizer,
@click.option('--log-level', default='info', type=click.Choice(['debug', 'info', 'warning', 'error', 'critical']), help='log level')
def convert(input_svg, output_gerbers, is_zip, dilate, curve_tolerance, subtract, trace_space, vectorizer,
vectorizer_map, exclude_groups, composite_drill, naming_scheme, circle_test_tolerance,
pattern_complete_tiles_only, use_apertures_for_patterns):
pattern_complete_tiles_only, use_apertures_for_patterns, log_level):
''' Convert SVG file directly to gerbers.
Unlike `gerbolyze paste`, this does not add the SVG's contents to existing gerbers. It allows you to directly create
PCBs using Inkscape similar to PCBModE.
'''
logging.basicConfig(level=getattr(logging, log_level.upper()))
subtract_map = parse_subtract_script('' if no_subtract else subtract, dilate, default_script=DEFAULT_CONVERT_SUB_SCRIPT)
subtract_map = parse_subtract_script(subtract, dilate, default_script='')
output_is_zip = output_gerbers.name.lower().endswith('.zip') if is_zip is None else is_zip
stack = gn.LayerStack({}, None, None, [], board_name=input_svg.stem, original_path=input_svg)
with tempfile.NamedTemporaryFile(suffix='.svg') as processed_svg:
run_cargo_command('usvg', *shlex.split(os.environ.get('USVG_OPTIONS', '')), input_svg, processed_svg.name)
for group_id, label in get_layers_from_svg(input_svg.read_text()):
if not group_id or not label or 'no export' in label:
continue
soup = BeautifulSoup(input_svg.read_text(), features='xml')
layers = {e.get('id'): e.get('inkscape:label') for e in soup.find_all('g', recursive=True)}
if label == 'outline':
side, use = 'mechanical', 'outline'
elif label == 'comments':
side, use = 'other', 'comments'
elif len(label.split()) != 2:
warnings.warn('Unknown layer {label}')
continue
else:
side, use = label.split()
stack = gn.LayerStack({}, None, None, [], board_name=input_svg.stem, original_path=input_svg)
grb = svg_to_gerber(input_svg,
trace_space=trace_space, vectorizer=vectorizer, vectorizer_map=vectorizer_map,
exclude_groups=exclude_groups, curve_tolerance=curve_tolerance, only_groups=group_id,
circle_test_tolerance=circle_test_tolerance, pattern_complete_tiles_only=pattern_complete_tiles_only,
use_apertures_for_patterns=(use_apertures_for_patterns and use not in ('outline', 'drill')),
outline_mode=(use == 'outline' or side == 'drill'))
grb.original_path = Path()
for group_id, label in layers.items():
label = label or ''
if not group_id or 'no export' in label:
continue
if side == 'drill':
if use == 'plated':
stack.drill_pth = grb.to_excellon(plated=True)
elif use == 'nonplated':
stack.drill_npth = grb.to_excellon(plated=False)
if not group_id.startswith('g-'):
continue
group_id = group_id[2:]
if group_id == 'outline':
side, use = 'mechanical', 'outline'
elif group_id == 'comments':
side, use = 'other', 'comments'
elif len(group_id.split('-')) != 2:
warnings.warn(f'Unknown layer {group_id}')
continue
else:
warnings.warn(f'Invalid drill layer type "{side}". Must be one of "plated" or "nonplated"')
side, use = group_id.split('-')
grb = svg_to_gerber(processed_svg.name, no_usvg=True,
trace_space=trace_space, vectorizer=vectorizer, vectorizer_map=vectorizer_map,
exclude_groups=exclude_groups, curve_tolerance=curve_tolerance, only_groups=f'g-{group_id}',
circle_test_tolerance=circle_test_tolerance, pattern_complete_tiles_only=pattern_complete_tiles_only,
use_apertures_for_patterns=(use_apertures_for_patterns and use not in ('outline', 'drill')),
outline_mode=(use == 'outline' or side == 'drill'))
grb.original_path = Path()
if side == 'drill':
if use == 'plated':
stack.drill_pth = grb.to_excellon(plated=True)
elif use == 'nonplated':
stack.drill_npth = grb.to_excellon(plated=False)
else:
warnings.warn(f'Invalid drill layer type "{side}". Must be one of "plated" or "nonplated"')
else:
stack.graphic_layers[(side, use)] = grb
bounds = stack.board_bounds()
@functools.lru_cache()
def do_dilate(layer, amount):
return dilate_gerber(layer, bounds, amount, curve_tolerance)
for (side, use), layer in stack.graphic_layers.items():
# dilated subtract layers on top of overlay
if side in ('top', 'bottom'): # do not process subtraction scripts for inner layers
dilations = subtract_map.get(use, [])
for d_layer, amount in dilations:
d_layer = stack.graphic_layers[(side, d_layer)]
dilated = do_dilate(d_layer, amount)
layer.merge(dilated, mode='above', keep_settings=True)
if composite_drill:
logging.info('Merging drill layers...')
stack.merge_drill_layers()
naming_scheme = getattr(gn.layers.NamingScheme, naming_scheme)
if output_is_zip:
stack.save_to_zipfile(output_gerbers, naming_scheme=naming_scheme)
else:
stack.graphic_layers[(side, use)] = grb
bounds = stack.board_bounds()
@functools.lru_cache()
def do_dilate(layer, amount):
return dilate_gerber(layer, bounds, amount, curve_tolerance)
for (side, use), layer in stack.graphic_layers.items():
# dilated subtract layers on top of overlay
if side in ('top', 'bottom'): # do not process subtraction scripts for inner layers
dilations = subtract_map.get(use, [])
for d_layer, amount in dilations:
d_layer = stack.graphic_layers[(side, d_layer)]
dilated = do_dilate(d_layer, amount)
layer.merge(dilated, mode='above', keep_settings=True)
if composite_drill:
logging.info('Merging drill layers...')
stack.merge_drill_layers()
naming_scheme = getattr(gn.layers.NamingScheme, naming_scheme)
if output_is_zip:
stack.save_to_zipfile(output_gerbers, naming_scheme=naming_scheme)
else:
stack.save_to_directory(output_gerbers, naming_scheme=naming_scheme)
stack.save_to_directory(output_gerbers, naming_scheme=naming_scheme)
# Subtraction script handling
@ -579,15 +590,5 @@ def svg_to_gerber(infile, outline_mode=False, **kwargs):
return gn.rs274x.GerberFile.open(temp_gbr.name)
def get_layers_from_svg(svg_data):
svg = etree.fromstring(svg_data.encode('utf-8'))
SVG_NS = '{http://www.w3.org/2000/svg}'
INKSCAPE_NS = '{http://www.inkscape.org/namespaces/inkscape}'
# find groups
for group in svg.findall(SVG_NS+'g'):
yield group.get('id'), group.get(INKSCAPE_NS+'label')
if __name__ == '__main__':
cli()

Wyświetl plik

@ -0,0 +1,29 @@
G04 Gerber file generated by Gerbonara*
%MOMM*%
%FSLAX46Y46*%
%IPPOS*%
G75
%LPD*%
%AMGNC*
1,1,$1,0,0,-57.29578X$4*
1,0,$2,0,0,0*
21,0,$2,$3,0,0,-57.29578X$4*
%
%AMGNR*
21,1,$1,$2,0,0,$5X-57.29578*
1,0,$3,0,0,0*
21,0,$3,$4,0,0,$5X-57.29578*
%
%AMGNO*
21,1,$1,$2,0,0,$5X-57.29578*
1,1,$2,$1/2,0,$5X-57.29578*
1,1,$2,(0-$1)/2,0,$5X-57.29578*
1,0,$3,0,0,0*
21,0,$3,$4,0,0,$5X-57.29578*
%
%AMGNP*
5,1,$2,0,0,$1,$3X-57.29578*
1,0,$4,0,0,0*
%
%ADD10C,0.05*%
M02*

Wyświetl plik

@ -0,0 +1,154 @@
; XNC file generated by gerbonara
M48
METRIC
T01C0000.50000
%
T01
G05
X0006.40777Y0046.75991
X0006.40777Y0046.00921
X0006.40777Y0045.25852
X0006.40777Y0044.50783
X0006.40777Y0043.75713
X0006.40777Y0043.00644
X0006.40777Y0042.25575
X0007.15847Y0046.00921
X0007.90916Y0045.25852
X0007.90916Y0044.50783
X0007.90916Y0043.75713
X0008.65985Y0043.00644
X0009.41055Y0046.75991
X0009.41055Y0046.00921
X0009.41055Y0045.25852
X0009.41055Y0044.50783
X0009.41055Y0043.75713
X0009.41055Y0043.00644
X0009.41055Y0042.25575
X0011.65588Y0046.00921
X0011.65588Y0045.25852
X0011.65588Y0044.50783
X0011.65588Y0043.75713
X0011.65588Y0043.00644
X0012.40657Y0046.75991
X0012.40657Y0042.25575
X0013.15726Y0046.75991
X0013.15726Y0042.25575
X0013.90796Y0046.75991
X0013.90796Y0042.25575
X0014.65865Y0046.00921
X0014.65865Y0045.25852
X0014.65865Y0044.50783
X0014.65865Y0043.75713
X0014.65865Y0043.00644
X0016.91003Y0046.75991
X0016.91003Y0046.00921
X0016.91003Y0045.25852
X0016.91003Y0044.50783
X0016.91003Y0043.75713
X0016.91003Y0043.00644
X0016.91003Y0042.25575
X0017.66073Y0046.00921
X0018.41142Y0045.25852
X0018.41142Y0044.50783
X0018.41142Y0043.75713
X0019.16211Y0043.00644
X0019.91281Y0046.75991
X0019.91281Y0046.00921
X0019.91281Y0045.25852
X0019.91281Y0044.50783
X0019.91281Y0043.75713
X0019.91281Y0043.00644
X0019.91281Y0042.25575
X0022.37703Y0046.75991
X0022.37703Y0046.00921
X0022.37703Y0045.25852
X0022.37703Y0044.50783
X0022.37703Y0043.75713
X0022.37703Y0043.00644
X0022.37703Y0042.25575
X0023.12773Y0046.75991
X0023.12772Y0044.50783
X0023.87842Y0046.75991
X0023.87842Y0044.50783
X0024.62911Y0046.75991
X0024.62911Y0044.50783
X0025.37981Y0046.00921
X0025.37981Y0045.25852
X0027.62514Y0046.75991
X0027.62514Y0046.00921
X0027.62514Y0045.25852
X0027.62514Y0044.50783
X0027.62514Y0043.75713
X0027.62514Y0043.00644
X0027.62514Y0042.25575
X0028.37583Y0042.25575
X0029.12652Y0042.25575
X0029.87721Y0042.25575
X0030.62791Y0042.25575
X0032.87324Y0046.00921
X0032.87324Y0045.25852
X0032.87324Y0044.50783
X0032.87324Y0043.75713
X0032.87324Y0043.00644
X0032.87324Y0042.25575
X0033.62393Y0046.75991
X0033.62393Y0043.75713
X0034.37463Y0046.75991
X0034.37463Y0043.75713
X0035.12532Y0046.75991
X0035.12532Y0043.75713
X0035.87601Y0046.00921
X0035.87601Y0045.25852
X0035.87601Y0044.50783
X0035.87601Y0043.75713
X0035.87601Y0043.00644
X0035.87601Y0042.25575
X0038.12134Y0046.75991
X0038.87204Y0046.75991
X0039.62273Y0046.75991
X0039.62273Y0046.00921
X0039.62273Y0045.25852
X0039.62273Y0044.50783
X0039.62273Y0043.75713
X0039.62273Y0043.00644
X0039.62273Y0042.25575
X0040.37342Y0046.75991
X0041.12412Y0046.75991
X0043.36945Y0046.75991
X0043.36945Y0046.00921
X0043.36945Y0045.25852
X0043.36945Y0044.50783
X0043.36945Y0043.75713
X0043.36945Y0043.00644
X0043.36945Y0042.25575
X0044.12014Y0046.75991
X0044.12014Y0044.50783
X0044.12014Y0042.25575
X0044.87083Y0046.75991
X0044.87083Y0044.50783
X0044.87083Y0042.25575
X0045.62153Y0046.75991
X0045.62153Y0044.50783
X0045.62153Y0042.25575
X0046.37222Y0046.75991
X0046.37222Y0044.50783
X0046.37222Y0042.25575
X0048.61755Y0046.75991
X0048.61755Y0046.00921
X0048.61755Y0045.25852
X0048.61755Y0044.50783
X0048.61755Y0043.75713
X0048.61755Y0043.00644
X0048.61755Y0042.25575
X0049.36825Y0046.75991
X0049.36825Y0042.25575
X0050.11894Y0046.75991
X0050.11894Y0042.25575
X0050.86963Y0046.75991
X0050.86963Y0042.25575
X0051.62033Y0046.00921
X0051.62033Y0045.25852
X0051.62033Y0044.50783
X0051.62033Y0043.75713
X0051.62033Y0043.00644
M30

Wyświetl plik

@ -0,0 +1,136 @@
; XNC file generated by gerbonara
M48
METRIC
T01C0000.70000
%
T01
G05
X0006.50749Y0058.18246
X0007.85409Y0058.18246
X0007.85409Y0057.13262
X0007.85409Y0056.08279
X0007.85409Y0055.03295
X0007.85409Y0053.98311
X0007.85409Y0052.93327
X0007.85409Y0051.88343
X0008.90393Y0058.18246
X0008.90393Y0057.13262
X0008.90393Y0056.08279
X0008.90393Y0055.03295
X0008.90393Y0053.98311
X0008.90393Y0052.93327
X0008.90393Y0051.88343
X0009.95377Y0058.18246
X0009.95377Y0057.13262
X0009.95377Y0056.08279
X0009.95377Y0055.03295
X0009.95377Y0053.98311
X0009.95377Y0052.93327
X0009.95377Y0051.88343
X0011.00360Y0058.18246
X0011.00360Y0057.13262
X0011.00360Y0056.08279
X0011.00360Y0055.03295
X0011.00360Y0053.98311
X0011.00360Y0052.93327
X0011.00360Y0051.88343
X0012.05344Y0058.18246
X0012.05344Y0057.13262
X0012.05344Y0056.08279
X0012.05344Y0055.03295
X0012.05344Y0053.98311
X0012.05344Y0052.93327
X0012.05344Y0051.88343
X0014.85905Y0058.18246
X0014.85905Y0057.13262
X0014.85905Y0056.08279
X0014.85905Y0055.03295
X0014.85905Y0053.98311
X0014.85905Y0052.93327
X0014.85905Y0051.88343
X0015.90889Y0058.18246
X0015.90889Y0055.03295
X0016.95873Y0058.18246
X0016.95873Y0055.03295
X0018.00857Y0058.18246
X0018.00857Y0055.03295
X0019.05841Y0057.13262
X0019.05841Y0056.08279
X0022.19849Y0058.18246
X0022.19849Y0057.13262
X0022.19849Y0056.08279
X0022.19849Y0055.03295
X0022.19849Y0053.98311
X0022.19849Y0052.93327
X0022.19849Y0051.88343
X0023.24833Y0051.88343
X0024.29816Y0051.88343
X0025.34800Y0051.88343
X0026.39784Y0051.88343
X0029.53792Y0057.13262
X0029.53792Y0056.08279
X0029.53792Y0055.03295
X0029.53792Y0053.98311
X0029.53792Y0052.93327
X0029.53792Y0051.88343
X0030.58776Y0058.18246
X0030.58776Y0053.98311
X0031.63760Y0058.18246
X0031.63760Y0053.98311
X0032.68744Y0058.18246
X0032.68744Y0053.98311
X0033.73728Y0057.13262
X0033.73728Y0056.08279
X0033.73728Y0055.03295
X0033.73728Y0053.98311
X0033.73728Y0052.93327
X0033.73728Y0051.88343
X0036.87735Y0058.18246
X0037.92719Y0058.18246
X0038.97703Y0058.18246
X0038.97703Y0057.13262
X0038.97703Y0056.08279
X0038.97703Y0055.03295
X0038.97703Y0053.98311
X0038.97703Y0052.93327
X0038.97703Y0051.88343
X0040.02687Y0058.18246
X0041.07671Y0058.18246
X0044.21679Y0058.18246
X0044.21679Y0057.13262
X0044.21679Y0056.08279
X0044.21679Y0055.03295
X0044.21679Y0053.98311
X0044.21679Y0052.93327
X0044.21679Y0051.88343
X0045.26663Y0058.18246
X0045.26663Y0055.03295
X0045.26663Y0051.88343
X0046.31647Y0058.18246
X0046.31647Y0055.03295
X0046.31647Y0051.88343
X0047.36631Y0058.18246
X0047.36631Y0055.03295
X0047.36631Y0051.88343
X0048.41615Y0058.18246
X0048.41615Y0055.03295
X0048.41615Y0051.88343
X0051.55622Y0058.18246
X0051.55622Y0057.13262
X0051.55622Y0056.08279
X0051.55622Y0055.03295
X0051.55622Y0053.98311
X0051.55622Y0052.93327
X0051.55622Y0051.88343
X0052.60606Y0058.18246
X0052.60606Y0051.88343
X0053.65590Y0058.18246
X0053.65590Y0051.88343
X0054.70574Y0058.18246
X0054.70574Y0051.88343
X0055.75558Y0057.13262
X0055.75558Y0056.08279
X0055.75558Y0055.03295
X0055.75558Y0053.98311
X0055.75558Y0052.93327
M30

File diff suppressed because one or more lines are too long

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 521 KiB

File diff suppressed because one or more lines are too long

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 192 KiB

Wyświetl plik

@ -16,6 +16,7 @@
#
import sys
import math
import subprocess
import tempfile
from pathlib import Path
@ -66,6 +67,41 @@ def test_template(reference):
run_command('python3', '-m', 'gerbolyze', 'template', '--top', '--force', infile, out_svg.name)
run_command('python3', '-m', 'gerbolyze', 'template', '--bottom', '--force', '--vector', infile, out_svg.name)
def test_paste():
in_gerbers = reference_path('layers-gerber')
top_overlay = reference_path('tpl-top.svg')
bottom_overlay = reference_path('tpl-bottom.svg')
with tempfile.TemporaryDirectory() as intermediate_gerbers,\
tempfile.TemporaryDirectory() as output_gerbers:
run_command('python3', '-m', 'gerbolyze', 'paste', '--no-subtract', in_gerbers, top_overlay, intermediate_gerbers)
run_command('python3', '-m', 'gerbolyze', 'paste', '--no-subtract', intermediate_gerbers, bottom_overlay, output_gerbers)
stack_old = gerbonara.layers.LayerStack.open(in_gerbers)
stack_new = gerbonara.layers.LayerStack.open(output_gerbers)
for (side, use), layer_old in stack_old.graphic_layers.items():
if use == 'outline':
continue
layer_new = stack_new[side, use]
bbox_old = layer_old.bounding_box(gerbonara.utils.MM)
bbox_new = layer_new.bounding_box(gerbonara.utils.MM)
print(side, use, bbox_old, bbox_new)
print(' -> ',
bbox_new[0][0]-bbox_old[0][0], bbox_new[0][1]-bbox_old[0][1],
bbox_new[1][0]-bbox_old[1][0], bbox_new[1][1]-bbox_old[1][1])
print(' -> ',
bbox_new[0][0], bbox_new[0][1],
bbox_new[1][0], bbox_new[1][1])
print(' old ->', layer_old)
print(' new ->', layer_new)
e = 0.8
assert math.isclose(bbox_new[0][0], bbox_old[0][0]-e, abs_tol=0.1)
assert math.isclose(bbox_new[0][1], bbox_old[0][1]-e, abs_tol=0.1)
assert math.isclose(bbox_new[1][0], bbox_old[1][0]+e, abs_tol=0.1)
assert math.isclose(bbox_new[1][1], bbox_old[1][1]+e, abs_tol=0.1)
def test_convert_layers():
infile = reference_path('layers.svg')
with tempfile.TemporaryDirectory() as out_dir: