summaryrefslogtreecommitdiff
path: root/gerbonara
diff options
context:
space:
mode:
authorjaseg <git@jaseg.de>2023-04-04 20:06:16 +0200
committerjaseg <git@jaseg.de>2023-04-04 20:06:16 +0200
commit495ae6e93221b38a5347d65aff35effea919a565 (patch)
treeed376ce0cf29915ebf1424a84ad82c08d29d4f97 /gerbonara
parent15867450d930d9a66c3ad1f9694e91d17ba40ee9 (diff)
downloadgerbonara-495ae6e93221b38a5347d65aff35effea919a565.tar.gz
gerbonara-495ae6e93221b38a5347d65aff35effea919a565.tar.bz2
gerbonara-495ae6e93221b38a5347d65aff35effea919a565.zip
cad: Fix outline reconstruction and add text feature
Diffstat (limited to 'gerbonara')
-rw-r--r--gerbonara/cad/primitives.py100
-rw-r--r--gerbonara/graphic_primitives.py6
-rw-r--r--gerbonara/layers.py10
3 files changed, 105 insertions, 11 deletions
diff --git a/gerbonara/cad/primitives.py b/gerbonara/cad/primitives.py
index 2d1977d..4eaeb5f 100644
--- a/gerbonara/cad/primitives.py
+++ b/gerbonara/cad/primitives.py
@@ -10,6 +10,7 @@ from ..utils import LengthUnit, MM, rotate_point, svg_arc, sum_bounds, bbox_inte
from ..layers import LayerStack
from ..graphic_objects import Line, Arc, Flash
from ..apertures import Aperture, CircleAperture, RectangleAperture, ExcellonTool
+from ..newstroke import Newstroke
def sgn(x):
@@ -23,6 +24,9 @@ class KeepoutError(ValueError):
self.keepout = keepout
+newstroke_font = None
+
+
class Board:
def __init__(self, w=None, h=None, corner_radius=1.5, center=False, default_via_hole=0.4, default_via_diameter=0.8, x=0, y=0, rotation=0, unit=MM):
self.x, self.y = x, y
@@ -53,6 +57,9 @@ class Board:
else:
self.extra_silk_bottom.append(obj)
+ def add_text(self, *args, **kwargs):
+ self.objects.append(Text(*args, **kwargs))
+
def add_keepout(self, bbox, unit=MM):
((_x_min, _y_min), (_x_max, _y_max)) = bbox
self.keepouts.append(MM.convert_bounds_from(unit, bbox))
@@ -153,6 +160,95 @@ class Positioned:
@dataclass
+class ObjectGroup(Positioned):
+ top_copper: list = field(default_factory=list)
+ top_mask: list = field(default_factory=list)
+ top_silk: list = field(default_factory=list)
+ top_paste: list = field(default_factory=list)
+ bottom_copper: list = field(default_factory=list)
+ bottom_mask: list = field(default_factory=list)
+ bottom_silk: list = field(default_factory=list)
+ 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
+ top, bottom = ('bottom', 'top') if self.side == 'bottom' else ('top', 'bottom')
+ for target, source in [
+ (layer_stack[top, 'copper'], self.top_copper),
+ (layer_stack[top, 'mask'], self.top_mask),
+ (layer_stack[top, 'silk'], self.top_silk),
+ (layer_stack[top, 'paste'], self.top_paste),
+ (layer_stack[bottom, 'copper'], self.bottom_copper),
+ (layer_stack[bottom, 'mask'], self.bottom_mask),
+ (layer_stack[bottom, 'silk'], self.bottom_silk),
+ (layer_stack[bottom, 'paste'], self.bottom_paste),
+ (layer_stack.drill_pth, self.drill_pth),
+ (layer_stack.drill_npth, self.drill_npth)]:
+ for fe in source:
+ target.objects.append(copy(fe).rotate(rotation).offset(x, y, self.unit))
+
+
+@dataclass
+class Text(Positioned):
+ text: str
+ font_size: float = 2.5
+ stroke_width: float = 0.25
+ 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
+
+ if newstroke_font is None:
+ newstroke_font = Newstroke()
+
+ strokes = list(newstroke_font.render(self.text, size=self.font_size))
+ xs = [x for points in strokes for x, _y in points]
+ ys = [y for points in strokes for _x, y in points]
+ min_x, min_y, max_x, max_y = min(xs), min(ys), max(xs), max(ys)
+
+ if self.h_align == 'left':
+ x0 = 0
+ elif self.h_align == 'center':
+ x0 = -max_x/2
+ elif self.h_align == 'right':
+ x0 = -max_x
+ else:
+ raise ValueError('h_align must be one of "left", "center", or "right".')
+
+ if self.v_align == 'top':
+ y0 = -max_y
+ elif self.v_align == 'middle':
+ y0 = -max_y/2
+ elif self.v_align == 'bottom':
+ y0 = 0
+ else:
+ raise ValueError('v_align must be one of "top", "middle", or "bottom".')
+
+ ap = CircleAperture(self.stroke_width, unit=self.unit)
+
+ for stroke in strokes:
+ for (x1, y1), (x2, y2) in zip(stroke[:-1], stroke[1:]):
+ obj = Line(x0+x1, y0-y1, x0+x2, y0-y2, aperture=ap, unit=self.unit, polarity_dark=self.polarity_dark)
+ obj.rotate(rotation)
+ obj.offset(obj_x, obj_y)
+ layer_stack[self.side, self.layer].objects.append(obj)
+
+
+@dataclass
class Pad(Positioned):
pass
@@ -454,7 +550,6 @@ class Trace:
def _route_demo():
from ..utils import setup_svg, Tag
- from ..newstroke import Newstroke
def pd_obj(objs):
objs = list(objs)
@@ -530,7 +625,8 @@ def _board_demo():
p2 = THTPad.rect(20, 15, 0.9, 1.8)
b.add(p2)
b.add(Trace(0.5, p1, p2, style='ortho', roundover=1.5))
- print(b.svg())
+ b.add_text(50, 50, 'Foobar')
+ print(b.pretty_svg())
b.layer_stack().save_to_directory('/tmp/testdir')
diff --git a/gerbonara/graphic_primitives.py b/gerbonara/graphic_primitives.py
index 27890b1..02eac8d 100644
--- a/gerbonara/graphic_primitives.py
+++ b/gerbonara/graphic_primitives.py
@@ -136,11 +136,11 @@ class ArcPoly(GraphicPrimitive):
if len(self.outline) == 0:
return
- yield f'M {self.outline[0][0]:.6} {self.outline[0][1]:.6}'
+ yield f'M {float(self.outline[0][0]):.6} {float(self.outline[0][1]):.6}'
for old, new, arc in self.segments:
if not arc:
- yield f'L {new[0]:.6} {new[1]:.6}'
+ yield f'L {float(new[0]):.6} {float(new[1]):.6}'
else:
clockwise, center = arc
yield svg_arc(old, new, center, clockwise)
@@ -214,7 +214,7 @@ class Arc(GraphicPrimitive):
def flip(self):
return replace(self, x1=self.x2, y1=self.y2, x2=self.x1, y2=self.y1,
- cx=(self.x + self.cx) - self.x2, cy=(self.y + self.cy) - self.y2, clockwise=not self.clockwise)
+ cx=(self.x1 + self.cx) - self.x2, cy=(self.y1 + self.cy) - self.y2, clockwise=not self.clockwise)
def bounding_box(self):
r = self.width/2
diff --git a/gerbonara/layers.py b/gerbonara/layers.py
index 6c61756..22a845f 100644
--- a/gerbonara/layers.py
+++ b/gerbonara/layers.py
@@ -1055,7 +1055,7 @@ class LayerStack:
joins = {}
for cur in lines:
- for i, (x, y) in enumerate([(cur.x1, cur.y1), (cur.x2, cur.y2)]):
+ for (i, x, y) in [(0, cur.x1, cur.y1), (1, cur.x2, cur.y2)]:
x_left = bisect.bisect_left (by_x, x, key=lambda elem: elem[0] + tol)
x_right = bisect.bisect_right(by_x, x, key=lambda elem: elem[0] - tol)
selected = { elem for elem_x, elem in by_x[x_left:x_right] if elem != cur }
@@ -1080,11 +1080,9 @@ class LayerStack:
joins[(cur, i)] = (nearest, j)
joins[(nearest, j)] = (cur, i)
- def flip_if(obj, i):
- if i:
- c = copy.copy(obj)
- c.flip()
- return c
+ def flip_if(obj, cond):
+ if cond:
+ return obj.flip()
else:
return obj