summaryrefslogtreecommitdiff
path: root/gerber
diff options
context:
space:
mode:
Diffstat (limited to 'gerber')
-rw-r--r--gerber/am_statements.py341
-rw-r--r--gerber/gerber_statements.py83
-rw-r--r--gerber/tests/test_am_statements.py77
3 files changed, 426 insertions, 75 deletions
diff --git a/gerber/am_statements.py b/gerber/am_statements.py
new file mode 100644
index 0000000..3f6ff1e
--- /dev/null
+++ b/gerber/am_statements.py
@@ -0,0 +1,341 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# copyright 2015 Hamilton Kibbe <ham@hamiltonkib.be> and Paulo Henrique Silva
+# <ph.silva@gmail.com>
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from .utils import validate_coordinates
+
+
+# TODO: Add support for aperture macro variables
+
+class AMPrimitive(object):
+ """ Aperture Macro Primitive Base Class
+ """
+ def __init__(self, code, exposure=None):
+ """ Initialize Aperture Macro Primitive base class
+
+ Parameters
+ ----------
+ code : int
+ primitive shape code
+
+ exposure : str
+ on or off Primitives with exposure on create a slid part of
+ the macro aperture, and primitives with exposure off erase the
+ solid part created previously in the aperture macro definition.
+ .. note::
+ The erasing effect is limited to the aperture definition in
+ which it occurs.
+
+ Returns
+ -------
+ primitive : :class: `gerber.am_statements.AMPrimitive`
+
+ Raises
+ ------
+ TypeError, ValueError
+ """
+ VALID_CODES = (0, 1, 2, 4, 5, 6, 7, 20, 21, 22)
+ if not isinstance(code, int):
+ raise TypeError('Aperture Macro Primitive code must be an integer')
+ elif code not in VALID_CODES:
+ raise ValueError('Invalid Code. Valid codes are %s.' % ', '.join(map(str, VALID_CODES)))
+ if exposure is not None and exposure.lower() not in ('on', 'off'):
+ raise ValueError('Exposure must be either on or off')
+ self.code = code
+ self.exposure = exposure.lower() if exposure is not None else None
+
+ def to_inch(self):
+ pass
+
+ def to_metric(self):
+ pass
+
+
+class AMCommentPrimitive(AMPrimitive):
+ """ Aperture Macro Comment primitive. Code 0
+ """
+ @classmethod
+ def from_gerber(cls, primitive):
+ primitive = primitive.strip()
+ code = int(primitive[0])
+ comment = primitive[1:]
+ return cls(code, comment)
+
+ def __init__(self, code, comment):
+ """ Initialize AMCommentPrimitive class
+
+ Parameters
+ ----------
+ code : int
+ Aperture Macro primitive code. 0 Indicates an AMCommentPrimitive
+
+ comment : str
+ The comment as a string.
+
+ Returns
+ -------
+ CommentPrimitive : :class:`gerber.am_statements.AMCommentPrimitive`
+ An Initialized AMCommentPrimitive
+
+ Raises
+ ------
+ ValueError
+ """
+ if code != 0:
+ raise ValueError('Not a valid Aperture Macro Comment statement')
+ super(AMCommentPrimitive, self).__init__(code)
+ self.comment = comment.strip(' *')
+
+ def to_gerber(self, settings=None):
+ return '0 %s *' % self.comment
+
+ def __str__(self):
+ return '<Aperture Macro Comment: %s>' % self.comment
+
+
+class AMCirclePrimitive(AMPrimitive):
+ """ Aperture macro Circle primitive. Code 1
+ """
+ @classmethod
+ def from_gerber(cls, primitive):
+ modifiers = primitive.strip(' *').split(',')
+ code = int(modifiers[0])
+ exposure = 'on' if modifiers[1].strip() == '1' else 'off'
+ diameter = float(modifiers[2])
+ position = (float(modifiers[3]), float(modifiers[4]))
+ return cls(code, exposure, diameter, position)
+
+ def __init__(self, code, exposure, diameter, position):
+ """ Initialize AMCirclePrimitive
+
+ Parameters
+ ----------
+ code : int
+ Circle Primitive code. Must be 1
+
+ exposure : string
+ 'on' or 'off'
+
+ diameter : float
+ Circle diameter
+
+ position : tuple (<float>, <float>)
+ Position of the circle relative to the macro origin
+
+ Returns
+ -------
+ CirclePrimitive : :class:`gerber.am_statements.AMCirclePrimitive`
+ An initialized AMCirclePrimitive
+
+ Raises
+ ------
+ ValueError, TypeError
+ """
+ validate_coordinates(position)
+ if code != 1:
+ raise ValueError('Not a valid Aperture Macro Circle statement')
+ super(AMCirclePrimitive, self).__init__(code, exposure)
+ self.diameter = diameter
+ self.position = position
+
+ def to_gerber(self, settings=None):
+ data = dict(code = self.code,
+ exposure = '1' if self.exposure == 'on' else 0,
+ diameter = self.diameter,
+ x = self.position[0],
+ y = self.position[1])
+ return '{code},{exposure},{diameter},{x},{y}*'.format(**data)
+
+
+class AMVectorLinePrimitive(AMPrimitive):
+ """ Aperture Macro Vector Line primitive. Code 2 or 20
+ """
+ @classmethod
+ def from_gerber(cls, primitive):
+ modifiers = primitive.strip(' *').split(',')
+ code = int(modifiers[0])
+ exposure = 'on' if modifiers[1].strip() == '1' else 'off'
+ width = float(modifiers[2])
+ start (float(modifiers[3]), float(modifiers[4]))
+ end = (float(modifiers[5]), float(modifiers[6]))
+ rotation = float(modifiers[7])
+ return cls(code, exposure, width, start, end, rotation)
+
+ def __init__(self, code, exposure, width, start, end, rotation):
+ """ Initialize AMVectorLinePrimitive
+
+ Parameters
+ ----------
+ code : int
+ Vector Line Primitive code. Must be either 2 or 20.
+
+ exposure : string
+ 'on' or 'off'
+
+ width : float
+ Line width
+
+ start : tuple (<float>, <float>)
+ coordinate of line start point
+
+ end : tuple (<float>, <float>)
+ coordinate of line end point
+
+ rotation : float
+ Line rotation about the origin.
+
+ Returns
+ -------
+ LinePrimitive : :class:`gerber.am_statements.AMVectorLinePrimitive`
+ An initialized AMVectorLinePrimitive
+
+ Raises
+ ------
+ ValueError, TypeError
+ """
+ validate_coordinates(start)
+ validate_coordinates(end)
+ if code not in (2, 20):
+ raise ValueError('Valid VectorLinePrimitive codes are 2 or 20')
+ super(AMVectorLinePrimitive, self).__init__(code, exposure)
+ self.width = width
+ self.start = start
+ self.end = end
+ self.rotation = rotation
+
+ def to_gerber(self, settings=None):
+ fmtstr = '{code},{exp},{width},{startx},{starty},{endx},{endy},{rot}*'
+ data = dict(code = self.code,
+ exp = 1 if self.exposure == 'on' else 0,
+ startx = self.start[0],
+ starty = self.start[1],
+ endx = self.end[0],
+ endy = self.end[1],
+ rotation = self.rotation)
+ return fmtstr.format(**data)
+
+# Code 4
+class AMOutlinePrimitive(AMPrimitive):
+
+ @classmethod
+ def from_gerber(cls, primitive):
+ modifiers = primitive.strip(' *').split(",")
+
+ code = int(modifiers[0])
+ exposure = "on" if modifiers[1].strip() == "1" else "off"
+ n = int(modifiers[2])
+ start_point = (float(modifiers[3]), float(modifiers[4]))
+ points = []
+
+ for i in range(n):
+ points.append((float(modifiers[5 + i*2]), float(modifiers[5 + i*2 + 1])))
+
+ rotation = float(modifiers[-1])
+
+ return cls(code, exposure, start_point, points, rotation)
+
+ def __init__(self, code, exposure, start_point, points, rotation):
+ """ Initialize AMOutlinePrimitive
+
+ Parameters
+ ----------
+ code : int
+ OutlinePrimitive code. Must be 4.
+
+ exposure : string
+ 'on' or 'off'
+
+ start_point : tuple (<float>, <float>)
+ coordinate of outline start point
+
+ points : list of tuples (<float>, <float>)
+ coordinates of subsequent points
+
+ rotation : float
+ outline rotation about the origin.
+
+ Returns
+ -------
+ OutlinePrimitive : :class:`gerber.am_statements.AMOutlineinePrimitive`
+ An initialized AMOutlinePrimitive
+
+ Raises
+ ------
+ ValueError, TypeError
+ """
+ validate_coordinates(start_point)
+ for point in points:
+ validate_coordinates(point)
+ super(AMOutlinePrimitive, self).__init__(code, exposure)
+ self.start_point = start_point
+ self.points = points
+ self.rotation = rotation
+
+ def to_inch(self):
+ self.start_point = tuple([x / 25.4 for x in self.start_point])
+ self.points = tuple([(x / 25.4, y / 25.4) for x, y in self.points])
+
+ def to_metric(self):
+ self.start_point = tuple([x * 25.4 for x in self.start_point])
+ self.points = tuple([(x * 25.4, y * 25.4) for x, y in self.points])
+
+ def to_gerber(self, settings=None):
+ data = dict(
+ code=self.code,
+ exposure="1" if self.exposure == "on" else "0",
+ n_points=len(self.points),
+ start_point="%.4f,%.4f" % self.start_point,
+ points=",".join(["%.4f,%.4f" % point for point in self.points]),
+ rotation=str(self.rotation)
+ )
+ return "{code},{exposure},{n_points},{start_point},{points},{rotation}*".format(**data)
+
+# Code 5
+class AMPolygonPrimitive(AMPrimitive):
+ pass
+
+
+# Code 6
+class AMMoirePrimitive(AMPrimitive):
+ pass
+
+
+# Code 7
+class AMThermalPrimitive(AMPrimitive):
+ pass
+
+
+# Code 21
+class AMCenterLinePrimitive(AMPrimitive):
+ pass
+
+
+# Code 22
+class AMLowerLeftLinePrimitive(AMPrimitive):
+ pass
+
+
+class AMUnsupportPrimitive(AMPrimitive):
+ @classmethod
+ def from_gerber(cls, primitive):
+ return cls(primitive)
+
+ def __init__(self, primitive):
+ self.primitive = primitive
+
+ def to_gerber(self, settings=None):
+ return self.primitive
diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py
index 0978aca..7b1b56d 100644
--- a/gerber/gerber_statements.py
+++ b/gerber/gerber_statements.py
@@ -21,6 +21,7 @@ Gerber (RS-274X) Statements
"""
from .utils import parse_gerber_value, write_gerber_value, decimal_string
+from .am_statements import *
class Statement(object):
@@ -290,77 +291,6 @@ class ADParamStmt(ParamStmt):
return '<Aperture Definition: %d: %s>' % (self.d, shape)
-class AMPrimitive(object):
-
- def __init__(self, code, exposure):
- self.code = code
- self.exposure = exposure
-
- def to_inch(self):
- pass
-
- def to_metric(self):
- pass
-
-
-class AMOutlinePrimitive(AMPrimitive):
-
- @classmethod
- def from_gerber(cls, primitive):
- modifiers = primitive.split(",")
-
- code = int(modifiers[0])
- exposure = "on" if modifiers[1] == "1" else "off"
- n = int(modifiers[2])
- start_point = (float(modifiers[3]), float(modifiers[4]))
- points = []
-
- for i in range(n):
- points.append((float(modifiers[5 + i*2]), float(modifiers[5 + i*2 + 1])))
-
- rotation = float(modifiers[-1])
-
- return cls(code, exposure, start_point, points, rotation)
-
- def __init__(self, code, exposure, start_point, points, rotation):
- super(AMOutlinePrimitive, self).__init__(code, exposure)
-
- self.start_point = start_point
- self.points = points
- self.rotation = rotation
-
- def to_inch(self):
- self.start_point = tuple([x / 25.4 for x in self.start_point])
- self.points = tuple([(x / 25.4, y / 25.4) for x, y in self.points])
-
- def to_metric(self):
- self.start_point = tuple([x * 25.4 for x in self.start_point])
- self.points = tuple([(x * 25.4, y * 25.4) for x, y in self.points])
-
- def to_gerber(self, settings=None):
- data = dict(
- code=self.code,
- exposure="1" if self.exposure == "on" else "0",
- n_points=len(self.points),
- start_point="%.4f,%.4f" % self.start_point,
- points=",".join(["%.4f,%.4f" % point for point in self.points]),
- rotation=str(self.rotation)
- )
- return "{code},{exposure},{n_points},{start_point},{points},{rotation}".format(**data)
-
-
-class AMUnsupportPrimitive(object):
- @classmethod
- def from_gerber(cls, primitive):
- return cls(primitive)
-
- def __init__(self, primitive):
- self.primitive = primitive
-
- def to_gerber(self, settings=None):
- return self.primitive
-
-
class AMParamStmt(ParamStmt):
""" AM - Aperture Macro Statement
"""
@@ -396,9 +326,12 @@ class AMParamStmt(ParamStmt):
def _parsePrimitives(self, macro):
primitives = []
-
- for primitive in macro.split("*"):
- if primitive[0] == "4":
+ for primitive in macro.split('*'):
+ # Couldn't find anything explicit about leading whitespace in the spec...
+ primitive = primitive.lstrip()
+ if primitive[0] == '0':
+ primitives.append(AMCommentPrimitive.from_gerber(primitive))
+ if primitive[0] == '4':
primitives.append(AMOutlinePrimitive.from_gerber(primitive))
else:
primitives.append(AMUnsupportPrimitive.from_gerber(primitive))
@@ -414,7 +347,7 @@ class AMParamStmt(ParamStmt):
primitive.to_metric()
def to_gerber(self, settings=None):
- return '%AM{0}*{1}*%'.format(self.name, "".join([primitive.to_gerber(settings) for primitive in self.primitives]))
+ return '%AM{0}*{1}%'.format(self.name, '\n'.join([primitive.to_gerber(settings) for primitive in self.primitives]))
def __str__(self):
return '<Aperture Macro %s: %s>' % (self.name, self.macro)
diff --git a/gerber/tests/test_am_statements.py b/gerber/tests/test_am_statements.py
new file mode 100644
index 0000000..2ba7733
--- /dev/null
+++ b/gerber/tests/test_am_statements.py
@@ -0,0 +1,77 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Author: Hamilton Kibbe <ham@hamiltonkib.be>
+
+from .tests import *
+from ..am_statements import *
+
+def test_AMPrimitive_ctor():
+ for exposure in ('on', 'off', 'ON', 'OFF'):
+ for code in (0, 1, 2, 4, 5, 6, 7, 20, 21, 22):
+ p = AMPrimitive(code, exposure)
+ assert_equal(p.code, code)
+ assert_equal(p.exposure, exposure.lower())
+
+
+def test_AMPrimitive_validation():
+ assert_raises(TypeError, AMPrimitive, '1', 'off')
+ assert_raises(ValueError, AMPrimitive, 0, 'exposed')
+ assert_raises(ValueError, AMPrimitive, 3, 'off')
+
+
+def test_AMCommentPrimitive_ctor():
+ c = AMCommentPrimitive(0, ' This is a comment *')
+ assert_equal(c.code, 0)
+ assert_equal(c.comment, 'This is a comment')
+
+
+def test_AMCommentPrimitive_validation():
+ assert_raises(ValueError, AMCommentPrimitive, 1, 'This is a comment')
+
+
+def test_AMCommentPrimitive_factory():
+ c = AMCommentPrimitive.from_gerber('0 Rectangle with rounded corners. *')
+ assert_equal(c.code, 0)
+ assert_equal(c.comment, 'Rectangle with rounded corners.')
+
+
+def test_AMCommentPrimitive_dump():
+ c = AMCommentPrimitive(0, 'Rectangle with rounded corners.')
+ assert_equal(c.to_gerber(), '0 Rectangle with rounded corners. *')
+
+
+def test_AMCirclePrimitive_ctor():
+ test_cases = ((1, 'on', 0, (0, 0)),
+ (1, 'off', 1, (0, 1)),
+ (1, 'on', 2.5, (0, 2)),
+ (1, 'off', 5.0, (3, 3)))
+ for code, exposure, diameter, position in test_cases:
+ c = AMCirclePrimitive(code, exposure, diameter, position)
+ assert_equal(c.code, code)
+ assert_equal(c.exposure, exposure)
+ assert_equal(c.diameter, diameter)
+ assert_equal(c.position, position)
+
+
+def test_AMCirclePrimitive_validation():
+ assert_raises(ValueError, AMCirclePrimitive, 2, 'on', 0, (0, 0))
+
+
+def test_AMCirclePrimitive_factory():
+ c = AMCirclePrimitive.from_gerber('1,0,5,0,0*')
+ assert_equal(c.code, 1)
+ assert_equal(c.exposure, 'off')
+ assert_equal(c.diameter, 5)
+ assert_equal(c.position, (0,0))
+
+
+def test_AMCirclePrimitive_dump():
+ c = AMCirclePrimitive(1, 'off', 5, (0, 0))
+ assert_equal(c.to_gerber(), '1,0,5,0,0*')
+ c = AMCirclePrimitive(1, 'on', 5, (0, 0))
+ assert_equal(c.to_gerber(), '1,1,5,0,0*')
+
+
+
+ \ No newline at end of file