kopia lustrzana https://github.com/jaseg/gerbolyze
protoboard: Add SMD patterns
rodzic
d09cf6ef3b
commit
c1cda48a4c
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
Ładowanie…
Reference in New Issue