From 695e3d9220be8773f6630bb5c512d122b8576742 Mon Sep 17 00:00:00 2001
From: Hamilton Kibbe <hamilton.kibbe@gmail.com>
Date: Sun, 28 Sep 2014 18:07:15 -0400
Subject: Added excellon support and refactored project

---
 gerber/render/__init__.py  |  28 ++++++++
 gerber/render/apertures.py |  58 +++++++++++++++
 gerber/render/render.py    | 133 +++++++++++++++++++++++++++++++++++
 gerber/render/svg.py       | 171 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 390 insertions(+)
 create mode 100644 gerber/render/__init__.py
 create mode 100644 gerber/render/apertures.py
 create mode 100644 gerber/render/render.py
 create mode 100644 gerber/render/svg.py

(limited to 'gerber/render')

diff --git a/gerber/render/__init__.py b/gerber/render/__init__.py
new file mode 100644
index 0000000..cc87ee0
--- /dev/null
+++ b/gerber/render/__init__.py
@@ -0,0 +1,28 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
+
+# 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.
+"""
+gerber.render
+============
+**Gerber Renderers**
+
+This module provides contexts for rendering images of gerber layers. Currently
+SVG is the only supported format.
+"""
+
+
+from svg import GerberSvgContext
+
diff --git a/gerber/render/apertures.py b/gerber/render/apertures.py
new file mode 100644
index 0000000..55e6a30
--- /dev/null
+++ b/gerber/render/apertures.py
@@ -0,0 +1,58 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
+
+# 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.
+"""
+gerber.render.apertures
+============
+**Gerber Aperture base classes**
+
+This module provides base classes for gerber apertures. These are used by
+the rendering engine to draw the gerber file.
+"""
+
+
+class Aperture(object):
+    """ Gerber Aperture base class
+    """
+    def draw(self, ctx, x, y):
+        raise NotImplementedError('The draw method must be implemented in an Aperture subclass.')
+    
+    def flash(self, ctx, x, y):
+        raise NotImplementedError('The flash method must be implemented in an Aperture subclass.')
+
+
+class Circle(Aperture):
+    """ Circular Aperture base class
+    """
+    def __init__(self, diameter=0.0):
+        self.diameter = diameter
+
+class Rect(Aperture):
+    """ Rectangular Aperture base class
+    """
+    def __init__(self, size=(0, 0)):
+        self.size = size
+
+class Obround(Aperture):
+    """ Obround Aperture base class
+    """
+    def __init__(self, size=(0, 0)):
+        self.size = size
+    
+class Polygon(Aperture):
+    """ Polygon Aperture base class
+    """
+    pass
\ No newline at end of file
diff --git a/gerber/render/render.py b/gerber/render/render.py
new file mode 100644
index 0000000..e15a36f
--- /dev/null
+++ b/gerber/render/render.py
@@ -0,0 +1,133 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
+# Modified from code by 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 ..statements import (
+    CommentStmt, UnknownStmt, EofStmt, ParamStmt, CoordStmt, ApertureStmt
+)
+
+
+class GerberContext(object):
+    settings = {}
+
+    x = 0
+    y = 0
+
+    aperture = 0
+    interpolation = 'linear'
+
+    image_polarity = 'positive'
+    level_polarity = 'dark'
+
+    def __init__(self):
+        pass 
+
+    def set_format(self, settings):
+        self.settings = settings
+    
+    def set_coord_format(self, zero_suppression, format, notation):
+        self.settings['zero_suppression'] = zero_suppression
+        self.settings['format'] = format
+        self.settings['notation'] = notation
+
+    def set_coord_notation(self, notation):
+        self.settings['notation'] = notation
+
+    def set_coord_unit(self, unit):
+        self.settings['units'] = unit
+
+    def set_image_polarity(self, polarity):
+        self.image_polarity = polarity
+        
+    def set_level_polarity(self, polarity):
+        self.level_polarity =  polarity
+
+    def set_interpolation(self, interpolation):
+        self.interpolation = 'linear' if interpolation in ("G01", "G1") else 'arc'
+
+    def set_aperture(self, d):
+        self.aperture = d
+
+    def resolve(self, x, y):
+        return  x or self.x, y or self.y
+        
+    def define_aperture(self, d, shape, modifiers):
+        pass
+
+    def move(self, x, y, resolve=True):
+        if resolve:
+            self.x, self.y = self.resolve(x, y)
+        else:
+            self.x, self.y = x, y
+
+    def stroke(self, x, y):
+        pass
+
+    def line(self, x, y):
+        pass
+
+    def arc(self, x, y):
+        pass
+
+    def flash(self, x, y):
+        pass
+
+    def drill(self, x, y, diameter):
+        pass
+
+    def evaluate(self, stmt):
+        if isinstance(stmt, (CommentStmt, UnknownStmt, EofStmt)):
+            return
+
+        elif isinstance(stmt, ParamStmt):
+            self._evaluate_param(stmt)
+
+        elif isinstance(stmt, CoordStmt):
+            self._evaluate_coord(stmt)
+
+        elif isinstance(stmt, ApertureStmt):
+            self._evaluate_aperture(stmt)
+
+        else:
+            raise Exception("Invalid statement to evaluate")
+
+    def _evaluate_param(self, stmt):
+        if stmt.param == "FS":
+            self.set_coord_format(stmt.zero_suppression, stmt.format, stmt.notation)
+            self.set_coord_notation(stmt.notation)
+        elif stmt.param == "MO:":
+            self.set_coord_unit(stmt.mode)
+        elif stmt.param == "IP:":
+            self.set_image_polarity(stmt.ip)
+        elif stmt.param == "LP:":
+            self.set_level_polarity(stmt.lp)
+        elif stmt.param == "AD":
+            self.define_aperture(stmt.d, stmt.shape, stmt.modifiers)
+
+    def _evaluate_coord(self, stmt):
+        if stmt.function in ("G01", "G1", "G02", "G2", "G03", "G3"):
+            self.set_interpolation(stmt.function)
+
+        if stmt.op == "D01":
+            self.stroke(stmt.x, stmt.y)
+        elif stmt.op == "D02":
+            self.move(stmt.x, stmt.y)
+        elif stmt.op == "D03":
+            self.flash(stmt.x, stmt.y)
+
+    def _evaluate_aperture(self, stmt):
+        self.set_aperture(stmt.d)
diff --git a/gerber/render/svg.py b/gerber/render/svg.py
new file mode 100644
index 0000000..b16e534
--- /dev/null
+++ b/gerber/render/svg.py
@@ -0,0 +1,171 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be>
+# Based on render_svg.py by 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 .render import GerberContext
+from .apertures import Circle, Rect, Obround, Polygon
+import svgwrite
+
+SCALE = 300
+
+
+class SvgCircle(Circle):
+    def draw(self, ctx, x, y):
+        return ctx.dwg.line(start=(ctx.x * SCALE, -ctx.y * SCALE),
+                            end=(x * SCALE, -y * SCALE),
+                            stroke="rgb(184, 115, 51)",
+                            stroke_width=SCALE * self.diameter,
+                            stroke_linecap="round")
+
+    def flash(self, ctx, x, y):
+        return [ctx.dwg.circle(center=(x * SCALE, -y * SCALE),
+                              r = SCALE * (self.diameter / 2.0),
+                              fill='rgb(184, 115, 51)'),]
+
+
+class SvgRect(Rect):
+    def draw(self, ctx, x, y):
+        return ctx.dwg.line(start=(ctx.x * SCALE, -ctx.y * SCALE),
+                            end=(x * SCALE, -y * SCALE),
+                            stroke="rgb(184, 115, 51)", stroke_width=2,
+                            stroke_linecap="butt")
+
+    def flash(self, ctx, x, y):
+        xsize, ysize = self.size
+        return [ctx.dwg.rect(insert=(SCALE * (x - (xsize / 2)),
+                                    -SCALE * (y + (ysize / 2))),
+                            size=(SCALE * xsize, SCALE * ysize),
+                            fill="rgb(184, 115, 51)"),]
+
+class SvgObround(Obround):
+    def draw(self, ctx, x, y):
+        pass
+    
+    def flash(self, ctx, x, y):
+        xsize, ysize = self.size
+        
+        # horizontal obround
+        if xsize == ysize:
+            return [ctx.dwg.circle(center=(x * SCALE, -y * SCALE),
+                              r = SCALE * (x / 2.0),
+                              fill='rgb(184, 115, 51)'),]
+        if xsize > ysize:
+            rectx = xsize - ysize
+            recty = ysize
+            lcircle = ctx.dwg.circle(center=((x - (rectx / 2.0)) * SCALE,
+                                            -y * SCALE),
+                                     r = SCALE * (ysize / 2.0),
+                                     fill='rgb(184, 115, 51)')
+            
+            rcircle = ctx.dwg.circle(center=((x + (rectx / 2.0)) * SCALE,
+                                            -y * SCALE),
+                                     r = SCALE * (ysize / 2.0),
+                                     fill='rgb(184, 115, 51)')
+        
+            rect = ctx.dwg.rect(insert=(SCALE * (x - (xsize / 2.)),
+                                        -SCALE * (y + (ysize / 2.))),
+                                size=(SCALE * xsize, SCALE * ysize),
+                                fill='rgb(184, 115, 51)')
+            return [lcircle, rcircle, rect,]
+        
+        # Vertical obround
+        else:
+            rectx = xsize
+            recty = ysize - xsize
+            lcircle = ctx.dwg.circle(center=(x * SCALE,
+                                            (y - (recty / 2.)) * -SCALE),
+                                     r = SCALE * (xsize / 2.),
+                                     fill='rgb(184, 115, 51)')
+            
+            ucircle = ctx.dwg.circle(center=(x * SCALE,
+                                             (y + (recty / 2.)) * -SCALE),
+                                     r = SCALE * (xsize / 2.),
+                                     fill='rgb(184, 115, 51)')
+        
+            rect = ctx.dwg.rect(insert=(SCALE * (x - (xsize / 2.)),
+                                        -SCALE * (y + (ysize / 2.))),
+                                size=(SCALE * xsize, SCALE * ysize),
+                                fill='rgb(184, 115, 51)')
+            return [lcircle, ucircle, rect,]
+    
+
+class GerberSvgContext(GerberContext):
+    def __init__(self):
+        GerberContext.__init__(self)
+
+        self.apertures = {}
+        self.dwg = svgwrite.Drawing()
+        #self.dwg.add(self.dwg.rect(insert=(0, 0), size=(2000, 2000), fill="black"))
+
+    def set_bounds(self, bounds):
+        xbounds, ybounds =  bounds
+        size = (SCALE * (xbounds[1] - xbounds[0]), SCALE * (ybounds[1] - ybounds[0]))
+        self.dwg.add(self.dwg.rect(insert=(SCALE * xbounds[0], -SCALE * ybounds[1]), size=size, fill="black"))
+        
+
+    def define_aperture(self, d, shape, modifiers):
+        aperture = None
+        if shape == 'C':
+            aperture = SvgCircle(diameter=float(modifiers[0][0]))
+        elif shape == 'R':
+            aperture = SvgRect(size=modifiers[0][0:2])
+        elif shape == 'O':
+            aperture = SvgObround(size=modifiers[0][0:2])
+        self.apertures[d] = aperture
+
+    def stroke(self, x, y):
+        super(GerberSvgContext, self).stroke(x, y)
+
+        if self.interpolation == 'linear':
+            self.line(x, y)
+        elif self.interpolation == 'arc':
+            #self.arc(x, y)
+            self.line(x,y)
+
+    def line(self, x, y):
+        super(GerberSvgContext, self).line(x, y)
+        x, y = self.resolve(x, y)
+        ap = self.apertures.get(self.aperture, None)
+        if ap is None:
+            return
+        self.dwg.add(ap.draw(self, x, y))
+        self.move(x, y, resolve=False)
+
+
+    def arc(self, x, y):
+        super(GerberSvgContext, self).arc(x, y)
+
+
+    def flash(self, x, y):
+        super(GerberSvgContext, self).flash(x, y)
+        x, y = self.resolve(x, y)
+        ap = self.apertures.get(self.aperture, None)
+        if ap is None:
+            return
+        for shape in ap.flash(self, x, y):
+            self.dwg.add(shape)
+        self.move(x, y, resolve=False)
+
+
+    def drill(self, x, y, diameter):
+        hit = self.dwg.circle(center=(x*SCALE, -y*SCALE), r=SCALE*(diameter/2.0), fill='gray')
+        self.dwg.add(hit)
+
+    def dump(self, filename):
+        self.dwg.saveas(filename)
+        
+        
-- 
cgit