diff options
Diffstat (limited to 'gerber')
-rw-r--r-- | gerber/primitives.py | 63 | ||||
-rw-r--r-- | gerber/render/cairo_backend.py | 20 | ||||
-rw-r--r-- | gerber/render/svgwrite_backend.py | 14 | ||||
-rw-r--r-- | gerber/tests/test_primitives.py | 85 | ||||
-rw-r--r-- | gerber/tests/tests.py | 5 |
5 files changed, 179 insertions, 8 deletions
diff --git a/gerber/primitives.py b/gerber/primitives.py index b3869e1..f934f74 100644 --- a/gerber/primitives.py +++ b/gerber/primitives.py @@ -44,8 +44,8 @@ class Line(Primitive): @property
def angle(self):
- delta_x, delta_y = tuple(map(sub, end, start))
- angle = degrees(math.tan(delta_y/delta_x))
+ delta_x, delta_y = tuple(map(sub, self.end, self.start))
+ angle = math.atan2(delta_y, delta_x)
return angle
@property
@@ -70,18 +70,71 @@ class Arc(Primitive): self.width = width
@property
+ def radius(self):
+ dy, dx = map(sub, self.start, self.center)
+ return math.sqrt(dy**2 + dx**2)
+
+ @property
def start_angle(self):
dy, dx = map(sub, self.start, self.center)
- return math.atan2(dy, dx)
+ return math.atan2(dx, dy)
@property
def end_angle(self):
dy, dx = map(sub, self.end, self.center)
- return math.atan2(dy, dx)
+ return math.atan2(dx, dy)
+
+ @property
+ def sweep_angle(self):
+ two_pi = 2 * math.pi
+ theta0 = (self.start_angle + two_pi) % two_pi
+ theta1 = (self.end_angle + two_pi) % two_pi
+ if self.direction == 'counterclockwise':
+ return abs(theta1 - theta0)
+ else:
+ theta0 += two_pi
+ return abs(theta0 - theta1) % two_pi
@property
def bounding_box(self):
- pass
+ two_pi = 2 * math.pi
+ theta0 = (self.start_angle + two_pi) % two_pi
+ theta1 = (self.end_angle + two_pi) % two_pi
+ points = [self.start, self.end]
+ #Shit's about to get ugly...
+ if self.direction == 'counterclockwise':
+ # Passes through 0 degrees
+ if theta0 > theta1:
+ points.append((self.center[0] + self.radius, self.center[1]))
+ # Passes through 90 degrees
+ if theta0 <= math.pi / 2. and (theta1 >= math.pi / 2. or theta1 < theta0):
+ points.append((self.center[0], self.center[1] + self.radius))
+ # Passes through 180 degrees
+ if theta0 <= math.pi and (theta1 >= math.pi or theta1 < theta0):
+ points.append((self.center[0] - self.radius, self.center[1]))
+ # Passes through 270 degrees
+ if theta0 <= math.pi * 1.5 and (theta1 >= math.pi * 1.5 or theta1 < theta0):
+ points.append((self.center[0], self.center[1] - self.radius ))
+ else:
+ # Passes through 0 degrees
+ if theta1 > theta0:
+ points.append((self.center[0] + self.radius, self.center[1]))
+ # Passes through 90 degrees
+ if theta1 <= math.pi / 2. and (theta0 >= math.pi / 2. or theta0 < theta1):
+ points.append((self.center[0], self.center[1] + self.radius))
+ # Passes through 180 degrees
+ if theta1 <= math.pi and (theta0 >= math.pi or theta0 < theta1):
+ points.append((self.center[0] - self.radius, self.center[1]))
+ # Passes through 270 degrees
+ if theta1 <= math.pi * 1.5 and (theta0 >= math.pi * 1.5 or theta0 < theta1):
+ points.append((self.center[0], self.center[1] - self.radius ))
+ x, y = zip(*points)
+ min_x = min(x)
+ max_x = max(x)
+ min_y = min(y)
+ max_y = max(y)
+ return ((min_x, max_x), (min_y, max_y))
+
class Circle(Primitive):
"""
diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py index 1c69725..125a125 100644 --- a/gerber/render/cairo_backend.py +++ b/gerber/render/cairo_backend.py @@ -56,13 +56,31 @@ class GerberCairoContext(GerberContext): self.ctx.line_to(*end)
self.ctx.stroke()
+ def _render_arc(self, arc, color):
+ center = map(mul, arc.center, self.scale)
+ start = map(mul, arc.start, self.scale)
+ end = map(mul, arc.end, self.scale)
+ radius = SCALE * arc.radius
+ angle1 = arc.start_angle
+ angle2 = arc.end_angle
+ width = arc.width if arc.width != 0 else 0.001
+ self.ctx.set_source_rgba(*color, alpha=self.alpha)
+ self.ctx.set_line_width(width * SCALE)
+ self.ctx.set_line_cap(cairo.LINE_CAP_ROUND)
+ self.ctx.move_to(*start) # You actually have to do this...
+ if arc.direction == 'counterclockwise':
+ self.ctx.arc(*center, radius=radius, angle1=angle1, angle2=angle2)
+ else:
+ self.ctx.arc_negative(*center, radius=radius, angle1=angle1, angle2=angle2)
+ self.ctx.move_to(*end) # ...lame
+
def _render_region(self, region, color):
points = [tuple(map(mul, point, self.scale)) for point in region.points]
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.move_to(*point)
+ self.ctx.line_to(*point)
self.ctx.fill()
def _render_circle(self, circle, color):
diff --git a/gerber/render/svgwrite_backend.py b/gerber/render/svgwrite_backend.py index aeb680c..27783d6 100644 --- a/gerber/render/svgwrite_backend.py +++ b/gerber/render/svgwrite_backend.py @@ -18,6 +18,7 @@ from .render import GerberContext from operator import mul +import math import svgwrite SCALE = 400. @@ -64,6 +65,19 @@ class GerberSvgContext(GerberContext): aline.stroke(opacity=self.alpha) self.dwg.add(aline) + def _render_arc(self, arc, color): + start = tuple(map(mul, arc.start, self.scale)) + end = tuple(map(mul, arc.end, self.scale)) + radius = SCALE * arc.radius + width = arc.width if arc.width != 0 else 0.001 + arc_path = self.dwg.path(d='M %f, %f' % start, + stroke=svg_color(color), + stroke_width=SCALE * width) + large_arc = arc.sweep_angle >= 2 * math.pi + direction = '-' if arc.direction == 'clockwise' else '+' + arc_path.push_arc(end, 0, radius, large_arc, direction, True) + self.dwg.add(arc_path) + def _render_region(self, region, color): points = [tuple(map(mul, point, self.scale)) for point in region.points] region_path = self.dwg.path(d='M %f, %f' % points[0], diff --git a/gerber/tests/test_primitives.py b/gerber/tests/test_primitives.py new file mode 100644 index 0000000..29036b4 --- /dev/null +++ b/gerber/tests/test_primitives.py @@ -0,0 +1,85 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +# Author: Hamilton Kibbe <ham@hamiltonkib.be> +from ..primitives import * +from tests import * + + + +def test_line_angle(): + """ Test Line primitive angle calculation + """ + cases = [((0, 0), (1, 0), math.radians(0)), + ((0, 0), (1, 1), math.radians(45)), + ((0, 0), (0, 1), math.radians(90)), + ((0, 0), (-1, 1), math.radians(135)), + ((0, 0), (-1, 0), math.radians(180)), + ((0, 0), (-1, -1), math.radians(225)), + ((0, 0), (0, -1), math.radians(270)), + ((0, 0), (1, -1), math.radians(315)),] + for start, end, expected in cases: + l = Line(start, end, 0) + line_angle = (l.angle + 2 * math.pi) % (2 * math.pi) + assert_almost_equal(line_angle, expected) + +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_arc_radius(): + """ Test Arc primitive radius calculation + """ + cases = [((-3, 4), (5, 0), (0, 0), 5), + ((0, 1), (1, 0), (0, 0), 1),] + + for start, end, center, radius in cases: + a = Arc(start, end, center, 'clockwise', 0) + assert_equal(a.radius, radius) + + +def test_arc_sweep_angle(): + """ Test Arc primitive sweep angle calculation + """ + cases = [((1, 0), (0, 1), (0, 0), 'counterclockwise', math.radians(90)), + ((1, 0), (0, 1), (0, 0), 'clockwise', math.radians(270)), + ((1, 0), (-1, 0), (0, 0), 'clockwise', math.radians(180)), + ((1, 0), (-1, 0), (0, 0), 'counterclockwise', math.radians(180)),] + + for start, end, center, direction, sweep in cases: + a = Arc(start, end, center, direction, 0) + assert_equal(a.sweep_angle, sweep) + + +def test_arc_bounds(): + """ Test Arc primitive bounding box calculation + """ + cases = [((1, 0), (0, 1), (0, 0), 'clockwise', ((-1, 1), (-1, 1))), + ((1, 0), (0, 1), (0, 0), 'counterclockwise', ((0, 1), (0, 1))), + #TODO: ADD MORE TEST CASES HERE + ] + + for start, end, center, direction, bounds in cases: + a = Arc(start, end, center, direction, 0) + assert_equal(a.bounding_box, bounds) + +def test_circle_radius(): + """ Test Circle primitive radius calculation + """ + c = Circle((1, 1), 2) + assert_equal(c.radius, 1) + +def test_circle_bounds(): + """ Test Circle bounding box calculation + """ + c = Circle((1, 1), 2) + assert_equal(c.bounding_box, ((0, 2), (0, 2))) + + diff --git a/gerber/tests/tests.py b/gerber/tests/tests.py index 29b7899..222eea3 100644 --- a/gerber/tests/tests.py +++ b/gerber/tests/tests.py @@ -7,6 +7,7 @@ from nose.tools import assert_in from nose.tools import assert_not_in from nose.tools import assert_equal from nose.tools import assert_not_equal +from nose.tools import assert_almost_equal from nose.tools import assert_true from nose.tools import assert_false from nose.tools import assert_raises @@ -14,5 +15,5 @@ from nose.tools import raises from nose import with_setup __all__ = ['assert_in', 'assert_not_in', 'assert_equal', 'assert_not_equal', - 'assert_true', 'assert_false', 'assert_raises', 'raises', - 'with_setup' ] + 'assert_almost_equal', 'assert_true', 'assert_false', + 'assert_raises', 'raises', 'with_setup' ] |