Protoboard generator WIP
This commit is contained in:
parent
04c4b3ff0c
commit
f721692bf3
3 changed files with 66 additions and 30 deletions
|
|
@ -4,7 +4,7 @@ import math
|
|||
import warnings
|
||||
from copy import copy
|
||||
from itertools import zip_longest, chain
|
||||
from dataclasses import dataclass, field, KW_ONLY
|
||||
from dataclasses import dataclass, field, replace, KW_ONLY
|
||||
from collections import defaultdict
|
||||
|
||||
from ..utils import LengthUnit, MM, rotate_point, svg_arc, sum_bounds, bbox_intersect, Tag, offset_bounds
|
||||
|
|
@ -14,6 +14,9 @@ from ..apertures import Aperture, CircleAperture, ObroundAperture, RectangleAper
|
|||
from ..newstroke import Newstroke
|
||||
|
||||
|
||||
class UNDEFINED:
|
||||
pass
|
||||
|
||||
def sgn(x):
|
||||
return -1 if x < 0 else 1
|
||||
|
||||
|
|
@ -329,16 +332,16 @@ class Text(Positioned):
|
|||
else:
|
||||
raise ValueError('h_align must be one of "left", "center", or "right".')
|
||||
|
||||
if self.v_align == 'top':
|
||||
if self.v_align == 'bottom':
|
||||
y0 = -(max_y - min_y)
|
||||
elif self.v_align == 'middle':
|
||||
y0 = -(max_y - min_y)/2
|
||||
elif self.v_align == 'bottom':
|
||||
y0 = (max_y - min_y)/2
|
||||
elif self.v_align == 'top':
|
||||
y0 = 0
|
||||
else:
|
||||
raise ValueError('v_align must be one of "top", "middle", or "bottom".')
|
||||
|
||||
if self.side == 'bottom':
|
||||
if self.flip:
|
||||
x0 += min_x + max_x
|
||||
x_sign = -1
|
||||
else:
|
||||
|
|
@ -348,7 +351,7 @@ class Text(Positioned):
|
|||
|
||||
for stroke in strokes:
|
||||
for (x1, y1), (x2, y2) in zip(stroke[:-1], stroke[1:]):
|
||||
obj = Line(x0+x_sign*x1, y0-y1, x0+x_sign*x2, y0-y2, aperture=ap, unit=self.unit, polarity_dark=self.polarity_dark)
|
||||
obj = Line(x0+x_sign*x1, y0+y1, x0+x_sign*x2, y0+y2, aperture=ap, unit=self.unit, polarity_dark=self.polarity_dark)
|
||||
obj.rotate(rotation)
|
||||
obj.offset(obj_x, obj_y)
|
||||
layer_stack['bottom' if flip else 'top', self.layer].objects.append(obj)
|
||||
|
|
@ -396,20 +399,20 @@ class PadStack:
|
|||
def flashes(self, x, y, rotation: float = 0, flip: bool = False):
|
||||
for ap in self.apertures:
|
||||
aperture = ap.aperture.rotated(ap.rotation + rotation)
|
||||
fl = Flash(ap.offset_x, ap.offset_y)
|
||||
fl = Flash(ap.offset_x, ap.offset_y, aperture, unit=self.unit)
|
||||
fl.rotate(rotation)
|
||||
fl.offset(x, y)
|
||||
side = fl.side
|
||||
side = ap.side
|
||||
if flip:
|
||||
side = {'top': 'bottom', 'bottom': 'top'}.get(side, side)
|
||||
yield side, fl.layer, fl
|
||||
yield side, ap.layer, fl
|
||||
|
||||
def render(self, layer_stack, x, y, rotation: float = 0, flip: bool = False):
|
||||
for side, layer, flash in self.flashes(x, y, rotation, flip):
|
||||
if side == 'drill' and use == 'plated':
|
||||
if side == 'drill' and layer == 'plated':
|
||||
layer_stack.drill_pth.objects.append(flash)
|
||||
|
||||
elif side == 'drill' and use == 'nonplated':
|
||||
elif side == 'drill' and layer == 'nonplated':
|
||||
layer_stack.drill_npth.objects.append(flash)
|
||||
|
||||
elif (side, layer) in layer_stack:
|
||||
|
|
@ -449,17 +452,37 @@ class SMDStack(PadStack):
|
|||
return kls(CircleAperture(dia, unit=unit), mask_expansion, paste_expansion, paste, flip, unit=unit)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class MechanicalHoleStack(PadStack):
|
||||
drill_dia: float
|
||||
mask_expansion: float = 0.0
|
||||
mask_aperture = None
|
||||
|
||||
@property
|
||||
def apertures(self):
|
||||
mask_aperture = self.mask_aperture or CircleAperture(self.drill_dia + self.mask_expansion, unit=self.unit)
|
||||
yield PadStackAperture(mask_aperture, 'top', 'mask')
|
||||
yield PadStackAperture(mask_aperture, 'bottom', 'mask')
|
||||
|
||||
@property
|
||||
def single_sided(self):
|
||||
return False
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class THTPad(PadStack):
|
||||
drill_dia: float
|
||||
pad_top: SMDStack
|
||||
pad_bottom: SMDStack = None
|
||||
aperture_inner: Aperture = None
|
||||
aperture_inner: Aperture = UNDEFINED
|
||||
plated: bool = True
|
||||
|
||||
def __post_init__(self):
|
||||
if self.pad_bottom is None:
|
||||
object.__setattr__(self, 'pad_bottom', replace(self.pad_top, flip=True))
|
||||
|
||||
if self.aperture_inner is UNDEFINED:
|
||||
object.__setattr__(self, 'aperture_inner', self.pad_top.aperture)
|
||||
|
||||
if self.pad_top.flip:
|
||||
raise ValueError('top pad cannot be flipped')
|
||||
|
|
@ -472,7 +495,8 @@ class THTPad(PadStack):
|
|||
def apertures(self):
|
||||
yield from self.pad_top.apertures
|
||||
yield from self.pad_bottom.apertures
|
||||
yield PadStackAperture(self.aperture_inner, 'inner', 'copper')
|
||||
if self.aperture_inner is not None:
|
||||
yield PadStackAperture(self.aperture_inner, 'inner', 'copper')
|
||||
yield PadStackAperture(ExcellonTool(self.drill_dia, plated=self.plated, unit=self.unit), 'drill', self.plating)
|
||||
|
||||
@property
|
||||
|
|
@ -538,6 +562,10 @@ class Via(FrozenPositioned):
|
|||
class Pad(Positioned):
|
||||
pad_stack: PadStack
|
||||
|
||||
def render(self, layer_stack, cache=None):
|
||||
x, y, rotation, flip = self.abs_pos
|
||||
self.pad_stack.render(layer_stack, x, y, rotation, flip)
|
||||
|
||||
@property
|
||||
def single_sided(self):
|
||||
return self.pad_stack.single_sided
|
||||
|
|
|
|||
|
|
@ -29,10 +29,11 @@ class ProtoBoard(Board):
|
|||
mounting_hole_offset = mounting_hole_offset or mounting_hole_dia*2
|
||||
ko = mounting_hole_offset*2
|
||||
|
||||
self.add(Hole(mounting_hole_offset, mounting_hole_offset, mounting_hole_dia, unit=unit))
|
||||
self.add(Hole(w-mounting_hole_offset, mounting_hole_offset, mounting_hole_dia, unit=unit))
|
||||
self.add(Hole(mounting_hole_offset, h-mounting_hole_offset, mounting_hole_dia, unit=unit))
|
||||
self.add(Hole(w-mounting_hole_offset, h-mounting_hole_offset, mounting_hole_dia, unit=unit))
|
||||
stack = MechanicalHoleStack(mounting_hole_dia, unit=unit)
|
||||
self.add(Pad(mounting_hole_offset, mounting_hole_offset, pad_stack=stack, unit=unit))
|
||||
self.add(Pad(w-mounting_hole_offset, mounting_hole_offset, pad_stack=stack, unit=unit))
|
||||
self.add(Pad(mounting_hole_offset, h-mounting_hole_offset, pad_stack=stack, unit=unit))
|
||||
self.add(Pad(w-mounting_hole_offset, h-mounting_hole_offset, pad_stack=stack, unit=unit))
|
||||
|
||||
self.keepouts.append(((0, 0), (ko, ko)))
|
||||
self.keepouts.append(((w-ko, 0), (w, ko)))
|
||||
|
|
@ -235,7 +236,7 @@ class PatternProtoArea:
|
|||
off_y = (h % unit(self.pitch_y, self.unit)) / 2
|
||||
|
||||
if self.numbers:
|
||||
for i, lno_i in list(zip(range(n_y), self.number_y_gen())):
|
||||
for i, lno_i in list(zip(reversed(range(n_y)), self.number_y_gen())):
|
||||
if i == 0 or i == n_y - 1 or (i+1) % self.interval_y == 0:
|
||||
t_y = off_y + y + (n_y - 1 - i + 0.5) * self.pitch_y
|
||||
|
||||
|
|
@ -243,13 +244,13 @@ class PatternProtoArea:
|
|||
t_x = x + off_x
|
||||
yield Text(t_x, t_y, lno_i, self.font_size, self.font_stroke, 'right', 'middle', unit=self.unit)
|
||||
if not self.single_sided:
|
||||
yield Text(t_x, t_y, lno_i, self.font_size, self.font_stroke, 'right', 'middle', side='bottom', unit=self.unit)
|
||||
yield Text(t_x, t_y, lno_i, self.font_size, self.font_stroke, 'right', 'middle', flip=True, unit=self.unit)
|
||||
|
||||
if border_text[1]:
|
||||
t_x = x + w - off_x
|
||||
yield Text(t_x, t_y, lno_i, self.font_size, self.font_stroke, 'left', 'middle', unit=self.unit)
|
||||
if not self.single_sided:
|
||||
yield Text(t_x, t_y, lno_i, self.font_size, self.font_stroke, 'left', 'middle', side='bottom', unit=self.unit)
|
||||
yield Text(t_x, t_y, lno_i, self.font_size, self.font_stroke, 'left', 'middle', flip=True, unit=self.unit)
|
||||
|
||||
for i, lno_i in zip(range(n_x), self.number_x_gen()):
|
||||
if i == 0 or i == n_x - 1 or (i+1) % self.interval_x == 0:
|
||||
|
|
@ -259,18 +260,24 @@ class PatternProtoArea:
|
|||
t_y = y + off_y
|
||||
yield Text(t_x, t_y, lno_i, self.font_size, self.font_stroke, 'center', 'top', unit=self.unit)
|
||||
if not self.single_sided:
|
||||
yield Text(t_x, t_y, lno_i, self.font_size, self.font_stroke, 'center', 'top', side='bottom', unit=self.unit)
|
||||
yield Text(t_x, t_y, lno_i, self.font_size, self.font_stroke, 'center', 'top', flip=True, unit=self.unit)
|
||||
|
||||
if border_text[0]:
|
||||
t_y = y + h - off_y
|
||||
t_y = y + h + off_y
|
||||
yield Text(t_x, t_y, lno_i, self.font_size, self.font_stroke, 'center', 'bottom', unit=self.unit)
|
||||
if not self.single_sided:
|
||||
yield Text(t_x, t_y, lno_i, self.font_size, self.font_stroke, 'center', 'bottom', side='bottom', unit=self.unit)
|
||||
yield Text(t_x, t_y, lno_i, self.font_size, self.font_stroke, 'center', 'bottom', flip=True, unit=self.unit)
|
||||
|
||||
|
||||
for i in range(n_x):
|
||||
for j in range(n_y):
|
||||
if hasattr(self.obj, 'inst'):
|
||||
if isinstance(self.obj, PadStack):
|
||||
px = self.unit(off_x + x, unit) + (i + 0.5) * self.pitch_x
|
||||
py = self.unit(off_y + y, unit) + (j + 0.5) * self.pitch_y
|
||||
yield Pad(px, py, pad_stack=self.obj, unit=self.unit)
|
||||
continue
|
||||
|
||||
elif hasattr(self.obj, 'inst'):
|
||||
inst = self.obj.inst(i, j, i == n_x-1, j == n_y-1)
|
||||
if not inst:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from quart import Quart, request, Response, send_file, abort
|
|||
|
||||
from . import protoboard as pb
|
||||
from . import protoserve_data
|
||||
from .primitives import SMDStack
|
||||
from ..utils import MM, Inch
|
||||
|
||||
|
||||
|
|
@ -62,10 +63,10 @@ def deserialize(obj, unit):
|
|||
case 'smd':
|
||||
match obj['pad_shape']:
|
||||
case 'rect':
|
||||
pad = pb.SMDPad.rect(0, 0, pitch_x-clearance, pitch_y-clearance, paste=False, unit=unit)
|
||||
stack = SMDStack.rect(pitch_x-clearance, pitch_y-clearance, paste=False, unit=unit)
|
||||
case 'circle':
|
||||
pad = pb.SMDPad.circle(0, 0, min(pitch_x, pitch_y)-clearance, paste=False, unit=unit)
|
||||
return pb.PatternProtoArea(pitch_x, pitch_y, obj=pad, unit=unit)
|
||||
stack = SMDStack.circle(min(pitch_x, pitch_y)-clearance, paste=False, unit=unit)
|
||||
return pb.PatternProtoArea(pitch_x, pitch_y, obj=stack, unit=unit)
|
||||
|
||||
case 'tht':
|
||||
hole_dia = mil(float(obj['hole_dia']))
|
||||
|
|
@ -79,11 +80,11 @@ def deserialize(obj, unit):
|
|||
|
||||
match obj['pad_shape']:
|
||||
case 'rect':
|
||||
pad = pb.THTPad.rect(0, 0, hole_dia, pitch_x-clearance, pitch_y-clearance, paste=False, plated=plated, unit=unit)
|
||||
pad = pb.THTPad.rect(hole_dia, pitch_x-clearance, pitch_y-clearance, paste=False, plated=plated, unit=unit)
|
||||
case 'circle':
|
||||
pad = pb.THTPad.circle(0, 0, hole_dia, min(pitch_x, pitch_y)-clearance, paste=False, plated=plated, unit=unit)
|
||||
pad = pb.THTPad.circle(hole_dia, min(pitch_x, pitch_y)-clearance, paste=False, plated=plated, unit=unit)
|
||||
case 'obround':
|
||||
pad = pb.THTPad.obround(0, 0, hole_dia, pitch_x-clearance, pitch_y-clearance, paste=False, plated=plated, unit=unit)
|
||||
pad = pb.THTPad.obround(hole_dia, pitch_x-clearance, pitch_y-clearance, paste=False, plated=plated, unit=unit)
|
||||
|
||||
if oneside:
|
||||
pad.pad_bottom = None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue