summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaulo Henrique Silva <ph.silva@gmail.com>2015-01-14 14:33:00 -0200
committerPaulo Henrique Silva <ph.silva@gmail.com>2015-01-14 14:33:00 -0200
commit137c73f3e42281de67bde8f1c0b16938f5b8aeeb (patch)
tree1c3140276d1f1b0cd16aec36d89cac183bf059d6
parentac89a3c36505bebff68305eb8e315482cba860fd (diff)
downloadgerbonara-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.
-rwxr-xr-xgerber/excellon.py51
-rw-r--r--gerber/excellon_statements.py114
-rw-r--r--gerber/tests/test_excellon_statements.py35
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():