kopia lustrzana https://gitlab.com/gerbolyze/gerbonara
Initial protoboard generation working
rodzic
44ca8349eb
commit
ae1f522862
|
@ -1,4 +1,5 @@
|
|||
|
||||
import sys
|
||||
import math
|
||||
import warnings
|
||||
from copy import copy
|
||||
|
@ -38,11 +39,15 @@ class Board:
|
|||
self.keepouts = []
|
||||
self.default_via_hole = MM(default_via_hole, unit)
|
||||
self.default_via_diameter = MM(default_via_diameter, unit)
|
||||
self.unit = unit
|
||||
if w or h:
|
||||
if w and h:
|
||||
self.rounded_rect_outline(w, h, r=corner_radius, center=center)
|
||||
self.w, self.h = w, h
|
||||
else:
|
||||
raise ValueError('Either both, w and h, or neither of them must be given.')
|
||||
else:
|
||||
self.w = self.h = None
|
||||
|
||||
@property
|
||||
def abs_pos(self):
|
||||
|
@ -75,6 +80,9 @@ class Board:
|
|||
warnings.warn(msg)
|
||||
elif keepout_errors == 'raise':
|
||||
raise KeepoutError(obj, ko, msg)
|
||||
else:
|
||||
import sys
|
||||
#print('skip', obj.bounding_box(MM), ko, file=sys.stderr)
|
||||
return
|
||||
|
||||
obj.parent = self
|
||||
|
@ -136,9 +144,13 @@ class Positioned:
|
|||
y: float
|
||||
_: KW_ONLY
|
||||
rotation: float = 0.0
|
||||
side: str = 'top'
|
||||
unit: LengthUnit = MM
|
||||
parent: object = None
|
||||
|
||||
def flip(self):
|
||||
self.side = 'top' if self.side == 'bottom' else 'bottom'
|
||||
|
||||
@property
|
||||
def abs_pos(self):
|
||||
if self.parent is None:
|
||||
|
@ -153,11 +165,18 @@ class Positioned:
|
|||
self.render(stack)
|
||||
objects = chain(*(l.objects for l in stack.graphic_layers.values()),
|
||||
stack.drill_pth.objects, stack.drill_npth.objects)
|
||||
objects = list(objects)
|
||||
#print('foo', type(self).__name__,
|
||||
# [(type(obj).__name__, [prim.bounding_box() for prim in obj.to_primitives(unit)]) for obj in objects], file=sys.stderr)
|
||||
return sum_bounds(prim.bounding_box() for obj in objects for prim in obj.to_primitives(unit))
|
||||
|
||||
def overlaps(self, bbox, unit=MM):
|
||||
return bbox_intersect(self.bounding_box(unit), bbox)
|
||||
|
||||
@property
|
||||
def single_sided(self):
|
||||
return True
|
||||
|
||||
|
||||
@dataclass
|
||||
class ObjectGroup(Positioned):
|
||||
|
@ -171,10 +190,6 @@ class ObjectGroup(Positioned):
|
|||
bottom_paste: list = field(default_factory=list)
|
||||
drill_npth: list = field(default_factory=list)
|
||||
drill_pth: list = field(default_factory=list)
|
||||
side: str = 'top'
|
||||
|
||||
def flip(self):
|
||||
self.side = 'top' if self.side == 'bottom' else 'bottom'
|
||||
|
||||
def render(self, layer_stack):
|
||||
x, y, rotation = self.abs_pos
|
||||
|
@ -193,6 +208,13 @@ class ObjectGroup(Positioned):
|
|||
for fe in source:
|
||||
target.objects.append(copy(fe).rotate(rotation).offset(x, y, self.unit))
|
||||
|
||||
@property
|
||||
def single_sided(self):
|
||||
any_top = self.top_copper or self.top_mask or self.top_paste or self.top_silk
|
||||
any_bottom = self.bottom_copper or self.bottom_mask or self.bottom_paste or self.bottom_silk
|
||||
any_drill = self.drill_npth or self.drill_pth
|
||||
return not (any_drill or (any_top and any_bottom))
|
||||
|
||||
|
||||
@dataclass
|
||||
class Text(Positioned):
|
||||
|
@ -202,12 +224,8 @@ class Text(Positioned):
|
|||
h_align: str = 'left'
|
||||
v_align: str = 'bottom'
|
||||
layer: str = 'silk'
|
||||
side: str = 'top'
|
||||
polarity_dark: bool = True
|
||||
|
||||
def flip(self):
|
||||
self.side = 'top' if self.side == 'bottom' else 'bottom'
|
||||
|
||||
def render(self, layer_stack):
|
||||
obj_x, obj_y, rotation = self.abs_pos
|
||||
global newstroke_font
|
||||
|
@ -259,7 +277,6 @@ class SMDPad(Pad):
|
|||
mask_aperture: Aperture
|
||||
paste_aperture: Aperture
|
||||
silk_features: list = field(default_factory=list)
|
||||
side: str = 'top'
|
||||
|
||||
def render(self, layer_stack):
|
||||
x, y, rotation = self.abs_pos
|
||||
|
@ -269,9 +286,6 @@ class SMDPad(Pad):
|
|||
layer_stack[self.side, 'silk' ].objects.extend([copy(feature).rotate(rotation).offset(x, y, self.unit)
|
||||
for feature in self.silk_features])
|
||||
|
||||
def flip(self):
|
||||
self.side = 'top' if self.side == 'bottom' else 'bottom'
|
||||
|
||||
@classmethod
|
||||
def rect(kls, x, y, w, h, rotation=0, side='top', mask_expansion=0.0, paste_expansion=0.0, unit=MM):
|
||||
ap_c = RectangleAperture(w, h, unit=unit)
|
||||
|
@ -285,8 +299,7 @@ class SMDPad(Pad):
|
|||
ap_c = CircleAperture(dia, unit=unit)
|
||||
ap_m = CircleAperture(dia+2*mask_expansion, unit=unit)
|
||||
ap_p = CircleAperture(dia+2*paste_expansion, unit=unit)
|
||||
return kls(x, y, side=side, copper_aperture=ap_c, mask_aperture=ap_m, paste_aperture=ap_p, rotation=rotation,
|
||||
unit=unit)
|
||||
return kls(x, y, side=side, copper_aperture=ap_c, mask_aperture=ap_m, paste_aperture=ap_p, unit=unit)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -310,7 +323,9 @@ class THTPad(Pad):
|
|||
|
||||
def render(self, layer_stack):
|
||||
x, y, rotation = self.abs_pos
|
||||
self.pad_top.parent = self
|
||||
self.pad_top.render(layer_stack)
|
||||
self.pad_bottom.parent = self
|
||||
self.pad_bottom.render(layer_stack)
|
||||
|
||||
if self.aperture_inner is None:
|
||||
|
@ -325,12 +340,16 @@ class THTPad(Pad):
|
|||
for (side, use), layer in layer_stack.inner_layers:
|
||||
layer.objects.append(Flash(x, y, self.aperture_inner.rotated(rotation), unit=self.unit))
|
||||
|
||||
hole = Flash(self.x, self.y, ExcellonTool(self.drill_dia, plated=self.plated, unit=self.unit), unit=self.unit)
|
||||
hole = Flash(x, y, ExcellonTool(self.drill_dia, plated=self.plated, unit=self.unit), unit=self.unit)
|
||||
if self.plated:
|
||||
layer_stack.drill_pth.objects.append(hole)
|
||||
else:
|
||||
layer_stack.drill_npth.objects.append(hole)
|
||||
|
||||
@property
|
||||
def single_sided(self):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def rect(kls, x, y, hole_dia, w, h=None, rotation=0, mask_expansion=0.0, paste_expansion=0.0, unit=MM):
|
||||
if h is None:
|
||||
|
@ -341,7 +360,7 @@ class THTPad(Pad):
|
|||
@classmethod
|
||||
def circle(kls, x, y, hole_dia, dia, mask_expansion=0.0, paste_expansion=0.0, unit=MM):
|
||||
pad = SMDPad.circle(0, 0, dia, mask_expansion=mask_expansion, paste_expansion=paste_expansion, unit=unit)
|
||||
return kls(x, y, hole_dia, pad, rotation=rotation, unit=unit)
|
||||
return kls(x, y, hole_dia, pad, unit=unit)
|
||||
|
||||
@classmethod
|
||||
def obround(kls, x, y, hole_dia, w, h, rotation=0, mask_expansion=0.0, paste_expanson=0.0, unit=MM):
|
||||
|
@ -352,6 +371,21 @@ class THTPad(Pad):
|
|||
return kls(x, y, hole_dia, pad, rotation=rotation, unit=unit)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Hole(Positioned):
|
||||
diameter: float
|
||||
|
||||
def render(self, layer_stack):
|
||||
x, y, rotation = self.abs_pos
|
||||
|
||||
hole = Flash(x, y, ExcellonTool(self.diameter, plated=False, unit=self.unit), unit=self.unit)
|
||||
layer_stack.drill_npth.objects.append(hole)
|
||||
|
||||
@property
|
||||
def single_sided(self):
|
||||
return False
|
||||
|
||||
|
||||
@dataclass
|
||||
class Via(Positioned):
|
||||
diameter: float
|
||||
|
@ -368,13 +402,16 @@ class Via(Positioned):
|
|||
|
||||
layer_stack.drill_pth.objects.append(Flash(x, y, tool, unit=self.unit))
|
||||
|
||||
@property
|
||||
def single_sided(self):
|
||||
return False
|
||||
|
||||
|
||||
@dataclass
|
||||
class Trace:
|
||||
width: float
|
||||
start: object = None
|
||||
end: object = None
|
||||
side: str = 'top'
|
||||
waypoints: [(float, float)] = field(default_factory=list)
|
||||
style: str = 'oblique'
|
||||
orientation: [str] = tuple() # 'top' or 'bottom'
|
||||
|
|
|
@ -528,7 +528,7 @@ def point_in_polygon(point, poly):
|
|||
|
||||
def bbox_intersect(a, b):
|
||||
(xa_min, ya_min), (xa_max, ya_max) = a
|
||||
(xb_min, yb_min), (xb_mbx, yb_mbx) = b
|
||||
(xb_min, yb_min), (xb_max, yb_max) = b
|
||||
|
||||
x_overlap = not (xa_max < xb_min or xb_max < xa_min)
|
||||
y_overlap = not (ya_max < yb_min or yb_max < ya_min)
|
||||
|
|
Ładowanie…
Reference in New Issue