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/render.py | 133 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 gerber/render/render.py (limited to 'gerber/render/render.py') 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) -- 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/render.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'gerber/render/render.py') 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 -- 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/render.py') 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/render.py') 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 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/render.py | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) (limited to 'gerber/render/render.py') 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": -- 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/render.py') 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 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) (limited to 'gerber/render/render.py') 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 -- 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/render.py') 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/render.py') 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/render.py') 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 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) (limited to 'gerber/render/render.py') 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 -- 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 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'gerber/render/render.py') 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. -- 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 +++++------------------------------------------- 1 file changed, 39 insertions(+), 379 deletions(-) (limited to 'gerber/render/render.py') 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 -- cgit