diff options
Diffstat (limited to 'gerber')
-rw-r--r-- | gerber/cnc.py | 117 | ||||
-rwxr-xr-x | gerber/excellon.py | 91 | ||||
-rw-r--r-- | gerber/gerber.py | 21 |
3 files changed, 206 insertions, 23 deletions
diff --git a/gerber/cnc.py b/gerber/cnc.py new file mode 100644 index 0000000..a7f3b85 --- /dev/null +++ b/gerber/cnc.py @@ -0,0 +1,117 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +# copyright 2014 Hamilton Kibbe <ham@hamiltonkib.be> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +gerber.cnc +============ +**CNC file classes** + +This module provides common base classes for Excellon/Gerber CNC files +""" + + +class FileSettings(object): + """ CNC File Settings + + Provides a common representation of gerber/excellon file settings + """ + def __init__(self, notation='absolute', units='inch', + zero_suppression='trailing', format=(2,5)): + if notation not in ['absolute', 'incremental']: + raise ValueError('Notation must be either absolute or incremental') + self.notation = notation + + if units not in ['inch', 'metric']: + raise ValueError('Units must be either inch or metric') + self.units = units + + if zero_suppression not in ['leading', 'trailing']: + raise ValueError('Zero suppression must be either leading or \ + trailling') + self.zero_suppression = zero_suppression + + if len(format) != 2: + raise ValueError('Format must be a tuple(n=2) of integers') + self.format = format + + def __getitem__(self, key): + if key == 'notation': + return self.notation + elif key == 'units': + return self.units + elif key =='zero_suppression': + return self.zero_suppression + elif key == 'format': + return self.format + else: + raise KeyError() + +class CncFile(object): + """ Base class for Gerber/Excellon files. + + Provides a common set of settings parameters. + + Parameters + ---------- + settings : FileSettings + The current file configuration. + + filename : string + Name of the file that this CncFile represents. + + Attributes + ---------- + settings : FileSettings + File settings as a FileSettings object + + notation : string + File notation setting. May be either 'absolute' or 'incremental' + + units : string + File units setting. May be 'inch' or 'metric' + + zero_suppression : string + File zero-suppression setting. May be either 'leading' or 'trailling' + + format : tuple (<int>, <int>) + File decimal representation format as a tuple of (integer digits, + decimal digits) + """ + + def __init__(self, settings=None, filename=None): + if settings is not None: + self.notation = settings['notation'] + self.units = settings['units'] + self.zero_suppression = settings['zero_suppression'] + self.format = settings['format'] + else: + self.notation = 'absolute' + self.units = 'inch' + self.zero_suppression = 'trailing' + self.format = (2,5) + self.filename = filename + + @property + def settings(self): + """ File settings + + Returns + ------- + settings : FileSettings (dict-like) + A FileSettings object with the specified configuration. + """ + return FileSettings(self.notation, self.units, self.zero_suppression, + self.format) diff --git a/gerber/excellon.py b/gerber/excellon.py index d92d57c..5cb33ad 100755 --- a/gerber/excellon.py +++ b/gerber/excellon.py @@ -2,6 +2,7 @@ import re
from itertools import tee, izip
from .utils import parse_gerber_value
+from .cnc import CncFile, FileSettings
def read(filename):
@@ -10,7 +11,7 @@ def read(filename): return ExcellonParser().parse(filename)
-class ExcellonFile(object):
+class ExcellonFile(CncFile):
""" A class representing a single excellon file
The ExcellonFile class represents a single excellon file.
@@ -34,11 +35,10 @@ class ExcellonFile(object): either 'inch' or 'metric'.
"""
- def __init__(self, tools, hits, settings, filename):
+ def __init__(self, tools, hits, settings, filename=None):
+ super(ExcellonFile, self).__init__(settings, filename)
self.tools = tools
self.hits = hits
- self.settings = settings
- self.filename = filename
def report(self):
""" Print drill report
@@ -53,11 +53,67 @@ class ExcellonFile(object): ctx.dump(filename)
-class Tool(object):
+class ExcellonTool(object):
""" Excellon Tool class
+
+ Parameters
+ ----------
+ settings : FileSettings (dict-like)
+ File-wide settings.
+
+ kwargs : dict-like
+ Tool settings from the excellon statement. Valid keys are:
+ diameter : Tool diameter [expressed in file units]
+ rpm : Tool RPM
+ feed_rate : Z-axis tool feed rate
+ retract_rate : Z-axis tool retraction rate
+ max_hit_count : Number of hits allowed before a tool change
+ depth_offset : Offset of tool depth from tip of tool.
+
+ Attributes
+ ----------
+ number : integer
+ Tool number from the excellon file
+
+ diameter : float
+ Tool diameter in file units
+
+ rpm : float
+ Tool RPM
+
+ feed_rate : float
+ Tool Z-axis feed rate.
+
+ retract_rate : float
+ Tool Z-axis retract rate
+
+ depth_offset : float
+ Offset of depth measurement from tip of tool
+
+ max_hit_count : integer
+ Maximum number of tool hits allowed before a tool change
+
+ hit_count : integer
+ Number of tool hits in excellon file.
"""
+
@classmethod
def from_line(cls, line, settings):
+ """ Create a Tool from an excellon gile tool definition line.
+
+ Parameters
+ ----------
+ line : string
+ Tool definition line from an excellon file.
+
+ settings : FileSettings (dict-like)
+ Excellon file-wide settings
+
+ Returns
+ -------
+ tool : Tool
+ An ExcellonTool representing the tool defined in `line`
+ """
commands = re.split('([BCFHSTZ])', line)[1:]
commands = [(command, value) for command, value in pairwise(commands)]
args = {}
@@ -89,13 +145,19 @@ class Tool(object): self.max_hit_count = kwargs.get('max_hit_count')
self.depth_offset = kwargs.get('depth_offset')
self.units = settings.get('units', 'inch')
+ self.hit_count = 0
+
+ def _hit(self):
+ self.hit_count += 1
def __repr__(self):
unit = 'in.' if self.units == 'inch' else 'mm'
- return '<Tool %d: %0.3f%s dia.>' % (self.number, self.diameter, unit)
+ return '<ExcellonTool %d: %0.3f%s dia.>' % (self.number, self.diameter, unit)
class ExcellonParser(object):
+ """ Excellon File Parser
+ """
def __init__(self, ctx=None):
self.ctx = ctx
self.notation = 'absolute'
@@ -115,13 +177,11 @@ class ExcellonParser(object): with open(filename, 'r') as f:
for line in f:
self._parse(line)
- settings = {'notation': self.notation, 'units': self.units,
- 'zero_suppression': self.zero_suppression,
- 'format': self.format}
- return ExcellonFile(self.tools, self.hits, settings, filename)
+ return ExcellonFile(self.tools, self.hits, self._settings(), filename)
def dump(self, filename):
- self.ctx.dump(filename)
+ if self.ctx is not None:
+ self.ctx.dump(filename)
def _parse(self, line):
if 'M48' in line:
@@ -159,7 +219,7 @@ class ExcellonParser(object): # tool definition
if line[0] == 'T' and self.state == 'HEADER':
- tool = Tool.from_line(line, self._settings())
+ tool = ExcellonTool.from_line(line, self._settings())
self.tools[tool.number] = tool
elif line[0] == 'T' and self.state != 'HEADER':
@@ -187,13 +247,16 @@ class ExcellonParser(object): self.pos[1] += y
if self.state == 'DRILL':
self.hits.append((self.active_tool, self.pos))
+ self.active_tool._hit()
if self.ctx is not None:
self.ctx.drill(self.pos[0], self.pos[1],
self.active_tool.diameter)
def _settings(self):
- return {'units': self.units, 'zero_suppression': self.zero_suppression,
- 'format': self.format}
+ return FileSettings(units=self.units, format=self.format,
+ zero_suppression=self.zero_suppression,
+ notation=self.notation)
+
def pairwise(iterator):
diff --git a/gerber/gerber.py b/gerber/gerber.py index 949037b..eb5821c 100644 --- a/gerber/gerber.py +++ b/gerber/gerber.py @@ -27,6 +27,9 @@ This module provides an RS-274-X class and parser import re import json from .statements import * +from .cnc import CncFile, FileSettings + + def read(filename): @@ -35,7 +38,7 @@ def read(filename): return GerberParser().parse(filename) -class GerberFile(object): +class GerberFile(CncFile): """ A class representing a single gerber file The GerberFile class represents a single gerber file. @@ -68,9 +71,8 @@ class GerberFile(object): """ def __init__(self, statements, settings, filename=None): - self.filename = filename + super(GerberFile, self).__init__(settings, filename) self.statements = statements - self.settings = settings @property def comments(self): @@ -90,7 +92,8 @@ class GerberFile(object): def bounds(self): xbounds = [0.0, 0.0] ybounds = [0.0, 0.0] - for stmt in [stmt for stmt in self.statements if isinstance(stmt, CoordStmt)]: + 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]: @@ -169,7 +172,7 @@ class GerberParser(object): EOF_STMT = re.compile(r"(?P<eof>M02)\*") def __init__(self): - self.settings = {} + self.settings = FileSettings() self.statements = [] def parse(self, filename): @@ -240,13 +243,13 @@ class GerberParser(object): if param: if param["param"] == "FS": stmt = FSParamStmt.from_dict(param) - self.settings = {'zero_suppression': stmt.zero_suppression, - 'format': stmt.format, - 'notation': stmt.notation} + self.settings.zero_suppression = stmt.zero_suppression + self.settings.format = stmt.format + self.settings.notation = stmt.notation yield stmt elif param["param"] == "MO": stmt = MOParamStmt.from_dict(param) - self.settings['units'] = stmt.mode + self.settings.units = stmt.mode yield stmt elif param["param"] == "IP": yield IPParamStmt.from_dict(param) |