summaryrefslogtreecommitdiff
path: root/gerber/render
diff options
context:
space:
mode:
Diffstat (limited to 'gerber/render')
-rw-r--r--gerber/render/__init__.py28
-rw-r--r--gerber/render/apertures.py58
-rw-r--r--gerber/render/render.py133
-rw-r--r--gerber/render/svg.py171
4 files changed, 390 insertions, 0 deletions
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)
+
+