summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gerber/am_statements.py41
-rw-r--r--gerber/gerber_statements.py3
-rw-r--r--gerber/primitives.py56
-rw-r--r--gerber/render/cairo_backend.py20
-rw-r--r--gerber/render/render.py5
5 files changed, 125 insertions, 0 deletions
diff --git a/gerber/am_statements.py b/gerber/am_statements.py
index 38f4d71..0e4f5f4 100644
--- a/gerber/am_statements.py
+++ b/gerber/am_statements.py
@@ -16,7 +16,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from math import pi
from .utils import validate_coordinates, inch, metric
+from .primitives import Circle, Line, Rectangle
# TODO: Add support for aperture macro variables
@@ -67,6 +69,12 @@ class AMPrimitive(object):
def to_metric(self):
raise NotImplementedError('Subclass must implement `to-metric`')
+
+ def to_primitive(self, units):
+ """
+ Convert to a primitive, as defines the primitives module (for drawing)
+ """
+ raise NotImplementedError('Subclass must implement `to-primitive`')
def __eq__(self, other):
return self.__dict__ == other.__dict__
@@ -120,6 +128,12 @@ class AMCommentPrimitive(AMPrimitive):
def to_gerber(self, settings=None):
return '0 %s *' % self.comment
+ def to_primitive(self, units):
+ """
+ Returns None - has not primitive representation
+ """
+ return None
+
def __str__(self):
return '<Aperture Macro Comment: %s>' % self.comment
@@ -189,6 +203,9 @@ class AMCirclePrimitive(AMPrimitive):
y = self.position[1])
return '{code},{exposure},{diameter},{x},{y}*'.format(**data)
+ def to_primitive(self, units):
+ return Circle((self.position), self.diameter, units=units)
+
class AMVectorLinePrimitive(AMPrimitive):
""" Aperture Macro Vector Line primitive. Code 2 or 20.
@@ -273,6 +290,9 @@ class AMVectorLinePrimitive(AMPrimitive):
endy = self.end[1],
rotation = self.rotation)
return fmtstr.format(**data)
+
+ def to_primitive(self, units):
+ return Line(self.start, self.end, Rectangle(None, self.width, self.width), units=units)
class AMOutlinePrimitive(AMPrimitive):
@@ -360,6 +380,9 @@ class AMOutlinePrimitive(AMPrimitive):
rotation=str(self.rotation)
)
return "{code},{exposure},{n_points},{start_point},{points},{rotation}*".format(**data)
+
+ def to_primitive(self, units):
+ raise NotImplementedError()
class AMPolygonPrimitive(AMPrimitive):
@@ -450,6 +473,9 @@ class AMPolygonPrimitive(AMPrimitive):
)
fmt = "{code},{exposure},{vertices},{position},{diameter},{rotation}*"
return fmt.format(**data)
+
+ def to_primitive(self, units):
+ raise NotImplementedError()
class AMMoirePrimitive(AMPrimitive):
@@ -562,6 +588,9 @@ class AMMoirePrimitive(AMPrimitive):
fmt = "{code},{position},{diameter},{ring_thickness},{gap},{max_rings},{crosshair_thickness},{crosshair_length},{rotation}*"
return fmt.format(**data)
+ def to_primitive(self, units):
+ raise NotImplementedError()
+
class AMThermalPrimitive(AMPrimitive):
""" Aperture Macro Thermal primitive. Code 7.
@@ -646,6 +675,9 @@ class AMThermalPrimitive(AMPrimitive):
fmt = "{code},{position},{outer_diameter},{inner_diameter},{gap}*"
return fmt.format(**data)
+ def to_primitive(self, units):
+ raise NotImplementedError()
+
class AMCenterLinePrimitive(AMPrimitive):
""" Aperture Macro Center Line primitive. Code 21.
@@ -729,6 +761,9 @@ class AMCenterLinePrimitive(AMPrimitive):
fmt = "{code},{exposure},{width},{height},{center},{rotation}*"
return fmt.format(**data)
+ def to_primitive(self, units):
+ return Rectangle(self.center, self.width, self.height, rotation=self.rotation * pi / 180.0, units=units)
+
class AMLowerLeftLinePrimitive(AMPrimitive):
""" Aperture Macro Lower Left Line primitive. Code 22.
@@ -811,6 +846,9 @@ class AMLowerLeftLinePrimitive(AMPrimitive):
fmt = "{code},{exposure},{width},{height},{lower_left},{rotation}*"
return fmt.format(**data)
+ def to_primitive(self, units):
+ raise NotImplementedError()
+
class AMUnsupportPrimitive(AMPrimitive):
@classmethod
@@ -829,3 +867,6 @@ class AMUnsupportPrimitive(AMPrimitive):
def to_gerber(self, settings=None):
return self.primitive
+
+ def to_primitive(self, units):
+ return None \ No newline at end of file
diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py
index fd1e629..14a431b 100644
--- a/gerber/gerber_statements.py
+++ b/gerber/gerber_statements.py
@@ -26,6 +26,7 @@ from .utils import (parse_gerber_value, write_gerber_value, decimal_string,
from .am_statements import *
from .am_read import read_macro
from .am_eval import eval_macro
+from .primitives import AMGroup
class Statement(object):
@@ -388,6 +389,8 @@ class AMParamStmt(ParamStmt):
self.primitives.append(AMThermalPrimitive.from_gerber(primitive))
else:
self.primitives.append(AMUnsupportPrimitive.from_gerber(primitive))
+
+ return AMGroup(self.primitives, units=self.units)
def to_inch(self):
if self.units == 'metric':
diff --git a/gerber/primitives.py b/gerber/primitives.py
index d964192..85035d2 100644
--- a/gerber/primitives.py
+++ b/gerber/primitives.py
@@ -18,6 +18,7 @@ import math
from operator import add, sub
from .utils import validate_coordinates, inch, metric
+from jsonpickle.util import PRIMITIVES
class Primitive(object):
@@ -425,6 +426,10 @@ class Ellipse(Primitive):
class Rectangle(Primitive):
"""
+ When rotated, the rotation is about the center point.
+
+ Only aperture macro generated Rectangle objects can be rotated. If you aren't in a AMGroup,
+ then you don't need to worry about rotation
"""
def __init__(self, position, width, height, **kwargs):
super(Rectangle, self).__init__(**kwargs)
@@ -702,6 +707,57 @@ class Polygon(Primitive):
def offset(self, x_offset=0, y_offset=0):
self.position = tuple(map(add, self.position, (x_offset, y_offset)))
+class AMGroup(Primitive):
+ """
+ """
+ def __init__(self, amprimitives, **kwargs):
+ super(AMGroup, self).__init__(**kwargs)
+
+ self.primitives = []
+ for amprim in amprimitives:
+ prim = amprim.to_primitive(self.units)
+ if prim:
+ self.primitives.append(prim)
+ self._position = None
+ self._to_convert = ['arimitives']
+
+ @property
+ def flashed(self):
+ return True
+
+ @property
+ def bounding_box(self):
+ xlims, ylims = zip(*[p.bounding_box for p in self.primitives])
+ minx, maxx = zip(*xlims)
+ miny, maxy = zip(*ylims)
+ min_x = min(minx)
+ max_x = max(maxx)
+ min_y = min(miny)
+ max_y = max(maxy)
+ return ((min_x, max_x), (min_y, max_y))
+
+ @property
+ def position(self):
+ return self._position
+
+ @position.setter
+ def position(self, new_pos):
+ '''
+ Sets the position of the AMGroup.
+ This offset all of the objects by the specified distance.
+ '''
+
+ if self._position:
+ dx = new_pos[0] - self._position[0]
+ dy = new_pos[0] - self._position[0]
+ else:
+ dx = new_pos[0]
+ dy = new_pos[1]
+
+ for primitive in self.primitives:
+ primitive.offset(dx, dy)
+
+ self._position = new_pos
class Region(Primitive):
"""
diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py
index 4d71199..3ee38ae 100644
--- a/gerber/render/cairo_backend.py
+++ b/gerber/render/cairo_backend.py
@@ -122,11 +122,27 @@ class GerberCairoContext(GerberContext):
def _render_rectangle(self, rectangle, color):
ll = map(mul, rectangle.lower_left, self.scale)
width, height = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale)))
+
+ if rectangle.rotation != 0:
+ self.ctx.save()
+
+ center = map(mul, rectangle.position, self.scale)
+ matrix = cairo.Matrix()
+ matrix.translate(center[0], center[1])
+ # For drawing, we already handles the translation
+ ll[0] = ll[0] - center[0]
+ ll[1] = ll[1] - center[1]
+ matrix.rotate(rectangle.rotation)
+ self.ctx.transform(matrix)
+
self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha)
self.ctx.set_operator(cairo.OPERATOR_OVER if (rectangle.level_polarity == "dark" and not self.invert) else cairo.OPERATOR_CLEAR)
self.ctx.set_line_width(0)
self.ctx.rectangle(ll[0], ll[1], width, height)
self.ctx.fill()
+
+ if rectangle.rotation != 0:
+ self.ctx.restore()
def _render_obround(self, obround, color):
self._render_circle(obround.subshapes['circle1'], color)
@@ -135,6 +151,10 @@ class GerberCairoContext(GerberContext):
def _render_drill(self, circle, color):
self._render_circle(circle, color)
+
+ def _render_amgroup(self, amgroup, color):
+ for primitive in amgroup.primitives:
+ self.render(primitive)
def _render_test_record(self, primitive, color):
self.ctx.select_font_face('monospace', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
diff --git a/gerber/render/render.py b/gerber/render/render.py
index 8f49796..ac01e52 100644
--- a/gerber/render/render.py
+++ b/gerber/render/render.py
@@ -150,6 +150,8 @@ class GerberContext(object):
self._render_polygon(primitive, color)
elif isinstance(primitive, Drill):
self._render_drill(primitive, self.drill_color)
+ elif isinstance(primitive, AMGroup):
+ self._render_amgroup(primitive, color)
elif isinstance(primitive, TestRecord):
self._render_test_record(primitive, color)
else:
@@ -178,6 +180,9 @@ class GerberContext(object):
def _render_drill(self, primitive, color):
pass
+
+ def _render_amgroup(self, primitive, color):
+ pass
def _render_test_record(self, primitive, color):
pass