From 695e3d9220be8773f6630bb5c512d122b8576742 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe 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 + +# 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 + +# 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 +# Modified from code by Paulo Henrique Silva + +# 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 +# Based on render_svg.py by Paulo Henrique Silva + +# 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 From 3a5dbcf1e13704b7352d5fb3c4777d7df3fed081 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 28 Sep 2014 21:17:13 -0400 Subject: added ExcellonFile class --- gerber/render/apertures.py | 9 ++++++--- gerber/render/render.py | 12 +++++------ gerber/render/svg.py | 50 ++++++++++++++++++++-------------------------- 3 files changed, 34 insertions(+), 37 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/apertures.py b/gerber/render/apertures.py index 55e6a30..f163b1f 100644 --- a/gerber/render/apertures.py +++ b/gerber/render/apertures.py @@ -29,7 +29,7 @@ class Aperture(object): """ 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.') @@ -40,19 +40,22 @@ class Circle(Aperture): 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 + pass diff --git a/gerber/render/render.py b/gerber/render/render.py index e15a36f..c372783 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -34,11 +34,11 @@ class GerberContext(object): level_polarity = 'dark' def __init__(self): - pass + 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 @@ -52,9 +52,9 @@ class GerberContext(object): def set_image_polarity(self, polarity): self.image_polarity = polarity - + def set_level_polarity(self, polarity): - self.level_polarity = polarity + self.level_polarity = polarity def set_interpolation(self, interpolation): self.interpolation = 'linear' if interpolation in ("G01", "G1") else 'arc' @@ -63,8 +63,8 @@ class GerberContext(object): self.aperture = d def resolve(self, x, y): - return x or self.x, y or self.y - + return x or self.x, y or self.y + def define_aperture(self, d, shape, modifiers): pass diff --git a/gerber/render/svg.py b/gerber/render/svg.py index b16e534..7d5c8fd 100644 --- a/gerber/render/svg.py +++ b/gerber/render/svg.py @@ -33,8 +33,8 @@ class SvgCircle(Circle): 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)'),] + r = SCALE * (self.diameter / 2.0), + fill='rgb(184, 115, 51)'), ] class SvgRect(Rect): @@ -47,41 +47,42 @@ class SvgRect(Rect): 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)"),] + -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)'),] + 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), + -y * SCALE), r = SCALE * (ysize / 2.0), fill='rgb(184, 115, 51)') - + rcircle = ctx.dwg.circle(center=((x + (rectx / 2.0)) * SCALE, - -y * 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,] - + return [lcircle, rcircle, rect, ] + # Vertical obround else: rectx = xsize @@ -90,18 +91,18 @@ class SvgObround(Obround): (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,] - + return [lcircle, ucircle, rect, ] + class GerberSvgContext(GerberContext): def __init__(self): @@ -112,10 +113,9 @@ class GerberSvgContext(GerberContext): #self.dwg.add(self.dwg.rect(insert=(0, 0), size=(2000, 2000), fill="black")) def set_bounds(self, bounds): - xbounds, ybounds = 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 @@ -133,8 +133,7 @@ class GerberSvgContext(GerberContext): if self.interpolation == 'linear': self.line(x, y) elif self.interpolation == 'arc': - #self.arc(x, y) - self.line(x,y) + self.arc(x, y) def line(self, x, y): super(GerberSvgContext, self).line(x, y) @@ -145,11 +144,9 @@ class GerberSvgContext(GerberContext): 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) @@ -160,12 +157,9 @@ class GerberSvgContext(GerberContext): 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 From f8449ad2b60b8a715d0867325e257a8297193b49 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 30 Sep 2014 23:42:02 -0400 Subject: tests update --- gerber/render/__init__.py | 2 +- gerber/render/svg.py | 165 -------------------------------------- gerber/render/svgwrite_backend.py | 165 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 166 deletions(-) delete mode 100644 gerber/render/svg.py create mode 100644 gerber/render/svgwrite_backend.py (limited to 'gerber/render') diff --git a/gerber/render/__init__.py b/gerber/render/__init__.py index cc87ee0..0d3527b 100644 --- a/gerber/render/__init__.py +++ b/gerber/render/__init__.py @@ -24,5 +24,5 @@ SVG is the only supported format. """ -from svg import GerberSvgContext +from svgwrite_backend import GerberSvgContext diff --git a/gerber/render/svg.py b/gerber/render/svg.py deleted file mode 100644 index 7d5c8fd..0000000 --- a/gerber/render/svg.py +++ /dev/null @@ -1,165 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -# Copyright 2014 Hamilton Kibbe -# Based on render_svg.py by Paulo Henrique Silva - -# 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) - - 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) diff --git a/gerber/render/svgwrite_backend.py b/gerber/render/svgwrite_backend.py new file mode 100644 index 0000000..7d5c8fd --- /dev/null +++ b/gerber/render/svgwrite_backend.py @@ -0,0 +1,165 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright 2014 Hamilton Kibbe +# Based on render_svg.py by Paulo Henrique Silva + +# 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) + + 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 From e565624b8181ea0a9dd5ea1585025a4eec72ac18 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 6 Oct 2014 11:50:38 -0400 Subject: Fix import error --- gerber/render/render.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index c372783..eab7d33 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -16,7 +16,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ..statements import ( +from ..gerber_statements import ( CommentStmt, UnknownStmt, EofStmt, ParamStmt, CoordStmt, ApertureStmt ) -- cgit From 5ff44efbcfca5316796a1ea0191b2a92894a59ee Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 7 Oct 2014 18:41:14 -0400 Subject: Fix resolve error --- gerber/render/render.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index eab7d33..e40960d 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -63,7 +63,9 @@ class GerberContext(object): self.aperture = d def resolve(self, x, y): - return x or self.x, y or self.y + x = x if x is not None else self.x + y = y if y is not None else self.y + return x, y def define_aperture(self, d, shape, modifiers): pass -- cgit From 1653ae5cbe88757e453bccf499dc1b8ccb278e58 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 8 Oct 2014 09:27:52 -0400 Subject: Update readme and example --- gerber/render/svgwrite_backend.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/svgwrite_backend.py b/gerber/render/svgwrite_backend.py index 7d5c8fd..8d84da1 100644 --- a/gerber/render/svgwrite_backend.py +++ b/gerber/render/svgwrite_backend.py @@ -110,12 +110,14 @@ class GerberSvgContext(GerberContext): self.apertures = {} self.dwg = svgwrite.Drawing() - #self.dwg.add(self.dwg.rect(insert=(0, 0), size=(2000, 2000), fill="black")) + self.background = False 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")) + if not self.background: + self.dwg.add(self.dwg.rect(insert=(SCALE * xbounds[0], -SCALE * ybounds[1]), size=size, fill="black")) + self.background = True def define_aperture(self, d, shape, modifiers): aperture = None -- cgit From bcb6cbc50dea975954b8a3864690f68ab5e984b7 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 8 Oct 2014 22:49:49 -0400 Subject: start arc --- gerber/render/apertures.py | 21 ++++++++++-- gerber/render/render.py | 42 ++++++++++++++--------- gerber/render/svgwrite_backend.py | 70 ++++++++++++++++++++++++--------------- 3 files changed, 88 insertions(+), 45 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/apertures.py b/gerber/render/apertures.py index f163b1f..52ae50c 100644 --- a/gerber/render/apertures.py +++ b/gerber/render/apertures.py @@ -22,16 +22,31 @@ gerber.render.apertures 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.') + 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.') + 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): diff --git a/gerber/render/render.py b/gerber/render/render.py index e40960d..e7ec6ee 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -22,19 +22,20 @@ from ..gerber_statements import ( class GerberContext(object): - settings = {} - - x = 0 - y = 0 - - aperture = 0 - interpolation = 'linear' - - image_polarity = 'positive' - level_polarity = 'dark' def __init__(self): - pass + self.settings = {} + self.x = 0 + self.y = 0 + + self.aperture = 0 + self.interpolation = 'linear' + self.direction = 'clockwise' + self.image_polarity = 'positive' + self.level_polarity = 'dark' + self.region_mode = 'off' + self.color = (0.7215, 0.451, 0.200) + self.drill_color = (0.25, 0.25, 0.25) def set_format(self, settings): self.settings = settings @@ -62,6 +63,12 @@ class GerberContext(object): def set_aperture(self, d): self.aperture = d + def set_color(self, color): + self.color = color + + def set_drill_color(self, color): + self.drill_color = color + def resolve(self, x, y): x = x if x is not None else self.x y = y if y is not None else self.y @@ -76,13 +83,13 @@ class GerberContext(object): else: self.x, self.y = x, y - def stroke(self, x, y): + def stroke(self, x, y, i, j): pass def line(self, x, y): pass - def arc(self, x, y): + def arc(self, x, y, i, j): pass def flash(self, x, y): @@ -109,7 +116,8 @@ class GerberContext(object): def _evaluate_param(self, stmt): if stmt.param == "FS": - self.set_coord_format(stmt.zero_suppression, stmt.format, stmt.notation) + 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) @@ -123,9 +131,11 @@ class GerberContext(object): def _evaluate_coord(self, stmt): if stmt.function in ("G01", "G1", "G02", "G2", "G03", "G3"): self.set_interpolation(stmt.function) - + if stmt.function not in ('G01', 'G1'): + self.direction = ('clockwise' if stmt.function in ('G02', 'G2') + else 'counterclockwise') if stmt.op == "D01": - self.stroke(stmt.x, stmt.y) + self.stroke(stmt.x, stmt.y, stmt.i, stmt.j) elif stmt.op == "D02": self.move(stmt.x, stmt.y) elif stmt.op == "D03": diff --git a/gerber/render/svgwrite_backend.py b/gerber/render/svgwrite_backend.py index 8d84da1..3b2f3c1 100644 --- a/gerber/render/svgwrite_backend.py +++ b/gerber/render/svgwrite_backend.py @@ -23,64 +23,71 @@ import svgwrite SCALE = 300 +def convert_color(color): + color = tuple([int(ch * 255) for ch in color]) + return 'rgb(%d, %d, %d)' % color + class SvgCircle(Circle): - def draw(self, ctx, x, y): + def line(self, ctx, x, y, color='rgb(184, 115, 51)'): return ctx.dwg.line(start=(ctx.x * SCALE, -ctx.y * SCALE), end=(x * SCALE, -y * SCALE), - stroke="rgb(184, 115, 51)", + stroke=color, stroke_width=SCALE * self.diameter, stroke_linecap="round") - def flash(self, ctx, x, y): + def arc(self, ctx, x, y, i, j, direction, color='rgb(184, 115, 51)'): + pass + + def flash(self, ctx, x, y, color='rgb(184, 115, 51)'): return [ctx.dwg.circle(center=(x * SCALE, -y * SCALE), r = SCALE * (self.diameter / 2.0), - fill='rgb(184, 115, 51)'), ] + fill=color), ] class SvgRect(Rect): - def draw(self, ctx, x, y): + def line(self, ctx, x, y, color='rgb(184, 115, 51)'): 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=color, stroke_width=2, stroke_linecap="butt") - def flash(self, ctx, x, y): + def flash(self, ctx, x, y, color='rgb(184, 115, 51)'): 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)"), ] + fill=color), ] class SvgObround(Obround): - def draw(self, ctx, x, y): + def line(self, ctx, x, y, color='rgb(184, 115, 51)'): pass - def flash(self, ctx, x, y): + def flash(self, ctx, x, y, color='rgb(184, 115, 51)'): 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)'), ] + fill=color), ] 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)') + fill=color) rcircle = ctx.dwg.circle(center=((x + (rectx / 2.0)) * SCALE, -y * SCALE), r = SCALE * (ysize / 2.0), - fill='rgb(184, 115, 51)') + fill=color) rect = ctx.dwg.rect(insert=(SCALE * (x - (xsize / 2.)), -SCALE * (y + (ysize / 2.))), size=(SCALE * xsize, SCALE * ysize), - fill='rgb(184, 115, 51)') + fill=color) return [lcircle, rcircle, rect, ] # Vertical obround @@ -90,17 +97,17 @@ class SvgObround(Obround): lcircle = ctx.dwg.circle(center=(x * SCALE, (y - (recty / 2.)) * -SCALE), r = SCALE * (xsize / 2.), - fill='rgb(184, 115, 51)') + fill=color) ucircle = ctx.dwg.circle(center=(x * SCALE, (y + (recty / 2.)) * -SCALE), r = SCALE * (xsize / 2.), - fill='rgb(184, 115, 51)') + fill=color) rect = ctx.dwg.rect(insert=(SCALE * (x - (xsize / 2.)), -SCALE * (y + (ysize / 2.))), size=(SCALE * xsize, SCALE * ysize), - fill='rgb(184, 115, 51)') + fill=color) return [lcircle, ucircle, rect, ] @@ -116,7 +123,9 @@ class GerberSvgContext(GerberContext): xbounds, ybounds = bounds size = (SCALE * (xbounds[1] - xbounds[0]), SCALE * (ybounds[1] - ybounds[0])) if not self.background: - self.dwg.add(self.dwg.rect(insert=(SCALE * xbounds[0], -SCALE * ybounds[1]), size=size, fill="black")) + self.dwg.add(self.dwg.rect(insert=(SCALE * xbounds[0], + -SCALE * ybounds[1]), + size=size, fill="black")) self.background = True def define_aperture(self, d, shape, modifiers): @@ -129,13 +138,13 @@ class GerberSvgContext(GerberContext): aperture = SvgObround(size=modifiers[0][0:2]) self.apertures[d] = aperture - def stroke(self, x, y): - super(GerberSvgContext, self).stroke(x, y) + def stroke(self, x, y, i, j): + super(GerberSvgContext, self).stroke(x, y, i, j) if self.interpolation == 'linear': self.line(x, y) elif self.interpolation == 'arc': - self.arc(x, y) + self.arc(x, y, i, j) def line(self, x, y): super(GerberSvgContext, self).line(x, y) @@ -143,11 +152,18 @@ class GerberSvgContext(GerberContext): ap = self.apertures.get(self.aperture, None) if ap is None: return - self.dwg.add(ap.draw(self, x, y)) + self.dwg.add(ap.line(self, x, y, convert_color(self.color))) self.move(x, y, resolve=False) - def arc(self, x, y): - super(GerberSvgContext, self).arc(x, y) + def arc(self, x, y, i, j): + super(GerberSvgContext, self).arc(x, y, i, j) + x, y = self.resolve(x, y) + ap = self.apertures.get(self.aperture, None) + if ap is None: + return + #self.dwg.add(ap.arc(self, x, y, i, j, self.direction, + # convert_color(self.color))) + self.move(x, y, resolve=False) def flash(self, x, y): super(GerberSvgContext, self).flash(x, y) @@ -155,12 +171,14 @@ class GerberSvgContext(GerberContext): ap = self.apertures.get(self.aperture, None) if ap is None: return - for shape in ap.flash(self, x, y): + for shape in ap.flash(self, x, y, convert_color(self.color)): 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') + hit = self.dwg.circle(center=(x*SCALE, -y*SCALE), + r=SCALE*(diameter/2.0), + fill=convert_color(self.drill_color)) self.dwg.add(hit) def dump(self, filename): -- cgit From 84bfd34e918251ff82f4b3818bc6268feab72efe Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 9 Oct 2014 09:51:29 -0400 Subject: Add mode statement parsing --- gerber/render/render.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index e7ec6ee..e91c71e 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -16,8 +16,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ..gerber_statements import ( - CommentStmt, UnknownStmt, EofStmt, ParamStmt, CoordStmt, ApertureStmt +from ..gerber_statements import (CommentStmt, UnknownStmt, EofStmt, ParamStmt, + CoordStmt, ApertureStmt, RegionModeStmt, + QuadrantModeStmt, ) @@ -111,9 +112,18 @@ class GerberContext(object): elif isinstance(stmt, ApertureStmt): self._evaluate_aperture(stmt) + elif isinstance(stmt, (RegionModeStmt, QuadrantModeStmt)): + self._evaluate_mode(stmt) + else: raise Exception("Invalid statement to evaluate") + def _evaluate_mode(self, stmt): + if stmt.type == 'RegionMode': + self.region_mode = stmt.mode + elif stmt.type == 'QuadrantMode': + self.quadrant_mode = stmt.mode + def _evaluate_param(self, stmt): if stmt.param == "FS": self.set_coord_format(stmt.zero_suppression, stmt.format, -- cgit From 8851bc17b94a921453b0afd9c2421cb30f8d4425 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 9 Oct 2014 18:09:17 -0400 Subject: Doc update --- gerber/render/render.py | 86 ++++++++++++++++++++++++++++++++++++++- gerber/render/svgwrite_backend.py | 8 +++- 2 files changed, 91 insertions(+), 3 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index e91c71e..8cfc5de 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -23,7 +23,60 @@ from ..gerber_statements import (CommentStmt, UnknownStmt, EofStmt, ParamStmt, 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 + ---------- + settings : FileSettings (dict-like) + Gerber file settings + + x : float + X-coordinate of the "photoplotter" head. + + y : float + Y-coordinate of the "photoplotter" head + + aperture : int + The aperture that is currently in use + + interpolation : str + Current interpolation mode. may be 'linear' or 'arc' + + direction : string + Current arc direction. May be either 'clockwise' or 'counterclockwise' + + image_polarity : string + Current image polarity setting. May be 'positive' or 'negative' + + level_polarity : string + Level polarity. May be 'dark' or 'clear'. Dark polarity indicates the + existance of copper/silkscreen/etc. in the exposed area, whereas clear + polarity indicates material should be removed from the exposed area. + + region_mode : string + Region mode. May be 'on' or 'off'. When region mode is set to 'on' the + following "contours" define the outline of a region. When region mode + is subsequently turned 'off', the defined area is filled. + + quadrant_mode : string + Quadrant mode. May be 'single-quadrant' or 'multi-quadrant'. Defines + how arcs are specified. + + color : tuple (, , ) + Color used for rendering as a tuple of normalized (red, green, blue) values. + + drill_color : tuple (, , ) + Color used for rendering drill hits. Format is the same as for `color`. + + background_color : tuple (, , ) + Color of the background. Used when exposing areas in 'clear' level + polarity mode. Format is the same as for `color`. + """ def __init__(self): self.settings = {} self.x = 0 @@ -35,13 +88,36 @@ class GerberContext(object): self.image_polarity = 'positive' self.level_polarity = 'dark' self.region_mode = 'off' + self.quadrant_mode = 'multi-quadrant' + 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) def set_format(self, settings): + """ Set source file format. + + Parameters + ---------- + settings : FileSettings instance or dict-like + Gerber file settings used in source file. + """ self.settings = settings def set_coord_format(self, zero_suppression, format, notation): + """ Set coordinate format used in source gerber file + + Parameters + ---------- + zero_suppression : string + Zero suppression mode. may be 'leading' or 'trailling' + + format : tuple (, ) + decimal precision format + + notation : string + notation mode. 'absolute' or 'incremental' + """ self.settings['zero_suppression'] = zero_suppression self.settings['format'] = format self.settings['notation'] = notation @@ -69,6 +145,9 @@ class GerberContext(object): def set_drill_color(self, color): self.drill_color = color + + def set_background_color(self, color): + self.background_color = color def resolve(self, x, y): x = x if x is not None else self.x @@ -120,6 +199,8 @@ class GerberContext(object): def _evaluate_mode(self, stmt): if stmt.type == 'RegionMode': + if self.region_mode == 'on' and stmt.mode == 'off': + self._fill_region() self.region_mode = stmt.mode elif stmt.type == 'QuadrantMode': self.quadrant_mode = stmt.mode @@ -153,3 +234,6 @@ class GerberContext(object): def _evaluate_aperture(self, stmt): self.set_aperture(stmt.d) + + def _fill_region(self): + pass diff --git a/gerber/render/svgwrite_backend.py b/gerber/render/svgwrite_backend.py index 3b2f3c1..7570c84 100644 --- a/gerber/render/svgwrite_backend.py +++ b/gerber/render/svgwrite_backend.py @@ -152,7 +152,9 @@ class GerberSvgContext(GerberContext): ap = self.apertures.get(self.aperture, None) if ap is None: return - self.dwg.add(ap.line(self, x, y, convert_color(self.color))) + color = (convert_color(self.color) if self.level_polarity == 'dark' + else convert_color(self.background_color)) + self.dwg.add(ap.line(self, x, y, color)) self.move(x, y, resolve=False) def arc(self, x, y, i, j): @@ -171,7 +173,9 @@ class GerberSvgContext(GerberContext): ap = self.apertures.get(self.aperture, None) if ap is None: return - for shape in ap.flash(self, x, y, convert_color(self.color)): + color = (convert_color(self.color) if self.level_polarity == 'dark' + else convert_color(self.background_color)) + for shape in ap.flash(self, x, y, color): self.dwg.add(shape) self.move(x, y, resolve=False) -- cgit From bf9f9451f555a47651e414faf839d8d83441c737 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 9 Oct 2014 21:52:04 -0400 Subject: doc update --- gerber/render/render.py | 227 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 187 insertions(+), 40 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index 8cfc5de..db3c743 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -24,57 +24,57 @@ from ..gerber_statements import (CommentStmt, UnknownStmt, EofStmt, ParamStmt, 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 + functions. Colors are stored internally as 32-bit RGB and may need to be converted to a native format in the rendering subclass. - + Attributes ---------- settings : FileSettings (dict-like) Gerber file settings - + x : float - X-coordinate of the "photoplotter" head. - + X-coordinate of the "photoplotter" head. + y : float Y-coordinate of the "photoplotter" head - + aperture : int The aperture that is currently in use - + interpolation : str Current interpolation mode. may be 'linear' or 'arc' - + direction : string Current arc direction. May be either 'clockwise' or 'counterclockwise' - + image_polarity : string Current image polarity setting. May be 'positive' or 'negative' - + level_polarity : string - Level polarity. May be 'dark' or 'clear'. Dark polarity indicates the - existance of copper/silkscreen/etc. in the exposed area, whereas clear - polarity indicates material should be removed from the exposed area. - + Level polarity. May be 'dark' or 'clear'. Dark polarity indicates the + existance of copper/silkscreen/etc. in the exposed area, whereas clear + polarity indicates material should be removed from the exposed area. + region_mode : string Region mode. May be 'on' or 'off'. When region mode is set to 'on' the - following "contours" define the outline of a region. When region mode + following "contours" define the outline of a region. When region mode is subsequently turned 'off', the defined area is filled. - + quadrant_mode : string Quadrant mode. May be 'single-quadrant' or 'multi-quadrant'. Defines how arcs are specified. - + color : tuple (, , ) Color used for rendering as a tuple of normalized (red, green, blue) values. - + drill_color : tuple (, , ) Color used for rendering drill hits. Format is the same as for `color`. - + background_color : tuple (, , ) - Color of the background. Used when exposing areas in 'clear' level + Color of the background. Used when exposing areas in 'clear' level polarity mode. Format is the same as for `color`. """ def __init__(self): @@ -89,14 +89,14 @@ class GerberContext(object): self.level_polarity = 'dark' self.region_mode = 'off' self.quadrant_mode = 'multi-quadrant' - + 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) def set_format(self, settings): """ Set source file format. - + Parameters ---------- settings : FileSettings instance or dict-like @@ -104,52 +104,178 @@ class GerberContext(object): """ self.settings = settings - def set_coord_format(self, zero_suppression, format, notation): + def set_coord_format(self, zero_suppression, decimal_format, notation): """ Set coordinate format used in source gerber file - + Parameters ---------- zero_suppression : string Zero suppression mode. may be 'leading' or 'trailling' - - format : tuple (, ) - decimal precision format - + + decimal_format : tuple (, ) + Decimal precision format specified as (integer digits, decimal digits) + notation : string - notation mode. 'absolute' or 'incremental' + Notation mode. 'absolute' or 'incremental' """ + if zero_suppression not in ('leading', 'trailling'): + raise ValueError('Zero suppression must be "leading" or "trailing"') self.settings['zero_suppression'] = zero_suppression - self.settings['format'] = format + self.settings['format'] = decimal_format self.settings['notation'] = notation def set_coord_notation(self, notation): + """ Set context notation mode + + Parameters + ---------- + notation : string + Notation mode. may be 'absolute' or 'incremental' + + Raises + ------ + ValueError + If `notation` is not either "absolute" or "incremental" + + """ + if notation not in ('absolute', 'incremental'): + raise ValueError('Notation may be "absolute" or "incremental"') self.settings['notation'] = notation def set_coord_unit(self, unit): + """ Set context measurement units + + Parameters + ---------- + unit : string + Measurement units. may be 'inch' or 'metric' + + Raises + ------ + ValueError + If `unit` is not 'inch' or 'metric' + """ + if unit not in ('inch', 'metric'): + raise ValueError('Unit may be "inch" or "metric"') self.settings['units'] = unit def set_image_polarity(self, polarity): + """ Set context image polarity + + Parameters + ---------- + polarity : string + Image polarity. May be "positive" or "negative" + + Raises + ------ + ValueError + If polarity is not 'positive' or 'negative' + """ + if polarity not in ('positive', 'negative'): + raise ValueError('Polarity may be "positive" or "negative"') self.image_polarity = polarity def set_level_polarity(self, polarity): + """ Set context level polarity + + Parameters + ---------- + polarity : string + Level polarity. May be "dark" or "clear" + + Raises + ------ + ValueError + If polarity is not 'dark' or 'clear' + """ + if polarity not in ('dark', 'clear'): + raise ValueError('Polarity may be "dark" or "clear"') self.level_polarity = polarity def set_interpolation(self, interpolation): - self.interpolation = 'linear' if interpolation in ("G01", "G1") else 'arc' + """ Set arc interpolation mode + + Parameters + ---------- + interpolation : string + Interpolation mode. May be 'linear' or 'arc' + + Raises + ------ + ValueError + If `interpolation` is not 'linear' or 'arc' + """ + if interpolation not in ('linear', 'arc'): + raise ValueError('Interpolation may be "linear" or "arc"') + self.interpolation = interpolation def set_aperture(self, d): + """ Set active aperture + + Parameters + ---------- + aperture : int + Aperture number to activate. + """ self.aperture = d def set_color(self, color): + """ Set rendering color. + + Parameters + ---------- + color : tuple (, , ) + 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): - self.drill_color = color - + """ Set color used for rendering drill hits. + + Parameters + ---------- + color : tuple (, , ) + 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 (, , ) + 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 resolve(self, x, y): + """ Resolve missing x or y coordinates in a coordinate command. + + Replace missing x or y values with the current x or y position. This + is the default method for handling coordinate pairs pulled from gerber + file statments, as a move/line/arc involving a change in only one axis + will drop the redundant axis coordinate to reduce file size. + + Parameters + ---------- + x : float + X-coordinate. If `None`, will be replaced with current + "photoplotter" head x-coordinate + + y : float + Y-coordinate. If `None`, will be replaced with current + "photoplotter" head y-coordinate + + Returns + ------- + coordinates : tuple (, ) + Coordinates in absolute notation + """ x = x if x is not None else self.x y = y if y is not None else self.y return x, y @@ -158,6 +284,26 @@ class GerberContext(object): pass def move(self, x, y, resolve=True): + """ Lights-off move. + + Move the "photoplotter" head to (x, y) without drawing a line. If x or + y is `None`, remain at the same point in that axis. + + Parameters + ----------- + x : float + X-coordinate to move to. If x is `None`, do not move in the X + direction + + y : float + Y-coordinate to move to. if y is `None`, do not move in the Y + direction + + resolve : bool + If resolve is `True` the context will replace missing x or y + coordinates with the current plotter head position. This is the + default behavior. + """ if resolve: self.x, self.y = self.resolve(x, y) else: @@ -220,11 +366,12 @@ class GerberContext(object): 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.function not in ('G01', 'G1'): - self.direction = ('clockwise' if stmt.function in ('G02', 'G2') - else 'counterclockwise') + if stmt.function in ("G01", "G1"): + self.set_interpolation('linear') + elif stmt.function in ('G02', 'G2', 'G03', 'G3'): + self.set_interpolation('arc') + self.direction = ('clockwise' if stmt.function in ('G02', 'G2') + else 'counterclockwise') if stmt.op == "D01": self.stroke(stmt.x, stmt.y, stmt.i, stmt.j) elif stmt.op == "D02": @@ -234,6 +381,6 @@ class GerberContext(object): def _evaluate_aperture(self, stmt): self.set_aperture(stmt.d) - + def _fill_region(self): pass -- cgit From f2f411493ea303075d5dbdd7656c572dda61cf67 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 9 Oct 2014 22:10:28 -0400 Subject: doc update --- gerber/render/render.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index db3c743..f2d23b4 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -15,7 +15,13 @@ # 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. +""" from ..gerber_statements import (CommentStmt, UnknownStmt, EofStmt, ParamStmt, CoordStmt, ApertureStmt, RegionModeStmt, QuadrantModeStmt, -- cgit From a9059df190be0238ce0e6fca8c59700e92ddf205 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 10 Oct 2014 09:35:06 -0400 Subject: doc update --- gerber/render/render.py | 100 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index f2d23b4..e76aed1 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -20,7 +20,8 @@ Rendering ============ **Gerber (RS-274X) and Excellon file rendering** -Render Gerber and Excellon files to a variety of formats. +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, @@ -316,21 +317,118 @@ class GerberContext(object): self.x, self.y = x, y def stroke(self, x, y, i, j): + """ Lights-on move. (draws a line or arc) + + The stroke method is called when a Lights-on move statement is + encountered. This will call the `line` or `arc` method as necessary + based on the move statement's parameters. The `stroke` method should + be overridden in `GerberContext` subclasses. + + Parameters + ---------- + x : float + X coordinate of target position + + y : float + Y coordinate of target position + + i : float + Offset in X-direction from current position of arc center. + + j : float + Offset in Y-direction from current position of arc center. + """ pass def line(self, x, y): + """ Draw a line + + Draws a line from the current position to (x, y) using the currently + selected aperture. The `line` method should be overridden in + `GerberContext` subclasses. + + Parameters + ---------- + x : float + X coordinate of target position + + y : float + Y coordinate of target position + """ pass def arc(self, x, y, i, j): + """ Draw an arc + + Draw an arc from the current position to (x, y) using the currently + selected aperture. `i` and `j` specify the offset from the starting + position to the center of the arc.The `arc` method should be + overridden in `GerberContext` subclasses. + + Parameters + ---------- + x : float + X coordinate of target position + + y : float + Y coordinate of target position + + i : float + Offset in X-direction from current position of arc center. + + j : float + Offset in Y-direction from current position of arc center. + """ pass def flash(self, x, y): + """ Flash the current aperture + + Draw a filled shape defined by the currently selected aperture. + + Parameters + ---------- + x : float + X coordinate of the position at which to flash + + y : float + Y coordinate of the position at which to flash + """ pass def drill(self, x, y, diameter): + """ Draw a drill hit + + Draw a filled circle representing a drill hit at the specified + position and with the specified diameter. + + Parameters + ---------- + x : float + X coordinate of the drill hit + + y : float + Y coordinate of the drill hit + + diameter : float + Finished hole diameter to draw. + """ pass def evaluate(self, stmt): + """ Evaluate Gerber statement and update image accordingly. + + This method is called once for each statement in a Gerber/Excellon + file when the file's `render` method is called. The evaluate method + should forward the statement on to the relevant handling method based + on the statement type. + + Parameters + ---------- + statement : Statement + Gerber/Excellon statement to evaluate. + + """ if isinstance(stmt, (CommentStmt, UnknownStmt, EofStmt)): return -- cgit From 76c03a55c91addff71339d80cf17560926f1580b Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 10 Oct 2014 20:36:38 -0400 Subject: Working region fills and level polarity. Renders Altium-generated gerbers like a champ! --- gerber/render/render.py | 23 +++++++++++++++-------- gerber/render/svgwrite_backend.py | 22 ++++++++++++++++++++-- 2 files changed, 35 insertions(+), 10 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index e76aed1..48a53f8 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -96,7 +96,7 @@ class GerberContext(object): self.level_polarity = 'dark' self.region_mode = 'off' self.quadrant_mode = 'multi-quadrant' - + self.step_and_repeat = (1, 1, 0, 0) 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) @@ -415,6 +415,12 @@ class GerberContext(object): """ pass + def region_contour(self, x, y): + pass + + def fill_region(self): + pass + def evaluate(self, stmt): """ Evaluate Gerber statement and update image accordingly. @@ -450,7 +456,7 @@ class GerberContext(object): def _evaluate_mode(self, stmt): if stmt.type == 'RegionMode': if self.region_mode == 'on' and stmt.mode == 'off': - self._fill_region() + self.fill_region() self.region_mode = stmt.mode elif stmt.type == 'QuadrantMode': self.quadrant_mode = stmt.mode @@ -460,11 +466,11 @@ class GerberContext(object): self.set_coord_format(stmt.zero_suppression, stmt.format, stmt.notation) self.set_coord_notation(stmt.notation) - elif stmt.param == "MO:": + elif stmt.param == "MO": self.set_coord_unit(stmt.mode) - elif stmt.param == "IP:": + elif stmt.param == "IP": self.set_image_polarity(stmt.ip) - elif stmt.param == "LP:": + elif stmt.param == "LP": self.set_level_polarity(stmt.lp) elif stmt.param == "AD": self.define_aperture(stmt.d, stmt.shape, stmt.modifiers) @@ -477,7 +483,10 @@ class GerberContext(object): self.direction = ('clockwise' if stmt.function in ('G02', 'G2') else 'counterclockwise') if stmt.op == "D01": - self.stroke(stmt.x, stmt.y, stmt.i, stmt.j) + if self.region_mode == 'on': + self.region_contour(stmt.x, stmt.y) + else: + self.stroke(stmt.x, stmt.y, stmt.i, stmt.j) elif stmt.op == "D02": self.move(stmt.x, stmt.y) elif stmt.op == "D03": @@ -486,5 +495,3 @@ class GerberContext(object): def _evaluate_aperture(self, stmt): self.set_aperture(stmt.d) - def _fill_region(self): - pass diff --git a/gerber/render/svgwrite_backend.py b/gerber/render/svgwrite_backend.py index 7570c84..886b4f8 100644 --- a/gerber/render/svgwrite_backend.py +++ b/gerber/render/svgwrite_backend.py @@ -118,6 +118,7 @@ class GerberSvgContext(GerberContext): self.apertures = {} self.dwg = svgwrite.Drawing() self.background = False + self.region_path = None def set_bounds(self, bounds): xbounds, ybounds = bounds @@ -125,7 +126,7 @@ class GerberSvgContext(GerberContext): if not self.background: self.dwg.add(self.dwg.rect(insert=(SCALE * xbounds[0], -SCALE * ybounds[1]), - size=size, fill="black")) + size=size, fill=convert_color(self.background_color))) self.background = True def define_aperture(self, d, shape, modifiers): @@ -173,7 +174,8 @@ class GerberSvgContext(GerberContext): ap = self.apertures.get(self.aperture, None) if ap is None: return - color = (convert_color(self.color) if self.level_polarity == 'dark' + + color = (convert_color(self.color) if self.level_polarity == 'dark' else convert_color(self.background_color)) for shape in ap.flash(self, x, y, color): self.dwg.add(shape) @@ -185,5 +187,21 @@ class GerberSvgContext(GerberContext): fill=convert_color(self.drill_color)) self.dwg.add(hit) + def region_contour(self, x, y): + super(GerberSvgContext, self).region_contour(x, y) + x, y = self.resolve(x, y) + color = (convert_color(self.color) if self.level_polarity == 'dark' + else convert_color(self.background_color)) + if self.region_path is None: + self.region_path = self.dwg.path(d = 'M %f, %f' % + (self.x*SCALE, -self.y*SCALE), + fill = color, stroke = 'none') + self.region_path.push('L %f, %f' % (x*SCALE, -y*SCALE)) + self.move(x, y, resolve=False) + + def fill_region(self): + self.dwg.add(self.region_path) + self.region_path = None + def dump(self, filename): self.dwg.saveas(filename) -- cgit From 8c5c7ec8bbc8a074884ef04b566f9c0ecd6e78bb Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 12 Oct 2014 12:38:40 -0400 Subject: update docs and example images --- gerber/render/render.py | 19 ++++++++++++++++++- gerber/render/svgwrite_backend.py | 7 +++++++ 2 files changed, 25 insertions(+), 1 deletion(-) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index 48a53f8..f7e4485 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -83,6 +83,9 @@ class GerberContext(object): background_color : tuple (, , ) 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): self.settings = {} @@ -100,7 +103,8 @@ class GerberContext(object): 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_format(self, settings): """ Set source file format. @@ -260,6 +264,19 @@ class GerberContext(object): """ 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 resolve(self, x, y): """ Resolve missing x or y coordinates in a coordinate command. diff --git a/gerber/render/svgwrite_backend.py b/gerber/render/svgwrite_backend.py index 886b4f8..78961da 100644 --- a/gerber/render/svgwrite_backend.py +++ b/gerber/render/svgwrite_backend.py @@ -117,6 +117,7 @@ class GerberSvgContext(GerberContext): self.apertures = {} self.dwg = svgwrite.Drawing() + self.dwg.transform = 'scale 1 -1' self.background = False self.region_path = None @@ -124,11 +125,17 @@ class GerberSvgContext(GerberContext): xbounds, ybounds = bounds size = (SCALE * (xbounds[1] - xbounds[0]), SCALE * (ybounds[1] - ybounds[0])) if not self.background: + self.dwg = svgwrite.Drawing(viewBox='%f, %f, %f, %f' % (SCALE*xbounds[0], -SCALE*ybounds[1],size[0], size[1])) self.dwg.add(self.dwg.rect(insert=(SCALE * xbounds[0], -SCALE * ybounds[1]), size=size, fill=convert_color(self.background_color))) self.background = True + def set_alpha(self, alpha): + super(GerberSvgContext, self).set_alpha(alpha) + import warnings + warnings.warn('SVG output does not support transparency') + def define_aperture(self, d, shape, modifiers): aperture = None if shape == 'C': -- cgit From c50949e15a839ecd27a6da273ccaf1dc3a7d7853 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 13 Oct 2014 13:26:32 -0400 Subject: Add SVG transparency --- gerber/render/svgwrite_backend.py | 72 ++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 28 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/svgwrite_backend.py b/gerber/render/svgwrite_backend.py index 78961da..15d7bd3 100644 --- a/gerber/render/svgwrite_backend.py +++ b/gerber/render/svgwrite_backend.py @@ -28,49 +28,59 @@ def convert_color(color): return 'rgb(%d, %d, %d)' % color class SvgCircle(Circle): - def line(self, ctx, x, y, color='rgb(184, 115, 51)'): - return ctx.dwg.line(start=(ctx.x * SCALE, -ctx.y * SCALE), - end=(x * SCALE, -y * SCALE), - stroke=color, - stroke_width=SCALE * self.diameter, - stroke_linecap="round") - - def arc(self, ctx, x, y, i, j, direction, color='rgb(184, 115, 51)'): + def line(self, ctx, x, y, color='rgb(184, 115, 51)', alpha=1.0): + aline = ctx.dwg.line(start=(ctx.x * SCALE, -ctx.y * SCALE), + end=(x * SCALE, -y * SCALE), + stroke=color, + stroke_width=SCALE * self.diameter, + stroke_linecap="round") + aline.stroke(opacity=alpha) + return aline + + def arc(self, ctx, x, y, i, j, direction, color='rgb(184, 115, 51)', alpha=1.0): pass - def flash(self, ctx, x, y, color='rgb(184, 115, 51)'): - return [ctx.dwg.circle(center=(x * SCALE, -y * SCALE), + def flash(self, ctx, x, y, color='rgb(184, 115, 51)', alpha=1.0): + circle = ctx.dwg.circle(center=(x * SCALE, -y * SCALE), r = SCALE * (self.diameter / 2.0), - fill=color), ] + fill=color) + circle.fill(opacity=alpha) + return [circle, ] class SvgRect(Rect): - def line(self, ctx, x, y, color='rgb(184, 115, 51)'): - return ctx.dwg.line(start=(ctx.x * SCALE, -ctx.y * SCALE), + def line(self, ctx, x, y, color='rgb(184, 115, 51)', alpha=1.0): + aline = ctx.dwg.line(start=(ctx.x * SCALE, -ctx.y * SCALE), end=(x * SCALE, -y * SCALE), stroke=color, stroke_width=2, stroke_linecap="butt") + aline.stroke(opacity=alpha) + return aline - def flash(self, ctx, x, y, color='rgb(184, 115, 51)'): + def flash(self, ctx, x, y, color='rgb(184, 115, 51)', alpha=1.0): xsize, ysize = self.size - return [ctx.dwg.rect(insert=(SCALE * (x - (xsize / 2)), + rectangle = ctx.dwg.rect(insert=(SCALE * (x - (xsize / 2)), -SCALE * (y + (ysize / 2))), size=(SCALE * xsize, SCALE * ysize), - fill=color), ] + fill=color) + rectangle.fill(opacity=alpha) + return [rectangle, ] class SvgObround(Obround): - def line(self, ctx, x, y, color='rgb(184, 115, 51)'): + def line(self, ctx, x, y, color='rgb(184, 115, 51)', alpha=1.0): pass - def flash(self, ctx, x, y, color='rgb(184, 115, 51)'): + def flash(self, ctx, x, y, color='rgb(184, 115, 51)', alpha=1.0): xsize, ysize = self.size # horizontal obround if xsize == ysize: - return [ctx.dwg.circle(center=(x * SCALE, -y * SCALE), + circle = ctx.dwg.circle(center=(x * SCALE, -y * SCALE), r = SCALE * (x / 2.0), - fill=color), ] + fill=color) + circle.fill(opacity=alpha) + return [circle, ] if xsize > ysize: rectx = xsize - ysize recty = ysize @@ -88,6 +98,9 @@ class SvgObround(Obround): -SCALE * (y + (ysize / 2.))), size=(SCALE * xsize, SCALE * ysize), fill=color) + lcircle.fill(opacity=alpha) + rcircle.fill(opacity=alpha) + rect.fill(opacity=alpha) return [lcircle, rcircle, rect, ] # Vertical obround @@ -108,6 +121,9 @@ class SvgObround(Obround): -SCALE * (y + (ysize / 2.))), size=(SCALE * xsize, SCALE * ysize), fill=color) + lcircle.fill(opacity=alpha) + ucircle.fill(opacity=alpha) + rect.fill(opacity=alpha) return [lcircle, ucircle, rect, ] @@ -131,11 +147,6 @@ class GerberSvgContext(GerberContext): size=size, fill=convert_color(self.background_color))) self.background = True - def set_alpha(self, alpha): - super(GerberSvgContext, self).set_alpha(alpha) - import warnings - warnings.warn('SVG output does not support transparency') - def define_aperture(self, d, shape, modifiers): aperture = None if shape == 'C': @@ -162,7 +173,8 @@ class GerberSvgContext(GerberContext): return color = (convert_color(self.color) if self.level_polarity == 'dark' else convert_color(self.background_color)) - self.dwg.add(ap.line(self, x, y, color)) + alpha = self.alpha if self.level_polarity == 'dark' else 1.0 + self.dwg.add(ap.line(self, x, y, color, alpha)) self.move(x, y, resolve=False) def arc(self, x, y, i, j): @@ -172,7 +184,7 @@ class GerberSvgContext(GerberContext): if ap is None: return #self.dwg.add(ap.arc(self, x, y, i, j, self.direction, - # convert_color(self.color))) + # convert_color(self.color), self.alpha)) self.move(x, y, resolve=False) def flash(self, x, y): @@ -184,7 +196,8 @@ class GerberSvgContext(GerberContext): color = (convert_color(self.color) if self.level_polarity == 'dark' else convert_color(self.background_color)) - for shape in ap.flash(self, x, y, color): + alpha = self.alpha if self.level_polarity == 'dark' else 1.0 + for shape in ap.flash(self, x, y, color, alpha): self.dwg.add(shape) self.move(x, y, resolve=False) @@ -192,6 +205,7 @@ class GerberSvgContext(GerberContext): hit = self.dwg.circle(center=(x*SCALE, -y*SCALE), r=SCALE*(diameter/2.0), fill=convert_color(self.drill_color)) + #hit.fill(opacity=self.alpha) self.dwg.add(hit) def region_contour(self, x, y): @@ -199,10 +213,12 @@ class GerberSvgContext(GerberContext): x, y = self.resolve(x, y) color = (convert_color(self.color) if self.level_polarity == 'dark' else convert_color(self.background_color)) + alpha = self.alpha if self.level_polarity == 'dark' else 1.0 if self.region_path is None: self.region_path = self.dwg.path(d = 'M %f, %f' % (self.x*SCALE, -self.y*SCALE), fill = color, stroke = 'none') + self.region_path.fill(opacity=alpha) self.region_path.push('L %f, %f' % (x*SCALE, -y*SCALE)) self.move(x, y, resolve=False) -- cgit From 6d2db67e6d0973ce26ce3a6700ca44295f73fea7 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 18 Oct 2014 01:44:51 -0400 Subject: Refactor rendering --- gerber/render/render.py | 418 ++++---------------------------------- gerber/render/svgwrite_backend.py | 305 +++++++++++---------------- 2 files changed, 154 insertions(+), 569 deletions(-) (limited to 'gerber/render') diff --git a/gerber/render/render.py b/gerber/render/render.py index f7e4485..f5c58d8 100644 --- a/gerber/render/render.py +++ b/gerber/render/render.py @@ -28,6 +28,7 @@ from ..gerber_statements import (CommentStmt, UnknownStmt, EofStmt, ParamStmt, QuadrantModeStmt, ) +from ..primitives import * class GerberContext(object): """ Gerber rendering context base class @@ -39,40 +40,8 @@ class GerberContext(object): Attributes ---------- - settings : FileSettings (dict-like) - Gerber file settings - - x : float - X-coordinate of the "photoplotter" head. - - y : float - Y-coordinate of the "photoplotter" head - - aperture : int - The aperture that is currently in use - - interpolation : str - Current interpolation mode. may be 'linear' or 'arc' - - direction : string - Current arc direction. May be either 'clockwise' or 'counterclockwise' - - image_polarity : string - Current image polarity setting. May be 'positive' or 'negative' - - level_polarity : string - Level polarity. May be 'dark' or 'clear'. Dark polarity indicates the - existance of copper/silkscreen/etc. in the exposed area, whereas clear - polarity indicates material should be removed from the exposed area. - - region_mode : string - Region mode. May be 'on' or 'off'. When region mode is set to 'on' the - following "contours" define the outline of a region. When region mode - is subsequently turned 'off', the defined area is filled. - - quadrant_mode : string - Quadrant mode. May be 'single-quadrant' or 'multi-quadrant'. Defines - how arcs are specified. + units : string + Measurement units color : tuple (, , ) Color used for rendering as a tuple of normalized (red, green, blue) values. @@ -87,73 +56,14 @@ class GerberContext(object): alpha : float Rendering opacity. Between 0.0 (transparent) and 1.0 (opaque.) """ - def __init__(self): - self.settings = {} - self.x = 0 - self.y = 0 - - self.aperture = 0 - self.interpolation = 'linear' - self.direction = 'clockwise' - self.image_polarity = 'positive' - self.level_polarity = 'dark' - self.region_mode = 'off' - self.quadrant_mode = 'multi-quadrant' - self.step_and_repeat = (1, 1, 0, 0) + 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_format(self, settings): - """ Set source file format. - - Parameters - ---------- - settings : FileSettings instance or dict-like - Gerber file settings used in source file. - """ - self.settings = settings - - def set_coord_format(self, zero_suppression, decimal_format, notation): - """ Set coordinate format used in source gerber file - Parameters - ---------- - zero_suppression : string - Zero suppression mode. may be 'leading' or 'trailling' - - decimal_format : tuple (, ) - Decimal precision format specified as (integer digits, decimal digits) - - notation : string - Notation mode. 'absolute' or 'incremental' - """ - if zero_suppression not in ('leading', 'trailling'): - raise ValueError('Zero suppression must be "leading" or "trailing"') - self.settings['zero_suppression'] = zero_suppression - self.settings['format'] = decimal_format - self.settings['notation'] = notation - - def set_coord_notation(self, notation): - """ Set context notation mode - - Parameters - ---------- - notation : string - Notation mode. may be 'absolute' or 'incremental' - - Raises - ------ - ValueError - If `notation` is not either "absolute" or "incremental" - - """ - if notation not in ('absolute', 'incremental'): - raise ValueError('Notation may be "absolute" or "incremental"') - self.settings['notation'] = notation - - def set_coord_unit(self, unit): + def set_units(self, units): """ Set context measurement units Parameters @@ -166,70 +76,9 @@ class GerberContext(object): ValueError If `unit` is not 'inch' or 'metric' """ - if unit not in ('inch', 'metric'): - raise ValueError('Unit may be "inch" or "metric"') - self.settings['units'] = unit - - def set_image_polarity(self, polarity): - """ Set context image polarity - - Parameters - ---------- - polarity : string - Image polarity. May be "positive" or "negative" - - Raises - ------ - ValueError - If polarity is not 'positive' or 'negative' - """ - if polarity not in ('positive', 'negative'): - raise ValueError('Polarity may be "positive" or "negative"') - self.image_polarity = polarity - - def set_level_polarity(self, polarity): - """ Set context level polarity - - Parameters - ---------- - polarity : string - Level polarity. May be "dark" or "clear" - - Raises - ------ - ValueError - If polarity is not 'dark' or 'clear' - """ - if polarity not in ('dark', 'clear'): - raise ValueError('Polarity may be "dark" or "clear"') - self.level_polarity = polarity - - def set_interpolation(self, interpolation): - """ Set arc interpolation mode - - Parameters - ---------- - interpolation : string - Interpolation mode. May be 'linear' or 'arc' - - Raises - ------ - ValueError - If `interpolation` is not 'linear' or 'arc' - """ - if interpolation not in ('linear', 'arc'): - raise ValueError('Interpolation may be "linear" or "arc"') - self.interpolation = interpolation - - def set_aperture(self, d): - """ Set active aperture - - Parameters - ---------- - aperture : int - Aperture number to activate. - """ - self.aperture = d + 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. @@ -277,238 +126,49 @@ class GerberContext(object): """ self.alpha = alpha - def resolve(self, x, y): - """ Resolve missing x or y coordinates in a coordinate command. - - Replace missing x or y values with the current x or y position. This - is the default method for handling coordinate pairs pulled from gerber - file statments, as a move/line/arc involving a change in only one axis - will drop the redundant axis coordinate to reduce file size. - - Parameters - ---------- - x : float - X-coordinate. If `None`, will be replaced with current - "photoplotter" head x-coordinate - - y : float - Y-coordinate. If `None`, will be replaced with current - "photoplotter" head y-coordinate - - Returns - ------- - coordinates : tuple (, ) - Coordinates in absolute notation - """ - x = x if x is not None else self.x - y = y if y is not None else self.y - return x, y - - def define_aperture(self, d, shape, modifiers): - pass - - def move(self, x, y, resolve=True): - """ Lights-off move. - - Move the "photoplotter" head to (x, y) without drawing a line. If x or - y is `None`, remain at the same point in that axis. - - Parameters - ----------- - x : float - X-coordinate to move to. If x is `None`, do not move in the X - direction - - y : float - Y-coordinate to move to. if y is `None`, do not move in the Y - direction - - resolve : bool - If resolve is `True` the context will replace missing x or y - coordinates with the current plotter head position. This is the - default behavior. - """ - if resolve: - self.x, self.y = self.resolve(x, y) + 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: - self.x, self.y = x, y - - def stroke(self, x, y, i, j): - """ Lights-on move. (draws a line or arc) - - The stroke method is called when a Lights-on move statement is - encountered. This will call the `line` or `arc` method as necessary - based on the move statement's parameters. The `stroke` method should - be overridden in `GerberContext` subclasses. - - Parameters - ---------- - x : float - X coordinate of target position - - y : float - Y coordinate of target position - - i : float - Offset in X-direction from current position of arc center. + return - j : float - Offset in Y-direction from current position of arc center. - """ + def _render_line(self, primitive, color): pass - def line(self, x, y): - """ Draw a line - - Draws a line from the current position to (x, y) using the currently - selected aperture. The `line` method should be overridden in - `GerberContext` subclasses. - - Parameters - ---------- - x : float - X coordinate of target position - - y : float - Y coordinate of target position - """ + def _render_arc(self, primitive, color): pass - def arc(self, x, y, i, j): - """ Draw an arc - - Draw an arc from the current position to (x, y) using the currently - selected aperture. `i` and `j` specify the offset from the starting - position to the center of the arc.The `arc` method should be - overridden in `GerberContext` subclasses. - - Parameters - ---------- - x : float - X coordinate of target position - - y : float - Y coordinate of target position - - i : float - Offset in X-direction from current position of arc center. - - j : float - Offset in Y-direction from current position of arc center. - """ + def _render_region(self, primitive, color): pass - def flash(self, x, y): - """ Flash the current aperture - - Draw a filled shape defined by the currently selected aperture. - - Parameters - ---------- - x : float - X coordinate of the position at which to flash - - y : float - Y coordinate of the position at which to flash - """ + def _render_circle(self, primitive, color): pass - def drill(self, x, y, diameter): - """ Draw a drill hit - - Draw a filled circle representing a drill hit at the specified - position and with the specified diameter. - - Parameters - ---------- - x : float - X coordinate of the drill hit - - y : float - Y coordinate of the drill hit - - diameter : float - Finished hole diameter to draw. - """ + def _render_rectangle(self, primitive, color): pass - def region_contour(self, x, y): - pass - - def fill_region(self): + def _render_obround(self, primitive, color): pass - - def evaluate(self, stmt): - """ Evaluate Gerber statement and update image accordingly. - - This method is called once for each statement in a Gerber/Excellon - file when the file's `render` method is called. The evaluate method - should forward the statement on to the relevant handling method based - on the statement type. - - Parameters - ---------- - statement : Statement - Gerber/Excellon statement to evaluate. - """ - 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) - - elif isinstance(stmt, (RegionModeStmt, QuadrantModeStmt)): - self._evaluate_mode(stmt) + def _render_polygon(self, primitive, color): + pass - else: - raise Exception("Invalid statement to evaluate") - - def _evaluate_mode(self, stmt): - if stmt.type == 'RegionMode': - if self.region_mode == 'on' and stmt.mode == 'off': - self.fill_region() - self.region_mode = stmt.mode - elif stmt.type == 'QuadrantMode': - self.quadrant_mode = stmt.mode - - 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"): - self.set_interpolation('linear') - elif stmt.function in ('G02', 'G2', 'G03', 'G3'): - self.set_interpolation('arc') - self.direction = ('clockwise' if stmt.function in ('G02', 'G2') - else 'counterclockwise') - if stmt.op == "D01": - if self.region_mode == 'on': - self.region_contour(stmt.x, stmt.y) - else: - self.stroke(stmt.x, stmt.y, stmt.i, stmt.j) - 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) + def _render_drill(self, primitive, color): + pass diff --git a/gerber/render/svgwrite_backend.py b/gerber/render/svgwrite_backend.py index 15d7bd3..d9456a5 100644 --- a/gerber/render/svgwrite_backend.py +++ b/gerber/render/svgwrite_backend.py @@ -17,214 +17,139 @@ # limitations under the License. from .render import GerberContext -from .apertures import Circle, Rect, Obround, Polygon +from operator import mul import svgwrite SCALE = 300 -def convert_color(color): +def svg_color(color): color = tuple([int(ch * 255) for ch in color]) return 'rgb(%d, %d, %d)' % color -class SvgCircle(Circle): - def line(self, ctx, x, y, color='rgb(184, 115, 51)', alpha=1.0): - aline = ctx.dwg.line(start=(ctx.x * SCALE, -ctx.y * SCALE), - end=(x * SCALE, -y * SCALE), - stroke=color, - stroke_width=SCALE * self.diameter, - stroke_linecap="round") - aline.stroke(opacity=alpha) - return aline - - def arc(self, ctx, x, y, i, j, direction, color='rgb(184, 115, 51)', alpha=1.0): - pass - - def flash(self, ctx, x, y, color='rgb(184, 115, 51)', alpha=1.0): - circle = ctx.dwg.circle(center=(x * SCALE, -y * SCALE), - r = SCALE * (self.diameter / 2.0), - fill=color) - circle.fill(opacity=alpha) - return [circle, ] - - -class SvgRect(Rect): - def line(self, ctx, x, y, color='rgb(184, 115, 51)', alpha=1.0): - aline = ctx.dwg.line(start=(ctx.x * SCALE, -ctx.y * SCALE), - end=(x * SCALE, -y * SCALE), - stroke=color, stroke_width=2, - stroke_linecap="butt") - aline.stroke(opacity=alpha) - return aline - - def flash(self, ctx, x, y, color='rgb(184, 115, 51)', alpha=1.0): - xsize, ysize = self.size - rectangle = ctx.dwg.rect(insert=(SCALE * (x - (xsize / 2)), - -SCALE * (y + (ysize / 2))), - size=(SCALE * xsize, SCALE * ysize), - fill=color) - rectangle.fill(opacity=alpha) - return [rectangle, ] - - -class SvgObround(Obround): - def line(self, ctx, x, y, color='rgb(184, 115, 51)', alpha=1.0): - pass - - def flash(self, ctx, x, y, color='rgb(184, 115, 51)', alpha=1.0): - xsize, ysize = self.size - - # horizontal obround - if xsize == ysize: - circle = ctx.dwg.circle(center=(x * SCALE, -y * SCALE), - r = SCALE * (x / 2.0), - fill=color) - circle.fill(opacity=alpha) - return [circle, ] - 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=color) - - rcircle = ctx.dwg.circle(center=((x + (rectx / 2.0)) * SCALE, - -y * SCALE), - r = SCALE * (ysize / 2.0), - fill=color) - - rect = ctx.dwg.rect(insert=(SCALE * (x - (xsize / 2.)), - -SCALE * (y + (ysize / 2.))), - size=(SCALE * xsize, SCALE * ysize), - fill=color) - lcircle.fill(opacity=alpha) - rcircle.fill(opacity=alpha) - rect.fill(opacity=alpha) - 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=color) - - ucircle = ctx.dwg.circle(center=(x * SCALE, - (y + (recty / 2.)) * -SCALE), - r = SCALE * (xsize / 2.), - fill=color) - - rect = ctx.dwg.rect(insert=(SCALE * (x - (xsize / 2.)), - -SCALE * (y + (ysize / 2.))), - size=(SCALE * xsize, SCALE * ysize), - fill=color) - lcircle.fill(opacity=alpha) - ucircle.fill(opacity=alpha) - rect.fill(opacity=alpha) - return [lcircle, ucircle, rect, ] - class GerberSvgContext(GerberContext): def __init__(self): GerberContext.__init__(self) - - self.apertures = {} + self.scale = (SCALE, -SCALE) self.dwg = svgwrite.Drawing() - self.dwg.transform = 'scale 1 -1' self.background = False - self.region_path = None + + 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])) + size = (SCALE * (xbounds[1] - xbounds[0]), + SCALE * (ybounds[1] - ybounds[0])) if not self.background: - self.dwg = svgwrite.Drawing(viewBox='%f, %f, %f, %f' % (SCALE*xbounds[0], -SCALE*ybounds[1],size[0], size[1])) - self.dwg.add(self.dwg.rect(insert=(SCALE * xbounds[0], - -SCALE * ybounds[1]), - size=size, fill=convert_color(self.background_color))) + 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 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, i, j): - super(GerberSvgContext, self).stroke(x, y, i, j) - - if self.interpolation == 'linear': - self.line(x, y) - elif self.interpolation == 'arc': - self.arc(x, y, i, j) - - 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 - color = (convert_color(self.color) if self.level_polarity == 'dark' - else convert_color(self.background_color)) - alpha = self.alpha if self.level_polarity == 'dark' else 1.0 - self.dwg.add(ap.line(self, x, y, color, alpha)) - self.move(x, y, resolve=False) - - def arc(self, x, y, i, j): - super(GerberSvgContext, self).arc(x, y, i, j) - x, y = self.resolve(x, y) - ap = self.apertures.get(self.aperture, None) - if ap is None: - return - #self.dwg.add(ap.arc(self, x, y, i, j, self.direction, - # convert_color(self.color), self.alpha)) - self.move(x, y, resolve=False) - - 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 - - color = (convert_color(self.color) if self.level_polarity == 'dark' - else convert_color(self.background_color)) - alpha = self.alpha if self.level_polarity == 'dark' else 1.0 - for shape in ap.flash(self, x, y, color, alpha): - 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=convert_color(self.drill_color)) - #hit.fill(opacity=self.alpha) - self.dwg.add(hit) - - def region_contour(self, x, y): - super(GerberSvgContext, self).region_contour(x, y) - x, y = self.resolve(x, y) - color = (convert_color(self.color) if self.level_polarity == 'dark' - else convert_color(self.background_color)) - alpha = self.alpha if self.level_polarity == 'dark' else 1.0 - if self.region_path is None: - self.region_path = self.dwg.path(d = 'M %f, %f' % - (self.x*SCALE, -self.y*SCALE), - fill = color, stroke = 'none') - self.region_path.fill(opacity=alpha) - self.region_path.push('L %f, %f' % (x*SCALE, -y*SCALE)) - self.move(x, y, resolve=False) - - def fill_region(self): - self.dwg.add(self.region_path) - self.region_path = None + 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) - def dump(self, filename): - self.dwg.saveas(filename) + # 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) -- cgit