summaryrefslogtreecommitdiff
path: root/gerber
diff options
context:
space:
mode:
Diffstat (limited to 'gerber')
-rw-r--r--gerber/__main__.py13
-rwxr-xr-xgerber/excellon.py62
-rw-r--r--gerber/gerber.py2
-rw-r--r--gerber/gerber_statements.py34
-rw-r--r--gerber/tests/test_excellon_statements.py48
-rw-r--r--gerber/tests/test_utils.py81
-rw-r--r--gerber/utils.py2
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: