summaryrefslogtreecommitdiff
path: root/gerber
diff options
context:
space:
mode:
authorHamilton Kibbe <ham@hamiltonkib.be>2014-10-06 18:28:32 -0400
committerHamilton Kibbe <ham@hamiltonkib.be>2014-10-06 18:28:32 -0400
commit22a6f87e94c1192b277a1353aefc7c0316f41f90 (patch)
treed44170d9bae5cb116427dbb16fef326383985dcc /gerber
parent08253b40f6f677c4edaeb7108177846d8f0d8703 (diff)
downloadgerbonara-22a6f87e94c1192b277a1353aefc7c0316f41f90.tar.gz
gerbonara-22a6f87e94c1192b277a1353aefc7c0316f41f90.tar.bz2
gerbonara-22a6f87e94c1192b277a1353aefc7c0316f41f90.zip
add excellon file write
Diffstat (limited to 'gerber')
-rwxr-xr-xgerber/excellon.py23
-rw-r--r--gerber/excellon_statements.py54
-rw-r--r--gerber/utils.py385
3 files changed, 239 insertions, 223 deletions
diff --git a/gerber/excellon.py b/gerber/excellon.py
index 45a8e4b..072bc31 100755
--- a/gerber/excellon.py
+++ b/gerber/excellon.py
@@ -74,6 +74,11 @@ class ExcellonFile(CncFile):
ctx.drill(pos[0], pos[1], tool.diameter)
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
@@ -155,9 +160,9 @@ 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[stmt.tool]
#self.active_tool = self.tools[int(line.strip().split('T')[1])]
- self.statements.append(statement)
+ self.statements.append(stmt)
elif line[0] in ['X', 'Y']:
stmt = CoordinateStmt.from_excellon(line, fmt, zs)
@@ -197,18 +202,8 @@ class ExcellonParser(object):
return FileSettings(units=self.units, format=self.format,
zero_suppression=self.zero_suppression,
notation=self.notation)
-
-
-def pairwise(iterator):
- """ Iterate over list taking two elements at a time.
-
- e.g. [1, 2, 3, 4, 5, 6] ==> [(1, 2), (3, 4), (5, 6)]
- """
- itr = iter(iterator)
- while True:
- yield tuple([itr.next() for i in range(2)])
if __name__ == '__main__':
- p = parser()
- p.parse('examples/ncdrill.txt')
+ p = ExcellonParser()
+ parsed = p.parse('examples/ncdrill.txt')
diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py
index faadd20..09b72c1 100644
--- a/gerber/excellon_statements.py
+++ b/gerber/excellon_statements.py
@@ -15,10 +15,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from .utils import write_gerber_value
+from .utils import parse_gerber_value, write_gerber_value, decimal_string
+import re
-__all__ = ['ExcellonTool', 'ToolSelectionStatment', 'CoordinateStmt',
+__all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt',
'CommentStmt', 'HeaderBeginStmt', 'HeaderEndStmt',
'RewindStopStmt', 'EndOfProgramStmt', 'UnitStmt',
'IncrementalModeStmt', 'VersionStmt', 'FormatStmt', 'LinkToolStmt',
@@ -138,20 +139,20 @@ class ExcellonTool(ExcellonStatement):
fmt = self.settings['format']
zs = self.settings['zero_suppression']
stmt = 'T%d' % self.number
- if self.retract_rate:
+ if self.retract_rate is not None:
stmt += 'B%s' % write_gerber_value(self.retract_rate, fmt, zs)
- if self.diameter:
- stmt += 'C%s' % write_gerber_value(self.diameter, fmt, zs)
- if self.feed_rate:
+ if self.feed_rate is not None:
stmt += 'F%s' % write_gerber_value(self.feed_rate, fmt, zs)
- if self.max_hit_count:
+ if self.max_hit_count is not None:
stmt += 'H%s' % write_gerber_value(self.max_hit_count, fmt, zs)
- if self.rpm:
+ if self.rpm is not None:
if self.rpm < 100000.:
stmt += 'S%s' % write_gerber_value(self.rpm / 1000., fmt, zs)
else:
stmt += 'S%g' % self.rpm / 1000.
- if self.depth_offset:
+ if self.diameter is not None:
+ stmt += 'C%s' % decimal_string(self.diameter, 5, True)
+ if self.depth_offset is not None:
stmt += 'Z%s' % write_gerber_value(self.depth_offset, fmt, zs)
return stmt
@@ -163,7 +164,7 @@ class ExcellonTool(ExcellonStatement):
return '<ExcellonTool %d: %0.3f%s dia.>' % (self.number, self.diameter, unit)
-class ToolSelectionStatment(ExcellonStatement):
+class ToolSelectionStmt(ExcellonStatement):
@classmethod
def from_excellon(cls, line):
@@ -189,6 +190,7 @@ class ToolSelectionStatment(ExcellonStatement):
class CoordinateStmt(ExcellonStatement):
+ @classmethod
def from_excellon(cls, line, format=(2, 5), zero_suppression='trailing'):
x = None
y = None
@@ -208,22 +210,23 @@ class CoordinateStmt(ExcellonStatement):
def to_excellon(self):
stmt = ''
if self.x is not None:
- stmt.append('X%s' % write_gerber_value(self.x))
+ stmt += 'X%s' % write_gerber_value(self.x)
if self.y is not None:
- stmt.append('Y%s' % write_gerber_value(self.y))
+ stmt += 'Y%s' % write_gerber_value(self.y)
return stmt
class CommentStmt(ExcellonStatement):
- def from_excellon(self, line):
+ @classmethod
+ def from_excellon(cls, line):
return cls(line.strip().lstrip(';'))
def __init__(self, comment):
self.comment = comment
def to_excellon(self):
- return ';%s' % comment
+ return ';%s' % self.comment
class HeaderBeginStmt(ExcellonStatement):
@@ -265,7 +268,7 @@ class EndOfProgramStmt(ExcellonStatement):
stmt += 'X%s' % write_gerber_value(self.x)
if self.y is not None:
stmt += 'Y%s' % write_gerber_value(self.y)
-
+ return stmt
class UnitStmt(ExcellonStatement):
@@ -281,8 +284,9 @@ class UnitStmt(ExcellonStatement):
def to_excellon(self):
stmt = '%s,%s' % ('INCH' if self.units == 'inch' else 'METRIC',
- 'LZ' if self.zero_suppression == 'trailing' else 'TZ')
-
+ 'LZ' if self.zero_suppression == 'trailing'
+ else 'TZ')
+ return stmt
class IncrementalModeStmt(ExcellonStatement):
@@ -292,7 +296,7 @@ class IncrementalModeStmt(ExcellonStatement):
def __init__(self, mode='off'):
if mode.lower() not in ['on', 'off']:
- raise ValueError('Mode may be "on" or "off")
+ raise ValueError('Mode may be "on" or "off"')
self.mode = 'off'
def to_excellon(self):
@@ -309,7 +313,7 @@ class VersionStmt(ExcellonStatement):
def __init__(self, version=1):
version = int(version)
if version not in [1, 2]:
- raise ValueError('Valid versions are 1 or 2'
+ raise ValueError('Valid versions are 1 or 2')
self.version = version
def to_excellon(self):
@@ -374,3 +378,15 @@ class UnknownStmt(ExcellonStatement):
def to_excellon(self):
return self.stmt
+
+
+
+
+def pairwise(iterator):
+ """ Iterate over list taking two elements at a time.
+
+ e.g. [1, 2, 3, 4, 5, 6] ==> [(1, 2), (3, 4), (5, 6)]
+ """
+ itr = iter(iterator)
+ while True:
+ yield tuple([itr.next() for i in range(2)]) \ No newline at end of file
diff --git a/gerber/utils.py b/gerber/utils.py
index 625a9e1..1721a7d 100644
--- a/gerber/utils.py
+++ b/gerber/utils.py
@@ -1,190 +1,195 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
-gerber.utils
-============
-**Gerber and Excellon file handling utilities**
-
-This module provides utility functions for working with Gerber and Excellon
-files.
-"""
-
-# Author: Hamilton Kibbe <ham@hamiltonkib.be>
-# License:
-
-
-def parse_gerber_value(value, format=(2, 5), zero_suppression='trailing'):
- """ Convert gerber/excellon formatted string to floating-point number
-
- .. note::
- Format and zero suppression are configurable. Note that the Excellon
- and Gerber formats use opposite terminology with respect to leading
- and trailing zeros. The Gerber format specifies which zeros are
- suppressed, while the Excellon format specifies which zeros are
- included. This function uses the Gerber-file convention, so an
- Excellon file in LZ (leading zeros) mode would use
- `zero_suppression='trailing'`
-
-
- Parameters
- ----------
- value : string
- A Gerber/Excellon-formatted string representing a numerical value.
-
- format : tuple (int,int)
- Gerber/Excellon precision format expressed as a tuple containing:
- (number of integer-part digits, number of decimal-part digits)
-
- zero_suppression : string
- Zero-suppression mode. May be 'leading' or 'trailing'
-
- Returns
- -------
- value : float
- The specified value as a floating-point number.
-
- """
- # Format precision
- integer_digits, decimal_digits = format
- MAX_DIGITS = integer_digits + decimal_digits
-
- # Absolute maximum number of digits supported. This will handle up to
- # 6:7 format, which is somewhat supported, even though the gerber spec
- # only allows up to 6:6
- if MAX_DIGITS > 13 or integer_digits > 6 or decimal_digits > 7:
- raise ValueError('Parser only supports precision up to 6:7 format')
-
- # Remove extraneous information
- value = value.strip()
- value = value.strip(' +')
- negative = '-' in value
- if negative:
- value = value.strip(' -')
-
- # Handle excellon edge case with explicit decimal. "That was easy!"
- if '.' in value:
- return float(value)
-
- digits = [digit for digit in '0' * MAX_DIGITS]
- offset = 0 if zero_suppression == 'trailing' else (MAX_DIGITS - len(value))
- for i, digit in enumerate(value):
- digits[i + offset] = digit
-
- result = float(''.join(digits[:integer_digits] + ['.'] + digits[integer_digits:]))
- return -1.0 * result if negative else result
-
-
-def write_gerber_value(value, format=(2, 5), zero_suppression='trailing'):
- """ Convert a floating point number to a Gerber/Excellon-formatted string.
-
- .. note::
- Format and zero suppression are configurable. Note that the Excellon
- and Gerber formats use opposite terminology with respect to leading
- and trailing zeros. The Gerber format specifies which zeros are
- suppressed, while the Excellon format specifies which zeros are
- included. This function uses the Gerber-file convention, so an
- Excellon file in LZ (leading zeros) mode would use
- `zero_suppression='trailing'`
-
- Parameters
- ----------
- value : float
- A floating point value.
-
- format : tuple (n=2)
- Gerber/Excellon precision format expressed as a tuple containing:
- (number of integer-part digits, number of decimal-part digits)
-
- zero_suppression : string
- Zero-suppression mode. May be 'leading' or 'trailing'
-
- Returns
- -------
- value : string
- The specified value as a Gerber/Excellon-formatted string.
- """
- # Format precision
- integer_digits, decimal_digits = format
- MAX_DIGITS = integer_digits + decimal_digits
-
- if MAX_DIGITS > 13 or integer_digits > 6 or decimal_digits > 7:
- raise ValueError('Parser only supports precision up to 6:7 format')
-
- # negative sign affects padding, so deal with it at the end...
- negative = value < 0.0
- if negative:
- value = -1.0 * value
-
- # Format string for padding out in both directions
- fmtstring = '%%0%d.0%df' % (MAX_DIGITS + 1, decimal_digits)
-
- digits = [val for val in fmtstring % value if val != '.']
-
- # Suppression...
- if zero_suppression == 'trailing':
- while digits[-1] == '0':
- digits.pop()
- else:
- while digits[0] == '0':
- digits.pop(0)
-
- return ''.join(digits) if not negative else ''.join(['-'] + digits)
-
-
-def decimal_string(value, precision=6):
- """ Convert float to string with limited precision
-
- Parameters
- ----------
- value : float
- A floating point value.
-
- precision :
- Maximum number of decimal places to print
-
- Returns
- -------
- value : string
- The specified value as a string.
-
- """
- floatstr = '%0.20g' % value
- integer = None
- decimal = None
- if '.' in floatstr:
- integer, decimal = floatstr.split('.')
- elif ',' in floatstr:
- integer, decimal = floatstr.split(',')
- if len(decimal) > precision:
- decimal = decimal[:precision]
- if integer or decimal:
- return ''.join([integer, '.', decimal])
- else:
- return int(floatstr)
-
-
-def detect_file_format(filename):
- """ Determine format of a file
-
- Parameters
- ----------
- filename : string
- Filename of the file to read.
-
- Returns
- -------
- format : string
- File format. either 'excellon' or 'rs274x'
- """
-
- # Read the first 20 lines
- with open(filename, 'r') as f:
- lines = [next(f) for x in xrange(20)]
-
- # Look for
- for line in lines:
- if 'M48' in line:
- return 'excellon'
- elif '%FS' in line:
- return'rs274x'
- return 'unknown'
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+gerber.utils
+============
+**Gerber and Excellon file handling utilities**
+
+This module provides utility functions for working with Gerber and Excellon
+files.
+"""
+
+# Author: Hamilton Kibbe <ham@hamiltonkib.be>
+# License:
+
+
+def parse_gerber_value(value, format=(2, 5), zero_suppression='trailing'):
+ """ Convert gerber/excellon formatted string to floating-point number
+
+ .. note::
+ Format and zero suppression are configurable. Note that the Excellon
+ and Gerber formats use opposite terminology with respect to leading
+ and trailing zeros. The Gerber format specifies which zeros are
+ suppressed, while the Excellon format specifies which zeros are
+ included. This function uses the Gerber-file convention, so an
+ Excellon file in LZ (leading zeros) mode would use
+ `zero_suppression='trailing'`
+
+
+ Parameters
+ ----------
+ value : string
+ A Gerber/Excellon-formatted string representing a numerical value.
+
+ format : tuple (int,int)
+ Gerber/Excellon precision format expressed as a tuple containing:
+ (number of integer-part digits, number of decimal-part digits)
+
+ zero_suppression : string
+ Zero-suppression mode. May be 'leading' or 'trailing'
+
+ Returns
+ -------
+ value : float
+ The specified value as a floating-point number.
+
+ """
+ # Format precision
+ integer_digits, decimal_digits = format
+ MAX_DIGITS = integer_digits + decimal_digits
+
+ # Absolute maximum number of digits supported. This will handle up to
+ # 6:7 format, which is somewhat supported, even though the gerber spec
+ # only allows up to 6:6
+ if MAX_DIGITS > 13 or integer_digits > 6 or decimal_digits > 7:
+ raise ValueError('Parser only supports precision up to 6:7 format')
+
+ # Remove extraneous information
+ value = value.strip()
+ value = value.strip(' +')
+ negative = '-' in value
+ if negative:
+ value = value.strip(' -')
+
+ # Handle excellon edge case with explicit decimal. "That was easy!"
+ if '.' in value:
+ return float(value)
+
+ digits = [digit for digit in '0' * MAX_DIGITS]
+ offset = 0 if zero_suppression == 'trailing' else (MAX_DIGITS - len(value))
+ for i, digit in enumerate(value):
+ digits[i + offset] = digit
+
+ result = float(''.join(digits[:integer_digits] + ['.'] + digits[integer_digits:]))
+ return -1.0 * result if negative else result
+
+
+def write_gerber_value(value, format=(2, 5), zero_suppression='trailing'):
+ """ Convert a floating point number to a Gerber/Excellon-formatted string.
+
+ .. note::
+ Format and zero suppression are configurable. Note that the Excellon
+ and Gerber formats use opposite terminology with respect to leading
+ and trailing zeros. The Gerber format specifies which zeros are
+ suppressed, while the Excellon format specifies which zeros are
+ included. This function uses the Gerber-file convention, so an
+ Excellon file in LZ (leading zeros) mode would use
+ `zero_suppression='trailing'`
+
+ Parameters
+ ----------
+ value : float
+ A floating point value.
+
+ format : tuple (n=2)
+ Gerber/Excellon precision format expressed as a tuple containing:
+ (number of integer-part digits, number of decimal-part digits)
+
+ zero_suppression : string
+ Zero-suppression mode. May be 'leading' or 'trailing'
+
+ Returns
+ -------
+ value : string
+ The specified value as a Gerber/Excellon-formatted string.
+ """
+ # Format precision
+ integer_digits, decimal_digits = format
+ MAX_DIGITS = integer_digits + decimal_digits
+
+ if MAX_DIGITS > 13 or integer_digits > 6 or decimal_digits > 7:
+ raise ValueError('Parser only supports precision up to 6:7 format')
+
+ # Edge case...
+ if value == 0:
+ return '00'
+
+ # negative sign affects padding, so deal with it at the end...
+ negative = value < 0.0
+ if negative:
+ value = -1.0 * value
+
+ # Format string for padding out in both directions
+ fmtstring = '%%0%d.0%df' % (MAX_DIGITS + 1, decimal_digits)
+ digits = [val for val in fmtstring % value if val != '.']
+
+ # Suppression...
+ if zero_suppression == 'trailing':
+ while digits[-1] == '0':
+ digits.pop()
+ else:
+ while digits[0] == '0':
+ digits.pop(0)
+
+ return ''.join(digits) if not negative else ''.join(['-'] + digits)
+
+
+def decimal_string(value, precision=6, padding=False):
+ """ Convert float to string with limited precision
+
+ Parameters
+ ----------
+ value : float
+ A floating point value.
+
+ precision :
+ Maximum number of decimal places to print
+
+ Returns
+ -------
+ value : string
+ The specified value as a string.
+
+ """
+ floatstr = '%0.10g' % value
+ integer = None
+ decimal = None
+ if '.' in floatstr:
+ integer, decimal = floatstr.split('.')
+ elif ',' in floatstr:
+ integer, decimal = floatstr.split(',')
+ if len(decimal) > precision:
+ decimal = decimal[:precision]
+ elif padding:
+ decimal = decimal + (precision - len(decimal)) * '0'
+ if integer or decimal:
+ return ''.join([integer, '.', decimal])
+ else:
+ return int(floatstr)
+
+
+def detect_file_format(filename):
+ """ Determine format of a file
+
+ Parameters
+ ----------
+ filename : string
+ Filename of the file to read.
+
+ Returns
+ -------
+ format : string
+ File format. either 'excellon' or 'rs274x'
+ """
+
+ # Read the first 20 lines
+ with open(filename, 'r') as f:
+ lines = [next(f) for x in xrange(20)]
+
+ # Look for
+ for line in lines:
+ if 'M48' in line:
+ return 'excellon'
+ elif '%FS' in line:
+ return'rs274x'
+ return 'unknown'