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.py76
-rw-r--r--gerber/render/render.py174
-rw-r--r--gerber/render/svgwrite_backend.py155
4 files changed, 433 insertions, 0 deletions
diff --git a/gerber/render/__init__.py b/gerber/render/__init__.py
new file mode 100644
index 0000000..0d3527b
--- /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 svgwrite_backend import GerberSvgContext
+
diff --git a/gerber/render/apertures.py b/gerber/render/apertures.py
new file mode 100644
index 0000000..52ae50c
--- /dev/null
+++ b/gerber/render/apertures.py
@@ -0,0 +1,76 @@
+#! /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.
+"""
+import math
+
+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.')
+
+ def _arc_params(self, startx, starty, x, y, i, j):
+ center = (startx + i, starty + j)
+ radius = math.sqrt(math.pow(center[0] - x, 2) +
+ math.pow(center[1] - y, 2))
+ delta_x0 = startx - center[0]
+ delta_y0 = center[1] - starty
+ delta_x1 = x - center[0]
+ delta_y1 = center[1] - y
+ start_angle = math.atan2(delta_y0, delta_x0)
+ end_angle = math.atan2(delta_y1, delta_x1)
+ return {'center': center, 'radius': radius,
+ 'start_angle': start_angle, 'end_angle': end_angle}
+
+
+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
diff --git a/gerber/render/render.py b/gerber/render/render.py
new file mode 100644
index 0000000..f5c58d8
--- /dev/null
+++ b/gerber/render/render.py
@@ -0,0 +1,174 @@
+#! /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.
+"""
+Rendering
+============
+**Gerber (RS-274X) and Excellon file rendering**
+
+Render Gerber and Excellon files to a variety of formats. The render module
+currently supports SVG rendering using the `svgwrite` library.
+"""
+from ..gerber_statements import (CommentStmt, UnknownStmt, EofStmt, ParamStmt,
+ CoordStmt, ApertureStmt, RegionModeStmt,
+ QuadrantModeStmt,
+)
+
+from ..primitives import *
+
+class GerberContext(object):
+ """ Gerber rendering context base class
+
+ Provides basic functionality and API for rendering gerber files. Medium-
+ specific renderers should subclass GerberContext and implement the drawing
+ functions. Colors are stored internally as 32-bit RGB and may need to be
+ converted to a native format in the rendering subclass.
+
+ Attributes
+ ----------
+ units : string
+ Measurement units
+
+ color : tuple (<float>, <float>, <float>)
+ Color used for rendering as a tuple of normalized (red, green, blue) values.
+
+ drill_color : tuple (<float>, <float>, <float>)
+ Color used for rendering drill hits. Format is the same as for `color`.
+
+ background_color : tuple (<float>, <float>, <float>)
+ Color of the background. Used when exposing areas in 'clear' level
+ polarity mode. Format is the same as for `color`.
+
+ alpha : float
+ Rendering opacity. Between 0.0 (transparent) and 1.0 (opaque.)
+ """
+ def __init__(self, units='inch'):
+ self.units = units
+ self.color = (0.7215, 0.451, 0.200)
+ self.drill_color = (0.25, 0.25, 0.25)
+ self.background_color = (0.0, 0.0, 0.0)
+ self.alpha = 1.0
+
+ def set_units(self, units):
+ """ Set context measurement units
+
+ Parameters
+ ----------
+ unit : string
+ Measurement units. may be 'inch' or 'metric'
+
+ Raises
+ ------
+ ValueError
+ If `unit` is not 'inch' or 'metric'
+ """
+ if units not in ('inch', 'metric'):
+ raise ValueError('Units may be "inch" or "metric"')
+ self.units = units
+
+ def set_color(self, color):
+ """ Set rendering color.
+
+ Parameters
+ ----------
+ color : tuple (<float>, <float>, <float>)
+ Color as a tuple of (red, green, blue) values. Each channel is
+ represented as a float value in (0, 1)
+ """
+ self.color = color
+
+ def set_drill_color(self, color):
+ """ Set color used for rendering drill hits.
+
+ Parameters
+ ----------
+ color : tuple (<float>, <float>, <float>)
+ Color as a tuple of (red, green, blue) values. Each channel is
+ represented as a float value in (0, 1)
+ """
+ self.drill_color = color
+
+ def set_background_color(self, color):
+ """ Set rendering background color
+
+ Parameters
+ ----------
+ color : tuple (<float>, <float>, <float>)
+ Color as a tuple of (red, green, blue) values. Each channel is
+ represented as a float value in (0, 1)
+ """
+ self.background_color = color
+
+ def set_alpha(self, alpha):
+ """ Set layer rendering opacity
+
+ .. note::
+ Not all backends/rendering devices support this parameter.
+
+ Parameters
+ ----------
+ alpha : float
+ Rendering opacity. must be between 0.0 (transparent) and 1.0 (opaque)
+ """
+ self.alpha = alpha
+
+ def render(self, primitive):
+ color = (self.color if primitive.level_polarity == 'dark'
+ else self.background_color)
+ if isinstance(primitive, Line):
+ self._render_line(primitive, color)
+ elif isinstance(primitive, Arc):
+ self._render_arc(primitive, color)
+ elif isinstance(primitive, Region):
+ self._render_region(primitive, color)
+ elif isinstance(primitive, Circle):
+ self._render_circle(primitive, color)
+ elif isinstance(primitive, Rectangle):
+ self._render_rectangle(primitive, color)
+ elif isinstance(primitive, Obround):
+ self._render_obround(primitive, color)
+ elif isinstance(primitive, Polygon):
+ self._render_polygon(Polygon, color)
+ elif isinstance(primitive, Drill):
+ self._render_drill(primitive, self.drill_color)
+ else:
+ return
+
+ def _render_line(self, primitive, color):
+ pass
+
+ def _render_arc(self, primitive, color):
+ pass
+
+ def _render_region(self, primitive, color):
+ pass
+
+ def _render_circle(self, primitive, color):
+ pass
+
+ def _render_rectangle(self, primitive, color):
+ pass
+
+ def _render_obround(self, primitive, color):
+ pass
+
+ def _render_polygon(self, primitive, color):
+ pass
+
+ def _render_drill(self, primitive, color):
+ pass
+
diff --git a/gerber/render/svgwrite_backend.py b/gerber/render/svgwrite_backend.py
new file mode 100644
index 0000000..d9456a5
--- /dev/null
+++ b/gerber/render/svgwrite_backend.py
@@ -0,0 +1,155 @@
+#! /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 operator import mul
+import svgwrite
+
+SCALE = 300
+
+
+def svg_color(color):
+ color = tuple([int(ch * 255) for ch in color])
+ return 'rgb(%d, %d, %d)' % color
+
+
+class GerberSvgContext(GerberContext):
+ def __init__(self):
+ GerberContext.__init__(self)
+ self.scale = (SCALE, -SCALE)
+ self.dwg = svgwrite.Drawing()
+ self.background = False
+
+ def dump(self, filename):
+ self.dwg.saveas(filename)
+
+ def set_bounds(self, bounds):
+ xbounds, ybounds = bounds
+ size = (SCALE * (xbounds[1] - xbounds[0]),
+ SCALE * (ybounds[1] - ybounds[0]))
+ if not self.background:
+ vbox = '%f, %f, %f, %f' % (SCALE * xbounds[0], -SCALE * ybounds[1],
+ size[0], size[1])
+ self.dwg = svgwrite.Drawing(viewBox=vbox)
+ rect = self.dwg.rect(insert=(SCALE * xbounds[0],
+ -SCALE * ybounds[1]),
+ size=size,
+ fill=svg_color(self.background_color))
+ self.dwg.add(rect)
+ self.background = True
+
+ def _render_line(self, line, color):
+ start = map(mul, line.start, self.scale)
+ end = map(mul, line.end, self.scale)
+ aline = self.dwg.line(start=start, end=end,
+ stroke=svg_color(color),
+ stroke_width=SCALE * line.width,
+ stroke_linecap='round')
+ aline.stroke(opacity=self.alpha)
+ self.dwg.add(aline)
+
+ def _render_region(self, region, color):
+ points = [tuple(map(mul, point, self.scale)) for point in region.points]
+ region_path = self.dwg.path(d='M %f, %f' % points[0],
+ fill=svg_color(color),
+ stroke='none')
+ region_path.fill(opacity=self.alpha)
+ for point in points[1:]:
+ region_path.push('L %f, %f' % point)
+ self.dwg.add(region_path)
+
+ def _render_circle(self, circle, color):
+ center = map(mul, circle.position, self.scale)
+ acircle = self.dwg.circle(center=center,
+ r = SCALE * circle.radius,
+ fill=svg_color(color))
+ acircle.fill(opacity=self.alpha)
+ self.dwg.add(acircle)
+
+ def _render_rectangle(self, rectangle, color):
+ center = map(mul, rectangle.position, self.scale)
+ size = tuple(map(mul, (rectangle.width, rectangle.height), map(abs, self.scale)))
+ insert = center[0] - size[0] / 2., center[1] - size[1] / 2.
+ arect = self.dwg.rect(insert=insert, size=size,
+ fill=svg_color(color))
+ arect.fill(opacity=self.alpha)
+ self.dwg.add(arect)
+
+ def _render_obround(self, obround, color):
+ x, y = tuple(map(mul, obround.position, self.scale))
+ xsize, ysize = tuple(map(mul, (obround.width, obround.height),
+ self.scale))
+ xscale, yscale = self.scale
+
+ # Corner case...
+ if xsize == ysize:
+ circle = self.dwg.circle(center=(x, y),
+ r = (xsize / 2.0),
+ fill=svg_color(color))
+ circle.fill(opacity=self.alpha)
+ self.dwg.add(circle)
+
+ # Horizontal obround
+ elif xsize > ysize:
+ rectx = xsize - ysize
+ recty = ysize
+ c1 = self.dwg.circle(center=(x - (rectx / 2.0), y),
+ r = (ysize / 2.0),
+ fill=svg_color(color))
+
+ c2 = self.dwg.circle(center=(x + (rectx / 2.0), y),
+ r = (ysize / 2.0),
+ fill=svg_color(color))
+
+ rect = self.dwg.rect(insert=(x, y),
+ size=(xsize, ysize),
+ fill=svg_color(color))
+ c1.fill(opacity=self.alpha)
+ c2.fill(opacity=self.alpha)
+ rect.fill(opacity=self.alpha)
+ self.dwg.add(c1)
+ self.dwg.add(c2)
+ self.dwg.add(rect)
+
+ # Vertical obround
+ else:
+ rectx = xsize
+ recty = ysize - xsize
+ c1 = self.dwg.circle(center=(x, y - (recty / 2.)),
+ r = (xsize / 2.),
+ fill=svg_color(color))
+
+ c2 = self.dwg.circle(center=(x, y + (recty / 2.)),
+ r = (xsize / 2.),
+ fill=svg_color(color))
+
+ rect = self.dwg.rect(insert=(x, y),
+ size=(xsize, ysize),
+ fill=svg_color(color))
+ c1.fill(opacity=self.alpha)
+ c2.fill(opacity=self.alpha)
+ rect.fill(opacity=self.alpha)
+ self.dwg.add(c1)
+ self.dwg.add(c2)
+ self.dwg.add(rect)
+
+ def _render_drill(self, primitive, color):
+ center = map(mul, primitive.position, self.scale)
+ hit = self.dwg.circle(center=center, r=SCALE * primitive.radius,
+ fill=svg_color(color))
+ self.dwg.add(hit)