This commit is contained in:
Hamilton Kibbe 2014-10-13 12:38:57 -04:00
commit 6adcdbae5f
30 changed files with 489 additions and 15565 deletions

View file

@ -5,8 +5,8 @@ gerber-tools
Tools to handle Gerber and Excellon files in Python.
Example:
Useage Example:
---------------
import gerber
from gerber.render import GerberSvgContext
@ -20,3 +20,12 @@ Example:
# Create SVG image
top_copper.render(ctx)
nc_drill.render(ctx, 'composite.svg')
Rendering Examples:
-------------------
###Top Composite rendering
![Composite Top Image](examples/composite_top.png)
###Bottom Composite rendering
![Composite Bottom Image](examples/composite_bottom.png)

View file

@ -1,3 +0,0 @@
* add command line utilities: gerber svg, gerber transform --rotate --scale --translate, gerber merge --blueprint
* AM defined apertures

View file

@ -30,6 +30,7 @@ sys.path.insert(0, os.path.abspath('../../'))
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'numpydoc',
]
@ -85,7 +86,7 @@ exclude_patterns = []
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

View file

@ -0,0 +1,42 @@
:mod:`excellon` --- Excellon file handling
==============================================
.. module:: excellon
:synopsis: Functions and classes for handling Excellon files
.. sectionauthor:: Hamilton Kibbe <ham@hamiltonkib.be>
The Excellon format is the most common format for exporting PCB drill
information. The Excellon format is used to program CNC drilling macines for
drilling holes in PCBs. As such, excellon files are sometimes refererred to as
NC-drill files. The Excellon format reference is available
`here <http://www.excellon.com/manuals/program.htm>`_. The :mod:`excellon`
submodule implements calsses to read and write excellon files without having
to know the precise details of the format.
The :mod:`excellon` submodule's :func:`read` function serves as a
simple interface for parsing excellon files. The :class:`ExcellonFile` class
stores all the information contained in an Excellon file allowing the file to
be analyzed, modified, and updated. The :class:`ExcellonParser` class is used
in the background for parsing RS-274X files.
.. _excellon-contents:
Functions
---------
The :mod:`excellon` module defines the following functions:
.. autofunction:: gerber.excellon.read
Classes
-------
The :mod:`excellon` module defines the following classes:
.. autoclass:: gerber.excellon.ExcellonFile
:members:
.. autoclass:: gerber.excellon.ExcellonParser
:members:

View file

@ -0,0 +1,11 @@
Gerber Tools Reference
======================
.. toctree::
:maxdepth: 2
Gerber (RS-274X) Files <rs274x>
Excellon Files <excellon>
Rendering <render>

View file

@ -0,0 +1,11 @@
:mod:`render` --- Gerber file Rendering
==============================================
.. module:: render
:synopsis: Functions and classes for handling Excellon files
.. sectionauthor:: Hamilton Kibbe <ham@hamiltonkib.be>
Render Module
-------------
.. automodule:: gerber.render.render
:members:

View file

@ -0,0 +1,37 @@
:mod:`rs274x` --- RS-274X file handling
==============================================
.. module:: rs274x
:synopsis: Functions and classes for handling RS-274X files
.. sectionauthor:: Hamilton Kibbe <ham@hamiltonkib.be>
The RS-274X (Gerber) format is the most common format for exporting PCB
artwork. The Specification is published by Ucamco and is available
`here <http://www.ucamco.com/files/downloads/file/81/the_gerber_file_format_specification.pdf>`_.
The :mod:`rs274x` submodule implements calsses to read and write
RS-274X files without having to know the precise details of the format.
The :mod:`rs274x` submodule's :func:`read` function serves as a
simple interface for parsing gerber files. The :class:`GerberFile` class
stores all the information contained in a gerber file allowing the file to be
analyzed, modified, and updated. The :class:`GerberParser` class is used in
the background for parsing RS-274X files.
.. _gerber-contents:
Functions
---------
The :mod:`rs274x` module defines the following functions:
.. autofunction:: gerber.rs274x.read
Classes
-------
The :mod:`rs274x` module defines the following classes:
.. autoclass:: gerber.rs274x.GerberFile
:members:
.. autoclass:: gerber.rs274x.GerberParser
:members:

