diff options
-rw-r--r-- | gerber/gerber_statements.py | 16 | ||||
-rw-r--r-- | gerber/primitives.py | 21 | ||||
-rw-r--r-- | gerber/tests/test_cam.py | 18 | ||||
-rw-r--r-- | gerber/tests/test_gerber_statements.py | 92 | ||||
-rw-r--r-- | gerber/tests/test_primitives.py | 119 | ||||
-rw-r--r-- | gerber/tests/test_utils.py | 10 | ||||
-rw-r--r-- | gerber/utils.py | 10 |
7 files changed, 240 insertions, 46 deletions
diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index d84b5e0..0978aca 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -145,12 +145,14 @@ class MOParamStmt(ParamStmt): @classmethod def from_dict(cls, stmt_dict): param = stmt_dict.get('param') - if stmt_dict.get('mo').lower() == 'in': + if stmt_dict.get('mo') is None: + mo = None + elif stmt_dict.get('mo').lower() not in ('in', 'mm'): + raise ValueError('Mode may be mm or in') + elif stmt_dict.get('mo').lower() == 'in': mo = 'inch' - elif stmt_dict.get('mo').lower() == 'mm': - mo = 'metric' else: - mo = None + mo = 'metric' return cls(param, mo) def __init__(self, param, mo): @@ -347,7 +349,7 @@ class AMOutlinePrimitive(AMPrimitive): return "{code},{exposure},{n_points},{start_point},{points},{rotation}".format(**data) -class AMUnsupportPrimitive: +class AMUnsupportPrimitive(object): @classmethod def from_gerber(cls, primitive): return cls(primitive) @@ -652,9 +654,9 @@ class OFParamStmt(ParamStmt): def __str__(self): offset_str = '' if self.a is not None: - offset_str += ('X: %f' % self.a) + offset_str += ('X: %f ' % self.a) if self.b is not None: - offset_str += ('Y: %f' % self.b) + offset_str += ('Y: %f ' % self.b) return ('<Offset: %s>' % offset_str) diff --git a/gerber/primitives.py b/gerber/primitives.py index da05127..2d666b8 100644 --- a/gerber/primitives.py +++ b/gerber/primitives.py @@ -16,6 +16,7 @@ # limitations under the License.
import math
from operator import sub
+from .utils import validate_coordinates
class Primitive(object):
@@ -45,7 +46,7 @@ class Primitive(object): Return ((min x, max x), (min y, max y))
"""
- pass
+ raise NotImplementedError('Bounding box calculation must be implemented in subclass')
class Line(Primitive):
@@ -155,6 +156,7 @@ class Circle(Primitive): """
def __init__(self, position, diameter, **kwargs):
super(Circle, self).__init__(**kwargs)
+ validate_coordinates(position)
self.position = position
self.diameter = diameter
@@ -180,6 +182,7 @@ class Ellipse(Primitive): """
def __init__(self, position, width, height, **kwargs):
super(Ellipse, self).__init__(**kwargs)
+ validate_coordinates(position)
self.position = position
self.width = width
self.height = height
@@ -205,6 +208,7 @@ class Rectangle(Primitive): """
def __init__(self, position, width, height, **kwargs):
super(Rectangle, self).__init__(**kwargs)
+ validate_coordinates(position)
self.position = position
self.width = width
self.height = height
@@ -239,6 +243,7 @@ class Diamond(Primitive): """
def __init__(self, position, width, height, **kwargs):
super(Diamond, self).__init__(**kwargs)
+ validate_coordinates(position)
self.position = position
self.width = width
self.height = height
@@ -272,6 +277,7 @@ class ChamferRectangle(Primitive): """
def __init__(self, position, width, height, chamfer, corners, **kwargs):
super(ChamferRectangle, self).__init__(**kwargs)
+ validate_coordinates(position)
self.position = position
self.width = width
self.height = height
@@ -307,6 +313,7 @@ class RoundRectangle(Primitive): """
def __init__(self, position, width, height, radius, corners, **kwargs):
super(RoundRectangle, self).__init__(**kwargs)
+ validate_coordinates(position)
self.position = position
self.width = width
self.height = height
@@ -342,6 +349,7 @@ class Obround(Primitive): """
def __init__(self, position, width, height, **kwargs):
super(Obround, self).__init__(**kwargs)
+ validate_coordinates(position)
self.position = position
self.width = width
self.height = height
@@ -397,6 +405,7 @@ class Polygon(Primitive): """
def __init__(self, position, sides, radius, **kwargs):
super(Polygon, self).__init__(**kwargs)
+ validate_coordinates(position)
self.position = position
self.sides = sides
self.radius = radius
@@ -432,6 +441,7 @@ class RoundButterfly(Primitive): """
def __init__(self, position, diameter, **kwargs):
super(RoundButterfly, self).__init__(**kwargs)
+ validate_coordinates(position)
self.position = position
self.diameter = diameter
@@ -452,6 +462,7 @@ class SquareButterfly(Primitive): """
def __init__(self, position, side, **kwargs):
super(SquareButterfly, self).__init__(**kwargs)
+ validate_coordinates(position)
self.position = position
self.side = side
@@ -470,8 +481,14 @@ class Donut(Primitive): """
def __init__(self, position, shape, inner_diameter, outer_diameter, **kwargs):
super(Donut, self).__init__(**kwargs)
+ if len(position) != 2:
+ raise TypeError('Position must be a tuple (n=2) of coordinates')
self.position = position
+ if shape not in ('round', 'square', 'hexagon', 'octagon'):
+ raise ValueError('Valid shapes are round, square, hexagon or octagon')
self.shape = shape
+ if inner_diameter >= outer_diameter:
+ raise ValueError('Outer diameter must be larger than inner diameter.')
self.inner_diameter = inner_diameter
self.outer_diameter = outer_diameter
if self.shape in ('round', 'square', 'octagon'):
@@ -507,6 +524,8 @@ class Drill(Primitive): """
def __init__(self, position, diameter):
super(Drill, self).__init__('dark')
+ if len(position) != 2:
+ raise TypeError('Position must be a tuple (n=2) of coordinates')
self.position = position
self.diameter = diameter
diff --git a/gerber/tests/test_cam.py b/gerber/tests/test_cam.py index 8e0270c..185e716 100644 --- a/gerber/tests/test_cam.py +++ b/gerber/tests/test_cam.py @@ -77,7 +77,6 @@ def test_zeros(): assert_equal(fs.zero_suppression, 'trailing') assert_equal(fs.zeros, 'leading') - fs['zero_suppression'] = 'leading' assert_equal(fs.zero_suppression, 'leading') assert_equal(fs.zeros, 'trailing') @@ -90,11 +89,26 @@ def test_zeros(): assert_equal(fs.zeros, 'trailing') assert_equal(fs.zero_suppression, 'leading') - fs.zeros= 'leading' assert_equal(fs.zeros, 'leading') assert_equal(fs.zero_suppression, 'trailing') + fs = FileSettings(zeros='leading') + assert_equal(fs.zeros, 'leading') + assert_equal(fs.zero_suppression, 'trailing') + + fs = FileSettings(zero_suppression='leading') + assert_equal(fs.zeros, 'trailing') + assert_equal(fs.zero_suppression, 'leading') + + fs = FileSettings(zeros='leading', zero_suppression='trailing') + assert_equal(fs.zeros, 'leading') + assert_equal(fs.zero_suppression, 'trailing') + + fs = FileSettings(zeros='trailing', zero_suppression='leading') + assert_equal(fs.zeros, 'trailing') + assert_equal(fs.zero_suppression, 'leading') + def test_filesettings_validation(): """ Test FileSettings constructor argument validation diff --git a/gerber/tests/test_gerber_statements.py b/gerber/tests/test_gerber_statements.py index ff967f9..e797d5a 100644 --- a/gerber/tests/test_gerber_statements.py +++ b/gerber/tests/test_gerber_statements.py @@ -82,6 +82,12 @@ def test_MOParamStmt_factory(): assert_equal(mo.param, 'MO') assert_equal(mo.mode, 'metric') + stmt = {'param': 'MO'} + mo = MOParamStmt.from_dict(stmt) + assert_equal(mo.mode, None) + stmt = {'param': 'MO', 'mo': 'degrees kelvin'} + assert_raises(ValueError, MOParamStmt.from_dict, stmt) + def test_MOParamStmt(): """ Test MOParamStmt initialization @@ -182,6 +188,13 @@ def test_OFParamStmt_dump(): assert_equal(of.to_gerber(), '%OFA0.12345B0.12345*%') +def test_OFParamStmt_string(): + """ Test OFParamStmt __str__ + """ + stmt = {'param': 'OF', 'a': '0.123456', 'b': '0.123456'} + of = OFParamStmt.from_dict(stmt) + assert_equal(str(of), '<Offset: X: 0.123456 Y: 0.123456 >') + def test_LPParamStmt_factory(): """ Test LPParamStmt factory """ @@ -377,38 +390,47 @@ def test_ADParamStmt_factory(): assert_equal(ad.d, 1) assert_equal(ad.shape, 'R') - stmt = {'param': 'AD', 'd': 2, 'shape': 'O'} - ad = ADParamStmt.from_dict(stmt) - assert_equal(ad.d, 2) - assert_equal(ad.shape, 'O') - - -def test_ADParamStmt_dump(): - """ Test ADParamStmt.to_gerber() - """ - stmt = {'param': 'AD', 'd': 0, 'shape': 'C'} - ad = ADParamStmt.from_dict(stmt) - assert_equal(ad.to_gerber(),'%ADD0C*%') - - stmt = {'param': 'AD', 'd': 1, 'shape': 'R'} - ad = ADParamStmt.from_dict(stmt) - assert_equal(ad.to_gerber(),'%ADD1R*%') - - stmt = {'param': 'AD', 'd': 2, 'shape': 'O'} - ad = ADParamStmt.from_dict(stmt) - assert_equal(ad.to_gerber(),'%ADD2O*%') - -def test_ADParamStmt_string(): - """ Test ADParamStmt.__str__() - """ - stmt = {'param': 'AD', 'd': 0, 'shape': 'C'} - ad = ADParamStmt.from_dict(stmt) - assert_equal(str(ad), '<Aperture Definition: 0: circle>') - - stmt = {'param': 'AD', 'd': 1, 'shape': 'R'} - ad = ADParamStmt.from_dict(stmt) - assert_equal(str(ad), '<Aperture Definition: 1: rectangle>') - - stmt = {'param': 'AD', 'd': 2, 'shape': 'O'} - ad = ADParamStmt.from_dict(stmt) - assert_equal(str(ad), '<Aperture Definition: 2: obround>') +def test_MIParamStmt_factory(): + stmt = {'param': 'MI', 'a': 1, 'b': 1} + mi = MIParamStmt.from_dict(stmt) + assert_equal(mi.a, 1) + assert_equal(mi.b, 1) + +def test_MIParamStmt_dump(): + stmt = {'param': 'MI', 'a': 1, 'b': 1} + mi = MIParamStmt.from_dict(stmt) + assert_equal(mi.to_gerber(), '%MIA1B1*%') + stmt = {'param': 'MI', 'a': 1} + mi = MIParamStmt.from_dict(stmt) + assert_equal(mi.to_gerber(), '%MIA1B0*%') + stmt = {'param': 'MI', 'b': 1} + mi = MIParamStmt.from_dict(stmt) + assert_equal(mi.to_gerber(), '%MIA0B1*%') + +def test_MIParamStmt_string(): + stmt = {'param': 'MI', 'a': 1, 'b': 1} + mi = MIParamStmt.from_dict(stmt) + assert_equal(str(mi), '<Image Mirror: A=1 B=1>') + + stmt = {'param': 'MI', 'b': 1} + mi = MIParamStmt.from_dict(stmt) + assert_equal(str(mi), '<Image Mirror: A=0 B=1>') + + stmt = {'param': 'MI', 'a': 1} + mi = MIParamStmt.from_dict(stmt) + assert_equal(str(mi), '<Image Mirror: A=1 B=0>') + + + +def test_coordstmt_ctor(): + cs = CoordStmt('G04', 0.0, 0.1, 0.2, 0.3, 'D01', FileSettings()) + assert_equal(cs.function, 'G04') + assert_equal(cs.x, 0.0) + assert_equal(cs.y, 0.1) + assert_equal(cs.i, 0.2) + assert_equal(cs.j, 0.3) + assert_equal(cs.op, 'D01') + + + +
\ No newline at end of file diff --git a/gerber/tests/test_primitives.py b/gerber/tests/test_primitives.py index e5ae770..14a3d39 100644 --- a/gerber/tests/test_primitives.py +++ b/gerber/tests/test_primitives.py @@ -6,6 +6,11 @@ from ..primitives import * from tests import * +def test_primitive_implementation_warning(): + p = Primitive() + assert_raises(NotImplementedError, p.bounding_box) + + def test_line_angle(): """ Test Line primitive angle calculation """ @@ -277,4 +282,118 @@ def test_polygon_bounds(): assert_array_almost_equal(ybounds, (-2, 6)) +def test_region_ctor(): + """ Test Region creation + """ + points = ((0, 0), (1,0), (1,1), (0,1)) + r = Region(points) + for i, point in enumerate(points): + assert_array_almost_equal(r.points[i], point) + + +def test_region_bounds(): + """ Test region bounding box calculation + """ + points = ((0, 0), (1,0), (1,1), (0,1)) + r = Region(points) + xbounds, ybounds = r.bounding_box + assert_array_almost_equal(xbounds, (0, 1)) + assert_array_almost_equal(ybounds, (0, 1)) + + +def test_round_butterfly_ctor(): + """ Test round butterfly creation + """ + test_cases = (((0,0), 3), ((0, 0), 5), ((1,1), 7)) + for pos, diameter in test_cases: + b = RoundButterfly(pos, diameter) + assert_equal(b.position, pos) + assert_equal(b.diameter, diameter) + assert_equal(b.radius, diameter/2.) + +def test_round_butterfly_ctor_validation(): + """ Test RoundButterfly argument validation + """ + assert_raises(TypeError, RoundButterfly, 3, 5) + assert_raises(TypeError, RoundButterfly, (3,4,5), 5) + +def test_round_butterfly_bounds(): + """ Test RoundButterfly bounding box calculation + """ + b = RoundButterfly((0, 0), 2) + xbounds, ybounds = b.bounding_box + assert_array_almost_equal(xbounds, (-1, 1)) + assert_array_almost_equal(ybounds, (-1, 1)) + +def test_square_butterfly_ctor(): + """ Test SquareButterfly creation + """ + test_cases = (((0,0), 3), ((0, 0), 5), ((1,1), 7)) + for pos, side in test_cases: + b = SquareButterfly(pos, side) + assert_equal(b.position, pos) + assert_equal(b.side, side) + +def test_square_butterfly_ctor_validation(): + """ Test SquareButterfly argument validation + """ + assert_raises(TypeError, SquareButterfly, 3, 5) + assert_raises(TypeError, SquareButterfly, (3,4,5), 5) + +def test_square_butterfly_bounds(): + """ Test SquareButterfly bounding box calculation + """ + b = SquareButterfly((0, 0), 2) + xbounds, ybounds = b.bounding_box + assert_array_almost_equal(xbounds, (-1, 1)) + assert_array_almost_equal(ybounds, (-1, 1)) + +def test_donut_ctor(): + """ Test Donut primitive creation + """ + test_cases = (((0,0), 'round', 3, 5), ((0, 0), 'square', 5, 7), + ((1,1), 'hexagon', 7, 9), ((2, 2), 'octagon', 9, 11)) + for pos, shape, in_d, out_d in test_cases: + d = Donut(pos, shape, in_d, out_d) + assert_equal(d.position, pos) + assert_equal(d.shape, shape) + assert_equal(d.inner_diameter, in_d) + assert_equal(d.outer_diameter, out_d) + +def test_donut_ctor_validation(): + assert_raises(TypeError, Donut, 3, 'round', 5, 7) + assert_raises(TypeError, Donut, (3, 4, 5), 'round', 5, 7) + assert_raises(ValueError, Donut, (0, 0), 'triangle', 3, 5) + assert_raises(ValueError, Donut, (0, 0), 'round', 5, 3) + +def test_donut_bounds(): + pass + +def test_drill_ctor(): + """ Test drill primitive creation + """ + test_cases = (((0, 0), 2), ((1, 1), 3), ((2, 2), 5)) + for position, diameter in test_cases: + d = Drill(position, diameter) + assert_equal(d.position, position) + assert_equal(d.diameter, diameter) + assert_equal(d.radius, diameter/2.) + +def test_drill_ctor_validation(): + """ Test drill argument validation + """ + assert_raises(TypeError, Drill, 3, 5) + assert_raises(TypeError, Drill, (3,4,5), 5) + +def test_drill_bounds(): + d = Drill((0, 0), 2) + xbounds, ybounds = d.bounding_box + assert_array_almost_equal(xbounds, (-1, 1)) + assert_array_almost_equal(ybounds, (-1, 1)) + d = Drill((1, 2), 2) + xbounds, ybounds = d.bounding_box + assert_array_almost_equal(xbounds, (0, 2)) + assert_array_almost_equal(ybounds, (1, 3)) + +
\ No newline at end of file diff --git a/gerber/tests/test_utils.py b/gerber/tests/test_utils.py index 1077022..1c3f1e5 100644 --- a/gerber/tests/test_utils.py +++ b/gerber/tests/test_utils.py @@ -4,7 +4,7 @@ # Author: Hamilton Kibbe <ham@hamiltonkib.be> from .tests import assert_equal, assert_raises -from ..utils import decimal_string, parse_gerber_value, write_gerber_value, detect_file_format +from ..utils import * def test_zero_suppression(): @@ -107,3 +107,11 @@ def test_detect_format_with_short_file(): """ Verify file format detection works with short files """ assert_equal('unknown', detect_file_format('gerber/tests/__init__.py')) + +def test_validate_coordinates(): + assert_raises(TypeError, validate_coordinates, 3) + assert_raises(TypeError, validate_coordinates, 3.1) + assert_raises(TypeError, validate_coordinates, '14') + assert_raises(TypeError, validate_coordinates, (0,)) + assert_raises(TypeError, validate_coordinates, (0,1,2)) + assert_raises(TypeError, validate_coordinates, (0,'string')) diff --git a/gerber/utils.py b/gerber/utils.py index 64cd6ed..86119ba 100644 --- a/gerber/utils.py +++ b/gerber/utils.py @@ -220,3 +220,13 @@ def detect_file_format(filename): elif '%FS' in line: return'rs274x' return 'unknown' + + +def validate_coordinates(position): + if position is not None: + if len(position) != 2: + raise TypeError('Position must be a tuple (n=2) of coordinates') + else: + for coord in position: + if not (isinstance(coord, int) or isinstance(coord, float)): + raise TypeError('Coordinates must be integers or floats') |