From 5e23d07bcb5103b4607c6ad591a2a547c97ee1f6 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 13 Feb 2015 09:37:27 -0500 Subject: Fix rendering for line with rectangular aperture per #12. Still need to do the same for arcs. --- gerber/cam.py | 5 +-- gerber/primitives.py | 72 ++++++++++++++++++++++++++++++++++----- gerber/render/cairo_backend.py | 13 ++++--- gerber/render/svgwrite_backend.py | 10 ++++-- gerber/tests/test_primitives.py | 35 +++++++++++++------ 5 files changed, 108 insertions(+), 27 deletions(-) (limited to 'gerber') diff --git a/gerber/cam.py b/gerber/cam.py index 9c731aa..f49f5dd 100644 --- a/gerber/cam.py +++ b/gerber/cam.py @@ -21,7 +21,7 @@ CAM File This module provides common base classes for Excellon/Gerber CNC files """ - +from operator import mul class FileSettings(object): """ CAM File Settings @@ -241,7 +241,8 @@ class CamFile(object): filename : string If provided, save the rendered image to `filename` """ - ctx.set_bounds(self.bounds) + bounds = [tuple([x * 1.2, y*1.2]) for x, y in self.bounds] + ctx.set_bounds(bounds) for p in self.primitives: ctx.render(p) if filename is not None: diff --git a/gerber/primitives.py b/gerber/primitives.py index a239cab..1663a53 100644 --- a/gerber/primitives.py +++ b/gerber/primitives.py @@ -64,14 +64,70 @@ class Line(Primitive): angle = math.atan2(delta_y, delta_x) return angle - #@property - #def bounding_box(self): - # width_2 = self.width / 2. - # min_x = min(self.start[0], self.end[0]) - width_2 - # max_x = max(self.start[0], self.end[0]) + width_2 - # min_y = min(self.start[1], self.end[1]) - width_2 - # max_y = max(self.start[1], self.end[1]) + width_2 - # return ((min_x, max_x), (min_y, max_y)) + @property + def bounding_box(self): + if isinstance(self.aperture, Circle): + width_2 = self.aperture.radius + height_2 = width_2 + else: + width_2 = self.aperture.width / 2. + height_2 = self.aperture.height / 2. + min_x = min(self.start[0], self.end[0]) - width_2 + max_x = max(self.start[0], self.end[0]) + width_2 + min_y = min(self.start[1], self.end[1]) - height_2 + max_y = max(self.start[1], self.end[1]) + height_2 + return ((min_x, max_x), (min_y, max_y)) + + @property + def vertices(self): + if not isinstance(self.aperture, Rectangle): + return None + else: + start = self.start + end = self.end + width = self.aperture.width + height = self.aperture.height + + # Find all the corners of the start and end position + start_ll = (start[0] - (width / 2.), + start[1] - (height / 2.)) + start_lr = (start[0] - (width / 2.), + start[1] + (height / 2.)) + start_ul = (start[0] + (width / 2.), + start[1] - (height / 2.)) + start_ur = (start[0] + (width / 2.), + start[1] + (height / 2.)) + end_ll = (end[0] - (width / 2.), + end[1] - (height / 2.)) + end_lr = (end[0] - (width / 2.), + end[1] + (height / 2.)) + end_ul = (end[0] + (width / 2.), + end[1] - (height / 2.)) + end_ur = (end[0] + (width / 2.), + end[1] + (height / 2.)) + + if end[0] == start[0] and end[1] == start[1]: + return (start_ll, start_lr, start_ur, start_ul) + elif end[0] == start[0] and end[1] > start[1]: + return (start_ll, start_lr, end_ur, end_ul) + elif end[0] > start[0] and end[1] > start[1]: + return (start_ll, start_lr, end_lr, end_ur, end_ul, start_ul) + elif end[0] > start[0] and end[1] == start[1]: + return (start_ll, end_lr, end_ur, start_ul) + elif end[0] > start[0] and end[1] < start[1]: + return (start_ll, end_ll, end_lr, end_ur, start_ur, start_ul) + elif end[0] == start[0] and end[1] < start[1]: + return (end_ll, end_lr, start_ur, start_ul) + elif end[0] < start[0] and end[1] < start[1]: + return (end_ll, end_lr, start_lr, start_ur, start_ul, end_ul) + elif end[0] < start[0] and end[1] == start[1]: + return (end_ll, start_lr, start_ur, end_ul) + elif end[0] < start[0] and end[1] > start[1]: + return (start_ll, start_lr, start_ur, end_ur, end_ul, end_ll) + else: + return None + + class Arc(Primitive): diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index c1df87a..999269b 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -26,7 +26,7 @@ SCALE = 400. class GerberCairoContext(GerberContext): - def __init__(self, surface=None, size=(1000, 1000)): + def __init__(self, surface=None, size=(10000, 10000)): GerberContext.__init__(self) if surface is None: self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, @@ -58,9 +58,14 @@ class GerberCairoContext(GerberContext): self.ctx.move_to(*start) self.ctx.line_to(*end) self.ctx.stroke() - elif isinstance(line.aperture, rectangle): - # TODO: Render rectangle strokes as a polygon... - pass + elif isinstance(line.aperture, Rectangle): + points = [tuple(map(mul, x, self.scale)) for x in line.vertices] + self.ctx.set_source_rgba(*color, alpha=self.alpha) + self.ctx.set_line_width(0) + self.ctx.move_to(*points[0]) + for point in points[1:]: + self.ctx.line_to(*point) + self.ctx.fill() def _render_arc(self, arc, color): center = map(mul, arc.center, self.scale) diff --git a/gerber/render/svgwrite_backend.py b/gerber/render/svgwrite_backend.py index 279d90f..9e6a5e4 100644 --- a/gerber/render/svgwrite_backend.py +++ b/gerber/render/svgwrite_backend.py @@ -68,8 +68,14 @@ class GerberSvgContext(GerberContext): aline.stroke(opacity=self.alpha) self.dwg.add(aline) elif isinstance(line.aperture, Rectangle): - # TODO: Render rectangle strokes as a polygon... - pass + points = [tuple(map(mul, point, self.scale)) for point in line.vertices] + path = self.dwg.path(d='M %f, %f' % points[0], + fill=svg_color(color), + stroke='none') + path.fill(opacity=self.alpha) + for point in points[1:]: + path.push('L %f, %f' % point) + self.dwg.add(path) def _render_arc(self, arc, color): start = tuple(map(mul, arc.start, self.scale)) diff --git a/gerber/tests/test_primitives.py b/gerber/tests/test_primitives.py index 912cebb..877823d 100644 --- a/gerber/tests/test_primitives.py +++ b/gerber/tests/test_primitives.py @@ -27,17 +27,30 @@ def test_line_angle(): line_angle = (l.angle + 2 * math.pi) % (2 * math.pi) assert_almost_equal(line_angle, expected) -# Need to update bounds calculation using aperture -#def test_line_bounds(): -# """ Test Line primitive bounding box calculation -# """ -# cases = [((0, 0), (1, 1), ((0, 1), (0, 1))), -# ((-1, -1), (1, 1), ((-1, 1), (-1, 1))), -# ((1, 1), (-1, -1), ((-1, 1), (-1, 1))), -# ((-1, 1), (1, -1), ((-1, 1), (-1, 1))),] -# for start, end, expected in cases: -# l = Line(start, end, 0) -# assert_equal(l.bounding_box, expected) +def test_line_bounds(): + """ Test Line primitive bounding box calculation + """ + cases = [((0, 0), (1, 1), ((-1, 2), (-1, 2))), + ((-1, -1), (1, 1), ((-2, 2), (-2, 2))), + ((1, 1), (-1, -1), ((-2, 2), (-2, 2))), + ((-1, 1), (1, -1), ((-2, 2), (-2, 2))),] + + c = Circle((0, 0), 2) + r = Rectangle((0, 0), 2, 2) + for shape in (c, r): + for start, end, expected in cases: + l = Line(start, end, shape) + assert_equal(l.bounding_box, expected) + # Test a non-square rectangle + r = Rectangle((0, 0), 3, 2) + cases = [((0, 0), (1, 1), ((-1.5, 2.5), (-1, 2))), + ((-1, -1), (1, 1), ((-2.5, 2.5), (-2, 2))), + ((1, 1), (-1, -1), ((-2.5, 2.5), (-2, 2))), + ((-1, 1), (1, -1), ((-2.5, 2.5), (-2, 2))),] + for start, end, expected in cases: + l = Line(start, end, r) + assert_equal(l.bounding_box, expected) + def test_arc_radius(): -- cgit