diff options
author | Paulo Henrique Silva <ph.silva@gmail.com> | 2015-01-14 14:33:00 -0200 |
---|---|---|
committer | Paulo Henrique Silva <ph.silva@gmail.com> | 2015-01-14 14:33:00 -0200 |
commit | 137c73f3e42281de67bde8f1c0b16938f5b8aeeb (patch) | |
tree | 1c3140276d1f1b0cd16aec36d89cac183bf059d6 /gerber | |
parent | ac89a3c36505bebff68305eb8e315482cba860fd (diff) | |
download | gerbonara-137c73f3e42281de67bde8f1c0b16938f5b8aeeb.tar.gz gerbonara-137c73f3e42281de67bde8f1c0b16938f5b8aeeb.tar.bz2 gerbonara-137c73f3e42281de67bde8f1c0b16938f5b8aeeb.zip |
Many additions to Excellon parsing/creation.
CAUTION: the original code used zero_suppression flags
in the opposite sense as Gerber functions. This
patch changes it to behave just like Gerber code.
* Add metric/inch conversion support
* Add settings context variable to to_gerber just like Gerber code.
* Add some missing Excellon values.
Tests are not entirely updated.
Diffstat (limited to 'gerber')
-rwxr-xr-x | gerber/excellon.py | 51 | ||||
-rw-r--r-- | gerber/excellon_statements.py | 114 | ||||
-rw-r--r-- | gerber/tests/test_excellon_statements.py | 35 |
3 files changed, 144 insertions, 56 deletions
diff --git a/gerber/excellon.py b/gerber/excellon.py index 9d09576..ee38367 100755 --- a/gerber/excellon.py +++ b/gerber/excellon.py @@ -2,7 +2,7 @@ # -*- 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
@@ -13,8 +13,8 @@ # 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. - +# limitations under the License.
+
"""
Excellon File module
====================
@@ -28,6 +28,7 @@ from .excellon_statements import * from .cam import CamFile, FileSettings
from .primitives import Drill
import math
+import re
def read(filename):
""" Read data from filename and return an ExcellonFile
@@ -42,10 +43,7 @@ def read(filename): An ExcellonFile created from the specified file.
"""
- detected_settings = detect_excellon_format(filename)
- settings = FileSettings(**detected_settings)
- zeros = ''
- return ExcellonParser(settings).parse(filename)
+ return ExcellonParser(None).parse(filename)
class ExcellonFile(CamFile):
@@ -104,7 +102,7 @@ class ExcellonFile(CamFile): def write(self, filename):
with open(filename, 'w') as f:
for statement in self.statements:
- f.write(statement.to_excellon() + '\n')
+ f.write(statement.to_excellon(self.settings) + '\n')
class ExcellonParser(object):
@@ -118,14 +116,14 @@ class ExcellonParser(object): def __init__(self, settings=None):
self.notation = 'absolute'
self.units = 'inch'
- self.zero_suppression = 'trailing'
- self.format = (2, 5)
+ self.zero_suppression = 'leading'
+ self.format = (2, 4)
self.state = 'INIT'
self.statements = []
self.tools = {}
self.hits = []
self.active_tool = None
- self.pos = [0., 0.] + self.pos = [0., 0.]
if settings is not None:
self.units = settings.units
self.zero_suppression = settings.zero_suppression
@@ -166,11 +164,19 @@ class ExcellonParser(object): self._settings(), filename)
def _parse(self, line):
- #line = line.strip()
- zs = self._settings().zero_suppression
- fmt = self._settings().format
+ # skip empty lines
+ if not line.strip():
+ return
+
if line[0] == ';':
- self.statements.append(CommentStmt.from_excellon(line))
+ comment_stmt = CommentStmt.from_excellon(line)
+ self.statements.append(comment_stmt)
+
+ # get format from altium comment
+ if "FILE_FORMAT" in comment_stmt.comment:
+ detected_format = tuple([int(x) for x in comment_stmt.comment.split('=')[1].split(":")])
+ if detected_format:
+ self.format = detected_format
elif line[:3] == 'M48':
self.statements.append(HeaderBeginStmt())
@@ -191,9 +197,11 @@ class ExcellonParser(object): self.statements.append(stmt)
elif line[:3] == 'G00':
+ self.statements.append(RouteModeStmt())
self.state = 'ROUT'
elif line[:3] == 'G05':
+ self.statements.append(DrillModeStmt())
self.state = 'DRILL'
elif (('INCH' in line or 'METRIC' in line) and
@@ -221,6 +229,9 @@ class ExcellonParser(object): stmt = FormatStmt.from_excellon(line)
self.statements.append(stmt)
+ elif line[:4] == 'G90':
+ self.statements.append(AbsoluteModeStmt())
+
elif line[0] == 'T' and self.state == 'HEADER':
tool = ExcellonTool.from_excellon(line, self._settings())
self.tools[tool.number] = tool
@@ -228,14 +239,16 @@ class ExcellonParser(object): elif line[0] == 'T' and self.state != 'HEADER':
stmt = ToolSelectionStmt.from_excellon(line)
- self.active_tool = self.tools[stmt.tool]
+ # T0 is used as END marker, just ignore
+ if stmt.tool != 0:
+ self.active_tool = self.tools[stmt.tool]
self.statements.append(stmt)
elif line[0] in ['X', 'Y']:
- stmt = CoordinateStmt.from_excellon(line, fmt, zs)
+ stmt = CoordinateStmt.from_excellon(line, self._settings())
x = stmt.x
y = stmt.y
- self.statements.append(stmt) + self.statements.append(stmt)
if self.notation == 'absolute':
if x is not None:
self.pos[0] = x
@@ -246,7 +259,7 @@ class ExcellonParser(object): self.pos[0] += x
if y is not None:
self.pos[1] += y
- if self.state == 'DRILL': + if self.state == 'DRILL':
self.hits.append((self.active_tool, tuple(self.pos)))
self.active_tool._hit()
else:
diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index feeda44..c4f4015 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -28,7 +28,8 @@ __all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt', 'CommentStmt', 'HeaderBeginStmt', 'HeaderEndStmt', 'RewindStopStmt', 'EndOfProgramStmt', 'UnitStmt', 'IncrementalModeStmt', 'VersionStmt', 'FormatStmt', 'LinkToolStmt', - 'MeasuringModeStmt', 'UnknownStmt', + 'MeasuringModeStmt', 'RouteModeStmt', 'DrillModeStmt', 'AbsoluteModeStmt', + 'UnknownStmt', ] @@ -39,7 +40,7 @@ class ExcellonStatement(object): def from_excellon(cls, line): pass - def to_excellon(self): + def to_excellon(self, settings=None): pass @@ -156,10 +157,10 @@ class ExcellonTool(ExcellonStatement): self.depth_offset = kwargs.get('depth_offset') self.hit_count = 0 - def to_excellon(self): + def to_excellon(self, settings=None): fmt = self.settings.format zs = self.settings.format - stmt = 'T%d' % self.number + stmt = 'T%02d' % self.number if self.retract_rate is not None: stmt += 'B%s' % write_gerber_value(self.retract_rate, fmt, zs) if self.feed_rate is not None: @@ -177,12 +178,20 @@ class ExcellonTool(ExcellonStatement): stmt += 'Z%s' % write_gerber_value(self.depth_offset, fmt, zs) return stmt + def to_inch(self): + if self.diameter is not None: + self.diameter = self.diameter / 25.4 + + def to_metric(self): + if self.diameter is not None: + self.diameter = self.diameter * 25.4 + def _hit(self): self.hit_count += 1 def __repr__(self): unit = 'in.' if self.settings.units == 'inch' else 'mm' - return '<ExcellonTool %d: %0.3f%s dia.>' % (self.number, self.diameter, unit) + return '<ExcellonTool %02d: %0.3f%s dia.>' % (self.number, self.diameter, unit) class ToolSelectionStmt(ExcellonStatement): @@ -215,7 +224,7 @@ class ToolSelectionStmt(ExcellonStatement): self.tool = tool self.compensation_index = compensation_index - def to_excellon(self): + def to_excellon(self, settings=None): stmt = 'T%02d' % self.tool if self.compensation_index is not None: stmt += '%02d' % self.compensation_index @@ -225,33 +234,51 @@ class ToolSelectionStmt(ExcellonStatement): class CoordinateStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line, nformat=(2, 5), zero_suppression='trailing'): + def from_excellon(cls, line, settings): x_coord = None y_coord = None if line[0] == 'X': splitline = line.strip('X').split('Y') - x_coord = parse_gerber_value(splitline[0], nformat, - zero_suppression) + x_coord = parse_gerber_value(splitline[0], settings.format, settings.zero_suppression) if len(splitline) == 2: - y_coord = parse_gerber_value(splitline[1], nformat, - zero_suppression) + y_coord = parse_gerber_value(splitline[1], settings.format, settings.zero_suppression) else: - y_coord = parse_gerber_value(line.strip(' Y'), nformat, - zero_suppression) + y_coord = parse_gerber_value(line.strip(' Y'), settings.format, settings.zero_suppression) return cls(x_coord, y_coord) def __init__(self, x=None, y=None): self.x = x self.y = y - def to_excellon(self): + def to_excellon(self, settings): stmt = '' if self.x is not None: - stmt += 'X%s' % write_gerber_value(self.x) + stmt += 'X%s' % write_gerber_value(self.x, settings.format, settings.zero_suppression) if self.y is not None: - stmt += 'Y%s' % write_gerber_value(self.y) + stmt += 'Y%s' % write_gerber_value(self.y, settings.format, settings.zero_suppression) return stmt + def to_inch(self): + if self.x is not None: + self.x = self.x / 25.4 + if self.y is not None: + self.y = self.y / 25.4 + + def to_metric(self): + if self.x is not None: + self.x = self.x * 25.4 + if self.y is not None: + self.y = self.y * 25.4 + + def __str__(self): + coord_str = '' + if self.x is not None: + coord_str += 'X: %f ' % self.x + if self.y is not None: + coord_str += 'Y: %f ' % self.y + + return '<Coordinate Statement: %s>' % coord_str + class CommentStmt(ExcellonStatement): @@ -262,7 +289,7 @@ class CommentStmt(ExcellonStatement): def __init__(self, comment): self.comment = comment - def to_excellon(self): + def to_excellon(self, settings=None): return ';%s' % self.comment @@ -271,7 +298,7 @@ class HeaderBeginStmt(ExcellonStatement): def __init__(self): pass - def to_excellon(self): + def to_excellon(self, settings=None): return 'M48' @@ -280,7 +307,7 @@ class HeaderEndStmt(ExcellonStatement): def __init__(self): pass - def to_excellon(self): + def to_excellon(self, settings=None): return 'M95' @@ -289,17 +316,21 @@ class RewindStopStmt(ExcellonStatement): def __init__(self): pass - def to_excellon(self): + def to_excellon(self, settings=None): return '%' class EndOfProgramStmt(ExcellonStatement): + @classmethod + def from_excellon(cls, line): + return cls() + def __init__(self, x=None, y=None): self.x = x self.y = y - def to_excellon(self): + def to_excellon(self, settings=None): stmt = 'M30' if self.x is not None: stmt += 'X%s' % write_gerber_value(self.x) @@ -320,7 +351,7 @@ class UnitStmt(ExcellonStatement): self.units = units.lower() self.zero_suppression = zero_suppression - def to_excellon(self): + def to_excellon(self, settings=None): stmt = '%s,%s' % ('INCH' if self.units == 'inch' else 'METRIC', 'LZ' if self.zero_suppression == 'trailing' else 'TZ') @@ -338,7 +369,7 @@ class IncrementalModeStmt(ExcellonStatement): raise ValueError('Mode may be "on" or "off"') self.mode = mode - def to_excellon(self): + def to_excellon(self, settings=None): return 'ICI,%s' % ('OFF' if self.mode == 'off' else 'ON') @@ -355,7 +386,7 @@ class VersionStmt(ExcellonStatement): raise ValueError('Valid versions are 1 or 2') self.version = version - def to_excellon(self): + def to_excellon(self, settings=None): return 'VER,%d' % self.version @@ -372,7 +403,7 @@ class FormatStmt(ExcellonStatement): raise ValueError('Valid formats are 1 or 2') self.format = format - def to_excellon(self): + def to_excellon(self, settings=None): return 'FMAT,%d' % self.format @@ -386,7 +417,7 @@ class LinkToolStmt(ExcellonStatement): def __init__(self, linked_tools): self.linked_tools = [int(x) for x in linked_tools] - def to_excellon(self): + def to_excellon(self, settings=None): return '/'.join([str(x) for x in self.linked_tools]) @@ -404,10 +435,37 @@ class MeasuringModeStmt(ExcellonStatement): raise ValueError('units must be "inch" or "metric"') self.units = units - def to_excellon(self): + def to_excellon(self, settings=None): return 'M72' if self.units == 'inch' else 'M71' +class RouteModeStmt(ExcellonStatement): + + def __init__(self): + pass + + def to_excellon(self, settings=None): + return 'G00' + + +class DrillModeStmt(ExcellonStatement): + + def __init__(self): + pass + + def to_excellon(self, settings=None): + return 'G05' + + +class AbsoluteModeStmt(ExcellonStatement): + + def __init__(self): + pass + + def to_excellon(self, settings=None): + return 'G90' + + class UnknownStmt(ExcellonStatement): @classmethod @@ -417,7 +475,7 @@ class UnknownStmt(ExcellonStatement): def __init__(self, stmt): self.stmt = stmt - def to_excellon(self): + def to_excellon(self, settings=None): return self.stmt diff --git a/gerber/tests/test_excellon_statements.py b/gerber/tests/test_excellon_statements.py index 0e1efa6..13733f8 100644 --- a/gerber/tests/test_excellon_statements.py +++ b/gerber/tests/test_excellon_statements.py @@ -68,18 +68,31 @@ def test_toolselection_dump(): def test_coordinatestmt_factory(): """ Test CoordinateStmt factory method """ + settings = FileSettings(format=(2, 5), zero_suppression='trailing', + units='inch', notation='absolute') + line = 'X0278207Y0065293' - stmt = CoordinateStmt.from_excellon(line) + stmt = CoordinateStmt.from_excellon(line, settings) assert_equal(stmt.x, 2.78207) assert_equal(stmt.y, 0.65293) - line = 'X02945' - stmt = CoordinateStmt.from_excellon(line) - assert_equal(stmt.x, 2.945) + # line = 'X02945' + # stmt = CoordinateStmt.from_excellon(line) + # assert_equal(stmt.x, 2.945) + + # line = 'Y00575' + # stmt = CoordinateStmt.from_excellon(line) + # assert_equal(stmt.y, 0.575) + + settings = FileSettings(format=(2, 4), zero_suppression='leading', + units='inch', notation='absolute') + + line = 'X9660Y4639' + stmt = CoordinateStmt.from_excellon(line, settings) + assert_equal(stmt.x, 0.9660) + assert_equal(stmt.y, 0.4639) + assert_equal(stmt.to_excellon(settings), "X9660Y4639") - line = 'Y00575' - stmt = CoordinateStmt.from_excellon(line) - assert_equal(stmt.y, 0.575) def test_coordinatestmt_dump(): @@ -88,9 +101,13 @@ def test_coordinatestmt_dump(): lines = ['X0278207Y0065293', 'X0243795', 'Y0082528', 'Y0086028', 'X0251295Y0081528', 'X02525Y0078', 'X0255Y00575', 'Y0052', 'X02675', 'Y00575', 'X02425', 'Y0052', 'X023', ] + + settings = FileSettings(format=(2, 4), zero_suppression='leading', + units='inch', notation='absolute') + for line in lines: - stmt = CoordinateStmt.from_excellon(line) - assert_equal(stmt.to_excellon(), line) + stmt = CoordinateStmt.from_excellon(line, settings) + assert_equal(stmt.to_excellon(settings), line) def test_commentstmt_factory(): |