From c1cda48a4ccd8b272b2af9ed1f51d0fb2aa63c51 Mon Sep 17 00:00:00 2001 From: jaseg Date: Mon, 20 Jun 2022 11:21:42 +0200 Subject: protoboard: Add SMD patterns --- gerbolyze/__init__.py | 6 ++-- gerbolyze/protoboard.py | 83 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 76 insertions(+), 13 deletions(-) diff --git a/gerbolyze/__init__.py b/gerbolyze/__init__.py index 3011833..0ba7b02 100755 --- a/gerbolyze/__init__.py +++ b/gerbolyze/__init__.py @@ -212,8 +212,9 @@ def empty_template(output_svg, size, force, copper_layers, no_default_layers, la @click.option('--vectorizer', help='passed through to svg-flatten') @click.option('--vectorizer-map', help='passed through to svg-flatten') @click.option('--exclude-groups', help='passed through to svg-flatten') +@click.option('--pattern-complete-tiles-only', 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, - vectorizer_map, exclude_groups, separate_drill, naming_scheme): + vectorizer_map, exclude_groups, separate_drill, naming_scheme, pattern_complete_tiles_only): ''' 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 @@ -242,6 +243,7 @@ def convert(input_svg, output_gerbers, is_zip, dilate, curve_tolerance, no_subtr 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, + pattern_complete_tiles_only=pattern_complete_tiles_only, outline_mode=(use == 'outline' or use == 'drill')) grb.original_path = Path() @@ -505,7 +507,7 @@ def svg_to_gerber(infile, outline_mode=False, **kwargs): ] for k, v in kwargs.items(): - if v is not None: + if v: args.append('--' + k.replace('_', '-')) if not isinstance(v, bool): args.append(str(v)) diff --git a/gerbolyze/protoboard.py b/gerbolyze/protoboard.py index 622c0e7..a4b6478 100644 --- a/gerbolyze/protoboard.py +++ b/gerbolyze/protoboard.py @@ -35,6 +35,17 @@ class CirclePattern(Pattern): def content(self): return f'' +class RectPattern(Pattern): + def __init__(self, rw, rh, w, h): + self.rw, self.rh = rw, rh + self.w, self.h = w, h + + @property + def content(self): + x = (self.w - self.rw) / 2 + y = (self.h - self.rh) / 2 + return f'' + make_layer = lambda layer_name, content: \ f'{svg_str(content)}' @@ -59,9 +70,13 @@ svg_template = textwrap.dedent(''' ''').strip() class PatternProtoArea: - def __init__(self, pitch_x, pitch_y=None): + def __init__(self, pitch_x, pitch_y=None, border=None): self.pitch_x = pitch_x self.pitch_y = pitch_y or pitch_x + match border: + case None: self.border = (0, 0, 0, 0) + case (t, r, b, l): self.border = border + case _: self.border = (border, border, border, border) @property def pitch(self): @@ -70,6 +85,9 @@ class PatternProtoArea: return self.pitch_x def fit_rect(self, x, y, w, h, center=True): + t, r, b, l = self.border + x, y, w, h = (x+l), (y+t), (w-l-r), (h-t-b) + w_mod, h_mod = round((w + 5e-7) % self.pitch_x, 6), round((h + 5e-7) % self.pitch_y, 6) w_fit, h_fit = round(w - w_mod, 6), round(h - h_mod, 6) @@ -81,9 +99,24 @@ class PatternProtoArea: else: return x, y, w_fit, h_fit + def generate(self, x, y, w, h, defs=None, center=True, clip=''): + return {} + +class EmptyProtoArea: + def __init__(self, copper=False, border=None): + match border: + case None: self.border = (0, 0, 0, 0) + case (t, r, b, l): self.border = border + case _: self.border = (border, border, border, border) + + def generate(self, x, y, w, h, defs=None, center=True, clip=''): + t, r, b, l = self.border + x, y, w, h = x+l, y+t, w-l-r, h-t-b + return { 'top copper': f'' } + class THTProtoAreaCircles(PatternProtoArea): - def __init__(self, pad_dia=2.0, drill=1.0, pitch=2.54, sides='both', plated=True): - super().__init__(pitch) + def __init__(self, pad_dia=2.0, drill=1.0, pitch=2.54, sides='both', plated=True, border=None): + super().__init__(pitch, border=border) self.pad_dia = pad_dia self.drill = drill self.drill_pattern = CirclePattern(self.drill, self.pitch) @@ -92,7 +125,7 @@ class THTProtoAreaCircles(PatternProtoArea): self.plated = plated self.sides = sides - def generate(self, x, y, w, h, center=True, clip=''): + def generate(self, x, y, w, h, defs=None, center=True, clip=''): x, y, w, h = self.fit_rect(x, y, w, h, center) drill = 'plated drill' if self.plated else 'nonplated drill' @@ -106,14 +139,32 @@ class THTProtoAreaCircles(PatternProtoArea): if self.sides in ('top', 'both'): d['top copper'] = make_rect(pad_id, x, y, w, h, clip) + d['top mask'] = make_rect(pad_id, x, y, w, h, clip) if self.sides in ('bottom', 'both'): d['bottom copper'] = make_rect(pad_id, x, y, w, h, clip) + d['bottom mask'] = make_rect(pad_id, x, y, w, h, clip) return d def __repr__(self): return f'THTCircles(d={self.pad_dia}, h={self.drill}, p={self.pitch}, sides={self.sides}, plated={self.plated})' +class SMDProtoAreaRectangles(PatternProtoArea): + def __init__(self, pitch_x, pitch_y, w=None, h=None, border=None): + super().__init__(pitch_x, pitch_y, border=border) + w = w or pitch_x - 0.15 + h = h or pitch_y - 0.15 + self.w, self.h = w, h + self.pad_pattern = RectPattern(w, h, pitch_x, pitch_y) + self.patterns = [self.pad_pattern] + + def generate(self, x, y, w, h, defs=None, center=True, clip=''): + x, y, w, h = self.fit_rect(x, y, w, h, center) + pad_id = str(uuid.uuid4()) + return {'defs': [self.pad_pattern.svg_def(pad_id, x, y)], + 'top copper': make_rect(pad_id, x, y, w, h, clip), + 'top mask': make_rect(pad_id, x, y, w, h, clip)} + LAYERS = [ 'top paste', 'top silk', @@ -129,10 +180,15 @@ LAYERS = [ ] class ProtoBoard: - def __init__(self, defs, expr, mounting_holes=None): + def __init__(self, defs, expr, mounting_holes=None, border=None, center=True): self.defs = eval_defs(defs) self.layout = parse_layout(expr) self.mounting_holes = mounting_holes + self.center = center + match border: + case None: self.border = (0, 0, 0, 0) + case (t, r, b, l): self.border = border + case _: self.border = (border, border, border, border) def generate(self, w, h): out = {l: [] for l in LAYERS} @@ -152,7 +208,8 @@ class ProtoBoard: f'', f'' ]) - for layer_dict in self.layout.generate(0, 0, w, h, self.defs, clip): + t, r, b, l = self.border + for layer_dict in self.layout.generate(l, t, w-l-r, h-t-b, self.defs, self.center, clip): for l in LAYERS: if l in layer_dict: out[l].append(layer_dict[l]) @@ -193,13 +250,13 @@ class PropLayout: if len(content) != len(proportions): raise ValueError('proportions and content must have same length') - def generate(self, x, y, w, h, defs, clip=''): + def generate(self, x, y, w, h, defs, center=True, clip=''): for (c_x, c_y, c_w, c_h), child in self.layout_2d(x, y, w, h): if isinstance(child, str): - yield defs[child].generate(c_x, c_y, c_w, c_h, defs, clip) + yield defs[child].generate(c_x, c_y, c_w, c_h, defs, center, clip) else: - yield from child.generate(c_x, c_y, c_w, c_h, defs, clip) + yield from child.generate(c_x, c_y, c_w, c_h, defs, center, clip) def layout_2d(self, x, y, w, h): for l, child in zip(self.layout(w if self.direction == 'h' else h), self.content): @@ -298,7 +355,9 @@ def parse_layout(expr): raise SyntaxError('Invalid layout expression') from e PROTO_AREA_TYPES = { - 'THTCircles': THTProtoAreaCircles + 'THTCircles': THTProtoAreaCircles, + 'SMDPads': SMDProtoAreaRectangles, + 'Empty': EmptyProtoArea, } def eval_defs(defs): @@ -358,5 +417,7 @@ if __name__ == '__main__': # print(line, '->', eval_defs(line)) # print() # print('===== Proto board =====') - b = ProtoBoard('tht = THTCircles(); tht_small = THTCircles(pad_dia=1.0, drill=0.6, pitch=1.27)', 'tht@1in|(tht_small@2/tht@1)', mounting_holes=(3.2, 5.0, 5.0)) + #b = ProtoBoard('tht = THTCircles(); tht_small = THTCircles(pad_dia=1.0, drill=0.6, pitch=1.27)', + # 'tht@1in|(tht_small@2/tht@1)', mounting_holes=(3.2, 5.0, 5.0), border=2, center=False) + b = ProtoBoard('smd = SMDPads(0.8, 1.27)', 'smd', mounting_holes=(3.2, 5.0, 5.0), border=2) print(b.generate(80, 60)) -- cgit