From ccb6eb7a766bd6edf314978f3ec4fc0dcd61652d Mon Sep 17 00:00:00 2001
From: Garret Fick <garret@ficksworkshop.com>
Date: Sat, 25 Jun 2016 16:46:44 +0800
Subject: Add support for polygon apertures

---
 gerber/am_statements.py         |  4 ++--
 gerber/gerber_statements.py     |  7 ++++++-
 gerber/primitives.py            |  7 ++++---
 gerber/render/cairo_backend.py  | 15 +++++++++++++++
 gerber/render/rs274x_backend.py | 26 ++++++++++++++++++++++----
 gerber/rs274x.py                | 14 ++++++++++++--
 6 files changed, 61 insertions(+), 12 deletions(-)

(limited to 'gerber')

diff --git a/gerber/am_statements.py b/gerber/am_statements.py
index a58f1dd..0d92a8c 100644
--- a/gerber/am_statements.py
+++ b/gerber/am_statements.py
@@ -523,7 +523,7 @@ class AMPolygonPrimitive(AMPrimitive):
         return fmt.format(**data)
     
     def to_primitive(self, units):
-        return Polygon(self.position, self.vertices, self.diameter / 2.0, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity)
+        return Polygon(self.position, self.vertices, self.diameter / 2.0, hole_radius=0, rotation=self.rotation, units=units, level_polarity=self._level_polarity)
 
 
 class AMMoirePrimitive(AMPrimitive):
@@ -897,7 +897,7 @@ class AMCenterLinePrimitive(AMPrimitive):
         return fmt.format(**data)
 
     def to_primitive(self, units):
-        return Rectangle(self.center, self.width, self.height, rotation=math.radians(self.rotation), units=units, level_polarity=self._level_polarity)
+        return Rectangle(self.center, self.width, self.height, rotation=self.rotation, units=units, level_polarity=self._level_polarity)
 
 
 class AMLowerLeftLinePrimitive(AMPrimitive):
diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py
index 725febf..234952e 100644
--- a/gerber/gerber_statements.py
+++ b/gerber/gerber_statements.py
@@ -285,9 +285,14 @@ class ADParamStmt(ParamStmt):
     
     @classmethod
     def obround(cls, dcode, width, height):
-        '''Create an obrou d aperture definition statement'''
+        '''Create an obround aperture definition statement'''
         return cls('AD', dcode, 'O', ([width, height],))
     
+    @classmethod
+    def polygon(cls, dcode, diameter, num_vertices, rotation, hole_diameter):
+        '''Create a polygon aperture definition statement'''
+        return cls('AD', dcode, 'P', ([diameter, num_vertices, rotation, hole_diameter],))
+    
     @classmethod
     def macro(cls, dcode, name):
         return cls('AD', dcode, name, '')
diff --git a/gerber/primitives.py b/gerber/primitives.py
index aa6e661..211acb8 100644
--- a/gerber/primitives.py
+++ b/gerber/primitives.py
@@ -721,14 +721,15 @@ class Obround(Primitive):
 
 class Polygon(Primitive):
     """
-    Polygon flash defined by a set number of sized.
+    Polygon flash defined by a set number of sides.
     """
-    def __init__(self, position, sides, radius, **kwargs):
+    def __init__(self, position, sides, radius, hole_radius, **kwargs):
         super(Polygon, self).__init__(**kwargs)
         validate_coordinates(position)
         self.position = position
         self.sides = sides
         self.radius = radius
+        self.hole_radius = hole_radius
         self._to_convert = ['position', 'radius']
         
     @property 
@@ -753,7 +754,7 @@ class Polygon(Primitive):
     @property
     def vertices(self):
         
-        offset = math.degrees(self.rotation)
+        offset = self.rotation
         da = 360.0 / self.sides
         
         points = []
diff --git a/gerber/render/cairo_backend.py b/gerber/render/cairo_backend.py
index c1bd60c..2b7c2ff 100644
--- a/gerber/render/cairo_backend.py
+++ b/gerber/render/cairo_backend.py
@@ -159,6 +159,9 @@ class GerberCairoContext(GerberContext):
         self._render_rectangle(obround.subshapes['rectangle'], color)
         
     def _render_polygon(self, polygon, color):