View file

@ -3,37 +3,16 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Gerber Tools's documentation!
Gerber-Tools!
========================================
Contents:
.. toctree::
:maxdepth: 2
.. automodule:: gerber
:members:
.. automodule:: gerber.gerber
:members:
.. automodule:: gerber.excellon
:members:
.. automodule:: gerber.render.render
:members:
.. automodule:: gerber.gerber_statements
:members:
:maxdepth: 1
.. automodule:: gerber.excellon_statements
:members:
.. automodule:: gerber.cnc
:members:
.. automodule:: gerber.utils
:members:
intro
documentation/index
Indices and tables
==================

19
doc/source/intro.rst Normal file
View file

@ -0,0 +1,19 @@
Gerber Tools Intro
==================
PCB CAM (Gerber) Files
------------
PCB design files (artwork) are most often stored in `Gerber` files. This is
a generic term that may refer to `RS-274X (Gerber) <http://en.wikipedia.org/wiki/Gerber_format>`_,
`ODB++ <http://en.wikipedia.org/wiki/ODB%2B%2B>`_, or `Excellon <http://en.wikipedia.org/wiki/Excellon_format>`_
files.
Gerber-Tools
------------
The gerber-tools module provides tools for working with and rendering Gerber
and Excellon files.

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 331 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 477 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 832 KiB

BIN
examples/composite_top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 212 KiB

View file

@ -1,73 +0,0 @@
# Gerber (RS-274X or Extended Gerber) is a bilevel, resolution independent image format.
# // graphic objects
# // draw: line segment, thickness, round or square line endings. (solid circle and rectangule apertures only)
# // arc: circular arc, thickness, round endings. (solid circle standard aperture only)
# // flash: replication of a given apertura (shape)
# // region: are defined by a countour (linear/arc segments.)
#
# // draw/arc: can have zero length (just flash the aperture)
# // flash: any aperture can be flashed
#
# // operation codes operates on coordinate data blocks. each operation code is for one coordinate data block pair and vice-versa.
# // D01: stroke an aperture from current point to coordinate pair. region mode off. lights-on move.
# // D02: move current point to this coordinate pair
# // D03: flash current aperture at this coordinate pair.
#
# // graphics state
# // all state controlled by codes and parameters, except current point
# //
# // state fixed? initial value
# // coordinate format fixed undefined
# // unit fixed undefined
# // image polarity fixed positive
# // steps/repeat variable 1,1,-,-
# // level polarity variable dark
# // region mode variable off
# // current aperture variable undefined
# // quadrant mode variable undefined
# // interpolation mode variable undefined
# // current point variable (0,0)
#
# // attributes: metadata, both standard and custom. No change on image.
#
# // G01: linear
# // G04: comment
# // M02: end of file
# // D: select aperture
# // G75: multi quadrant mode (circles)
# // G36: region begin
# // G37: region end
#
# // [G01] [Xnnfffff] [Ynnffff] D01*
#
# // ASCII 32-126, CR LF.
# // * end-of-block
# // % parameer delimiter
# // , field separator
# // <space> only in comments
# // case sensitive
#
# // int: +/- 32 bit signed
# // decimal: +/- digits
# // names: [a-zA-Z_$]{[a-zA-Z_$0-9]+} (255)
# // strings: [a-zA-Z0-9_+-/!?<>”’(){}.\|&@# ]+ (65535)
#
# // data block: end in *
# // statement: one or more data block, if contain parameters starts and end in % (parameter statement)
# // statement: [%]<Data Block>{<Data Block>}[%]
# // statements: function code, coordinate data, parameters
#
# // function code: operation codes (D01..) or code that set state.
# // function codes applies before operation codes act on coordinates
#
# // coordinate data: <Coordinate data>: [X<Number>][Y<Number>][I<Number>][J<Number>](D01|D02|D03)
# // offsets are not modal
#
# // parameter: %Parameter code<required modifiers>[optional modifiers]*%
# // code: 2 characters
#
# // parameters can have line separators: %<Parameter>{{<Line separator>}<Parameter>}%
#
# // function code: (GDM){1}[number], parameters: [AZ]{2}

