aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2022-06-20 11:21:42 +0200
committerjaseg <git@jaseg.de>2022-06-20 11:21:42 +0200
commitc1cda48a4ccd8b272b2af9ed1f51d0fb2aa63c51 (patch)
tree55c9c67c97b99c0a53a994e7c9983a502a08584f
parentd09cf6ef3b6ea82f7ff67719527cd563569e0893 (diff)
downloadgerbolyze-c1cda48a4ccd8b272b2af9ed1f51d0fb2aa63c51.tar.gz
gerbolyze-c1cda48a4ccd8b272b2af9ed1f51d0fb2aa63c51.tar.bz2
gerbolyze-c1cda48a4ccd8b272b2af9ed1f51d0fb2aa63c51.zip
protoboard: Add SMD patterns
-rwxr-xr-xgerbolyze/__init__.py6
-rw-r--r--gerbolyze/protoboard.py83
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'<circle cx="{self.w/2}" cy="{self.h/2}" r="{self.d/2}"/>'
+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'<rect x="{x}" y="{y}" width="{self.rw}" height="{self.rh}"/>'
+
make_layer = lambda layer_name, content: \
f'<g id="g-{layer_name.replace(" ", "-")}" inkscape:label="{layer_name}" inkscape:groupmode="layer">{svg_str(content)}</g>'
@@ -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'<rect x="{x}" y="{y}" width="{w}" height="{h}" {clip} fill="black"/>' }
+
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'<circle cx="{w-o}" cy="{h-o}" r="{d/2}"/>',
f'<circle cx="{o}" cy="{h-o}" r="{d/2}"/>' ])
- 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))