diff options
-rw-r--r-- | gerber/__main__.py | 13 | ||||
-rwxr-xr-x | gerber/excellon.py | 62 | ||||
-rw-r--r-- | gerber/gerber.py | 2 | ||||
-rw-r--r-- | gerber/gerber_statements.py | 34 | ||||
-rw-r--r-- | gerber/tests/test_excellon_statements.py | 48 | ||||
-rw-r--r-- | gerber/tests/test_utils.py | 81 | ||||
-rw-r--r-- | gerber/utils.py | 2 |
7 files changed, 157 insertions, 85 deletions
diff --git a/gerber/__main__.py b/gerber/__main__.py index 31b70f8..26f36e1 100644 --- a/gerber/__main__.py +++ b/gerber/__main__.py @@ -16,21 +16,20 @@ # the License. if __name__ == '__main__': - from .gerber import GerberFile - from .excellon import ExcellonParser + import gerber + import excellon from .render import GerberSvgContext #import sys # - #if len(sys.argv) < 2: + #if len(sys.argv) < 2:` # print >> sys.stderr, "Usage: python -m gerber <filename> <filename>..." # sys.exit(1) # ##for filename in sys.argv[1]: ## print "parsing %s" % filename ctx = GerberSvgContext() - g = GerberFile.read('SCB.GTL') + g = gerber.read('examples/test.gtl') g.render('test.svg', ctx) - p = ExcellonParser(ctx) - p.parse('ncdrill.txt') - p.dump('testwithdrill.svg') + p = excellon.read('ncdrill.txt') + p.render('testwithdrill.svg', ctx) diff --git a/gerber/excellon.py b/gerber/excellon.py index 9a5ef22..66b9ea2 100755 --- a/gerber/excellon.py +++ b/gerber/excellon.py @@ -21,9 +21,9 @@ Excellon module This module provides Excellon file classes and parsing utilities
"""
-import re
+
+
from .excellon_statements import *
-from .utils import parse_gerber_value
from .cnc import CncFile, FileSettings
@@ -70,15 +70,18 @@ class ExcellonFile(CncFile): def render(self, filename, ctx):
""" Generate image of file
"""
+ count = 0
for tool, pos in self.hits:
ctx.drill(pos[0], pos[1], tool.diameter)
+ count += 1
+ print('Drilled %d hits' % count)
ctx.dump(filename)
def write(self, filename):
with open(filename, 'w') as f:
for statement in self.statements:
f.write(statement.to_excellon() + '\n')
-
+
class ExcellonParser(object):
""" Excellon File Parser
@@ -95,27 +98,21 @@ class ExcellonParser(object): self.hits = []
self.active_tool = None
self.pos = [0., 0.]
- if ctx is not None:
- self.ctx.set_coord_format(zero_suppression='trailing',
- format=(2, 5), notation='absolute')
def parse(self, filename):
with open(filename, 'r') as f:
for line in f:
self._parse(line)
- return ExcellonFile(self.statements, self.tools, self.hits, self._settings(), filename)
-
- def dump(self, filename):
- if self.ctx is not None:
- self.ctx.dump(filename)
+ return ExcellonFile(self.statements, self.tools, self.hits,
+ self._settings(), filename)
def _parse(self, line):
+ line = line.strip()
zs = self._settings()['zero_suppression']
fmt = self._settings()['format']
-
if line[0] == ';':
self.statements.append(CommentStmt.from_excellon(line))
-
+
elif line[:3] == 'M48':
self.statements.append(HeaderBeginStmt())
self.state = 'HEADER'
@@ -130,29 +127,41 @@ class ExcellonParser(object): if self.state == 'HEADER':
self.state = 'DRILL'
+ elif line[:3] == 'M30':
+ stmt = EndOfProgramStmt.from_excellon(line)
+ self.statements.append(stmt)
+
elif line[:3] == 'G00':
self.state = 'ROUT'
elif line[:3] == 'G05':
self.state = 'DRILL'
-
- elif ('INCH' in line or 'METRIC' in line) and ('LZ' in line or 'TZ' in line):
+
+ elif (('INCH' in line or 'METRIC' in line) and
+ ('LZ' in line or 'TZ' in line)):
stmt = UnitStmt.from_excellon(line)
self.units = stmt.units
self.zero_suppression = stmt.zero_suppression
self.statements.append(stmt)
-
+
elif line[:3] == 'M71' or line [:3] == 'M72':
stmt = MeasuringModeStmt.from_excellon(line)
self.units = stmt.units
self.statements.append(stmt)
-
+
elif line[:3] == 'ICI':
stmt = IncrementalModeStmt.from_excellon(line)
self.notation = 'incremental' if stmt.mode == 'on' else 'absolute'
self.statements.append(stmt)
- # tool definition
+ elif line[:3] == 'VER':
+ stmt = VersionStmt.from_excellon(line)
+ self.statements.append(stmt)
+
+ elif line[:4] == 'FMAT':
+ stmt = FormatStmt.from_excellon(line)
+ self.statements.append(stmt)
+
elif line[0] == 'T' and self.state == 'HEADER':
tool = ExcellonTool.from_excellon(line, self._settings())
self.tools[tool.number] = tool
@@ -161,7 +170,6 @@ class ExcellonParser(object): elif line[0] == 'T' and self.state != 'HEADER':
stmt = ToolSelectionStmt.from_excellon(line)
self.active_tool = self.tools[stmt.tool]
- #self.active_tool = self.tools[int(line.strip().split('T')[1])]
self.statements.append(stmt)
elif line[0] in ['X', 'Y']:
@@ -169,15 +177,6 @@ class ExcellonParser(object): x = stmt.x
y = stmt.y
self.statements.append(stmt)
- #x = None
- #y = None
- #if line[0] == 'X':
- # splitline = line.strip('X').split('Y')
- # x = parse_gerber_value(splitline[0].strip(), fmt, zs)
- # if len(splitline) == 2:
- # y = parse_gerber_value(splitline[1].strip(), fmt, zs)
- #else:
- # y = parse_gerber_value(line.strip(' Y'), fmt, zs)
if self.notation == 'absolute':
if x is not None:
self.pos[0] = x
@@ -189,11 +188,8 @@ class ExcellonParser(object): if y is not None:
self.pos[1] += y
if self.state == 'DRILL':
- self.hits.append((self.active_tool, self.pos))
+ self.hits.append((self.active_tool, tuple(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)
else:
self.statements.append(UnknownStmt.from_excellon(line))
@@ -201,7 +197,7 @@ class ExcellonParser(object): return FileSettings(units=self.units, format=self.format,
zero_suppression=self.zero_suppression,
notation=self.notation)
-
+
if __name__ == '__main__':
p = ExcellonParser()
diff --git a/gerber/gerber.py b/gerber/gerber.py index 0278b0d..220405c 100644 --- a/gerber/gerber.py +++ b/gerber/gerber.py @@ -256,7 +256,7 @@ class GerberParser(object): elif param["param"] == "IN": yield INParamStmt.from_dict(param) elif param["param"] == "LN": - yield LNParamStmtfrom_dict(param) + yield LNParamStmt.from_dict(param) else: yield UnknownStmt(line) did_something = True diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py index 5a9d046..2f58a37 100644 --- a/gerber/gerber_statements.py +++ b/gerber/gerber_statements.py @@ -16,8 +16,8 @@ __all__ = ['FSParamStmt', 'MOParamStmt', 'IPParamStmt', 'OFParamStmt', class Statement(object): - def __init__(self, type): - self.type = type + def __init__(self, stype): + self.type = stype def __str__(self): s = "<{0} ".format(self.__class__.__name__) @@ -47,8 +47,8 @@ class FSParamStmt(ParamStmt): zeros = 'leading' if stmt_dict.get('zero') == 'L' else 'trailing' notation = 'absolute' if stmt_dict.get('notation') == 'A' else 'incremental' x = map(int, stmt_dict.get('x').strip()) - format = (x[0], x[1]) - return cls(param, zeros, notation, format) + fmt = (x[0], x[1]) + return cls(param, zeros, notation, fmt) def __init__(self, param, zero_suppression='leading', notation='absolute', format=(2, 4)): @@ -88,9 +88,9 @@ class FSParamStmt(ParamStmt): def to_gerber(self): zero_suppression = 'L' if self.zero_suppression == 'leading' else 'T' notation = 'A' if self.notation == 'absolute' else 'I' - format = ''.join(map(str, self.format)) + fmt = ''.join(map(str, self.format)) return '%FS{0}{1}X{2}Y{3}*%'.format(zero_suppression, notation, - format, format) + fmt, fmt) def __str__(self): return ('<Format Spec: %d:%d %s zero suppression %s notation>' % @@ -588,6 +588,28 @@ class EofStmt(Statement): def __str__(self): return '<EOF Statement>' +class QuadrantModeStmt(Statement): + + @classmethod + def from_gerber(cls, line): + line = line.strip() + if 'G74' not in line and 'G75' not in line: + raise ValueError('%s is not a valid quadrant mode statement' + % line) + return (cls('single-quadrant') if line[:3] == 'G74' + else cls('multi-quadrant')) + + def __init__(self, mode): + super(QuadrantModeStmt, self).__init__('Quadrant Mode') + mode = mode.lower + if mode not in ['single-quadrant', 'multi-quadrant']: + raise ValueError('Quadrant mode must be "single-quadrant" \ + or "multi-quadrant"') + self.mode = mode + + def to_gerber(self): + return 'G74*' if self.mode == 'single-quadrant' else 'G75*' + class UnknownStmt(Statement): """ Unknown Statement diff --git a/gerber/tests/test_excellon_statements.py b/gerber/tests/test_excellon_statements.py index 4fa2b35..5e5e8dc 100644 --- a/gerber/tests/test_excellon_statements.py +++ b/gerber/tests/test_excellon_statements.py @@ -3,7 +3,7 @@ # Author: Hamilton Kibbe <ham@hamiltonkib.be> -from .tests import * +from .tests import assert_equal, assert_raises from ..excellon_statements import * @@ -65,6 +65,8 @@ def test_toolselection_dump(): def test_coordinatestmt_factory(): + """ Test CoordinateStmt factory method + """ line = 'X0278207Y0065293' stmt = CoordinateStmt.from_excellon(line) assert_equal(stmt.x, 2.78207) @@ -80,6 +82,8 @@ def test_coordinatestmt_factory(): def test_coordinatestmt_dump(): + """ Test CoordinateStmt to_excellon() + """ lines = ['X0278207Y0065293', 'X0243795', 'Y0082528', 'Y0086028', 'X0251295Y0081528', 'X02525Y0078', 'X0255Y00575', 'Y0052', 'X02675', 'Y00575', 'X02425', 'Y0052', 'X023', ] @@ -89,6 +93,8 @@ def test_coordinatestmt_dump(): def test_commentstmt_factory(): + """ Test CommentStmt factory method + """ line = ';Layer_Color=9474304' stmt = CommentStmt.from_excellon(line) assert_equal(stmt.comment, line[1:]) @@ -103,6 +109,8 @@ def test_commentstmt_factory(): def test_commentstmt_dump(): + """ Test CommentStmt to_excellon() + """ lines = [';Layer_Color=9474304', ';FILE_FORMAT=2:5', ';TYPE=PLATED', ] for line in lines: stmt = CommentStmt.from_excellon(line) @@ -110,6 +118,8 @@ def test_commentstmt_dump(): def test_unitstmt_factory(): + """ Test UnitStmt factory method + """ line = 'INCH,LZ' stmt = UnitStmt.from_excellon(line) assert_equal(stmt.units, 'inch') @@ -122,6 +132,8 @@ def test_unitstmt_factory(): def test_unitstmt_dump(): + """ Test UnitStmt to_excellon() + """ lines = ['INCH,LZ', 'INCH,TZ', 'METRIC,LZ', 'METRIC,TZ', ] for line in lines: stmt = UnitStmt.from_excellon(line) @@ -129,6 +141,8 @@ def test_unitstmt_dump(): def test_incrementalmode_factory(): + """ Test IncrementalModeStmt factory method + """ line = 'ICI,ON' stmt = IncrementalModeStmt.from_excellon(line) assert_equal(stmt.mode, 'on') @@ -139,6 +153,8 @@ def test_incrementalmode_factory(): def test_incrementalmode_dump(): + """ Test IncrementalModeStmt to_excellon() + """ lines = ['ICI,ON', 'ICI,OFF', ] for line in lines: stmt = IncrementalModeStmt.from_excellon(line) @@ -146,10 +162,14 @@ def test_incrementalmode_dump(): def test_incrementalmode_validation(): + """ Test IncrementalModeStmt input validation + """ assert_raises(ValueError, IncrementalModeStmt, 'OFF-ISH') def test_versionstmt_factory(): + """ Test VersionStmt factory method + """ line = 'VER,1' stmt = VersionStmt.from_excellon(line) assert_equal(stmt.version, 1) @@ -160,16 +180,22 @@ def test_versionstmt_factory(): def test_versionstmt_dump(): + """ Test VersionStmt to_excellon() + """ lines = ['VER,1', 'VER,2', ] for line in lines: stmt = VersionStmt.from_excellon(line) assert_equal(stmt.to_excellon(), line) def test_versionstmt_validation(): + """ Test VersionStmt input validation + """ assert_raises(ValueError, VersionStmt, 3) def test_formatstmt_factory(): + """ Test FormatStmt factory method + """ line = 'FMAT,1' stmt = FormatStmt.from_excellon(line) assert_equal(stmt.format, 1) @@ -180,6 +206,8 @@ def test_formatstmt_factory(): def test_formatstmt_dump(): + """ Test FormatStmt to_excellon() + """ lines = ['FMAT,1', 'FMAT,2', ] for line in lines: stmt = FormatStmt.from_excellon(line) @@ -187,10 +215,14 @@ def test_formatstmt_dump(): def test_formatstmt_validation(): + """ Test FormatStmt input validation + """ assert_raises(ValueError, FormatStmt, 3) def test_linktoolstmt_factory(): + """ Test LinkToolStmt factory method + """ line = '1/2/3/4' stmt = LinkToolStmt.from_excellon(line) assert_equal(stmt.linked_tools, [1, 2, 3, 4]) @@ -201,13 +233,17 @@ def test_linktoolstmt_factory(): def test_linktoolstmt_dump(): + """ Test LinkToolStmt to_excellon() + """ lines = ['1/2/3/4', '5/6/7', ] for line in lines: stmt = LinkToolStmt.from_excellon(line) assert_equal(stmt.to_excellon(), line) -def test_measuringmodestmt_factory(): +def test_measmodestmt_factory(): + """ Test MeasuringModeStmt factory method + """ line = 'M72' stmt = MeasuringModeStmt.from_excellon(line) assert_equal(stmt.units, 'inch') @@ -217,13 +253,17 @@ def test_measuringmodestmt_factory(): assert_equal(stmt.units, 'metric') -def test_measuringmodestmt_dump(): +def test_measmodestmt_dump(): + """ Test MeasuringModeStmt to_excellon() + """ lines = ['M71', 'M72', ] for line in lines: stmt = MeasuringModeStmt.from_excellon(line) assert_equal(stmt.to_excellon(), line) -def test_measuringmodestmt_validation(): +def test_measmodestmt_validation(): + """ Test MeasuringModeStmt input validation + """ assert_raises(ValueError, MeasuringModeStmt.from_excellon, 'M70') assert_raises(ValueError, MeasuringModeStmt, 'millimeters') diff --git a/gerber/tests/test_utils.py b/gerber/tests/test_utils.py index 50e2403..001a32f 100644 --- a/gerber/tests/test_utils.py +++ b/gerber/tests/test_utils.py @@ -3,6 +3,7 @@ # Author: Hamilton Kibbe <ham@hamiltonkib.be> +from .tests import assert_equal from ..utils import decimal_string, parse_gerber_value, write_gerber_value @@ -10,59 +11,73 @@ def test_zero_suppression(): """ Test gerber value parser and writer handle zero suppression correctly. """ # Default format - format = (2, 5) - + fmt = (2, 5) + # Test leading zero suppression zero_suppression = 'leading' test_cases = [('1', 0.00001), ('10', 0.0001), ('100', 0.001), - ('1000', 0.01), ('10000', 0.1), ('100000', 1.0),('1000000', 10.0), - ('-1', -0.00001), ('-10', -0.0001), ('-100', -0.001), - ('-1000', -0.01), ('-10000', -0.1), ('-100000', -1.0),('-1000000', -10.0),] + ('1000', 0.01), ('10000', 0.1), ('100000', 1.0), + ('1000000', 10.0), ('-1', -0.00001), ('-10', -0.0001), + ('-100', -0.001), ('-1000', -0.01), ('-10000', -0.1), + ('-100000', -1.0), ('-1000000', -10.0), ] for string, value in test_cases: - assert(value == parse_gerber_value(string,format,zero_suppression)) - assert(string == write_gerber_value(value,format,zero_suppression)) - + assert(value == parse_gerber_value(string, fmt, zero_suppression)) + assert(string == write_gerber_value(value, fmt, zero_suppression)) + # Test trailing zero suppression zero_suppression = 'trailing' test_cases = [('1', 10.0), ('01', 1.0), ('001', 0.1), ('0001', 0.01), - ('00001', 0.001), ('000001', 0.0001), ('0000001', 0.00001), - ('-1', -10.0), ('-01', -1.0), ('-001', -0.1), ('-0001', -0.01), - ('-00001', -0.001), ('-000001', -0.0001), ('-0000001', -0.00001)] + ('00001', 0.001), ('000001', 0.0001), + ('0000001', 0.00001), ('-1', -10.0), ('-01', -1.0), + ('-001', -0.1), ('-0001', -0.01), ('-00001', -0.001), + ('-000001', -0.0001), ('-0000001', -0.00001)] for string, value in test_cases: - assert(value == parse_gerber_value(string,format,zero_suppression)) - assert(string == write_gerber_value(value,format,zero_suppression)) - + assert(value == parse_gerber_value(string, fmt, zero_suppression)) + assert(string == write_gerber_value(value, fmt, zero_suppression)) def test_format(): """ Test gerber value parser and writer handle format correctly """ zero_suppression = 'leading' - test_cases = [((2,7),'1',0.0000001), ((2,6),'1',0.000001), - ((2,5),'1',0.00001), ((2,4),'1',0.0001), ((2,3),'1',0.001), - ((2,2),'1',0.01), ((2,1),'1',0.1), ((2,7),'-1',-0.0000001), - ((2,6),'-1',-0.000001), ((2,5),'-1',-0.00001), ((2,4),'-1',-0.0001), - ((2,3),'-1',-0.001), ((2,2),'-1',-0.01), ((2,1),'-1',-0.1),] - for format, string, value in test_cases: - assert(value == parse_gerber_value(string,format,zero_suppression)) - assert(string == write_gerber_value(value,format,zero_suppression)) - + test_cases = [((2, 7), '1', 0.0000001), ((2, 6), '1', 0.000001), + ((2, 5), '1', 0.00001), ((2, 4), '1', 0.0001), + ((2, 3), '1', 0.001), ((2, 2), '1', 0.01), + ((2, 1), '1', 0.1), ((2, 7), '-1', -0.0000001), + ((2, 6), '-1', -0.000001), ((2, 5), '-1', -0.00001), + ((2, 4), '-1', -0.0001), ((2, 3), '-1', -0.001), + ((2, 2), '-1', -0.01), ((2, 1), '-1', -0.1), ] + for fmt, string, value in test_cases: + assert(value == parse_gerber_value(string, fmt, zero_suppression)) + assert(string == write_gerber_value(value, fmt, zero_suppression)) + zero_suppression = 'trailing' - test_cases = [((6, 5), '1' , 100000.0), ((5, 5), '1', 10000.0), - ((4, 5), '1', 1000.0), ((3, 5), '1', 100.0),((2, 5), '1', 10.0), - ((1, 5), '1', 1.0), ((6, 5), '-1' , -100000.0), - ((5, 5), '-1', -10000.0), ((4, 5), '-1', -1000.0), - ((3, 5), '-1', -100.0),((2, 5), '-1', -10.0), ((1, 5), '-1', -1.0),] - for format, string, value in test_cases: - assert(value == parse_gerber_value(string,format,zero_suppression)) - assert(string == write_gerber_value(value,format,zero_suppression)) + test_cases = [((6, 5), '1', 100000.0), ((5, 5), '1', 10000.0), + ((4, 5), '1', 1000.0), ((3, 5), '1', 100.0), + ((2, 5), '1', 10.0), ((1, 5), '1', 1.0), + ((6, 5), '-1', -100000.0), ((5, 5), '-1', -10000.0), + ((4, 5), '-1', -1000.0), ((3, 5), '-1', -100.0), + ((2, 5), '-1', -10.0), ((1, 5), '-1', -1.0), ] + for fmt, string, value in test_cases: + assert(value == parse_gerber_value(string, fmt, zero_suppression)) + assert(string == write_gerber_value(value, fmt, zero_suppression)) def test_decimal_truncation(): - """ Test decimal string truncates value to the correct precision + """ Test decimal_string truncates value to the correct precision """ value = 1.123456789 for x in range(10): result = decimal_string(value, precision=x) calculated = '1.' + ''.join(str(y) for y in range(1,x+1)) - assert(result == calculated)
\ No newline at end of file + assert(result == calculated) + + +def test_decimal_padding(): + """ Test decimal_string padding + """ + value = 1.123 + assert_equal(decimal_string(value, precision=3, padding=True), '1.123') + assert_equal(decimal_string(value, precision=4, padding=True), '1.1230') + assert_equal(decimal_string(value, precision=5, padding=True), '1.12300') + assert_equal(decimal_string(value, precision=6, padding=True), '1.123000') diff --git a/gerber/utils.py b/gerber/utils.py index 1721a7d..fce6369 100644 --- a/gerber/utils.py +++ b/gerber/utils.py @@ -113,7 +113,7 @@ def write_gerber_value(value, format=(2, 5), zero_suppression='trailing'): # Edge case...
if value == 0:
return '00'
-
+
# negative sign affects padding, so deal with it at the end...
negative = value < 0.0
if negative:
|