View file

@ -15,16 +15,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""
gerber.cnc
CAM File
============
**CNC file classes**
**AM file classes**
This module provides common base classes for Excellon/Gerber CNC files
"""
class FileSettings(object):
""" CNC File Settings
""" CAM File Settings
Provides a common representation of gerber/excellon file settings
"""
@ -60,7 +60,7 @@ class FileSettings(object):
raise KeyError()
class CncFile(object):
class CamFile(object):
""" Base class for Gerber/Excellon files.
Provides a common set of settings parameters.
@ -71,7 +71,10 @@ class CncFile(object):
The current file configuration.
filename : string
Name of the file that this CncFile represents.
Name of the file that this CamFile represents.
layer_name : string
Name of the PCB layer that the file represents
Attributes
----------
@ -92,7 +95,8 @@ class CncFile(object):
decimal digits)
"""
def __init__(self, statements=None, settings=None, filename=None):
def __init__(self, statements=None, settings=None, filename=None,
layer_name=None):
if settings is not None:
self.notation = settings['notation']
self.units = settings['units']
@ -105,6 +109,7 @@ class CncFile(object):
self.format = (2, 5)
self.statements = statements if statements is not None else []
self.filename = filename
self.layer_name = layer_name
@property
def settings(self):

View file

@ -30,12 +30,12 @@ def read(filename):
CncFile object representing the file, either GerberFile or
ExcellonFile. Returns None if file is not an Excellon or Gerber file.
"""
import gerber
import rs274x
import excellon
from utils import detect_file_format
fmt = detect_file_format(filename)
if fmt == 'rs274x':
return gerber.read(filename)
return rs274x.read(filename)
elif fmt == 'excellon':
return excellon.read(filename)
else:

View file

@ -24,16 +24,32 @@ This module provides Excellon file classes and parsing utilities
from .excellon_statements import *
from .cnc import CncFile, FileSettings
from .cam import CamFile, FileSettings
import math
def read(filename):
""" Read data from filename and return an ExcellonFile
Parameters
----------
filename : string
Filename of file to parse
Returns
-------
file : :class:`gerber.excellon.ExcellonFile`
An ExcellonFile created from the specified file.
"""
return ExcellonParser().parse(filename)
detected_settings = detect_excellon_format(filename)
settings = FileSettings(**detected_settings)
zeros = ''
print('Detected %d:%d format with %s zero suppression' %
(settings.format[0], settings.format[1], settings.zero_suppression))
return ExcellonParser(settings).parse(filename)
class ExcellonFile(CncFile):
class ExcellonFile(CamFile):
""" A class representing a single excellon file
The ExcellonFile class represents a single excellon file.
@ -69,6 +85,14 @@ class ExcellonFile(CncFile):
def render(self, ctx, filename=None):
""" Generate image of file
Parameters
----------
ctx : :class:`gerber.render.GerberContext`
GerberContext subclass used for rendering the image
filename : string <optional>
If provided, the rendered image will be saved to `filename`
"""
for tool, pos in self.hits:
ctx.drill(pos[0], pos[1], tool.diameter)
@ -83,8 +107,13 @@ class ExcellonFile(CncFile):
class ExcellonParser(object):
""" Excellon File Parser
Parameters
----------
settings : FileSettings or dict-like
Excellon file settings to use when interpreting the excellon file.
"""
def __init__(self):
def __init__(self, settings=None):
self.notation = 'absolute'
self.units = 'inch'
self.zero_suppression = 'trailing'
@ -95,6 +124,37 @@ class ExcellonParser(object):
self.hits = []
self.active_tool = None
self.pos = [0., 0.]
if settings is not None:
self.units = settings['units']
self.zero_suppression = settings['zero_suppression']
self.notation = settings['notation']
self.format = settings['format']
@property
def coordinates(self):
return [(stmt.x, stmt.y) for stmt in self.statements if isinstance(stmt, CoordinateStmt)]
@property
def bounds(self):
xmin = ymin = 100000000000
xmax = ymax = -100000000000
for x, y in self.coordinates:
if x is not None:
xmin = x if x < xmin else xmin
xmax = x if x > xmax else xmax
if y is not None:
ymin = y if y < ymin else ymin
ymax = y if y > ymax else ymax
return ((xmin, xmax), (ymin, ymax))
@property
def hole_sizes(self):
return [stmt.diameter for stmt in self.statements if isinstance(stmt, ExcellonTool)]
@property
def hole_count(self):
return len(self.hits)
def parse(self, filename):
with open(filename, 'r') as f:
@ -194,3 +254,105 @@ class ExcellonParser(object):
return FileSettings(units=self.units, format=self.format,
zero_suppression=self.zero_suppression,
notation=self.notation)
def detect_excellon_format(filename):
""" Detect excellon file decimal format and zero-suppression settings.
Parameters
----------
filename : string
Name of the file to parse. This does not check if the file is actually
an Excellon file, so do that before calling this.
Returns
-------
settings : dict
Detected excellon file settings. Keys are
- `format`: decimal format as tuple (<int part>, <decimal part>)
- `zero_suppression`: zero suppression, 'leading' or 'trailing'
"""
results = {}
detected_zeros = None
detected_format = None
zs_options = ('leading', 'trailing', )
format_options = ((2, 4), (2, 5), (3, 3),)
# Check for obvious clues:
p = ExcellonParser()
p.parse(filename)
# Get zero_suppression from a unit statement
zero_statements = [stmt.zero_suppression for stmt in p.statements
if isinstance(stmt, UnitStmt)]
# get format from altium comment
format_comment = [stmt.comment for stmt in p.statements
if isinstance(stmt, CommentStmt)
and 'FILE_FORMAT' in stmt.comment]
detected_format = (tuple([int(val) for val in
format_comment[0].split('=')[1].split(':')])
if len(format_comment) == 1 else None)
detected_zeros = zero_statements[0] if len(zero_statements) == 1 else None
# Bail out here if possible
if detected_format is not None and detected_zeros is not None:
return {'format': detected_format, 'zero_suppression': detected_zeros}
# Only look at remaining options
if detected_format is not None:
format_options = (detected_format,)
if detected_zeros is not None:
zs_options = (detected_zeros,)
# Brute force all remaining options, and pick the best looking one...
for zs in zs_options:
for fmt in format_options:
key = (fmt, zs)
settings = FileSettings(zero_suppression=zs, format=fmt)
try:
p = ExcellonParser(settings)
p.parse(filename)
size = tuple([t[1] - t[0] for t in p.bounds])
hole_area = 0.0
for hit in p.hits:
tool = hit[0]
hole_area += math.pow(math.pi * tool.diameter / 2., 2)
results[key] = (size, p.hole_count, hole_area)
except:
pass
# See if any of the dimensions are left with only a single option
formats = set(key[0] for key in results.iterkeys())
zeros = set(key[1] for key in results.iterkeys())
if len(formats) == 1:
detected_format = formats.pop()
if len(zeros) == 1:
detected_zeros = zeros.pop()
# Bail out here if we got everything....
if detected_format is not None and detected_zeros is not None:
return {'format': detected_format, 'zero_suppression': detected_zeros}
# Otherwise score each option and pick the best candidate
else:
scores = {}
for key in results.keys():
size, count, diameter = results[key]
scores[key] = _layer_size_score(size, count, diameter)
minscore = min(scores.values())
for key in scores.iterkeys():
if scores[key] == minscore:
return {'format': key[0], 'zero_suppression': key[1]}
def _layer_size_score(size, hole_count, hole_area):
""" Heuristic used for determining the correct file number interpretation.
Lower is better.
"""
board_area = size[0] * size[1]
hole_percentage = hole_area / board_area
hole_score = (hole_percentage - 0.25) ** 2
size_score = (board_area - 8) **2
return hole_score * size_score

