summaryrefslogtreecommitdiff
path: root/gerber/excellon_statements.py
diff options
context:
space:
mode:
authorPaulo Henrique Silva <ph.silva@gmail.com>2014-10-26 20:55:22 -0200
committerPaulo Henrique Silva <ph.silva@gmail.com>2014-10-26 20:55:22 -0200
commitd5c8d896d87e02d32d0c0758a09f051c141006f6 (patch)
tree50e2788e5dfc63db909a3613fff6934a0e80b153 /gerber/excellon_statements.py
parentd0eedf3dd7ee4fbf19f51de319e48dd964b93561 (diff)
parent4f076d7b769b0f488888d268a9a199b7545afdd7 (diff)
downloadgerbonara-d5c8d896d87e02d32d0c0758a09f051c141006f6.tar.gz
gerbonara-d5c8d896d87e02d32d0c0758a09f051c141006f6.tar.bz2
gerbonara-d5c8d896d87e02d32d0c0758a09f051c141006f6.zip
Merge pull request #4 from hamiltonkibbe/master
Many fixes in parsing, rendering and new features
Diffstat (limited to 'gerber/excellon_statements.py')
-rw-r--r--gerber/excellon_statements.py431
1 files changed, 431 insertions, 0 deletions
diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py
new file mode 100644
index 0000000..feeda44
--- /dev/null
+++ b/gerber/excellon_statements.py
@@ -0,0 +1,431 @@
+#!/usr/bin/env python
+# -*- 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
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# 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.
+"""
+Excellon Statements
+====================
+**Excellon file statement classes**
+
+"""
+
+from .utils import parse_gerber_value, write_gerber_value, decimal_string
+import re
+
+__all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt',
+ 'CommentStmt', 'HeaderBeginStmt', 'HeaderEndStmt',
+ 'RewindStopStmt', 'EndOfProgramStmt', 'UnitStmt',
+ 'IncrementalModeStmt', 'VersionStmt', 'FormatStmt', 'LinkToolStmt',
+ 'MeasuringModeStmt', 'UnknownStmt',
+ ]
+
+
+class ExcellonStatement(object):
+ """ Excellon Statement abstract base class
+ """
+ @classmethod
+ def from_excellon(cls, line):
+ pass
+
+ def to_excellon(self):
+ pass
+
+
+class ExcellonTool(ExcellonStatement):
+ """ Excellon Tool class
+
+ Parameters
+ ----------
+ settings : FileSettings (dict-like)
+ File-wide settings.
+
+ kwargs : dict-like
+ Tool settings from the excellon statement. Valid keys are:
+ - `diameter` : Tool diameter [expressed in file units]
+ - `rpm` : Tool RPM
+ - `feed_rate` : Z-axis tool feed rate
+ - `retract_rate` : Z-axis tool retraction rate
+ - `max_hit_count` : Number of hits allowed before a tool change
+ - `depth_offset` : Offset of tool depth from tip of tool.
+
+ Attributes
+ ----------
+ number : integer
+ Tool number from the excellon file
+
+ diameter : float
+ Tool diameter in file units
+
+ rpm : float
+ Tool RPM
+
+ feed_rate : float
+ Tool Z-axis feed rate.
+
+ retract_rate : float
+ Tool Z-axis retract rate
+
+ depth_offset : float
+ Offset of depth measurement from tip of tool
+
+ max_hit_count : integer
+ Maximum number of tool hits allowed before a tool change
+
+ hit_count : integer
+ Number of tool hits in excellon file.
+ """
+
+ @classmethod
+ def from_excellon(cls, line, settings):
+ """ Create a Tool from an excellon file tool definition line.
+
+ Parameters
+ ----------
+ line : string
+ Tool definition line from an excellon file.
+
+ settings : FileSettings (dict-like)
+ Excellon file-wide settings
+
+ Returns
+ -------
+ tool : Tool
+ An ExcellonTool representing the tool defined in `line`
+ """
+ commands = re.split('([BCFHSTZ])', line)[1:]
+ commands = [(command, value) for command, value in pairwise(commands)]
+ args = {}
+ nformat = settings.format
+ zero_suppression = settings.zero_suppression
+ for cmd, val in commands:
+ if cmd == 'B':
+ args['retract_rate'] = parse_gerber_value(val, nformat, zero_suppression)
+ elif cmd == 'C':
+ args['diameter'] = parse_gerber_value(val, nformat, zero_suppression)
+ elif cmd == 'F':
+ args['feed_rate'] = parse_gerber_value(val, nformat, zero_suppression)
+ elif cmd == 'H':
+ args['max_hit_count'] = parse_gerber_value(val, nformat, zero_suppression)
+ elif cmd == 'S':
+ args['rpm'] = 1000 * parse_gerber_value(val, nformat, zero_suppression)
+ elif cmd == 'T':
+ args['number'] = int(val)
+ elif cmd == 'Z':
+ args['depth_offset'] = parse_gerber_value(val, nformat, zero_suppression)
+ return cls(settings, **args)
+
+ @classmethod
+ def from_dict(cls, settings, tool_dict):
+ """ Create an ExcellonTool from a dict.
+
+ Parameters
+ ----------
+ settings : FileSettings (dict-like)
+ Excellon File-wide settings
+
+ tool_dict : dict
+ Excellon tool parameters as a dict
+
+ Returns
+ -------
+ tool : ExcellonTool
+ An ExcellonTool initialized with the parameters in tool_dict.
+ """
+ return cls(settings, tool_dict)
+
+ def __init__(self, settings, **kwargs):
+ self.settings = settings
+ self.number = kwargs.get('number')
+ self.feed_rate = kwargs.get('feed_rate')
+ self.retract_rate = kwargs.get('retract_rate')
+ self.rpm = kwargs.get('rpm')
+ self.diameter = kwargs.get('diameter')
+ self.max_hit_count = kwargs.get('max_hit_count')
+ self.depth_offset = kwargs.get('depth_offset')
+ self.hit_count = 0
+
+ def to_excellon(self):
+ fmt = self.settings.format
+ zs = self.settings.format
+ stmt = 'T%d' % 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:
+ stmt += 'F%s' % write_gerber_value(self.feed_rate, fmt, zs)
+ if self.max_hit_count is not None:
+ stmt += 'H%s' % write_gerber_value(self.max_hit_count, fmt, zs)
+ 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.diameter is not None:
+ stmt += 'C%s' % decimal_string(self.diameter, fmt[1], True)
+ if self.depth_offset is not None:
+ stmt += 'Z%s' % write_gerber_value(self.depth_offset, fmt, zs)
+ return stmt
+
+ 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)
+
+
+class ToolSelectionStmt(ExcellonStatement):
+
+ @classmethod
+ def from_excellon(cls, line):
+ """ Create a ToolSelectionStmt from an excellon file line.
+
+ Parameters
+ ----------
+ line : string
+ Line from an Excellon file
+
+ Returns
+ -------
+ tool_statement : ToolSelectionStmt
+ ToolSelectionStmt representation of `line.`
+ """
+ line = line[1:]
+ compensation_index = None
+ tool = int(line[:2])
+ if len(line) > 2:
+ compensation_index = int(line[2:])
+ return cls(tool, compensation_index)
+
+ def __init__(self, tool, compensation_index=None):
+ tool = int(tool)
+ compensation_index = (int(compensation_index) if compensation_index
+ is not None else None)
+ self.tool = tool
+ self.compensation_index = compensation_index
+
+ def to_excellon(self):
+ stmt = 'T%02d' % self.tool
+ if self.compensation_index is not None:
+ stmt += '%02d' % self.compensation_index
+ return stmt
+
+
+class CoordinateStmt(ExcellonStatement):
+
+ @classmethod
+ def from_excellon(cls, line, nformat=(2, 5), zero_suppression='trailing'):
+ 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)
+ if len(splitline) == 2:
+ y_coord = parse_gerber_value(splitline[1], nformat,
+ zero_suppression)
+ else:
+ y_coord = parse_gerber_value(line.strip(' Y'), nformat,
+ 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):
+ stmt = ''
+ if self.x is not None:
+ 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 CommentStmt(ExcellonStatement):
+
+ @classmethod
+ def from_excellon(cls, line):
+ return cls(line.lstrip(';'))
+
+ def __init__(self, comment):
+ self.comment = comment
+
+ def to_excellon(self):
+ return ';%s' % self.comment
+
+
+class HeaderBeginStmt(ExcellonStatement):
+
+ def __init__(self):
+ pass
+
+ def to_excellon(self):
+ return 'M48'
+
+
+class HeaderEndStmt(ExcellonStatement):
+
+ def __init__(self):
+ pass
+
+ def to_excellon(self):
+ return 'M95'
+
+
+class RewindStopStmt(ExcellonStatement):
+
+ def __init__(self):
+ pass
+
+ def to_excellon(self):
+ return '%'
+
+
+class EndOfProgramStmt(ExcellonStatement):
+
+ def __init__(self, x=None, y=None):
+ self.x = x
+ self.y = y
+
+ def to_excellon(self):
+ stmt = 'M30'
+ if self.x is not None:
+ 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):
+
+ @classmethod
+ def from_excellon(cls, line):
+ units = 'inch' if 'INCH' in line else 'metric'
+ zero_suppression = 'trailing' if 'LZ' in line else 'leading'
+ return cls(units, zero_suppression)
+
+ def __init__(self, units='inch', zero_suppression='trailing'):
+ self.units = units.lower()
+ self.zero_suppression = zero_suppression
+
+ def to_excellon(self):
+ stmt = '%s,%s' % ('INCH' if self.units == 'inch' else 'METRIC',
+ 'LZ' if self.zero_suppression == 'trailing'
+ else 'TZ')
+ return stmt
+
+
+class IncrementalModeStmt(ExcellonStatement):
+
+ @classmethod
+ def from_excellon(cls, line):
+ return cls('off') if 'OFF' in line else cls('on')
+
+ def __init__(self, mode='off'):
+ if mode.lower() not in ['on', 'off']:
+ raise ValueError('Mode may be "on" or "off"')
+ self.mode = mode
+
+ def to_excellon(self):
+ return 'ICI,%s' % ('OFF' if self.mode == 'off' else 'ON')
+
+
+class VersionStmt(ExcellonStatement):
+
+ @classmethod
+ def from_excellon(cls, line):
+ version = int(line.split(',')[1])
+ return cls(version)
+
+ def __init__(self, version=1):
+ version = int(version)
+ if version not in [1, 2]:
+ raise ValueError('Valid versions are 1 or 2')
+ self.version = version
+
+ def to_excellon(self):
+ return 'VER,%d' % self.version
+
+
+class FormatStmt(ExcellonStatement):
+
+ @classmethod
+ def from_excellon(cls, line):
+ fmt = int(line.split(',')[1])
+ return cls(fmt)
+
+ def __init__(self, format=1):
+ format = int(format)
+ if format not in [1, 2]:
+ raise ValueError('Valid formats are 1 or 2')
+ self.format = format
+
+ def to_excellon(self):
+ return 'FMAT,%d' % self.format
+
+
+class LinkToolStmt(ExcellonStatement):
+
+ @classmethod
+ def from_excellon(cls, line):
+ linked = [int(tool) for tool in line.split('/')]
+ return cls(linked)
+
+ def __init__(self, linked_tools):
+ self.linked_tools = [int(x) for x in linked_tools]
+
+ def to_excellon(self):
+ return '/'.join([str(x) for x in self.linked_tools])
+
+
+class MeasuringModeStmt(ExcellonStatement):
+
+ @classmethod
+ def from_excellon(cls, line):
+ if not ('M71' in line or 'M72' in line):
+ raise ValueError('Not a measuring mode statement')
+ return cls('inch') if 'M72' in line else cls('metric')
+
+ def __init__(self, units='inch'):
+ units = units.lower()
+ if units not in ['inch', 'metric']:
+ raise ValueError('units must be "inch" or "metric"')
+ self.units = units
+
+ def to_excellon(self):
+ return 'M72' if self.units == 'inch' else 'M71'
+
+
+class UnknownStmt(ExcellonStatement):
+
+ @classmethod
+ def from_excellon(cls, line):
+ return cls(line)
+
+ def __init__(self, stmt):
+ self.stmt = stmt
+
+ 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)])