From 8409fbb90835c61bd675dc0070ea38ac671540f8 Mon Sep 17 00:00:00 2001 From: jaseg Date: Wed, 26 Apr 2023 22:36:20 +0200 Subject: Export flashes as svg tags --- gerbonara/cam.py | 20 +++++++------------- gerbonara/layers.py | 23 +++++++++++++++++++---- gerbonara/rs274x.py | 21 ++++++++++++++++++++- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/gerbonara/cam.py b/gerbonara/cam.py index d94175e..3c92441 100644 --- a/gerbonara/cam.py +++ b/gerbonara/cam.py @@ -291,21 +291,15 @@ class CamFile: return setup_svg(tags, bounds, margin=margin, arg_unit=arg_unit, svg_unit=svg_unit, pagecolor=bg, tag=tag) - def svg_objects(self, svg_unit=MM, fg='black', bg='white', tag=Tag): + def svg_objects(self, svg_unit=MM, fg='black', bg='white', aperture_map={}, tag=Tag): pl = None for i, obj in enumerate(self.objects): - #if isinstance(obj, go.Flash): - # if pl: - # tags.append(pl.to_svg(tag, fg, bg)) - # pl = None - - # mask_tags = [ prim.to_svg(tag, 'white', 'black') for prim in obj.to_primitives(unit=svg_unit) ] - # mask_tags.insert(0, tag('rect', width='100%', height='100%', fill='black')) - # mask_id = f'mask{i}' - # tag('mask', mask_tags, id=mask_id) - # tag('rect', width='100%', height='100%', mask='url(#{mask_id})', fill=fg) - - #else: + if isinstance(obj, go.Flash) and id(obj.aperture) in aperture_map: + yield tag('use', href='#'+aperture_map[id(obj.aperture)], + x=f'{svg_unit(obj.x, obj.unit):.3f}', + y=f'{svg_unit(obj.y, obj.unit):.3f}') + + else: for primitive in obj.to_primitives(unit=svg_unit): if isinstance(primitive, gp.Line): if not pl: diff --git a/gerbonara/layers.py b/gerbonara/layers.py index 97e15b0..f4c8279 100644 --- a/gerbonara/layers.py +++ b/gerbonara/layers.py @@ -753,7 +753,7 @@ class LayerStack: return setup_svg(tags, bounds, margin=margin, arg_unit=arg_unit, svg_unit=svg_unit, tag=tag) def to_pretty_svg(self, side='top', margin=0, arg_unit=MM, svg_unit=MM, force_bounds=None, tag=Tag, inkscape=False, - colors=None): + colors=None, use=True): """ Convert this layer stack to a pretty SVG string that is suitable for display or for editing in tools such as Inkscape. If you want to process the resulting SVG in other tools, consider using :py:meth:`~layers.LayerStack.to_svg` instead, which produces output without color styling or blending based on @@ -779,10 +779,12 @@ class LayerStack: :py:obj:`"bottom"`, and :py:obj:`"mechanical"` as well as :py:obj:`"inner1"`, :py:obj:`"inner2"` etc. for internal layers. Valid use values are :py:obj:`"mask"`, :py:obj:`"silk"`, :py:obj:`"paste"`, and :py:obj:`"copper"`. For internal layers, only :py:obj:`"copper"` is valid. + :param use: Enable/disable ```` tags for aperture flashes. Defaults to :py:obj:`True` (enabled). :rtype: :py:obj:`str` """ if colors is None: colors = DEFAULT_COLORS + use_use = use colors_alpha = {} for layer, color in colors.items(): @@ -818,6 +820,8 @@ class LayerStack: inkscape_attrs = lambda label: dict(inkscape__groupmode='layer', inkscape__label=label) if inkscape else {} stroke_attrs = {'stroke_linejoin': 'round', 'stroke_linecap': 'round'} + use_defs = [] + layers = [] for use in ['copper', 'mask', 'silk', 'paste']: if (side, use) not in self: @@ -825,13 +829,24 @@ class LayerStack: continue layer = self[(side, use)] - fg, bg = ('white', 'black') if use != 'mask' else ('black', 'white') + fg, bg = ('white', 'black') if use != 'mask' else ('black', 'white') default_fill = {'copper': fg, 'mask': fg, 'silk': 'none', 'paste': fg}[use] default_stroke = {'copper': 'none', 'mask': 'none', 'silk': fg, 'paste': 'none'}[use] + use_map = {} + if use_use: + layer.dedup_apertures() + for obj in layer.objects: + if hasattr(obj, 'aperture') and obj.polarity_dark and id(obj.aperture) not in use_map: + children = [prim.to_svg(fg, bg, tag=tag) + for prim in obj.aperture.flash(0, 0, svg_unit, polarity_dark=True)] + use_id = f'a{len(use_defs)}' + use_defs.append(tag('g', children, id=use_id)) + use_map[id(obj.aperture)] = use_id + objects = [] - for obj in layer.instance.svg_objects(svg_unit=svg_unit, fg=fg, bg=bg, tag=Tag): + for obj in layer.instance.svg_objects(svg_unit=svg_unit, fg=fg, bg=bg, aperture_map=use_map, tag=Tag): if obj.attrs.get('fill') == default_fill: del obj.attrs['fill'] elif 'fill' not in obj.attrs: @@ -858,7 +873,7 @@ class LayerStack: id=f'l-mechanical-outline', **stroke_attrs, **inkscape_attrs(f'outline'))) layer_group = tag('g', layers, transform=f'translate(0 {bounds[0][1] + bounds[1][1]}) scale(1 -1)') - tags = [tag('defs', filter_defs), layer_group] + tags = [tag('defs', filter_defs + use_defs), layer_group] return setup_svg(tags, bounds, margin=margin, arg_unit=arg_unit, svg_unit=svg_unit, pagecolor="white", tag=tag, inkscape=inkscape) def bounding_box(self, unit=MM, default=None): diff --git a/gerbonara/rs274x.py b/gerbonara/rs274x.py index 01cdccf..fb70869 100644 --- a/gerbonara/rs274x.py +++ b/gerbonara/rs274x.py @@ -228,6 +228,26 @@ class GerberFile(CamFile): parser.parse(data, filename=filename) return obj + def dedup_apertures(self, settings=None): + settings = settings or FileSettings.defaults() + + defined_apertures = {} + ap_map = {} + for obj in self.objects: + if not hasattr(obj, 'aperture'): + continue + + if id(obj.aperture) in ap_map: + obj.aperture = ap_map[id(obj.aperture)] + + ap_def = obj.aperture.to_gerber(settings) + if ap_def in defined_apertures: + ap_map[id(obj.aperture)] = obj.aperture = defined_apertures[ap_def] + else: + ap_map[id(obj.aperture)] = defined_apertures[ap_def] = obj.aperture + + self.apertures = list(ap_map.values()) + def _generate_statements(self, settings, drop_comments=True): """ Export this file as Gerber code, yields one str per line. """ self.sync_apertures() @@ -265,7 +285,6 @@ class GerberFile(CamFile): defined_apertures = {} number = 10 for aperture in self.apertures: - if isinstance(aperture, apertures.ApertureMacroInstance): macro_def = am_stmt(aperture.rotated().macro) if macro_def not in processed_macros: -- cgit