View file

@ -17,9 +17,9 @@ __all__ = ['FSParamStmt', 'MOParamStmt', 'IPParamStmt', 'OFParamStmt',
class Statement(object):
""" Gerber statement Base class
The statement class provides a type attribute.
Parameters
----------
type : string
@ -27,7 +27,7 @@ class Statement(object):
Attributes
----------
type : string
type : string
String identifying the statement type.
"""
def __init__(self, stype):
@ -45,9 +45,9 @@ class Statement(object):
class ParamStmt(Statement):
""" Gerber parameter statement Base class
The parameter statement class provides a parameter type attribute.
Parameters
----------
param : string
@ -55,7 +55,7 @@ class ParamStmt(Statement):
Attributes
----------
param : string
param : string
Parameter type code
"""
def __init__(self, param):
@ -133,7 +133,12 @@ class MOParamStmt(ParamStmt):
@classmethod
def from_dict(cls, stmt_dict):
param = stmt_dict.get('param')
mo = 'inch' if stmt_dict.get('mo') == 'IN' else 'metric'
if stmt_dict.get('mo').lower() == 'in':
mo = 'inch'
elif stmt_dict.get('mo').lower() == 'mm':
mo = 'metric'
else:
mo = None
return cls(param, mo)
def __init__(self, param, mo):
@ -260,7 +265,7 @@ class LPParamStmt(ParamStmt):
@classmethod
def from_dict(cls, stmt_dict):
param = stmt_dict.get('lp')
param = stmt_dict['param']
lp = 'clear' if stmt_dict.get('lp') == 'C' else 'dark'
return cls(param, lp)
@ -667,6 +672,6 @@ class UnknownStmt(Statement):
def __init__(self, line):
Statement.__init__(self, "UNKNOWN")
self.line = line
def to_gerber(self):
return self.line

View file

@ -83,6 +83,9 @@ class GerberContext(object):
background_color : tuple (<float>, <float>, <float>)
Color of the background. Used when exposing areas in 'clear' level
polarity mode. Format is the same as for `color`.
alpha : float
Rendering opacity. Between 0.0 (transparent) and 1.0 (opaque.)
"""
def __init__(self):
self.settings = {}
@ -96,11 +99,12 @@ 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)
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.
@ -415,6 +432,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 +473,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 +483,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 +500,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 +512,3 @@ class GerberContext(object):
def _evaluate_aperture(self, stmt):
self.set_aperture(stmt.d)
def _fill_region(self):
pass

View file

@ -117,17 +117,25 @@ class GerberSvgContext(GerberContext):
self.apertures = {}
self.dwg = svgwrite.Drawing()
self.dwg.transform = 'scale 1 -1'
self.background = False
self.region_path = None
def set_bounds(self, bounds):
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="black"))
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':
@ -173,7 +181,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 +194,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)

View file

@ -15,30 +15,35 @@
# 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 File module
==================
**Gerber File module**
This module provides an RS-274-X class and parser
""" This module provides an RS-274-X class and parser.
"""
import re
import json
from .gerber_statements import *
from .cnc import CncFile, FileSettings
from .cam import CamFile, FileSettings
def read(filename):
""" Read data from filename and return a GerberFile
Parameters
----------
filename : string
Filename of file to parse
Returns
-------
file : :class:`gerber.rs274x.GerberFile`
A GerberFile created from the specified file.
"""
return GerberParser().parse(filename)
class GerberFile(CncFile):
class GerberFile(CamFile):
""" A class representing a single gerber file
The GerberFile class represents a single gerber file.
@ -86,24 +91,19 @@ class GerberFile(CncFile):
ybounds = [0.0, 0.0]
for stmt in [stmt for stmt in self.statements
if isinstance(stmt, CoordStmt)]:
if stmt.x is not None and stmt.x < xbounds[0]:
xbounds[0] = stmt.x
if stmt.x is not None and stmt.x > xbounds[1]:
xbounds[1] = stmt.x
if stmt.i is not None and stmt.i < xbounds[0]:
xbounds[0] = stmt.i
if stmt.i is not None and stmt.i > xbounds[1]:
xbounds[1] = stmt.i
if stmt.y is not None and stmt.y < ybounds[0]:
ybounds[0] = stmt.y
if stmt.y is not None and stmt.y > ybounds[1]:
ybounds[1] = stmt.y
if stmt.j is not None and stmt.j < ybounds[0]:
ybounds[0] = stmt.j
if stmt.j is not None and stmt.j > ybounds[1]:
ybounds[1] = stmt.j
if stmt.x is not None:
if stmt.x < xbounds[0]:
xbounds[0] = stmt.x
elif stmt.x > xbounds[1]:
xbounds[1] = stmt.x
if stmt.y is not None:
if stmt.y < ybounds[0]:
ybounds[0] = stmt.y
elif stmt.y > ybounds[1]:
ybounds[1] = stmt.y
return (xbounds, ybounds)
def write(self, filename):
""" Write data out to a gerber file
"""
@ -113,6 +113,14 @@ class GerberFile(CncFile):
def render(self, ctx, filename=None):
""" Generate image of layer.
Parameters
----------
ctx : :class:`GerberContext`
GerberContext subclass used for rendering the image
filename : string <optional>
If provided, the rendered image will be saved to `filename`
"""
ctx.set_bounds(self.bounds)
for statement in self.statements:

View file

@ -3,7 +3,7 @@
# Author: Hamilton Kibbe <ham@hamiltonkib.be>
from ..cnc import CncFile, FileSettings
from ..cam import CamFile, FileSettings
from tests import *
@ -46,5 +46,5 @@ def test_filesettings_assign():
assert_equal(fs.zero_suppression, 'test')
assert_equal(fs.format, 'test')
def test_smoke_cncfile():
pass
def test_smoke_camfile():
cf = CamFile

View file

@ -8,7 +8,7 @@ from ..gerber_statements import *
def test_FSParamStmt_factory():
""" Test FSParamStruct factory correctly handles parameters
""" Test FSParamStruct factory
"""
stmt = {'param': 'FS', 'zero': 'L', 'notation': 'A', 'x': '27'}
fs = FSParamStmt.from_dict(stmt)
@ -24,6 +24,18 @@ def test_FSParamStmt_factory():
assert_equal(fs.notation, 'incremental')
assert_equal(fs.format, (2, 7))
def test_FSParamStmt():
""" Test FSParamStmt initialization
"""
param = 'FS'
zeros = 'trailing'
notation = 'absolute'
fmt = (2, 5)
stmt = FSParamStmt(param, zeros, notation, fmt)
assert_equal(stmt.param, param)
assert_equal(stmt.zero_suppression, zeros)
assert_equal(stmt.notation, notation)
assert_equal(stmt.format, fmt)
def test_FSParamStmt_dump():
""" Test FSParamStmt to_gerber()
@ -38,17 +50,31 @@ def test_FSParamStmt_dump():
def test_MOParamStmt_factory():
""" Test MOParamStruct factory correctly handles parameters
""" Test MOParamStruct factory
"""
stmt = {'param': 'MO', 'mo': 'IN'}
mo = MOParamStmt.from_dict(stmt)
assert_equal(mo.param, 'MO')
assert_equal(mo.mode, 'inch')
stmts = [{'param': 'MO', 'mo': 'IN'}, {'param': 'MO', 'mo': 'in'}, ]
for stmt in stmts:
mo = MOParamStmt.from_dict(stmt)
assert_equal(mo.param, 'MO')
assert_equal(mo.mode, 'inch')
stmt = {'param': 'MO', 'mo': 'MM'}
mo = MOParamStmt.from_dict(stmt)
assert_equal(mo.param, 'MO')
assert_equal(mo.mode, 'metric')
stmts = [{'param': 'MO', 'mo': 'MM'}, {'param': 'MO', 'mo': 'mm'}, ]
for stmt in stmts:
mo = MOParamStmt.from_dict(stmt)
assert_equal(mo.param, 'MO')
assert_equal(mo.mode, 'metric')
def test_MOParamStmt():
""" Test MOParamStmt initialization
"""
param = 'MO'
mode = 'inch'
stmt = MOParamStmt(param, mode)
assert_equal(stmt.param, param)
for mode in ['inch', 'metric']:
stmt = MOParamStmt(param, mode)
assert_equal(stmt.mode, mode)
def test_MOParamStmt_dump():
@ -64,7 +90,7 @@ def test_MOParamStmt_dump():
def test_IPParamStmt_factory():
""" Test IPParamStruct factory correctly handles parameters
""" Test IPParamStruct factory
"""
stmt = {'param': 'IP', 'ip': 'POS'}
ip = IPParamStmt.from_dict(stmt)
@ -74,6 +100,15 @@ def test_IPParamStmt_factory():
ip = IPParamStmt.from_dict(stmt)
assert_equal(ip.ip, 'negative')
def test_IPParamStmt():
""" Test IPParamStmt initialization
"""
param = 'IP'
for ip in ['positive', 'negative']:
stmt = IPParamStmt(param, ip)
assert_equal(stmt.param, param)
assert_equal(stmt.ip, ip)
def test_IPParamStmt_dump():
""" Test IPParamStmt to_gerber()
@ -88,14 +123,23 @@ def test_IPParamStmt_dump():
def test_OFParamStmt_factory():
""" Test OFParamStmt factory correctly handles parameters
""" Test OFParamStmt factory
"""
stmt = {'param': 'OF', 'a': '0.1234567', 'b': '0.1234567'}
of = OFParamStmt.from_dict(stmt)
assert_equal(of.a, 0.1234567)
assert_equal(of.b, 0.1234567)
def test_OFParamStmt():
""" Test IPParamStmt initialization
"""
param = 'OF'
for val in [0.0, -3.4567]:
stmt = OFParamStmt(param, val, val)
assert_equal(stmt.param, param)
assert_equal(stmt.a, val)
assert_equal(stmt.b, val)
def test_OFParamStmt_dump():
""" Test OFParamStmt to_gerber()
"""
@ -105,7 +149,7 @@ def test_OFParamStmt_dump():
def test_LPParamStmt_factory():
""" Test LPParamStmt factory correctly handles parameters
""" Test LPParamStmt factory
"""
stmt = {'param': 'LP', 'lp': 'C'}
lp = LPParamStmt.from_dict(stmt)
@ -128,7 +172,7 @@ def test_LPParamStmt_dump():
def test_INParamStmt_factory():
""" Test INParamStmt factory correctly handles parameters
""" Test INParamStmt factory
"""
stmt = {'param': 'IN', 'name': 'test'}
inp = INParamStmt.from_dict(stmt)
@ -143,7 +187,7 @@ def test_INParamStmt_dump():
def test_LNParamStmt_factory():
""" Test LNParamStmt factory correctly handles parameters
""" Test LNParamStmt factory
"""
stmt = {'param': 'LN', 'name': 'test'}
lnp = LNParamStmt.from_dict(stmt)