From 4eb0e063bcd34c21b737023aa6ed5baed80658d1 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 13 Jun 2021 15:00:17 +0200 Subject: Repo re-org, make gerberex tests run --- gerbonara/gerber/excellon_statements.py | 979 ++++++++++++++++++++++++++++++++ 1 file changed, 979 insertions(+) create mode 100644 gerbonara/gerber/excellon_statements.py (limited to 'gerbonara/gerber/excellon_statements.py') diff --git a/gerbonara/gerber/excellon_statements.py b/gerbonara/gerber/excellon_statements.py new file mode 100644 index 0000000..2c50ef9 --- /dev/null +++ b/gerbonara/gerber/excellon_statements.py @@ -0,0 +1,979 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# copyright 2014 Hamilton Kibbe +# +# 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** + +""" + +import re +import uuid +import itertools +from .utils import (parse_gerber_value, write_gerber_value, decimal_string, + inch, metric) + + +__all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt', + 'CommentStmt', 'HeaderBeginStmt', 'HeaderEndStmt', + 'RewindStopStmt', 'EndOfProgramStmt', 'UnitStmt', + 'IncrementalModeStmt', 'VersionStmt', 'FormatStmt', 'LinkToolStmt', + 'MeasuringModeStmt', 'RouteModeStmt', 'LinearModeStmt', 'DrillModeStmt', + 'AbsoluteModeStmt', 'RepeatHoleStmt', 'UnknownStmt', + 'ExcellonStatement', 'ZAxisRoutPositionStmt', + 'RetractWithClampingStmt', 'RetractWithoutClampingStmt', + 'CutterCompensationOffStmt', 'CutterCompensationLeftStmt', + 'CutterCompensationRightStmt', 'ZAxisInfeedRateStmt', + 'NextToolSelectionStmt', 'SlotStmt'] + + +class ExcellonStatement(object): + """ Excellon Statement abstract base class + """ + + @classmethod + def from_excellon(cls, line): + raise NotImplementedError('from_excellon must be implemented in a ' + 'subclass') + + def __init__(self, unit='inch', id=None): + self.units = unit + self.id = uuid.uuid4().int if id is None else id + + def to_excellon(self, settings=None): + raise NotImplementedError('to_excellon must be implemented in a ' + 'subclass') + + def to_inch(self): + self.units = 'inch' + + def to_metric(self): + self.units = 'metric' + + def offset(self, x_offset=0, y_offset=0): + pass + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + + +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. + """ + + PLATED_UNKNOWN = None + PLATED_YES = 'plated' + PLATED_NO = 'nonplated' + PLATED_OPTIONAL = 'optional' + + @classmethod + def from_tool(cls, tool): + args = {} + + args['depth_offset'] = tool.depth_offset + args['diameter'] = tool.diameter + args['feed_rate'] = tool.feed_rate + args['max_hit_count'] = tool.max_hit_count + args['number'] = tool.number + args['plated'] = tool.plated + args['retract_rate'] = tool.retract_rate + args['rpm'] = tool.rpm + + return cls(None, **args) + + @classmethod + def from_excellon(cls, line, settings, id=None, plated=None): + """ 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 = pairwise(re.split('([BCFHSTZ])', line)[1:]) + args = {} + args['id'] = id + 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) + + if plated != ExcellonTool.PLATED_UNKNOWN: + # Sometimees we can can parse the plating status + args['plated'] = plated + 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): + if kwargs.get('id') is not None: + super(ExcellonTool, self).__init__(id=kwargs.get('id')) + 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.plated = kwargs.get('plated') + + self.hit_count = 0 + + def to_excellon(self, settings=None): + if self.settings and not settings: + settings = self.settings + fmt = settings.format + zs = settings.zero_suppression + 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: + 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 to_inch(self): + if self.settings.units != 'inch': + self.settings.units = 'inch' + if self.diameter is not None: + self.diameter = inch(self.diameter) + + def to_metric(self): + if self.settings.units != 'metric': + self.settings.units = 'metric' + if self.diameter is not None: + self.diameter = metric(self.diameter) + + def _hit(self): + self.hit_count += 1 + + def equivalent(self, other): + """ + Is the other tool equal to this, ignoring the tool number, and other file specified properties + """ + + if type(self) != type(other): + return False + + return (self.diameter == other.diameter + and self.feed_rate == other.feed_rate + and self.retract_rate == other.retract_rate + and self.rpm == other.rpm + and self.depth_offset == other.depth_offset + and self.max_hit_count == other.max_hit_count + and self.plated == other.plated + and self.settings.units == other.settings.units) + + def __repr__(self): + unit = 'in.' if self.settings.units == 'inch' else 'mm' + fmtstr = '' % self.settings.format + return fmtstr % (self.number, self.diameter, unit) + + +class ToolSelectionStmt(ExcellonStatement): + + @classmethod + def from_excellon(cls, line, **kwargs): + """ 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 + + # up to 3 characters for tool number (Frizting uses that) + if len(line) <= 3: + tool = int(line) + else: + tool = int(line[:2]) + compensation_index = int(line[2:]) + + return cls(tool, compensation_index, **kwargs) + + def __init__(self, tool, compensation_index=None, **kwargs): + super(ToolSelectionStmt, self).__init__(**kwargs) + 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, settings=None): + stmt = 'T%02d' % self.tool + if self.compensation_index is not None: + stmt += '%02d' % self.compensation_index + return stmt + +class NextToolSelectionStmt(ExcellonStatement): + + # TODO the statement exists outside of the context of the file, + # so it is imposible to know that it is really the next tool + + def __init__(self, cur_tool, next_tool, **kwargs): + """ + Select the next tool in the wheel. + Parameters + ---------- + cur_tool : the tool that is currently selected + next_tool : the that that is now selected + """ + super(NextToolSelectionStmt, self).__init__(**kwargs) + + self.cur_tool = cur_tool + self.next_tool = next_tool + + def to_excellon(self, settings=None): + stmt = 'M00' + return stmt + +class ZAxisInfeedRateStmt(ExcellonStatement): + + @classmethod + def from_excellon(cls, line, **kwargs): + """ Create a ZAxisInfeedRate from an excellon file line. + + Parameters + ---------- + line : string + Line from an Excellon file + + Returns + ------- + z_axis_infeed_rate : ToolSelectionStmt + ToolSelectionStmt representation of `line.` + """ + rate = int(line[1:]) + + return cls(rate, **kwargs) + + def __init__(self, rate, **kwargs): + super(ZAxisInfeedRateStmt, self).__init__(**kwargs) + self.rate = rate + + def to_excellon(self, settings=None): + return 'F%02d' % self.rate + + +class CoordinateStmt(ExcellonStatement): + + @classmethod + def from_point(cls, point, mode=None): + + stmt = cls(point[0], point[1]) + if mode: + stmt.mode = mode + return stmt + + @classmethod + def from_excellon(cls, line, settings, **kwargs): + x_coord = None + y_coord = None + if line[0] == 'X': + splitline = line.strip('X').split('Y') + x_coord = parse_gerber_value(splitline[0], settings.format, + settings.zero_suppression) + if len(splitline) == 2: + y_coord = parse_gerber_value(splitline[1], settings.format, + settings.zero_suppression) + else: + y_coord = parse_gerber_value(line.strip(' Y'), settings.format, + settings.zero_suppression) + c = cls(x_coord, y_coord, **kwargs) + c.units = settings.units + return c + + def __init__(self, x=None, y=None, **kwargs): + super(CoordinateStmt, self).__init__(**kwargs) + self.x = x + self.y = y + self.mode = None + + def to_excellon(self, settings): + stmt = '' + if self.mode == "ROUT": + stmt += "G00" + if self.mode == "LINEAR": + stmt += "G01" + if self.x is not None: + 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, settings.format, + settings.zero_suppression) + return stmt + + def to_inch(self): + if self.units == 'metric': + self.units = 'inch' + if self.x is not None: + self.x = inch(self.x) + if self.y is not None: + self.y = inch(self.y) + + def to_metric(self): + if self.units == 'inch': + self.units = 'metric' + if self.x is not None: + self.x = metric(self.x) + if self.y is not None: + self.y = metric(self.y) + + def offset(self, x_offset=0, y_offset=0): + if self.x is not None: + self.x += x_offset + if self.y is not None: + self.y += y_offset + + def __str__(self): + coord_str = '' + if self.x is not None: + coord_str += 'X: %g ' % self.x + if self.y is not None: + coord_str += 'Y: %g ' % self.y + + return '' % coord_str + + +class RepeatHoleStmt(ExcellonStatement): + + @classmethod + def from_excellon(cls, line, settings, **kwargs): + match = re.compile(r'R(?P[0-9]*)X?(?P[+\-]?\d*\.?\d*)?Y?' + '(?P[+\-]?\d*\.?\d*)?').match(line) + stmt = match.groupdict() + count = int(stmt['rcount']) + xdelta = (parse_gerber_value(stmt['xdelta'], settings.format, + settings.zero_suppression) + if stmt['xdelta'] is not '' else None) + ydelta = (parse_gerber_value(stmt['ydelta'], settings.format, + settings.zero_suppression) + if stmt['ydelta'] is not '' else None) + c = cls(count, xdelta, ydelta, **kwargs) + c.units = settings.units + return c + + def __init__(self, count, xdelta=0.0, ydelta=0.0, **kwargs): + super(RepeatHoleStmt, self).__init__(**kwargs) + self.count = count + self.xdelta = xdelta + self.ydelta = ydelta + + def to_excellon(self, settings): + stmt = 'R%d' % self.count + if self.xdelta is not None and self.xdelta != 0.0: + stmt += 'X%s' % write_gerber_value(self.xdelta, settings.format, + settings.zero_suppression) + if self.ydelta is not None and self.ydelta != 0.0: + stmt += 'Y%s' % write_gerber_value(self.ydelta, settings.format, + settings.zero_suppression) + return stmt + + def to_inch(self): + if self.units == 'metric': + self.units = 'inch' + if self.xdelta is not None: + self.xdelta = inch(self.xdelta) + if self.ydelta is not None: + self.ydelta = inch(self.ydelta) + + def to_metric(self): + if self.units == 'inch': + self.units = 'metric' + if self.xdelta is not None: + self.xdelta = metric(self.xdelta) + if self.ydelta is not None: + self.ydelta = metric(self.ydelta) + + def __str__(self): + return '' % ( + self.count, + self.xdelta if self.xdelta is not None else 0, + self.ydelta if self.ydelta is not None else 0) + + +class CommentStmt(ExcellonStatement): + + @classmethod + def from_excellon(cls, line, **kwargs): + return cls(line.lstrip(';')) + + def __init__(self, comment, **kwargs): + super(CommentStmt, self).__init__(**kwargs) + self.comment = comment + + def to_excellon(self, settings=None): + return ';%s' % self.comment + + +class HeaderBeginStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(HeaderBeginStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return 'M48' + + +class HeaderEndStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(HeaderEndStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return 'M95' + + +class RewindStopStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(RewindStopStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return '%' + + +class ZAxisRoutPositionStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(ZAxisRoutPositionStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return 'M15' + + +class RetractWithClampingStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(RetractWithClampingStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return 'M16' + + +class RetractWithoutClampingStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(RetractWithoutClampingStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return 'M17' + + +class CutterCompensationOffStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(CutterCompensationOffStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return 'G40' + + +class CutterCompensationLeftStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(CutterCompensationLeftStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return 'G41' + + +class CutterCompensationRightStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(CutterCompensationRightStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return 'G42' + + +class EndOfProgramStmt(ExcellonStatement): + + @classmethod + def from_excellon(cls, line, settings, **kwargs): + match = re.compile(r'M30X?(?P\d*\.?\d*)?Y?' + '(?P\d*\.?\d*)?').match(line) + stmt = match.groupdict() + x = (parse_gerber_value(stmt['x'], settings.format, + settings.zero_suppression) + if stmt['x'] is not '' else None) + y = (parse_gerber_value(stmt['y'], settings.format, + settings.zero_suppression) + if stmt['y'] is not '' else None) + c = cls(x, y, **kwargs) + c.units = settings.units + return c + + def __init__(self, x=None, y=None, **kwargs): + super(EndOfProgramStmt, self).__init__(**kwargs) + self.x = x + self.y = y + + def to_excellon(self, settings=None): + 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 + + def to_inch(self): + if self.units == 'metric': + self.units = 'inch' + if self.x is not None: + self.x = inch(self.x) + if self.y is not None: + self.y = inch(self.y) + + def to_metric(self): + if self.units == 'inch': + self.units = 'metric' + if self.x is not None: + self.x = metric(self.x) + if self.y is not None: + self.y = metric(self.y) + + def offset(self, x_offset=0, y_offset=0): + if self.x is not None: + self.x += x_offset + if self.y is not None: + self.y += y_offset + + +class UnitStmt(ExcellonStatement): + + @classmethod + def from_settings(cls, settings): + """Create the unit statement from the FileSettings""" + + return cls(settings.units, settings.zeros) + + @classmethod + def from_excellon(cls, line, **kwargs): + units = 'inch' if 'INCH' in line else 'metric' + zeros = 'leading' if 'LZ' in line else 'trailing' + if '0000.00' in line: + format = (4, 2) + elif '000.000' in line: + format = (3, 3) + elif '00.0000' in line: + format = (2, 4) + else: + format = None + return cls(units, zeros, format, **kwargs) + + def __init__(self, units='inch', zeros='leading', format=None, **kwargs): + super(UnitStmt, self).__init__(**kwargs) + self.units = units.lower() + self.zeros = zeros + self.format = format + + def to_excellon(self, settings=None): + # TODO This won't export the invalid format statement if it exists + stmt = '%s,%s' % ('INCH' if self.units == 'inch' else 'METRIC', + 'LZ' if self.zeros == 'leading' + else 'TZ') + return stmt + + def to_inch(self): + self.units = 'inch' + + def to_metric(self): + self.units = 'metric' + + +class IncrementalModeStmt(ExcellonStatement): + + @classmethod + def from_excellon(cls, line, **kwargs): + return cls('off', **kwargs) if 'OFF' in line else cls('on', **kwargs) + + def __init__(self, mode='off', **kwargs): + super(IncrementalModeStmt, self).__init__(**kwargs) + if mode.lower() not in ['on', 'off']: + raise ValueError('Mode may be "on" or "off"') + self.mode = mode + + def to_excellon(self, settings=None): + return 'ICI,%s' % ('OFF' if self.mode == 'off' else 'ON') + + +class VersionStmt(ExcellonStatement): + + @classmethod + def from_excellon(cls, line, **kwargs): + version = int(line.split(',')[1]) + return cls(version, **kwargs) + + def __init__(self, version=1, **kwargs): + super(VersionStmt, self).__init__(**kwargs) + version = int(version) + if version not in [1, 2]: + raise ValueError('Valid versions are 1 or 2') + self.version = version + + def to_excellon(self, settings=None): + return 'VER,%d' % self.version + + +class FormatStmt(ExcellonStatement): + + @classmethod + def from_excellon(cls, line, **kwargs): + fmt = int(line.split(',')[1]) + return cls(fmt, **kwargs) + + def __init__(self, format=1, **kwargs): + super(FormatStmt, self).__init__(**kwargs) + format = int(format) + if format not in [1, 2]: + raise ValueError('Valid formats are 1 or 2') + self.format = format + + def to_excellon(self, settings=None): + return 'FMAT,%d' % self.format + + @property + def format_tuple(self): + return (self.format, 6 - self.format) + + +class LinkToolStmt(ExcellonStatement): + + @classmethod + def from_excellon(cls, line, **kwargs): + linked = [int(tool) for tool in line.split('/')] + return cls(linked, **kwargs) + + def __init__(self, linked_tools, **kwargs): + super(LinkToolStmt, self).__init__(**kwargs) + self.linked_tools = [int(x) for x in linked_tools] + + def to_excellon(self, settings=None): + return '/'.join([str(x) for x in self.linked_tools]) + + +class MeasuringModeStmt(ExcellonStatement): + + @classmethod + def from_excellon(cls, line, **kwargs): + if not ('M71' in line or 'M72' in line): + raise ValueError('Not a measuring mode statement') + return cls('inch', **kwargs) if 'M72' in line else cls('metric', **kwargs) + + def __init__(self, units='inch', **kwargs): + super(MeasuringModeStmt, self).__init__(**kwargs) + units = units.lower() + if units not in ['inch', 'metric']: + raise ValueError('units must be "inch" or "metric"') + self.units = units + + def to_excellon(self, settings=None): + return 'M72' if self.units == 'inch' else 'M71' + + def to_inch(self): + self.units = 'inch' + + def to_metric(self): + self.units = 'metric' + + +class RouteModeStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(RouteModeStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return 'G00' + + +class LinearModeStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(LinearModeStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return 'G01' + + +class DrillModeStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(DrillModeStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return 'G05' + + +class AbsoluteModeStmt(ExcellonStatement): + + def __init__(self, **kwargs): + super(AbsoluteModeStmt, self).__init__(**kwargs) + + def to_excellon(self, settings=None): + return 'G90' + + +class UnknownStmt(ExcellonStatement): + + @classmethod + def from_excellon(cls, line, **kwargs): + return cls(line, **kwargs) + + def __init__(self, stmt, **kwargs): + super(UnknownStmt, self).__init__(**kwargs) + self.stmt = stmt + + def to_excellon(self, settings=None): + return self.stmt + + def __str__(self): + return "" % self.stmt + + +class SlotStmt(ExcellonStatement): + """ + G85 statement. Defines a slot created by multiple drills between two specified points. + + Format is two coordinates, split by G85in the middle, for example, XnY0nG85XnYn + """ + + @classmethod + def from_points(cls, start, end): + + return cls(start[0], start[1], end[0], end[1]) + + @classmethod + def from_excellon(cls, line, settings, **kwargs): + # Split the line based on the G85 separator + sub_coords = line.split('G85') + (x_start_coord, y_start_coord) = SlotStmt.parse_sub_coords(sub_coords[0], settings) + (x_end_coord, y_end_coord) = SlotStmt.parse_sub_coords(sub_coords[1], settings) + + # Some files seem to specify only one of the coordinates + if x_end_coord == None: + x_end_coord = x_start_coord + if y_end_coord == None: + y_end_coord = y_start_coord + + c = cls(x_start_coord, y_start_coord, x_end_coord, y_end_coord, **kwargs) + c.units = settings.units + return c + + @staticmethod + def parse_sub_coords(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], settings.format, + settings.zero_suppression) + if len(splitline) == 2: + y_coord = parse_gerber_value(splitline[1], settings.format, + settings.zero_suppression) + else: + y_coord = parse_gerber_value(line.strip(' Y'), settings.format, + settings.zero_suppression) + + return (x_coord, y_coord) + + + def __init__(self, x_start=None, y_start=None, x_end=None, y_end=None, **kwargs): + super(SlotStmt, self).__init__(**kwargs) + self.x_start = x_start + self.y_start = y_start + self.x_end = x_end + self.y_end = y_end + self.mode = None + + def to_excellon(self, settings): + stmt = '' + + if self.x_start is not None: + stmt += 'X%s' % write_gerber_value(self.x_start, settings.format, + settings.zero_suppression) + if self.y_start is not None: + stmt += 'Y%s' % write_gerber_value(self.y_start, settings.format, + settings.zero_suppression) + + stmt += 'G85' + + if self.x_end is not None: + stmt += 'X%s' % write_gerber_value(self.x_end, settings.format, + settings.zero_suppression) + if self.y_end is not None: + stmt += 'Y%s' % write_gerber_value(self.y_end, settings.format, + settings.zero_suppression) + + return stmt + + def to_inch(self): + if self.units == 'metric': + self.units = 'inch' + if self.x_start is not None: + self.x_start = inch(self.x_start) + if self.y_start is not None: + self.y_start = inch(self.y_start) + if self.x_end is not None: + self.x_end = inch(self.x_end) + if self.y_end is not None: + self.y_end = inch(self.y_end) + + def to_metric(self): + if self.units == 'inch': + self.units = 'metric' + if self.x_start is not None: + self.x_start = metric(self.x_start) + if self.y_start is not None: + self.y_start = metric(self.y_start) + if self.x_end is not None: + self.x_end = metric(self.x_end) + if self.y_end is not None: + self.y_end = metric(self.y_end) + + def offset(self, x_offset=0, y_offset=0): + if self.x_start is not None: + self.x_start += x_offset + if self.y_start is not None: + self.y_start += y_offset + if self.x_end is not None: + self.x_end += x_offset + if self.y_end is not None: + self.y_end += y_offset + + def __str__(self): + start_str = '' + if self.x_start is not None: + start_str += 'X: %g ' % self.x_start + if self.y_start is not None: + start_str += 'Y: %g ' % self.y_start + + end_str = '' + if self.x_end is not None: + end_str += 'X: %g ' % self.x_end + if self.y_end is not None: + end_str += 'Y: %g ' % self.y_end + + return '' % (start_str, end_str) + +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)] + """ + a, b = itertools.tee(iterator) + itr = zip(itertools.islice(a, 0, None, 2), itertools.islice(b, 1, None, 2)) + for elem in itr: + yield elem -- cgit