+        if polygon.hole_radius > 0:
+            self.ctx.push_group()
+        
         vertices = polygon.vertices 
         
         self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha)
@@ -172,6 +175,18 @@ class GerberCairoContext(GerberContext):
             self.ctx.line_to(*map(mul, v, self.scale))
 
         self.ctx.fill()
+        
+        if polygon.hole_radius > 0:
+            # Render the center clear
+            center = tuple(map(mul, polygon.position, self.scale))
+            self.ctx.set_source_rgba(color[0], color[1], color[2], self.alpha)
+            self.ctx.set_operator(cairo.OPERATOR_CLEAR)        
+            self.ctx.set_line_width(0)
+            self.ctx.arc(center[0], center[1], polygon.hole_radius * self.scale[0], 0, 2 * math.pi)
+            self.ctx.fill()
+            
+            self.ctx.pop_group_to_source()
+            self.ctx.paint_with_alpha(1)
 
     def _render_drill(self, circle, color):
         self._render_circle(circle, color)
diff --git a/gerber/render/rs274x_backend.py b/gerber/render/rs274x_backend.py
index bb784b1..c37529e 100644
--- a/gerber/render/rs274x_backend.py
+++ b/gerber/render/rs274x_backend.py
@@ -310,7 +310,7 @@ class Rs274xContext(GerberContext):
                 self._next_dcode = max(dcode + 1, self._next_dcode)
             
             aper = ADParamStmt.obround(dcode, width, height)
-            self._obrounds[(width, height)] = aper
+            self._obrounds[key] = aper
             self.header.append(aper)
     
         return aper
@@ -320,10 +320,28 @@ class Rs274xContext(GerberContext):
         aper = self._get_obround(obround.width, obround.height)
         self._render_flash(obround, aper)
         
-        pass
-        
     def _render_polygon(self, polygon, color):
-        raise ValueError('Polygons can only exist in the context of aperture macro')
+        
+        aper = self._get_polygon(polygon.radius, polygon.sides, polygon.rotation, polygon.hole_radius)
+        self._render_flash(polygon, aper)
+        
+    def _get_polygon(self, radius, num_vertices, rotation, hole_radius, dcode = None):
+        
+        key = (radius, num_vertices, rotation, hole_radius)
+        aper = self._polygons.get(key, None)
+        
+        if not aper:
+            if not dcode:
+                dcode = self._next_dcode
+                self._next_dcode += 1
+            else:
+                self._next_dcode = max(dcode + 1, self._next_dcode)
+                
+            aper = ADParamStmt.polygon(dcode, radius * 2, num_vertices, rotation, hole_radius * 2)
+            self._polygons[key] = aper
+            self.header.append(aper)
+            
+        return aper
         
     def _render_drill(self, drill, color):
         raise ValueError('Drills are not valid in RS274X files')
diff --git a/gerber/rs274x.py b/gerber/rs274x.py
index 7eba1c2..ffac66d 100644
--- a/gerber/rs274x.py
+++ b/gerber/rs274x.py
@@ -483,8 +483,18 @@ class GerberParser(object):
             height = modifiers[0][1]
             aperture = Obround(position=None, width=width, height=height, units=self.settings.units)
         elif shape == 'P':
-            # FIXME: not supported yet?
-            pass
+            outer_diameter = modifiers[0][0]
+            number_vertices = int(modifiers[0][1])
+            if len(modifiers[0]) > 2:
+                rotation = modifiers[0][2]
+            else:
+                rotation = 0
+                
+            if len(modifiers[0]) > 3:
+                hole_diameter = modifiers[0][3]
+            else:
+                hole_diameter = 0
+            aperture = Polygon(position=None, sides=number_vertices, radius=outer_diameter/2.0, hole_radius=hole_diameter/2.0, rotation=rotation)
         else:
             aperture = self.macros[shape].build(modifiers)
 
-- 
cgit