summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2023-04-05 01:29:33 +0200
committerjaseg <git@jaseg.de>2023-04-10 23:57:15 +0200
commitae1f522862099e51472fc70046f913348f49e6f3 (patch)
tree88b604876b53f2ab7f6a313677b935a19fd64bfd
parent44ca8349ebfef90b363ea765af44f5d6261778fe (diff)
downloadgerbonara-ae1f522862099e51472fc70046f913348f49e6f3.tar.gz
gerbonara-ae1f522862099e51472fc70046f913348f49e6f3.tar.bz2
gerbonara-ae1f522862099e51472fc70046f913348f49e6f3.zip
Initial protoboard generation working
-rw-r--r--gerbonara/cad/primitives.py71
-rw-r--r--gerbonara/utils.py2
2 files changed, 55 insertions, 18 deletions
diff --git a/gerbonara/cad/primitives.py b/gerbonara/cad/primitives.py
index 4eaeb5f..8d1ccdc 100644
--- a/gerbonara/cad/primitives.py
+++ b/gerbonara/cad/primitives.py
@@ -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):
@@ -353,6 +372,21 @@ class THTPad(Pad):
@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
hole: 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'
diff --git a/gerbonara/utils.py b/gerbonara/utils.py
index 32954bd..47fb178 100644
--- a/gerbonara/utils.py
+++ b/gerbonara/utils.py
@@ -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)