aboutsummaryrefslogtreecommitdiff
path: root/gerbolyze/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'gerbolyze/__init__.py')
-rwxr-xr-xgerbolyze/__init__.py139
1 files changed, 70 insertions, 69 deletions
diff --git a/gerbolyze/__init__.py b/gerbolyze/__init__.py
index 76cedd6..936821f 100755
--- a/gerbolyze/__init__.py
+++ b/gerbolyze/__init__.py
@@ -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()
-
- 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()
-
- 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)
+ stack = gn.LayerStack({}, None, None, [], board_name=input_svg.stem, original_path=input_svg)
+
+ for group_id, label in layers.items():
+ label = label or ''
+ if not group_id or 'no export' in label:
+ continue
+
+ 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('-')
- else:
- stack.graphic_layers[(side, use)] = grb
+ 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()
- bounds = stack.board_bounds()
- @functools.lru_cache()
- def do_dilate(layer, amount):
- return dilate_gerber(layer, bounds, amount, curve_tolerance)
+ 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"')
- 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)
+ 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)
# 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()