From 597427d785d6f44348fe15631f2c184504195fb0 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 6 Oct 2014 08:33:53 -0400 Subject: add excellon statements --- gerber/excellon_statements.py | 266 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 gerber/excellon_statements.py (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py new file mode 100644 index 0000000..4544f08 --- /dev/null +++ b/gerber/excellon_statements.py @@ -0,0 +1,266 @@ +#!/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. + +from .utils import write_gerber_value + + +__all__ = ['ExcellonTool', 'CommentStmt', 'HeaderBeginStmt', 'HeaderEndStmt', + ] + + +class ExcellonStatement(object): + """ Excellon Statement abstract base class + """ + 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_line(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 = {} + format = settings['format'] + zero_suppression = settings['zero_suppression'] + for cmd, val in commands: + if cmd == 'B': + args['retract_rate'] = parse_gerber_value(val, format, zero_suppression) + elif cmd == 'C': + args['diameter'] = parse_gerber_value(val, format, zero_suppression) + elif cmd == 'F': + args['feed_rate'] = parse_gerber_value(val, format, zero_suppression) + elif cmd == 'H': + args['max_hit_count'] = parse_gerber_value(val, format, zero_suppression) + elif cmd == 'S': + args['rpm'] = 1000 * parse_gerber_value(val, format, zero_suppression) + elif cmd == 'T': + args['number'] = int(val) + elif cmd == 'Z': + args['depth_offset'] = parse_gerber_value(val, format, zero_suppression) + return cls(settings, **args) + + def from_dict(cls, settings, 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['zero_suppression'] + stmt = 'T%d' % self.number + if self.retract_rate: + 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: + stmt += 'F%s' % write_gerber_value(self.feed_rate, fmt, zs) + if self.max_hit_count: + stmt += 'H%s' % write_gerber_value(self.max_hit_count, fmt, zs) + if self.rpm: + 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: + 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 '' % (self.number, self.diameter, unit) + + +class CommentStmt(ExcellonStatement): + def __init__(self, comment): + self.comment = comment + + def to_excellon(self): + return ';%s' % 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) + + +class UnitStmt(ExcellonStatement): + + 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') + + +class IncrementalModeStmt(ExcellonStatement): + + def __init__(self, mode='off'): + if mode.lower() not in ['on', 'off']: + raise ValueError('Mode may be "on" or "off") + self.mode = 'off' + + def to_excellon(self): + return 'ICI,%s' % 'OFF' if self.mode == 'off' else 'ON' + + +class VersionStmt(ExcellonStatement): + + def __init__(self, version=1): + self.version = int(version) + + def to_excellon(self): + return 'VER,%d' % self.version + + +class FormatStmt(ExcellonStatement): + def __init__(self, format=1): + self.format = int(format) + + def to_excellon(self): + return 'FMAT,%d' % self.format + + +class LinkToolStmt(ExcellonStatement): + + 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): + + 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' + + -- cgit From 08253b40f6f677c4edaeb7108177846d8f0d8703 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 6 Oct 2014 13:11:16 -0400 Subject: Update excellon statements/ExcellonFile --- gerber/excellon_statements.py | 122 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 6 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 4544f08..faadd20 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -18,13 +18,21 @@ from .utils import write_gerber_value -__all__ = ['ExcellonTool', 'CommentStmt', 'HeaderBeginStmt', 'HeaderEndStmt', +__all__ = ['ExcellonTool', 'ToolSelectionStatment', '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 @@ -74,7 +82,7 @@ class ExcellonTool(ExcellonStatement): """ @classmethod - def from_line(cls, line, settings): + def from_excellon(cls, line, settings): """ Create a Tool from an excellon file tool definition line. Parameters @@ -155,7 +163,62 @@ class ExcellonTool(ExcellonStatement): return '' % (self.number, self.diameter, unit) +class ToolSelectionStatment(ExcellonStatement): + + @classmethod + def from_excellon(cls, line): + line = line.strip()[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 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): + + def from_excellon(cls, line, format=(2, 5), zero_suppression='trailing'): + x = None + y = None + if line[0] == 'X': + splitline = line.strip('X').split('Y') + x = parse_gerber_value(splitline[0].strip(), format, zero_suppression) + if len(splitline) == 2: + y = parse_gerber_value(splitline[1].strip(), format, zero_suppression) + else: + y = parse_gerber_value(line.strip(' Y'), format, zero_suppression) + return cls(x, y) + + 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.append('X%s' % write_gerber_value(self.x)) + if self.y is not None: + stmt.append('Y%s' % write_gerber_value(self.y)) + return stmt + + class CommentStmt(ExcellonStatement): + + def from_excellon(self, line): + return cls(line.strip().lstrip(';')) + def __init__(self, comment): self.comment = comment @@ -206,6 +269,12 @@ class EndOfProgramStmt(ExcellonStatement): 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 @@ -217,6 +286,10 @@ class UnitStmt(ExcellonStatement): 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") @@ -228,16 +301,33 @@ class IncrementalModeStmt(ExcellonStatement): class VersionStmt(ExcellonStatement): + @classmethod + def from_excellon(cls, line): + version = int(line.split(',')[1]) + return cls(version) + def __init__(self, version=1): - self.version = int(version) + 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): - self.format = int(format) + 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 @@ -245,6 +335,11 @@ class FormatStmt(ExcellonStatement): class LinkToolStmt(ExcellonStatement): + @classmethod + def from_excellon(cls, line): + linked = [int(tool) for tool in line.strip().split('/')] + return cls(linked) + def __init__(self, linked_tools): self.linked_tools = [int(x) for x in linked_tools] @@ -253,14 +348,29 @@ class LinkToolStmt(ExcellonStatement): class MeasuringModeStmt(ExcellonStatement): - + + @classmethod + def from_excellon(cls, line): + 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 -- cgit From 22a6f87e94c1192b277a1353aefc7c0316f41f90 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 6 Oct 2014 18:28:32 -0400 Subject: add excellon file write --- gerber/excellon_statements.py | 54 ++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 19 deletions(-) (limited to 'gerber/excellon_statements.py') 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 '' % (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 -- cgit From 2abb7159be80beb0565d35e856f3279d2f1f693b Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Mon, 6 Oct 2014 23:52:57 -0400 Subject: add tests --- gerber/excellon_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 09b72c1..caf1626 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -151,7 +151,7 @@ class ExcellonTool(ExcellonStatement): else: stmt += 'S%g' % self.rpm / 1000. if self.diameter is not None: - stmt += 'C%s' % decimal_string(self.diameter, 5, True) + 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 -- cgit From f7c19398730d95bd4f34834ebcf66d9a68273055 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 7 Oct 2014 00:16:39 -0400 Subject: add tests --- gerber/excellon_statements.py | 73 ++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 22 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index caf1626..dbd807a 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -102,26 +102,42 @@ class ExcellonTool(ExcellonStatement): commands = re.split('([BCFHSTZ])', line)[1:] commands = [(command, value) for command, value in pairwise(commands)] args = {} - format = settings['format'] + nformat = settings['format'] zero_suppression = settings['zero_suppression'] for cmd, val in commands: if cmd == 'B': - args['retract_rate'] = parse_gerber_value(val, format, zero_suppression) + args['retract_rate'] = parse_gerber_value(val, nformat, zero_suppression) elif cmd == 'C': - args['diameter'] = parse_gerber_value(val, format, zero_suppression) + args['diameter'] = parse_gerber_value(val, nformat, zero_suppression) elif cmd == 'F': - args['feed_rate'] = parse_gerber_value(val, format, zero_suppression) + args['feed_rate'] = parse_gerber_value(val, nformat, zero_suppression) elif cmd == 'H': - args['max_hit_count'] = parse_gerber_value(val, format, zero_suppression) + args['max_hit_count'] = parse_gerber_value(val, nformat, zero_suppression) elif cmd == 'S': - args['rpm'] = 1000 * parse_gerber_value(val, format, zero_suppression) + 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, format, zero_suppression) + 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): @@ -168,6 +184,18 @@ 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.strip()[1:] compensation_index = None tool = int(line[:2]) @@ -177,7 +205,8 @@ class ToolSelectionStmt(ExcellonStatement): def __init__(self, tool, compensation_index=None): tool = int(tool) - compensation_index = int(compensation_index) if compensation_index else None + compensation_index = (int(compensation_index) if compensation_index + is not None else None) self.tool = tool self.compensation_index = compensation_index @@ -191,16 +220,16 @@ class ToolSelectionStmt(ExcellonStatement): class CoordinateStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line, format=(2, 5), zero_suppression='trailing'): + def from_excellon(cls, line, nformat=(2, 5), zero_suppression='trailing'): x = None y = None if line[0] == 'X': splitline = line.strip('X').split('Y') - x = parse_gerber_value(splitline[0].strip(), format, zero_suppression) + x = parse_gerber_value(splitline[0].strip(), nformat, zero_suppression) if len(splitline) == 2: - y = parse_gerber_value(splitline[1].strip(), format, zero_suppression) + y = parse_gerber_value(splitline[1].strip(), nformat, zero_suppression) else: - y = parse_gerber_value(line.strip(' Y'), format, zero_suppression) + y = parse_gerber_value(line.strip(' Y'), nformat, zero_suppression) return cls(x, y) def __init__(self, x=None, y=None): @@ -270,6 +299,7 @@ class EndOfProgramStmt(ExcellonStatement): stmt += 'Y%s' % write_gerber_value(self.y) return stmt + class UnitStmt(ExcellonStatement): @classmethod @@ -284,10 +314,11 @@ class UnitStmt(ExcellonStatement): def to_excellon(self): stmt = '%s,%s' % ('INCH' if self.units == 'inch' else 'METRIC', - 'LZ' if self.zero_suppression == 'trailing' + 'LZ' if self.zero_suppression == 'trailing' else 'TZ') return stmt + class IncrementalModeStmt(ExcellonStatement): @classmethod @@ -327,14 +358,14 @@ class FormatStmt(ExcellonStatement): fmt = int(line.split(',')[1]) return cls(fmt) - def __init__(self, format=1): - format = int(format) - if format not in [1, 2]: + def __init__(self, nformat=1): + nformat = int(nformat) + if nformat not in [1, 2]: raise ValueError('Valid formats are 1 or 2') - self.format = format + self.nformat = nformat def to_excellon(self): - return 'FMAT,%d' % self.format + return 'FMAT,%d' % self.n class LinkToolStmt(ExcellonStatement): @@ -379,9 +410,7 @@ class UnknownStmt(ExcellonStatement): def to_excellon(self): return self.stmt - - - + def pairwise(iterator): """ Iterate over list taking two elements at a time. @@ -389,4 +418,4 @@ def pairwise(iterator): """ itr = iter(iterator) while True: - yield tuple([itr.next() for i in range(2)]) \ No newline at end of file + yield tuple([itr.next() for i in range(2)]) -- cgit From 8ac771db92633fab9aa0ff9ecc7333e6a412e577 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 7 Oct 2014 09:12:46 -0400 Subject: More tests --- gerber/excellon_statements.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index dbd807a..13f763e 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -221,16 +221,19 @@ class CoordinateStmt(ExcellonStatement): @classmethod def from_excellon(cls, line, nformat=(2, 5), zero_suppression='trailing'): - x = None - y = None + x_coord = None + y_coord = None if line[0] == 'X': splitline = line.strip('X').split('Y') - x = parse_gerber_value(splitline[0].strip(), nformat, zero_suppression) + x_coord = parse_gerber_value(splitline[0].strip(), nformat, + zero_suppression) if len(splitline) == 2: - y = parse_gerber_value(splitline[1].strip(), nformat, zero_suppression) + y_coord = parse_gerber_value(splitline[1].strip(), nformat, + zero_suppression) else: - y = parse_gerber_value(line.strip(' Y'), nformat, zero_suppression) - return cls(x, y) + 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 -- cgit From b971dacd3f058772326a479a562ceed0a9594deb Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Tue, 7 Oct 2014 18:36:03 -0400 Subject: more tests --- gerber/excellon_statements.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 13f763e..5eba12c 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -331,10 +331,10 @@ class IncrementalModeStmt(ExcellonStatement): def __init__(self, mode='off'): if mode.lower() not in ['on', 'off']: raise ValueError('Mode may be "on" or "off"') - self.mode = 'off' + self.mode = mode def to_excellon(self): - return 'ICI,%s' % 'OFF' if self.mode == 'off' else 'ON' + return 'ICI,%s' % ('OFF' if self.mode == 'off' else 'ON') class VersionStmt(ExcellonStatement): @@ -361,14 +361,14 @@ class FormatStmt(ExcellonStatement): fmt = int(line.split(',')[1]) return cls(fmt) - def __init__(self, nformat=1): - nformat = int(nformat) - if nformat not in [1, 2]: + def __init__(self, format=1): + format = int(format) + if format not in [1, 2]: raise ValueError('Valid formats are 1 or 2') - self.nformat = nformat + self.format = format def to_excellon(self): - return 'FMAT,%d' % self.n + return 'FMAT,%d' % self.format class LinkToolStmt(ExcellonStatement): @@ -389,6 +389,8 @@ 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'): -- cgit From f2f411493ea303075d5dbdd7656c572dda61cf67 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 9 Oct 2014 22:10:28 -0400 Subject: doc update --- gerber/excellon_statements.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 5eba12c..7300b60 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -14,7 +14,12 @@ # 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 -- cgit From a9059df190be0238ce0e6fca8c59700e92ddf205 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 10 Oct 2014 09:35:06 -0400 Subject: doc update --- gerber/excellon_statements.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 7300b60..4b92f07 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -53,12 +53,12 @@ class ExcellonTool(ExcellonStatement): 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. + - `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 ---------- -- cgit From 18e3b87625ddb739faeddffcaed48e12db6c7e8b Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 19 Oct 2014 22:23:00 -0400 Subject: Test update --- gerber/excellon_statements.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 4b92f07..feeda44 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -107,8 +107,8 @@ class ExcellonTool(ExcellonStatement): commands = re.split('([BCFHSTZ])', line)[1:] commands = [(command, value) for command, value in pairwise(commands)] args = {} - nformat = settings['format'] - zero_suppression = settings['zero_suppression'] + 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) @@ -157,8 +157,8 @@ class ExcellonTool(ExcellonStatement): self.hit_count = 0 def to_excellon(self): - fmt = self.settings['format'] - zs = self.settings['zero_suppression'] + 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) @@ -201,7 +201,7 @@ class ToolSelectionStmt(ExcellonStatement): tool_statement : ToolSelectionStmt ToolSelectionStmt representation of `line.` """ - line = line.strip()[1:] + line = line[1:] compensation_index = None tool = int(line[:2]) if len(line) > 2: @@ -230,10 +230,10 @@ class CoordinateStmt(ExcellonStatement): y_coord = None if line[0] == 'X': splitline = line.strip('X').split('Y') - x_coord = parse_gerber_value(splitline[0].strip(), nformat, + x_coord = parse_gerber_value(splitline[0], nformat, zero_suppression) if len(splitline) == 2: - y_coord = parse_gerber_value(splitline[1].strip(), nformat, + y_coord = parse_gerber_value(splitline[1], nformat, zero_suppression) else: y_coord = parse_gerber_value(line.strip(' Y'), nformat, @@ -257,7 +257,7 @@ class CommentStmt(ExcellonStatement): @classmethod def from_excellon(cls, line): - return cls(line.strip().lstrip(';')) + return cls(line.lstrip(';')) def __init__(self, comment): self.comment = comment @@ -380,7 +380,7 @@ class LinkToolStmt(ExcellonStatement): @classmethod def from_excellon(cls, line): - linked = [int(tool) for tool in line.strip().split('/')] + linked = [int(tool) for tool in line.split('/')] return cls(linked) def __init__(self, linked_tools): -- cgit From 137c73f3e42281de67bde8f1c0b16938f5b8aeeb Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Wed, 14 Jan 2015 14:33:00 -0200 Subject: Many additions to Excellon parsing/creation. CAUTION: the original code used zero_suppression flags in the opposite sense as Gerber functions. This patch changes it to behave just like Gerber code. * Add metric/inch conversion support * Add settings context variable to to_gerber just like Gerber code. * Add some missing Excellon values. Tests are not entirely updated. --- gerber/excellon_statements.py | 114 +++++++++++++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 28 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index feeda44..c4f4015 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -28,7 +28,8 @@ __all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt', 'CommentStmt', 'HeaderBeginStmt', 'HeaderEndStmt', 'RewindStopStmt', 'EndOfProgramStmt', 'UnitStmt', 'IncrementalModeStmt', 'VersionStmt', 'FormatStmt', 'LinkToolStmt', - 'MeasuringModeStmt', 'UnknownStmt', + 'MeasuringModeStmt', 'RouteModeStmt', 'DrillModeStmt', 'AbsoluteModeStmt', + 'UnknownStmt', ] @@ -39,7 +40,7 @@ class ExcellonStatement(object): def from_excellon(cls, line): pass - def to_excellon(self): + def to_excellon(self, settings=None): pass @@ -156,10 +157,10 @@ class ExcellonTool(ExcellonStatement): self.depth_offset = kwargs.get('depth_offset') self.hit_count = 0 - def to_excellon(self): + def to_excellon(self, settings=None): fmt = self.settings.format zs = self.settings.format - stmt = 'T%d' % self.number + 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: @@ -177,12 +178,20 @@ class ExcellonTool(ExcellonStatement): stmt += 'Z%s' % write_gerber_value(self.depth_offset, fmt, zs) return stmt + def to_inch(self): + if self.diameter is not None: + self.diameter = self.diameter / 25.4 + + def to_metric(self): + if self.diameter is not None: + self.diameter = self.diameter * 25.4 + def _hit(self): self.hit_count += 1 def __repr__(self): unit = 'in.' if self.settings.units == 'inch' else 'mm' - return '' % (self.number, self.diameter, unit) + return '' % (self.number, self.diameter, unit) class ToolSelectionStmt(ExcellonStatement): @@ -215,7 +224,7 @@ class ToolSelectionStmt(ExcellonStatement): self.tool = tool self.compensation_index = compensation_index - def to_excellon(self): + def to_excellon(self, settings=None): stmt = 'T%02d' % self.tool if self.compensation_index is not None: stmt += '%02d' % self.compensation_index @@ -225,33 +234,51 @@ class ToolSelectionStmt(ExcellonStatement): class CoordinateStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line, nformat=(2, 5), zero_suppression='trailing'): + def from_excellon(cls, 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], nformat, - zero_suppression) + x_coord = parse_gerber_value(splitline[0], settings.format, settings.zero_suppression) if len(splitline) == 2: - y_coord = parse_gerber_value(splitline[1], nformat, - zero_suppression) + y_coord = parse_gerber_value(splitline[1], settings.format, settings.zero_suppression) else: - y_coord = parse_gerber_value(line.strip(' Y'), nformat, - zero_suppression) + y_coord = parse_gerber_value(line.strip(' Y'), settings.format, settings.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): + def to_excellon(self, settings): stmt = '' if self.x is not None: - stmt += 'X%s' % write_gerber_value(self.x) + 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) + stmt += 'Y%s' % write_gerber_value(self.y, settings.format, settings.zero_suppression) return stmt + def to_inch(self): + if self.x is not None: + self.x = self.x / 25.4 + if self.y is not None: + self.y = self.y / 25.4 + + def to_metric(self): + if self.x is not None: + self.x = self.x * 25.4 + if self.y is not None: + self.y = self.y * 25.4 + + def __str__(self): + coord_str = '' + if self.x is not None: + coord_str += 'X: %f ' % self.x + if self.y is not None: + coord_str += 'Y: %f ' % self.y + + return '' % coord_str + class CommentStmt(ExcellonStatement): @@ -262,7 +289,7 @@ class CommentStmt(ExcellonStatement): def __init__(self, comment): self.comment = comment - def to_excellon(self): + def to_excellon(self, settings=None): return ';%s' % self.comment @@ -271,7 +298,7 @@ class HeaderBeginStmt(ExcellonStatement): def __init__(self): pass - def to_excellon(self): + def to_excellon(self, settings=None): return 'M48' @@ -280,7 +307,7 @@ class HeaderEndStmt(ExcellonStatement): def __init__(self): pass - def to_excellon(self): + def to_excellon(self, settings=None): return 'M95' @@ -289,17 +316,21 @@ class RewindStopStmt(ExcellonStatement): def __init__(self): pass - def to_excellon(self): + def to_excellon(self, settings=None): return '%' class EndOfProgramStmt(ExcellonStatement): + @classmethod + def from_excellon(cls, line): + return cls() + def __init__(self, x=None, y=None): self.x = x self.y = y - def to_excellon(self): + def to_excellon(self, settings=None): stmt = 'M30' if self.x is not None: stmt += 'X%s' % write_gerber_value(self.x) @@ -320,7 +351,7 @@ class UnitStmt(ExcellonStatement): self.units = units.lower() self.zero_suppression = zero_suppression - def to_excellon(self): + def to_excellon(self, settings=None): stmt = '%s,%s' % ('INCH' if self.units == 'inch' else 'METRIC', 'LZ' if self.zero_suppression == 'trailing' else 'TZ') @@ -338,7 +369,7 @@ class IncrementalModeStmt(ExcellonStatement): raise ValueError('Mode may be "on" or "off"') self.mode = mode - def to_excellon(self): + def to_excellon(self, settings=None): return 'ICI,%s' % ('OFF' if self.mode == 'off' else 'ON') @@ -355,7 +386,7 @@ class VersionStmt(ExcellonStatement): raise ValueError('Valid versions are 1 or 2') self.version = version - def to_excellon(self): + def to_excellon(self, settings=None): return 'VER,%d' % self.version @@ -372,7 +403,7 @@ class FormatStmt(ExcellonStatement): raise ValueError('Valid formats are 1 or 2') self.format = format - def to_excellon(self): + def to_excellon(self, settings=None): return 'FMAT,%d' % self.format @@ -386,7 +417,7 @@ class LinkToolStmt(ExcellonStatement): def __init__(self, linked_tools): self.linked_tools = [int(x) for x in linked_tools] - def to_excellon(self): + def to_excellon(self, settings=None): return '/'.join([str(x) for x in self.linked_tools]) @@ -404,10 +435,37 @@ class MeasuringModeStmt(ExcellonStatement): raise ValueError('units must be "inch" or "metric"') self.units = units - def to_excellon(self): + def to_excellon(self, settings=None): return 'M72' if self.units == 'inch' else 'M71' +class RouteModeStmt(ExcellonStatement): + + def __init__(self): + pass + + def to_excellon(self, settings=None): + return 'G00' + + +class DrillModeStmt(ExcellonStatement): + + def __init__(self): + pass + + def to_excellon(self, settings=None): + return 'G05' + + +class AbsoluteModeStmt(ExcellonStatement): + + def __init__(self): + pass + + def to_excellon(self, settings=None): + return 'G90' + + class UnknownStmt(ExcellonStatement): @classmethod @@ -417,7 +475,7 @@ class UnknownStmt(ExcellonStatement): def __init__(self, stmt): self.stmt = stmt - def to_excellon(self): + def to_excellon(self, settings=None): return self.stmt -- cgit From 0f36084aadc85670b96ca63a8258d18db4b18cf8 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Thu, 15 Jan 2015 05:01:40 -0200 Subject: Add Repeat Hole Stmt and fix UnitStmt parsing * Repeat hole support (with no real parsing, just a copy) * Fix UnitStmt to works even when a ,TZ or ,LZ information is not provided. --- gerber/excellon_statements.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index c4f4015..02bb923 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -29,7 +29,7 @@ __all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt', 'RewindStopStmt', 'EndOfProgramStmt', 'UnitStmt', 'IncrementalModeStmt', 'VersionStmt', 'FormatStmt', 'LinkToolStmt', 'MeasuringModeStmt', 'RouteModeStmt', 'DrillModeStmt', 'AbsoluteModeStmt', - 'UnknownStmt', + 'RepeatHoleStmt', 'UnknownStmt', ] @@ -280,6 +280,22 @@ class CoordinateStmt(ExcellonStatement): return '' % coord_str +class RepeatHoleStmt(ExcellonStatement): + + @classmethod + def from_excellon(cls, line, settings): + return cls(line) + + def __init__(self, line): + self.line = line + + def to_excellon(self, settings): + return self.line + + def __str__(self): + return '' % self.line + + class CommentStmt(ExcellonStatement): @classmethod @@ -478,6 +494,9 @@ class UnknownStmt(ExcellonStatement): def to_excellon(self, settings=None): return self.stmt + def __str__(self): + return "" % self.stmt + def pairwise(iterator): """ Iterate over list taking two elements at a time. -- cgit From b495d51354eff7b858dbbd41740865eba7f39100 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 25 Jan 2015 14:19:48 -0500 Subject: Changed zeros/zero suppression conventions to match file format specs --- gerber/excellon_statements.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 02bb923..71009d8 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -360,16 +360,16 @@ 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) + zeros = 'leading' if 'LZ' in line else 'trailing' + return cls(units, zeros) - def __init__(self, units='inch', zero_suppression='trailing'): + def __init__(self, units='inch', zeros='leading'): self.units = units.lower() - self.zero_suppression = zero_suppression + self.zeros = zeros def to_excellon(self, settings=None): stmt = '%s,%s' % ('INCH' if self.units == 'inch' else 'METRIC', - 'LZ' if self.zero_suppression == 'trailing' + 'LZ' if self.zeros == 'leading' else 'TZ') return stmt -- cgit From 5cf1fa74b42eb8feaab23078bef6f31f6d647c33 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 15 Feb 2015 02:20:02 -0500 Subject: Tests and bugfixes --- gerber/excellon_statements.py | 61 ++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 18 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 71009d8..a56c4a5 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -29,7 +29,7 @@ __all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt', 'RewindStopStmt', 'EndOfProgramStmt', 'UnitStmt', 'IncrementalModeStmt', 'VersionStmt', 'FormatStmt', 'LinkToolStmt', 'MeasuringModeStmt', 'RouteModeStmt', 'DrillModeStmt', 'AbsoluteModeStmt', - 'RepeatHoleStmt', 'UnknownStmt', + 'RepeatHoleStmt', 'UnknownStmt', 'ExcellonStatement' ] @@ -38,10 +38,10 @@ class ExcellonStatement(object): """ @classmethod def from_excellon(cls, line): - pass + raise NotImplementedError('`from_excellon` must be implemented in a subclass') def to_excellon(self, settings=None): - pass + raise NotImplementedError('`to_excellon` must be implemented in a subclass') class ExcellonTool(ExcellonStatement): @@ -144,7 +144,7 @@ class ExcellonTool(ExcellonStatement): tool : ExcellonTool An ExcellonTool initialized with the parameters in tool_dict. """ - return cls(settings, tool_dict) + return cls(settings, **tool_dict) def __init__(self, settings, **kwargs): self.settings = settings @@ -159,7 +159,7 @@ class ExcellonTool(ExcellonStatement): def to_excellon(self, settings=None): fmt = self.settings.format - zs = self.settings.format + zs = self.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) @@ -171,7 +171,7 @@ class ExcellonTool(ExcellonStatement): if self.rpm < 100000.: stmt += 'S%s' % write_gerber_value(self.rpm / 1000., fmt, zs) else: - stmt += 'S%g' % self.rpm / 1000. + 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: @@ -191,7 +191,8 @@ class ExcellonTool(ExcellonStatement): def __repr__(self): unit = 'in.' if self.settings.units == 'inch' else 'mm' - return '' % (self.number, self.diameter, unit) + fmtstr = '' % self.settings.format + return fmtstr % (self.number, self.diameter, unit) class ToolSelectionStmt(ExcellonStatement): @@ -273,9 +274,9 @@ class CoordinateStmt(ExcellonStatement): def __str__(self): coord_str = '' if self.x is not None: - coord_str += 'X: %f ' % self.x + coord_str += 'X: %g ' % self.x if self.y is not None: - coord_str += 'Y: %f ' % self.y + coord_str += 'Y: %g ' % self.y return '' % coord_str @@ -284,16 +285,32 @@ class RepeatHoleStmt(ExcellonStatement): @classmethod def from_excellon(cls, line, settings): - return cls(line) - - def __init__(self, line): - self.line = line + 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) + return cls(count, xdelta, ydelta) + + def __init__(self, count, xdelta=None, ydelta=None): + self.count = count + self.xdelta = xdelta + self.ydelta = ydelta def to_excellon(self, settings): - return self.line + stmt = 'R%d' % self.count + if self.xdelta is not None: + stmt += 'X%s' % write_gerber_value(self.xdelta, settings.format, settings.zero_suppression) + if self.ydelta is not None: + stmt += 'Y%s' % write_gerber_value(self.ydelta, settings.format, settings.zero_suppression) + return stmt def __str__(self): - return '' % self.line + return '' % self.count class CommentStmt(ExcellonStatement): @@ -339,8 +356,16 @@ class RewindStopStmt(ExcellonStatement): class EndOfProgramStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line): - return cls() + def from_excellon(cls, line, settings): + 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) + return cls(x, y) def __init__(self, x=None, y=None): self.x = x @@ -495,7 +520,7 @@ class UnknownStmt(ExcellonStatement): return self.stmt def __str__(self): - return "" % self.stmt + return "" % self.stmt def pairwise(iterator): -- cgit From bfe14841604b6be403e7123e8b6667b1f0aff6f6 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sun, 15 Feb 2015 03:29:47 -0500 Subject: Add cairo example code, and use example-generated image in readme --- gerber/excellon_statements.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index a56c4a5..7e2772c 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -296,16 +296,16 @@ class RepeatHoleStmt(ExcellonStatement): if stmt['ydelta'] is not '' else None) return cls(count, xdelta, ydelta) - def __init__(self, count, xdelta=None, ydelta=None): + def __init__(self, count, xdelta=0.0, ydelta=0.0): 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: + if self.xdelta != 0.0: stmt += 'X%s' % write_gerber_value(self.xdelta, settings.format, settings.zero_suppression) - if self.ydelta is not None: + if self.ydelta != 0.0: stmt += 'Y%s' % write_gerber_value(self.ydelta, settings.format, settings.zero_suppression) return stmt -- cgit From 288ac27084b47166ac662402ea340d0aa25d8f56 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 18 Feb 2015 04:31:23 -0500 Subject: Get unit conversion working for Gerber/Excellon files Started operations module for file operations/transforms --- gerber/excellon_statements.py | 102 ++++++++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 23 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 7e2772c..99f7d46 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -21,16 +21,19 @@ Excellon Statements """ -from .utils import parse_gerber_value, write_gerber_value, decimal_string import re +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', 'DrillModeStmt', 'AbsoluteModeStmt', - 'RepeatHoleStmt', 'UnknownStmt', 'ExcellonStatement' - ] + 'MeasuringModeStmt', 'RouteModeStmt', 'DrillModeStmt', + 'AbsoluteModeStmt', 'RepeatHoleStmt', 'UnknownStmt', + 'ExcellonStatement',] class ExcellonStatement(object): @@ -38,11 +41,21 @@ class ExcellonStatement(object): """ @classmethod def from_excellon(cls, line): - raise NotImplementedError('`from_excellon` must be implemented in a subclass') + raise NotImplementedError('from_excellon must be implemented in a ' + 'subclass') def to_excellon(self, settings=None): - raise NotImplementedError('`to_excellon` must be implemented in a subclass') + raise NotImplementedError('to_excellon must be implemented in a ' + 'subclass') + + def to_inch(self): + pass + + def to_metric(self): + pass + def __eq__(self, other): + return self.__dict__ == other.__dict__ class ExcellonTool(ExcellonStatement): """ Excellon Tool class @@ -179,12 +192,17 @@ class ExcellonTool(ExcellonStatement): return stmt def to_inch(self): - if self.diameter is not None: - self.diameter = self.diameter / 25.4 + 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.diameter is not None: - self.diameter = self.diameter * 25.4 + 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 @@ -240,11 +258,14 @@ class CoordinateStmt(ExcellonStatement): 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) + 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) + 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) + y_coord = parse_gerber_value(line.strip(' Y'), settings.format, + settings.zero_suppression) return cls(x_coord, y_coord) def __init__(self, x=None, y=None): @@ -254,22 +275,24 @@ class CoordinateStmt(ExcellonStatement): def to_excellon(self, settings): stmt = '' if self.x is not None: - stmt += 'X%s' % write_gerber_value(self.x, settings.format, settings.zero_suppression) + 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) + stmt += 'Y%s' % write_gerber_value(self.y, settings.format, + settings.zero_suppression) return stmt def to_inch(self): if self.x is not None: - self.x = self.x / 25.4 + self.x = inch(self.x) if self.y is not None: - self.y = self.y / 25.4 + self.y = inch(self.y) def to_metric(self): if self.x is not None: - self.x = self.x * 25.4 + self.x = metric(self.x) if self.y is not None: - self.y = self.y * 25.4 + self.y = metric(self.y) def __str__(self): coord_str = '' @@ -285,7 +308,8 @@ class RepeatHoleStmt(ExcellonStatement): @classmethod def from_excellon(cls, line, settings): - match = re.compile(r'R(?P[0-9]*)X?(?P\d*\.?\d*)?Y?(?P\d*\.?\d*)?').match(line) + 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, @@ -304,11 +328,21 @@ class RepeatHoleStmt(ExcellonStatement): def to_excellon(self, settings): stmt = 'R%d' % self.count if self.xdelta != 0.0: - stmt += 'X%s' % write_gerber_value(self.xdelta, settings.format, settings.zero_suppression) + stmt += 'X%s' % write_gerber_value(self.xdelta, settings.format, + settings.zero_suppression) if self.ydelta != 0.0: - stmt += 'Y%s' % write_gerber_value(self.ydelta, settings.format, settings.zero_suppression) + stmt += 'Y%s' % write_gerber_value(self.ydelta, settings.format, + settings.zero_suppression) return stmt + def to_inch(self): + self.xdelta = inch(self.xdelta) + self.ydelta = inch(self.ydelta) + + def to_metric(self): + self.xdelta = metric(self.xdelta) + self.ydelta = metric(self.ydelta) + def __str__(self): return '' % self.count @@ -357,7 +391,8 @@ class EndOfProgramStmt(ExcellonStatement): @classmethod def from_excellon(cls, line, settings): - match = re.compile(r'M30X?(?P\d*\.?\d*)?Y?(?P\d*\.?\d*)?').match(line) + 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) @@ -379,6 +414,17 @@ class EndOfProgramStmt(ExcellonStatement): stmt += 'Y%s' % write_gerber_value(self.y) return stmt + def to_inch(self): + 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.x is not None: + self.x = metric(self.x) + if self.y is not None: + self.y = metric(self.y) class UnitStmt(ExcellonStatement): @@ -398,6 +444,11 @@ class UnitStmt(ExcellonStatement): else 'TZ') return stmt + def to_inch(self): + self.units = 'inch' + + def to_metric(self): + self.units = 'metric' class IncrementalModeStmt(ExcellonStatement): @@ -479,6 +530,11 @@ class MeasuringModeStmt(ExcellonStatement): 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): -- cgit From e71d7a24b5be3e68d36494869595eec934db4bd2 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 18 Feb 2015 21:14:30 -0500 Subject: Python 3 tests passing --- gerber/excellon_statements.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 99f7d46..356a96b 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -586,4 +586,4 @@ def pairwise(iterator): """ itr = iter(iterator) while True: - yield tuple([itr.next() for i in range(2)]) + yield tuple([next(itr) for i in range(2)]) -- cgit From 5966d7830bda7f37ed5ddcc1bfccb93e7f780eaa Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Wed, 18 Feb 2015 23:13:23 -0500 Subject: Add offset operation --- gerber/excellon_statements.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 356a96b..83a96a0 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -54,6 +54,9 @@ class ExcellonStatement(object): def to_metric(self): pass + def offset(self, x_offset=0, y_offset=0): + pass + def __eq__(self, other): return self.__dict__ == other.__dict__ @@ -294,6 +297,12 @@ class CoordinateStmt(ExcellonStatement): 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: @@ -426,6 +435,12 @@ class EndOfProgramStmt(ExcellonStatement): 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 -- cgit From b9b20a9644ca7b87493ca5786e2a25ecab132b75 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Wed, 18 Mar 2015 03:38:52 -0300 Subject: Fix Excellon repeat command --- gerber/excellon_statements.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 83a96a0..53ea951 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -317,16 +317,16 @@ class RepeatHoleStmt(ExcellonStatement): @classmethod def from_excellon(cls, line, settings): - match = re.compile(r'R(?P[0-9]*)X?(?P\d*\.?\d*)?Y?' - '(?P\d*\.?\d*)?').match(line) + 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) + 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) + if stmt['ydelta'] is not '' else None) return cls(count, xdelta, ydelta) def __init__(self, count, xdelta=0.0, ydelta=0.0): @@ -336,24 +336,31 @@ class RepeatHoleStmt(ExcellonStatement): def to_excellon(self, settings): stmt = 'R%d' % self.count - if self.xdelta != 0.0: + 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 != 0.0: + 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): - self.xdelta = inch(self.xdelta) - self.ydelta = inch(self.ydelta) + 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): - self.xdelta = metric(self.xdelta) - self.ydelta = metric(self.ydelta) + 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 + 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): -- cgit From 21d963d244cbc762a736527b25cd8e82ff147f25 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Mon, 27 Apr 2015 03:58:39 -0300 Subject: Allow 3 digits on Excellon tool selection Fritzing uses more than 2 digits for tool in their Excellons. To comply with that, I check specifically for 3 or less digits and use as tool number, more than that we treat as the standard (2 for tool and 2 for compensation index) --- gerber/excellon_statements.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 53ea951..95347d1 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -234,9 +234,14 @@ class ToolSelectionStmt(ExcellonStatement): """ line = line[1:] compensation_index = None - tool = int(line[:2]) - if len(line) > 2: + + # 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) def __init__(self, tool, compensation_index=None): -- cgit From 8ec3077be988681bbbafcef18ea3a2f84dd61b2b Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 16 May 2015 09:45:34 -0400 Subject: Add checks to ensure statement unit conversions are idempotent --- gerber/excellon_statements.py | 80 +++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 30 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 95347d1..31a3c72 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -39,6 +39,9 @@ __all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt', class ExcellonStatement(object): """ Excellon Statement abstract base class """ + + units = 'inch' + @classmethod def from_excellon(cls, line): raise NotImplementedError('from_excellon must be implemented in a ' @@ -47,12 +50,11 @@ class ExcellonStatement(object): def to_excellon(self, settings=None): raise NotImplementedError('to_excellon must be implemented in a ' 'subclass') - def to_inch(self): - pass + self.units = 'inch' def to_metric(self): - pass + self.units = 'metric' def offset(self, x_offset=0, y_offset=0): pass @@ -274,7 +276,9 @@ class CoordinateStmt(ExcellonStatement): else: y_coord = parse_gerber_value(line.strip(' Y'), settings.format, settings.zero_suppression) - return cls(x_coord, y_coord) + c = cls(x_coord, y_coord) + c.units = settings.units + return c def __init__(self, x=None, y=None): self.x = x @@ -291,16 +295,20 @@ class CoordinateStmt(ExcellonStatement): return stmt def to_inch(self): - if self.x is not None: - self.x = inch(self.x) - if self.y is not None: - self.y = inch(self.y) + 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.x is not None: - self.x = metric(self.x) - if self.y is not None: - self.y = metric(self.y) + 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: @@ -332,7 +340,9 @@ class RepeatHoleStmt(ExcellonStatement): ydelta = (parse_gerber_value(stmt['ydelta'], settings.format, settings.zero_suppression) if stmt['ydelta'] is not '' else None) - return cls(count, xdelta, ydelta) + c = cls(count, xdelta, ydelta) + c.units = settings.units + return c def __init__(self, count, xdelta=0.0, ydelta=0.0): self.count = count @@ -350,16 +360,20 @@ class RepeatHoleStmt(ExcellonStatement): return stmt def to_inch(self): - if self.xdelta is not None: - self.xdelta = inch(self.xdelta) - if self.ydelta is not None: - self.ydelta = inch(self.ydelta) + 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.xdelta is not None: - self.xdelta = metric(self.xdelta) - if self.ydelta is not None: - self.ydelta = metric(self.ydelta) + 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 '' % ( @@ -421,7 +435,9 @@ class EndOfProgramStmt(ExcellonStatement): y = (parse_gerber_value(stmt['y'], settings.format, settings.zero_suppression) if stmt['y'] is not '' else None) - return cls(x, y) + c = cls(x, y) + c.units = settings.units + return c def __init__(self, x=None, y=None): self.x = x @@ -436,16 +452,20 @@ class EndOfProgramStmt(ExcellonStatement): return stmt def to_inch(self): - if self.x is not None: - self.x = inch(self.x) - if self.y is not None: - self.y = inch(self.y) + 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.x is not None: - self.x = metric(self.x) - if self.y is not None: - self.y = metric(self.y) + 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: -- cgit From 94f3976915d64a77135a1fdc8983085ee8d2e1f9 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 11 Jun 2015 11:20:56 -0400 Subject: Add keys to statements for linking to primitives. Add some API features to ExcellonFile, such as getting a tool path length and changing tool parameters. Excellonfiles write method generates statements based on the drill hits in the hits member, so drill hits in a generated file can be re-ordered by re-ordering the drill hits in ExcellonFile.hits. see #30 --- gerber/excellon_statements.py | 121 ++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 52 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 31a3c72..fa05e53 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -22,7 +22,7 @@ Excellon Statements """ import re - +import uuid from .utils import (parse_gerber_value, write_gerber_value, decimal_string, inch, metric) @@ -40,13 +40,15 @@ class ExcellonStatement(object): """ Excellon Statement abstract base class """ - units = 'inch' - @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') @@ -107,7 +109,7 @@ class ExcellonTool(ExcellonStatement): """ @classmethod - def from_excellon(cls, line, settings): + def from_excellon(cls, line, settings, id=None): """ Create a Tool from an excellon file tool definition line. Parameters @@ -126,6 +128,7 @@ class ExcellonTool(ExcellonStatement): commands = re.split('([BCFHSTZ])', line)[1:] commands = [(command, value) for command, value in pairwise(commands)] args = {} + args['id'] = id nformat = settings.format zero_suppression = settings.zero_suppression for cmd, val in commands: @@ -165,6 +168,8 @@ class ExcellonTool(ExcellonStatement): 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') @@ -221,7 +226,7 @@ class ExcellonTool(ExcellonStatement): class ToolSelectionStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line): + def from_excellon(cls, line, **kwargs): """ Create a ToolSelectionStmt from an excellon file line. Parameters @@ -244,9 +249,10 @@ class ToolSelectionStmt(ExcellonStatement): tool = int(line[:2]) compensation_index = int(line[2:]) - return cls(tool, compensation_index) + return cls(tool, compensation_index, **kwargs) - def __init__(self, tool, compensation_index=None): + 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) @@ -263,7 +269,7 @@ class ToolSelectionStmt(ExcellonStatement): class CoordinateStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line, settings): + def from_excellon(cls, line, settings, **kwargs): x_coord = None y_coord = None if line[0] == 'X': @@ -276,11 +282,12 @@ class CoordinateStmt(ExcellonStatement): else: y_coord = parse_gerber_value(line.strip(' Y'), settings.format, settings.zero_suppression) - c = cls(x_coord, y_coord) + c = cls(x_coord, y_coord, **kwargs) c.units = settings.units return c - def __init__(self, x=None, y=None): + def __init__(self, x=None, y=None, **kwargs): + super(CoordinateStmt, self).__init__(**kwargs) self.x = x self.y = y @@ -329,7 +336,7 @@ class CoordinateStmt(ExcellonStatement): class RepeatHoleStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line, settings): + 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() @@ -340,11 +347,12 @@ class RepeatHoleStmt(ExcellonStatement): ydelta = (parse_gerber_value(stmt['ydelta'], settings.format, settings.zero_suppression) if stmt['ydelta'] is not '' else None) - c = cls(count, xdelta, ydelta) + c = cls(count, xdelta, ydelta, **kwargs) c.units = settings.units return c - def __init__(self, count, xdelta=0.0, ydelta=0.0): + 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 @@ -385,10 +393,11 @@ class RepeatHoleStmt(ExcellonStatement): class CommentStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line): + def from_excellon(cls, line, **kwargs): return cls(line.lstrip(';')) - def __init__(self, comment): + def __init__(self, comment, **kwargs): + super(CommentStmt, self).__init__(**kwargs) self.comment = comment def to_excellon(self, settings=None): @@ -397,8 +406,8 @@ class CommentStmt(ExcellonStatement): class HeaderBeginStmt(ExcellonStatement): - def __init__(self): - pass + def __init__(self, **kwargs): + super(HeaderBeginStmt, self).__init__(**kwargs) def to_excellon(self, settings=None): return 'M48' @@ -406,8 +415,8 @@ class HeaderBeginStmt(ExcellonStatement): class HeaderEndStmt(ExcellonStatement): - def __init__(self): - pass + def __init__(self, **kwargs): + super(HeaderEndStmt, self).__init__(**kwargs) def to_excellon(self, settings=None): return 'M95' @@ -415,8 +424,8 @@ class HeaderEndStmt(ExcellonStatement): class RewindStopStmt(ExcellonStatement): - def __init__(self): - pass + def __init__(self, **kwargs): + super(RewindStopStmt, self).__init__(**kwargs) def to_excellon(self, settings=None): return '%' @@ -425,7 +434,7 @@ class RewindStopStmt(ExcellonStatement): class EndOfProgramStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line, settings): + def from_excellon(cls, line, settings, **kwargs): match = re.compile(r'M30X?(?P\d*\.?\d*)?Y?' '(?P\d*\.?\d*)?').match(line) stmt = match.groupdict() @@ -435,11 +444,12 @@ class EndOfProgramStmt(ExcellonStatement): y = (parse_gerber_value(stmt['y'], settings.format, settings.zero_suppression) if stmt['y'] is not '' else None) - c = cls(x, y) + c = cls(x, y, **kwargs) c.units = settings.units return c - def __init__(self, x=None, y=None): + def __init__(self, x=None, y=None, **kwargs): + super(EndOfProgramStmt, self).__init__(**kwargs) self.x = x self.y = y @@ -476,12 +486,13 @@ class EndOfProgramStmt(ExcellonStatement): class UnitStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line): + def from_excellon(cls, line, **kwargs): units = 'inch' if 'INCH' in line else 'metric' zeros = 'leading' if 'LZ' in line else 'trailing' - return cls(units, zeros) + return cls(units, zeros, **kwargs) - def __init__(self, units='inch', zeros='leading'): + def __init__(self, units='inch', zeros='leading', **kwargs): + super(UnitStmt, self).__init__(**kwargs) self.units = units.lower() self.zeros = zeros @@ -500,10 +511,11 @@ class UnitStmt(ExcellonStatement): class IncrementalModeStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line): - return cls('off') if 'OFF' in line else cls('on') + def from_excellon(cls, line, **kwargs): + return cls('off', **kwargs) if 'OFF' in line else cls('on', **kwargs) - def __init__(self, mode='off'): + 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 @@ -515,11 +527,12 @@ class IncrementalModeStmt(ExcellonStatement): class VersionStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line): + def from_excellon(cls, line, **kwargs): version = int(line.split(',')[1]) - return cls(version) + return cls(version, **kwargs) - def __init__(self, version=1): + 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') @@ -532,11 +545,12 @@ class VersionStmt(ExcellonStatement): class FormatStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line): + def from_excellon(cls, line, **kwargs): fmt = int(line.split(',')[1]) - return cls(fmt) + return cls(fmt, **kwargs) - def __init__(self, format=1): + 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') @@ -549,11 +563,12 @@ class FormatStmt(ExcellonStatement): class LinkToolStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line): + def from_excellon(cls, line, **kwargs): linked = [int(tool) for tool in line.split('/')] - return cls(linked) + return cls(linked, **kwargs) - def __init__(self, linked_tools): + 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): @@ -563,12 +578,13 @@ class LinkToolStmt(ExcellonStatement): class MeasuringModeStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line): + 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') if 'M72' in line else cls('metric') + return cls('inch', **kwargs) if 'M72' in line else cls('metric', **kwargs) - def __init__(self, units='inch'): + 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"') @@ -585,8 +601,8 @@ class MeasuringModeStmt(ExcellonStatement): class RouteModeStmt(ExcellonStatement): - def __init__(self): - pass + def __init__(self, **kwargs): + super(RouteModeStmt, self).__init__(**kwargs) def to_excellon(self, settings=None): return 'G00' @@ -594,8 +610,8 @@ class RouteModeStmt(ExcellonStatement): class DrillModeStmt(ExcellonStatement): - def __init__(self): - pass + def __init__(self, **kwargs): + super(DrillModeStmt, self).__init__(**kwargs) def to_excellon(self, settings=None): return 'G05' @@ -603,8 +619,8 @@ class DrillModeStmt(ExcellonStatement): class AbsoluteModeStmt(ExcellonStatement): - def __init__(self): - pass + def __init__(self, **kwargs): + super(AbsoluteModeStmt, self).__init__(**kwargs) def to_excellon(self, settings=None): return 'G90' @@ -613,10 +629,11 @@ class AbsoluteModeStmt(ExcellonStatement): class UnknownStmt(ExcellonStatement): @classmethod - def from_excellon(cls, line): - return cls(line) + def from_excellon(cls, line, **kwargs): + return cls(line, **kwargs) - def __init__(self, stmt): + def __init__(self, stmt, **kwargs): + super(UnknownStmt, self).__init__(**kwargs) self.stmt = stmt def to_excellon(self, settings=None): -- cgit From 9ca75f991a240b0ea233382ff23264a009b0324e Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Fri, 13 Nov 2015 03:31:32 -0200 Subject: Improve Excellon parsing coverage Add some not so used codes that were generating unknown stmt. --- gerber/excellon_statements.py | 109 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 5 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index fa05e53..2be7a05 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -31,24 +31,27 @@ __all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt', 'CommentStmt', 'HeaderBeginStmt', 'HeaderEndStmt', 'RewindStopStmt', 'EndOfProgramStmt', 'UnitStmt', 'IncrementalModeStmt', 'VersionStmt', 'FormatStmt', 'LinkToolStmt', - 'MeasuringModeStmt', 'RouteModeStmt', 'DrillModeStmt', + 'MeasuringModeStmt', 'RouteModeStmt', 'LinearModeStmt', 'DrillModeStmt', 'AbsoluteModeStmt', 'RepeatHoleStmt', 'UnknownStmt', - 'ExcellonStatement',] + 'ExcellonStatement', 'ZAxisRoutPositionStmt', + 'RetractWithClampingStmt', 'RetractWithoutClampingStmt', + 'CutterCompensationOffStmt', 'CutterCompensationLeftStmt', + 'CutterCompensationRightStmt', 'ZAxisInfeedRateStmt'] 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') @@ -266,6 +269,34 @@ class ToolSelectionStmt(ExcellonStatement): 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 @@ -290,9 +321,14 @@ class CoordinateStmt(ExcellonStatement): 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) @@ -431,6 +467,60 @@ class RewindStopStmt(ExcellonStatement): 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 @@ -608,6 +698,15 @@ class RouteModeStmt(ExcellonStatement): 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): -- cgit From 4e838df32ac6d283429e30d2a3151b7d7e8e82b2 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 19 Dec 2015 11:44:12 +0800 Subject: Parse misc nc drill files --- gerber/excellon_statements.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 2be7a05..9499c51 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -36,7 +36,8 @@ __all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt', 'ExcellonStatement', 'ZAxisRoutPositionStmt', 'RetractWithClampingStmt', 'RetractWithoutClampingStmt', 'CutterCompensationOffStmt', 'CutterCompensationLeftStmt', - 'CutterCompensationRightStmt', 'ZAxisInfeedRateStmt'] + 'CutterCompensationRightStmt', 'ZAxisInfeedRateStmt', + 'NextToolSelectionStmt'] class ExcellonStatement(object): @@ -267,7 +268,28 @@ class ToolSelectionStmt(ExcellonStatement): 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): -- cgit From 2e42d1a4705f8cf30a9ae1f987567ce97a39ae11 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Wed, 30 Dec 2015 16:11:25 +0800 Subject: Support KiCad format statement where FMAT,2 is 2:4 with inch --- gerber/excellon_statements.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 9499c51..e10308a 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -670,6 +670,10 @@ class FormatStmt(ExcellonStatement): 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): -- cgit From e84f131720e5952ba0dc20de8729bfd1d7aa0fe7 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 31 Jan 2016 14:17:35 +0800 Subject: Add support for more excellon formats. Dont consider line width when determinging region bounding box --- gerber/excellon_statements.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index e10308a..d2ba233 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -601,14 +601,24 @@ class UnitStmt(ExcellonStatement): def from_excellon(cls, line, **kwargs): units = 'inch' if 'INCH' in line else 'metric' zeros = 'leading' if 'LZ' in line else 'trailing' - return cls(units, zeros, **kwargs) + 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', **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') -- cgit From 0f1d1c3a29017ea82e1f0f7795798405ef346706 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 5 Mar 2016 14:56:08 +0800 Subject: Remove some testing code from gerber writer. More implementation for excellon writer - not working yet --- gerber/excellon_statements.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index d2ba233..66641a1 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -220,6 +220,22 @@ class ExcellonTool(ExcellonStatement): 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.settings.units == other.settings.units) def __repr__(self): unit = 'in.' if self.settings.units == 'inch' else 'mm' @@ -321,6 +337,10 @@ class ZAxisInfeedRateStmt(ExcellonStatement): class CoordinateStmt(ExcellonStatement): + @classmethod + def from_point(cls, point): + return cls(point[0], point[1]) + @classmethod def from_excellon(cls, line, settings, **kwargs): x_coord = None -- cgit From 7053d320f0b3e9404edb4c05710001ea58d44995 Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 13 Mar 2016 14:27:09 +0800 Subject: Better detection of plated tools --- gerber/excellon_statements.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 66641a1..18eaea1 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -111,9 +111,14 @@ class ExcellonTool(ExcellonStatement): 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_excellon(cls, line, settings, id=None): + def from_excellon(cls, line, settings, id=None, plated=None): """ Create a Tool from an excellon file tool definition line. Parameters @@ -150,6 +155,10 @@ class ExcellonTool(ExcellonStatement): 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 + args['plated'] = plated return cls(settings, **args) @classmethod @@ -182,6 +191,8 @@ class ExcellonTool(ExcellonStatement): 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): @@ -235,6 +246,7 @@ class ExcellonTool(ExcellonStatement): 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): -- cgit From d12f6385a434c02677bfbb7b075dd9d8e49627fe Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Thu, 24 Mar 2016 00:10:34 +0800 Subject: Basic rendering of excellon works, but still has issues --- gerber/excellon_statements.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 18eaea1..cabdf6c 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -116,6 +116,21 @@ class ExcellonTool(ExcellonStatement): 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): @@ -196,8 +211,10 @@ class ExcellonTool(ExcellonStatement): self.hit_count = 0 def to_excellon(self, settings=None): - fmt = self.settings.format - zs = self.settings.zero_suppression + 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) -- cgit From acde19f205898188c03a46e5d8a7a6a4d4637a2d Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 26 Mar 2016 15:59:42 +0800 Subject: Support for the G85 slot statement --- gerber/excellon_statements.py | 130 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index cabdf6c..a6a5a5e 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -37,7 +37,7 @@ __all__ = ['ExcellonTool', 'ToolSelectionStmt', 'CoordinateStmt', 'RetractWithClampingStmt', 'RetractWithoutClampingStmt', 'CutterCompensationOffStmt', 'CutterCompensationLeftStmt', 'CutterCompensationRightStmt', 'ZAxisInfeedRateStmt', - 'NextToolSelectionStmt'] + 'NextToolSelectionStmt', 'SlotStmt'] class ExcellonStatement(object): @@ -645,6 +645,12 @@ class EndOfProgramStmt(ExcellonStatement): 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): @@ -827,6 +833,128 @@ class UnknownStmt(ExcellonStatement): 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) + + + 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. -- cgit From 288f49955ecc1a811752aa4b1e713f9954e3033b Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sun, 27 Mar 2016 14:24:11 +0800 Subject: Actually fix the rout rendering to be correct --- gerber/excellon_statements.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index a6a5a5e..c9367b4 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -367,8 +367,12 @@ class ZAxisInfeedRateStmt(ExcellonStatement): class CoordinateStmt(ExcellonStatement): @classmethod - def from_point(cls, point): - return cls(point[0], point[1]) + 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): -- cgit From fca36a29b9a07dc0cb031ae87b72385150b55c3e Mon Sep 17 00:00:00 2001 From: Garret Fick Date: Sat, 4 Jun 2016 14:57:21 +0800 Subject: Handle 85 statements that omit one value --- gerber/excellon_statements.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index c9367b4..7153c82 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -856,6 +856,11 @@ class SlotStmt(ExcellonStatement): (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 -- cgit From 8cd842a41a55ab3d8f558a2e3e198beba7da58a1 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Thu, 21 Jan 2016 03:57:44 -0500 Subject: Manually mere rendering changes --- gerber/excellon_statements.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index 7153c82..ac9c528 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -56,6 +56,7 @@ class ExcellonStatement(object): def to_excellon(self, settings=None): raise NotImplementedError('to_excellon must be implemented in a ' 'subclass') + def to_inch(self): self.units = 'inch' @@ -68,6 +69,7 @@ class ExcellonStatement(object): def __eq__(self, other): return self.__dict__ == other.__dict__ + class ExcellonTool(ExcellonStatement): """ Excellon Tool class @@ -239,7 +241,6 @@ class ExcellonTool(ExcellonStatement): if self.diameter is not None: self.diameter = inch(self.diameter) - def to_metric(self): if self.settings.units != 'metric': self.settings.units = 'metric' @@ -648,6 +649,7 @@ class EndOfProgramStmt(ExcellonStatement): if self.y is not None: self.y += y_offset + class UnitStmt(ExcellonStatement): @classmethod @@ -689,6 +691,7 @@ class UnitStmt(ExcellonStatement): def to_metric(self): self.units = 'metric' + class IncrementalModeStmt(ExcellonStatement): @classmethod @@ -784,6 +787,7 @@ class MeasuringModeStmt(ExcellonStatement): def to_metric(self): self.units = 'metric' + class RouteModeStmt(ExcellonStatement): def __init__(self, **kwargs): -- cgit From 41a7b90dff19b69ef03fed4104ecfdcbfcb21641 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Fri, 18 Nov 2016 07:55:43 -0500 Subject: Excellon update --- gerber/excellon_statements.py | 66 +++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 33 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index ac9c528..bcf35e4 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -113,16 +113,16 @@ class ExcellonTool(ExcellonStatement): 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 @@ -131,7 +131,7 @@ class ExcellonTool(ExcellonStatement): args['plated'] = tool.plated args['retract_rate'] = tool.retract_rate args['rpm'] = tool.rpm - + return cls(None, **args) @classmethod @@ -172,9 +172,9 @@ class ExcellonTool(ExcellonStatement): 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 + # Sometimees we can can parse the plating status args['plated'] = plated return cls(settings, **args) @@ -209,7 +209,7 @@ class ExcellonTool(ExcellonStatement): 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): @@ -249,15 +249,15 @@ class ExcellonTool(ExcellonStatement): 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 @@ -314,12 +314,12 @@ class ToolSelectionStmt(ExcellonStatement): 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. @@ -329,10 +329,10 @@ class NextToolSelectionStmt(ExcellonStatement): 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 @@ -651,11 +651,11 @@ class EndOfProgramStmt(ExcellonStatement): class UnitStmt(ExcellonStatement): - + @classmethod def from_settings(cls, settings): """Create the unit statement from the FileSettings""" - + return cls(settings.units, settings.zeros) @classmethod @@ -742,7 +742,7 @@ class FormatStmt(ExcellonStatement): def to_excellon(self, settings=None): return 'FMAT,%d' % self.format - + @property def format_tuple(self): return (self.format, 6 - self.format) @@ -844,38 +844,38 @@ class UnknownStmt(ExcellonStatement): 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 - + 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, @@ -886,7 +886,7 @@ class SlotStmt(ExcellonStatement): else: y_coord = parse_gerber_value(line.strip(' Y'), settings.format, settings.zero_suppression) - + return (x_coord, y_coord) @@ -907,16 +907,16 @@ class SlotStmt(ExcellonStatement): 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): @@ -959,7 +959,7 @@ class SlotStmt(ExcellonStatement): 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 -- cgit From c08457f7addf2001f07fac5d30091b33b3ddcb0c Mon Sep 17 00:00:00 2001 From: C4dmium <41113988+MarinMikael@users.noreply.github.com> Date: Thu, 1 Aug 2019 22:26:06 +0900 Subject: Update excellon_statements.py --- gerber/excellon_statements.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'gerber/excellon_statements.py') diff --git a/gerber/excellon_statements.py b/gerber/excellon_statements.py index bcf35e4..2c50ef9 100644 --- a/gerber/excellon_statements.py +++ b/gerber/excellon_statements.py @@ -23,6 +23,7 @@ Excellon Statements import re import uuid +import itertools from .utils import (parse_gerber_value, write_gerber_value, decimal_string, inch, metric) @@ -151,8 +152,7 @@ class ExcellonTool(ExcellonStatement): 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)] + commands = pairwise(re.split('([BCFHSTZ])', line)[1:]) args = {} args['id'] = id nformat = settings.format @@ -973,6 +973,7 @@ def pairwise(iterator): e.g. [1, 2, 3, 4, 5, 6] ==> [(1, 2), (3, 4), (5, 6)] """ - itr = iter(iterator) - while True: - yield tuple([next(itr) for i in range(2)]) + 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