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.py | 128 ++----- gerber/excellon_statements.py | 266 +++++++++++++++ gerber/gerber.py | 2 +- gerber/gerber_statements.py | 597 +++++++++++++++++++++++++++++++++ gerber/statements.py | 597 --------------------------------- gerber/tests/test_gerber_statements.py | 96 ++++++ gerber/tests/test_statements.py | 97 ------ 7 files changed, 980 insertions(+), 803 deletions(-) create mode 100644 gerber/excellon_statements.py create mode 100644 gerber/gerber_statements.py delete mode 100644 gerber/statements.py create mode 100644 gerber/tests/test_gerber_statements.py delete mode 100644 gerber/tests/test_statements.py (limited to 'gerber') diff --git a/gerber/excellon.py b/gerber/excellon.py index 7c7d0c6..6ae182b 100755 --- a/gerber/excellon.py +++ b/gerber/excellon.py @@ -22,6 +22,7 @@ Excellon module This module provides Excellon file classes and parsing utilities """ import re +from .excellon_statements import * from .utils import parse_gerber_value from .cnc import CncFile, FileSettings @@ -74,108 +75,6 @@ class ExcellonFile(CncFile): ctx.dump(filename) -class ExcellonTool(object): - """ 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 gile 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 __init__(self, settings, **kwargs): - 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.units = settings.get('units', 'inch') - self.hit_count = 0 - - def _hit(self): - self.hit_count += 1 - - def __repr__(self): - unit = 'in.' if self.units == 'inch' else 'mm' - return '' % (self.number, self.diameter, unit) - - class ExcellonParser(object): """ Excellon File Parser """ @@ -186,6 +85,7 @@ class ExcellonParser(object): self.zero_suppression = 'trailing' self.format = (2, 5) self.state = 'INIT' + self.statements = [] self.tools = {} self.hits = [] self.active_tool = None @@ -206,15 +106,23 @@ class ExcellonParser(object): def _parse(self, line): if 'M48' in line: + self.statements.append(HeaderBeginStmt()) self.state = 'HEADER' - if 'G00' in line: - self.state = 'ROUT' + elif line[0] == '%': + self.statements.append(RewindStopStmt()) + if self.state == 'HEADER': + self.state = 'DRILL' - if 'G05' in line: - self.state = 'DRILL' + elif 'M95' in line: + self.statements.append(HeaderEndStmt()) + if self.state == 'HEADER': + self.state = 'DRILL' + + elif 'G00' in line: + self.state = 'ROUT' - elif line[0] == '%' and self.state == 'HEADER': + elif 'G05' in line: self.state = 'DRILL' if 'INCH' in line or line.strip() == 'M72': @@ -279,11 +187,15 @@ class ExcellonParser(object): notation=self.notation) - def pairwise(iterator): + """ Iterate over list taking two elements at a time. + + e.g. [1, 2, 3, 4, 5, 6] ==> [(1, 2), (3, 4), (5, 6)] + """ itr = iter(iterator) while True: yield tuple([itr.next() for i in range(2)]) + if __name__ == '__main__': p = parser() 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' + + diff --git a/gerber/gerber.py b/gerber/gerber.py index 3f93ed4..9ad5dc9 100644 --- a/gerber/gerber.py +++ b/gerber/gerber.py @@ -26,7 +26,7 @@ This module provides an RS-274-X class and parser import re import json -from .statements import * +from .gerber_statements import * from .cnc import CncFile, FileSettings diff --git a/gerber/gerber_statements.py b/gerber/gerber_statements.py new file mode 100644 index 0000000..5a9d046 --- /dev/null +++ b/gerber/gerber_statements.py @@ -0,0 +1,597 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- +""" +gerber.statements +================= +**Gerber file statement classes** + +""" +from .utils import parse_gerber_value, write_gerber_value, decimal_string + + +__all__ = ['FSParamStmt', 'MOParamStmt', 'IPParamStmt', 'OFParamStmt', + 'LPParamStmt', 'ADParamStmt', 'AMParamStmt', 'INParamStmt', + 'LNParamStmt', 'CoordStmt', 'ApertureStmt', 'CommentStmt', + 'EofStmt', 'UnknownStmt'] + + +class Statement(object): + def __init__(self, type): + self.type = type + + def __str__(self): + s = "<{0} ".format(self.__class__.__name__) + + for key, value in self.__dict__.items(): + s += "{0}={1} ".format(key, value) + + s = s.rstrip() + ">" + return s + + +class ParamStmt(Statement): + def __init__(self, param): + Statement.__init__(self, "PARAM") + self.param = param + + +class FSParamStmt(ParamStmt): + """ FS - Gerber Format Specification Statement + """ + + @classmethod + def from_dict(cls, stmt_dict): + """ + """ + param = stmt_dict.get('param').strip() + zeros = 'leading' if stmt_dict.get('zero') == 'L' else 'trailing' + notation = 'absolute' if stmt_dict.get('notation') == 'A' else 'incremental' + x = map(int, stmt_dict.get('x').strip()) + format = (x[0], x[1]) + return cls(param, zeros, notation, format) + + def __init__(self, param, zero_suppression='leading', + notation='absolute', format=(2, 4)): + """ Initialize FSParamStmt class + + .. note:: + The FS command specifies the format of the coordinate data. It + must only be used once at the beginning of a file. It must be + specified before the first use of coordinate data. + + Parameters + ---------- + param : string + Parameter. + + zero_suppression : string + Zero-suppression mode. May be either 'leading' or 'trailing' + + notation : string + Notation mode. May be either 'absolute' or 'incremental' + + format : tuple (int, int) + Gerber precision format expressed as a tuple containing: + (number of integer-part digits, number of decimal-part digits) + + Returns + ------- + ParamStmt : FSParamStmt + Initialized FSParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.zero_suppression = zero_suppression + self.notation = notation + self.format = format + + def to_gerber(self): + zero_suppression = 'L' if self.zero_suppression == 'leading' else 'T' + notation = 'A' if self.notation == 'absolute' else 'I' + format = ''.join(map(str, self.format)) + return '%FS{0}{1}X{2}Y{3}*%'.format(zero_suppression, notation, + format, format) + + def __str__(self): + return ('' % + (self.format[0], self.format[1], self.zero_suppression, + self.notation)) + + +class MOParamStmt(ParamStmt): + """ MO - Gerber Mode (measurement units) Statement. + """ + + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + mo = 'inch' if stmt_dict.get('mo') == 'IN' else 'metric' + return cls(param, mo) + + def __init__(self, param, mo): + """ Initialize MOParamStmt class + + Parameters + ---------- + param : string + Parameter. + + mo : string + Measurement units. May be either 'inch' or 'metric' + + Returns + ------- + ParamStmt : MOParamStmt + Initialized MOParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.mode = mo + + def to_gerber(self): + mode = 'MM' if self.mode == 'metric' else 'IN' + return '%MO{0}*%'.format(mode) + + def __str__(self): + mode_str = 'millimeters' if self.mode == 'metric' else 'inches' + return ('' % mode_str) + + +class IPParamStmt(ParamStmt): + """ IP - Gerber Image Polarity Statement. (Deprecated) + """ + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + ip = 'positive' if stmt_dict.get('ip') == 'POS' else 'negative' + return cls(param, ip) + + def __init__(self, param, ip): + """ Initialize IPParamStmt class + + Parameters + ---------- + param : string + Parameter string. + + ip : string + Image polarity. May be either'positive' or 'negative' + + Returns + ------- + ParamStmt : IPParamStmt + Initialized IPParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.ip = ip + + def to_gerber(self): + ip = 'POS' if self.ip == 'positive' else 'NEG' + return '%IP{0}*%'.format(ip) + + def __str__(self): + return ('' % self.ip) + + +class OFParamStmt(ParamStmt): + """ OF - Gerber Offset statement (Deprecated) + """ + + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + a = float(stmt_dict.get('a')) + b = float(stmt_dict.get('b')) + return cls(param, a, b) + + def __init__(self, param, a, b): + """ Initialize OFParamStmt class + + Parameters + ---------- + param : string + Parameter + + a : float + Offset along the output device A axis + + b : float + Offset along the output device B axis + + Returns + ------- + ParamStmt : OFParamStmt + Initialized OFParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.a = a + self.b = b + + def to_gerber(self): + ret = '%OF' + if self.a: + ret += 'A' + decimal_string(self.a, precision=6) + if self.b: + ret += 'B' + decimal_string(self.b, precision=6) + return ret + '*%' + + def __str__(self): + offset_str = '' + if self.a: + offset_str += ('X: %f' % self.a) + if self.b: + offset_str += ('Y: %f' % self.b) + return ('' % offset_str) + + +class LPParamStmt(ParamStmt): + """ LP - Gerber Level Polarity statement + """ + + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('lp') + lp = 'clear' if stmt_dict.get('lp') == 'C' else 'dark' + return cls(param, lp) + + def __init__(self, param, lp): + """ Initialize LPParamStmt class + + Parameters + ---------- + param : string + Parameter + + lp : string + Level polarity. May be either 'clear' or 'dark' + + Returns + ------- + ParamStmt : LPParamStmt + Initialized LPParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.lp = lp + + def to_gerber(self, settings): + lp = 'C' if self.lp == 'clear' else 'dark' + return '%LP{0}*%'.format(self.lp) + + def __str__(self): + return '' % self.lp + + +class ADParamStmt(ParamStmt): + """ AD - Gerber Aperture Definition Statement + """ + + @classmethod + def from_dict(cls, stmt_dict): + param = stmt_dict.get('param') + d = int(stmt_dict.get('d')) + shape = stmt_dict.get('shape') + modifiers = stmt_dict.get('modifiers') + if modifiers is not None: + modifiers = [[float(x) for x in m.split('X')] + for m in modifiers.split(',')] + return cls(param, d, shape, modifiers) + + def __init__(self, param, d, shape, modifiers): + """ Initialize ADParamStmt class + + Parameters + ---------- + param : string + Parameter code + + d : int + Aperture D-code + + shape : string + aperture name + + modifiers : list of lists of floats + Shape modifiers + + Returns + ------- + ParamStmt : LPParamStmt + Initialized LPParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.d = d + self.shape = shape + self.modifiers = modifiers + + def to_gerber(self, settings): + return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, + ','.join(['X'.join(e) for e in self.modifiers])) + + def __str__(self): + if self.shape == 'C': + shape = 'circle' + elif self.shape == 'R': + shape = 'rectangle' + elif self.shape == 'O': + shape = 'oblong' + else: + shape = self.shape + + return '' % (self.d, shape) + + +class AMParamStmt(ParamStmt): + """ AM - Aperture Macro Statement + """ + + @classmethod + def from_dict(cls, stmt_dict): + return cls(**stmt_dict) + + def __init__(self, param, name, macro): + """ Initialize AMParamStmt class + + Parameters + ---------- + param : string + Parameter code + + name : string + Aperture macro name + + macro : string + Aperture macro string + + Returns + ------- + ParamStmt : AMParamStmt + Initialized AMParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.name = name + self.macro = macro + + def to_gerber(self): + return '%AM{0}*{1}*%'.format(self.name, self.macro) + + def __str__(self): + return '' % (self.name, macro) + + +class INParamStmt(ParamStmt): + """ IN - Image Name Statement + """ + @classmethod + def from_dict(cls, stmt_dict): + return cls(**stmt_dict) + + def __init__(self, param, name): + """ Initialize INParamStmt class + + Parameters + ---------- + param : string + Parameter code + + name : string + Image name + + Returns + ------- + ParamStmt : INParamStmt + Initialized INParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.name = name + + def to_gerber(self): + return '%IN{0}*%'.format(self.name) + + def __str__(self): + return '' % self.name + + +class LNParamStmt(ParamStmt): + """ LN - Level Name Statement (Deprecated) + """ + @classmethod + def from_dict(cls, stmt_dict): + return cls(**stmt_dict) + + def __init__(self, param, name): + """ Initialize LNParamStmt class + + Parameters + ---------- + param : string + Parameter code + + name : string + Level name + + Returns + ------- + ParamStmt : LNParamStmt + Initialized LNParamStmt class. + + """ + ParamStmt.__init__(self, param) + self.name = name + + def to_gerber(self): + return '%LN{0}*%'.format(self.name) + + def __str__(self): + return '' % self.name + + +class CoordStmt(Statement): + """ Coordinate Data Block + """ + + @classmethod + def from_dict(cls, stmt_dict, settings): + zeros = settings['zero_suppression'] + format = settings['format'] + function = stmt_dict.get('function') + x = stmt_dict.get('x') + y = stmt_dict.get('y') + i = stmt_dict.get('i') + j = stmt_dict.get('j') + op = stmt_dict.get('op') + + if x is not None: + x = parse_gerber_value(stmt_dict.get('x'), + format, zeros) + if y is not None: + y = parse_gerber_value(stmt_dict.get('y'), + format, zeros) + if i is not None: + i = parse_gerber_value(stmt_dict.get('i'), + format, zeros) + if j is not None: + j = parse_gerber_value(stmt_dict.get('j'), + format, zeros) + return cls(function, x, y, i, j, op, settings) + + def __init__(self, function, x, y, i, j, op, settings): + """ Initialize CoordStmt class + + Parameters + ---------- + function : string + function + + x : float + X coordinate + + y : float + Y coordinate + + i : float + Coordinate offset in the X direction + + j : float + Coordinate offset in the Y direction + + op : string + Operation code + + settings : dict {'zero_suppression', 'format'} + Gerber file coordinate format + + Returns + ------- + Statement : CoordStmt + Initialized CoordStmt class. + + """ + Statement.__init__(self, "COORD") + self.zero_suppression = settings['zero_suppression'] + self.format = settings['format'] + self.function = function + self.x = x + self.y = y + self.i = i + self.j = j + self.op = op + + def to_gerber(self): + ret = '' + if self.function: + ret += self.function + if self.x: + ret += 'X{0}'.format(write_gerber_value(self.x, self.zeros, + self.format)) + if self.y: + ret += 'Y{0}'.format(write_gerber_value(self.y, self. zeros, + self.format)) + if self.i: + ret += 'I{0}'.format(write_gerber_value(self.i, self.zeros, + self.format)) + if self.j: + ret += 'J{0}'.format(write_gerber_value(self.j, self.zeros, + self.format)) + if self.op: + ret += self.op + return ret + '*' + + def __str__(self): + coord_str = '' + if self.function: + coord_str += 'Fn: %s ' % self.function + if self.x: + coord_str += 'X: %f ' % self.x + if self.y: + coord_str += 'Y: %f ' % self.y + if self.i: + coord_str += 'I: %f ' % self.i + if self.j: + coord_str += 'J: %f ' % self.j + if self.op: + if self.op == 'D01': + op = 'Lights On' + elif self.op == 'D02': + op = 'Lights Off' + elif self.op == 'D03': + op = 'Flash' + else: + op = self.op + coord_str += 'Op: %s' % op + + return '' % coord_str + + +class ApertureStmt(Statement): + """ Aperture Statement + """ + def __init__(self, d): + Statement.__init__(self, "APERTURE") + self.d = int(d) + + def to_gerber(self): + return 'G54D{0}*'.format(self.d) + + def __str__(self): + return '' % self.d + + +class CommentStmt(Statement): + """ Comment Statment + """ + def __init__(self, comment): + Statement.__init__(self, "COMMENT") + self.comment = comment + + def to_gerber(self): + return 'G04{0}*'.format(self.comment) + + def __str__(self): + return '' % self.comment + + +class EofStmt(Statement): + """ EOF Statement + """ + def __init__(self): + Statement.__init__(self, "EOF") + + def to_gerber(self): + return 'M02*' + + def __str__(self): + return '' + + +class UnknownStmt(Statement): + """ Unknown Statement + """ + def __init__(self, line): + Statement.__init__(self, "UNKNOWN") + self.line = line diff --git a/gerber/statements.py b/gerber/statements.py deleted file mode 100644 index 5a9d046..0000000 --- a/gerber/statements.py +++ /dev/null @@ -1,597 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- -""" -gerber.statements -================= -**Gerber file statement classes** - -""" -from .utils import parse_gerber_value, write_gerber_value, decimal_string - - -__all__ = ['FSParamStmt', 'MOParamStmt', 'IPParamStmt', 'OFParamStmt', - 'LPParamStmt', 'ADParamStmt', 'AMParamStmt', 'INParamStmt', - 'LNParamStmt', 'CoordStmt', 'ApertureStmt', 'CommentStmt', - 'EofStmt', 'UnknownStmt'] - - -class Statement(object): - def __init__(self, type): - self.type = type - - def __str__(self): - s = "<{0} ".format(self.__class__.__name__) - - for key, value in self.__dict__.items(): - s += "{0}={1} ".format(key, value) - - s = s.rstrip() + ">" - return s - - -class ParamStmt(Statement): - def __init__(self, param): - Statement.__init__(self, "PARAM") - self.param = param - - -class FSParamStmt(ParamStmt): - """ FS - Gerber Format Specification Statement - """ - - @classmethod - def from_dict(cls, stmt_dict): - """ - """ - param = stmt_dict.get('param').strip() - zeros = 'leading' if stmt_dict.get('zero') == 'L' else 'trailing' - notation = 'absolute' if stmt_dict.get('notation') == 'A' else 'incremental' - x = map(int, stmt_dict.get('x').strip()) - format = (x[0], x[1]) - return cls(param, zeros, notation, format) - - def __init__(self, param, zero_suppression='leading', - notation='absolute', format=(2, 4)): - """ Initialize FSParamStmt class - - .. note:: - The FS command specifies the format of the coordinate data. It - must only be used once at the beginning of a file. It must be - specified before the first use of coordinate data. - - Parameters - ---------- - param : string - Parameter. - - zero_suppression : string - Zero-suppression mode. May be either 'leading' or 'trailing' - - notation : string - Notation mode. May be either 'absolute' or 'incremental' - - format : tuple (int, int) - Gerber precision format expressed as a tuple containing: - (number of integer-part digits, number of decimal-part digits) - - Returns - ------- - ParamStmt : FSParamStmt - Initialized FSParamStmt class. - - """ - ParamStmt.__init__(self, param) - self.zero_suppression = zero_suppression - self.notation = notation - self.format = format - - def to_gerber(self): - zero_suppression = 'L' if self.zero_suppression == 'leading' else 'T' - notation = 'A' if self.notation == 'absolute' else 'I' - format = ''.join(map(str, self.format)) - return '%FS{0}{1}X{2}Y{3}*%'.format(zero_suppression, notation, - format, format) - - def __str__(self): - return ('' % - (self.format[0], self.format[1], self.zero_suppression, - self.notation)) - - -class MOParamStmt(ParamStmt): - """ MO - Gerber Mode (measurement units) Statement. - """ - - @classmethod - def from_dict(cls, stmt_dict): - param = stmt_dict.get('param') - mo = 'inch' if stmt_dict.get('mo') == 'IN' else 'metric' - return cls(param, mo) - - def __init__(self, param, mo): - """ Initialize MOParamStmt class - - Parameters - ---------- - param : string - Parameter. - - mo : string - Measurement units. May be either 'inch' or 'metric' - - Returns - ------- - ParamStmt : MOParamStmt - Initialized MOParamStmt class. - - """ - ParamStmt.__init__(self, param) - self.mode = mo - - def to_gerber(self): - mode = 'MM' if self.mode == 'metric' else 'IN' - return '%MO{0}*%'.format(mode) - - def __str__(self): - mode_str = 'millimeters' if self.mode == 'metric' else 'inches' - return ('' % mode_str) - - -class IPParamStmt(ParamStmt): - """ IP - Gerber Image Polarity Statement. (Deprecated) - """ - @classmethod - def from_dict(cls, stmt_dict): - param = stmt_dict.get('param') - ip = 'positive' if stmt_dict.get('ip') == 'POS' else 'negative' - return cls(param, ip) - - def __init__(self, param, ip): - """ Initialize IPParamStmt class - - Parameters - ---------- - param : string - Parameter string. - - ip : string - Image polarity. May be either'positive' or 'negative' - - Returns - ------- - ParamStmt : IPParamStmt - Initialized IPParamStmt class. - - """ - ParamStmt.__init__(self, param) - self.ip = ip - - def to_gerber(self): - ip = 'POS' if self.ip == 'positive' else 'NEG' - return '%IP{0}*%'.format(ip) - - def __str__(self): - return ('' % self.ip) - - -class OFParamStmt(ParamStmt): - """ OF - Gerber Offset statement (Deprecated) - """ - - @classmethod - def from_dict(cls, stmt_dict): - param = stmt_dict.get('param') - a = float(stmt_dict.get('a')) - b = float(stmt_dict.get('b')) - return cls(param, a, b) - - def __init__(self, param, a, b): - """ Initialize OFParamStmt class - - Parameters - ---------- - param : string - Parameter - - a : float - Offset along the output device A axis - - b : float - Offset along the output device B axis - - Returns - ------- - ParamStmt : OFParamStmt - Initialized OFParamStmt class. - - """ - ParamStmt.__init__(self, param) - self.a = a - self.b = b - - def to_gerber(self): - ret = '%OF' - if self.a: - ret += 'A' + decimal_string(self.a, precision=6) - if self.b: - ret += 'B' + decimal_string(self.b, precision=6) - return ret + '*%' - - def __str__(self): - offset_str = '' - if self.a: - offset_str += ('X: %f' % self.a) - if self.b: - offset_str += ('Y: %f' % self.b) - return ('' % offset_str) - - -class LPParamStmt(ParamStmt): - """ LP - Gerber Level Polarity statement - """ - - @classmethod - def from_dict(cls, stmt_dict): - param = stmt_dict.get('lp') - lp = 'clear' if stmt_dict.get('lp') == 'C' else 'dark' - return cls(param, lp) - - def __init__(self, param, lp): - """ Initialize LPParamStmt class - - Parameters - ---------- - param : string - Parameter - - lp : string - Level polarity. May be either 'clear' or 'dark' - - Returns - ------- - ParamStmt : LPParamStmt - Initialized LPParamStmt class. - - """ - ParamStmt.__init__(self, param) - self.lp = lp - - def to_gerber(self, settings): - lp = 'C' if self.lp == 'clear' else 'dark' - return '%LP{0}*%'.format(self.lp) - - def __str__(self): - return '' % self.lp - - -class ADParamStmt(ParamStmt): - """ AD - Gerber Aperture Definition Statement - """ - - @classmethod - def from_dict(cls, stmt_dict): - param = stmt_dict.get('param') - d = int(stmt_dict.get('d')) - shape = stmt_dict.get('shape') - modifiers = stmt_dict.get('modifiers') - if modifiers is not None: - modifiers = [[float(x) for x in m.split('X')] - for m in modifiers.split(',')] - return cls(param, d, shape, modifiers) - - def __init__(self, param, d, shape, modifiers): - """ Initialize ADParamStmt class - - Parameters - ---------- - param : string - Parameter code - - d : int - Aperture D-code - - shape : string - aperture name - - modifiers : list of lists of floats - Shape modifiers - - Returns - ------- - ParamStmt : LPParamStmt - Initialized LPParamStmt class. - - """ - ParamStmt.__init__(self, param) - self.d = d - self.shape = shape - self.modifiers = modifiers - - def to_gerber(self, settings): - return '%ADD{0}{1},{2}*%'.format(self.d, self.shape, - ','.join(['X'.join(e) for e in self.modifiers])) - - def __str__(self): - if self.shape == 'C': - shape = 'circle' - elif self.shape == 'R': - shape = 'rectangle' - elif self.shape == 'O': - shape = 'oblong' - else: - shape = self.shape - - return '' % (self.d, shape) - - -class AMParamStmt(ParamStmt): - """ AM - Aperture Macro Statement - """ - - @classmethod - def from_dict(cls, stmt_dict): - return cls(**stmt_dict) - - def __init__(self, param, name, macro): - """ Initialize AMParamStmt class - - Parameters - ---------- - param : string - Parameter code - - name : string - Aperture macro name - - macro : string - Aperture macro string - - Returns - ------- - ParamStmt : AMParamStmt - Initialized AMParamStmt class. - - """ - ParamStmt.__init__(self, param) - self.name = name - self.macro = macro - - def to_gerber(self): - return '%AM{0}*{1}*%'.format(self.name, self.macro) - - def __str__(self): - return '' % (self.name, macro) - - -class INParamStmt(ParamStmt): - """ IN - Image Name Statement - """ - @classmethod - def from_dict(cls, stmt_dict): - return cls(**stmt_dict) - - def __init__(self, param, name): - """ Initialize INParamStmt class - - Parameters - ---------- - param : string - Parameter code - - name : string - Image name - - Returns - ------- - ParamStmt : INParamStmt - Initialized INParamStmt class. - - """ - ParamStmt.__init__(self, param) - self.name = name - - def to_gerber(self): - return '%IN{0}*%'.format(self.name) - - def __str__(self): - return '' % self.name - - -class LNParamStmt(ParamStmt): - """ LN - Level Name Statement (Deprecated) - """ - @classmethod - def from_dict(cls, stmt_dict): - return cls(**stmt_dict) - - def __init__(self, param, name): - """ Initialize LNParamStmt class - - Parameters - ---------- - param : string - Parameter code - - name : string - Level name - - Returns - ------- - ParamStmt : LNParamStmt - Initialized LNParamStmt class. - - """ - ParamStmt.__init__(self, param) - self.name = name - - def to_gerber(self): - return '%LN{0}*%'.format(self.name) - - def __str__(self): - return '' % self.name - - -class CoordStmt(Statement): - """ Coordinate Data Block - """ - - @classmethod - def from_dict(cls, stmt_dict, settings): - zeros = settings['zero_suppression'] - format = settings['format'] - function = stmt_dict.get('function') - x = stmt_dict.get('x') - y = stmt_dict.get('y') - i = stmt_dict.get('i') - j = stmt_dict.get('j') - op = stmt_dict.get('op') - - if x is not None: - x = parse_gerber_value(stmt_dict.get('x'), - format, zeros) - if y is not None: - y = parse_gerber_value(stmt_dict.get('y'), - format, zeros) - if i is not None: - i = parse_gerber_value(stmt_dict.get('i'), - format, zeros) - if j is not None: - j = parse_gerber_value(stmt_dict.get('j'), - format, zeros) - return cls(function, x, y, i, j, op, settings) - - def __init__(self, function, x, y, i, j, op, settings): - """ Initialize CoordStmt class - - Parameters - ---------- - function : string - function - - x : float - X coordinate - - y : float - Y coordinate - - i : float - Coordinate offset in the X direction - - j : float - Coordinate offset in the Y direction - - op : string - Operation code - - settings : dict {'zero_suppression', 'format'} - Gerber file coordinate format - - Returns - ------- - Statement : CoordStmt - Initialized CoordStmt class. - - """ - Statement.__init__(self, "COORD") - self.zero_suppression = settings['zero_suppression'] - self.format = settings['format'] - self.function = function - self.x = x - self.y = y - self.i = i - self.j = j - self.op = op - - def to_gerber(self): - ret = '' - if self.function: - ret += self.function - if self.x: - ret += 'X{0}'.format(write_gerber_value(self.x, self.zeros, - self.format)) - if self.y: - ret += 'Y{0}'.format(write_gerber_value(self.y, self. zeros, - self.format)) - if self.i: - ret += 'I{0}'.format(write_gerber_value(self.i, self.zeros, - self.format)) - if self.j: - ret += 'J{0}'.format(write_gerber_value(self.j, self.zeros, - self.format)) - if self.op: - ret += self.op - return ret + '*' - - def __str__(self): - coord_str = '' - if self.function: - coord_str += 'Fn: %s ' % self.function - if self.x: - coord_str += 'X: %f ' % self.x - if self.y: - coord_str += 'Y: %f ' % self.y - if self.i: - coord_str += 'I: %f ' % self.i - if self.j: - coord_str += 'J: %f ' % self.j - if self.op: - if self.op == 'D01': - op = 'Lights On' - elif self.op == 'D02': - op = 'Lights Off' - elif self.op == 'D03': - op = 'Flash' - else: - op = self.op - coord_str += 'Op: %s' % op - - return '' % coord_str - - -class ApertureStmt(Statement): - """ Aperture Statement - """ - def __init__(self, d): - Statement.__init__(self, "APERTURE") - self.d = int(d) - - def to_gerber(self): - return 'G54D{0}*'.format(self.d) - - def __str__(self): - return '' % self.d - - -class CommentStmt(Statement): - """ Comment Statment - """ - def __init__(self, comment): - Statement.__init__(self, "COMMENT") - self.comment = comment - - def to_gerber(self): - return 'G04{0}*'.format(self.comment) - - def __str__(self): - return '' % self.comment - - -class EofStmt(Statement): - """ EOF Statement - """ - def __init__(self): - Statement.__init__(self, "EOF") - - def to_gerber(self): - return 'M02*' - - def __str__(self): - return '' - - -class UnknownStmt(Statement): - """ Unknown Statement - """ - def __init__(self, line): - Statement.__init__(self, "UNKNOWN") - self.line = line diff --git a/gerber/tests/test_gerber_statements.py b/gerber/tests/test_gerber_statements.py new file mode 100644 index 0000000..121aa42 --- /dev/null +++ b/gerber/tests/test_gerber_statements.py @@ -0,0 +1,96 @@ +#! /usr/bin/env python +# -*- coding: utf-8 -*- + +# Author: Hamilton Kibbe + +from .tests import * +from ..gerber_statements import * + + +def test_FSParamStmt_factory(): + """ Test FSParamStruct factory correctly handles parameters + """ + stmt = {'param': 'FS', 'zero': 'L', 'notation': 'A', 'x': '27'} + fs = FSParamStmt.from_dict(stmt) + assert_equal(fs.param, 'FS') + assert_equal(fs.zero_suppression, 'leading') + assert_equal(fs.notation, 'absolute') + assert_equal(fs.format, (2, 7)) + + stmt = {'param': 'FS', 'zero': 'T', 'notation': 'I', 'x': '27'} + fs = FSParamStmt.from_dict(stmt) + assert_equal(fs.param, 'FS') + assert_equal(fs.zero_suppression, 'trailing') + assert_equal(fs.notation, 'incremental') + assert_equal(fs.format, (2, 7)) + + +def test_FSParamStmt_dump(): + """ Test FSParamStmt to_gerber() + """ + stmt = {'param': 'FS', 'zero': 'L', 'notation': 'A', 'x': '27'} + fs = FSParamStmt.from_dict(stmt) + assert_equal(fs.to_gerber(), '%FSLAX27Y27*%') + + stmt = {'param': 'FS', 'zero': 'T', 'notation': 'I', 'x': '25'} + fs = FSParamStmt.from_dict(stmt) + assert_equal(fs.to_gerber(), '%FSTIX25Y25*%') + + +def test_MOParamStmt_factory(): + """ Test MOParamStruct factory correctly handles parameters + """ + stmt = {'param': 'MO', 'mo': 'IN'} + mo = MOParamStmt.from_dict(stmt) + assert_equal(mo.param, 'MO') + assert_equal(mo.mode, 'inch') + + stmt = {'param': 'MO', 'mo': 'MM'} + mo = MOParamStmt.from_dict(stmt) + assert_equal(mo.param, 'MO') + assert_equal(mo.mode, 'metric') + + +def test_MOParamStmt_dump(): + """ Test MOParamStmt to_gerber() + """ + stmt = {'param': 'MO', 'mo': 'IN'} + mo = MOParamStmt.from_dict(stmt) + assert_equal(mo.to_gerber(), '%MOIN*%') + + stmt = {'param': 'MO', 'mo': 'MM'} + mo = MOParamStmt.from_dict(stmt) + assert_equal(mo.to_gerber(), '%MOMM*%') + + +def test_IPParamStmt_factory(): + """ Test IPParamStruct factory correctly handles parameters + """ + stmt = {'param': 'IP', 'ip': 'POS'} + ip = IPParamStmt.from_dict(stmt) + assert_equal(ip.ip, 'positive') + + stmt = {'param': 'IP', 'ip': 'NEG'} + ip = IPParamStmt.from_dict(stmt) + assert_equal(ip.ip, 'negative') + + +def test_IPParamStmt_dump(): + """ Test IPParamStmt to_gerber() + """ + stmt = {'param': 'IP', 'ip': 'POS'} + ip = IPParamStmt.from_dict(stmt) + assert_equal(ip.to_gerber(), '%IPPOS*%') + + stmt = {'param': 'IP', 'ip': 'NEG'} + ip = IPParamStmt.from_dict(stmt) + assert_equal(ip.to_gerber(), '%IPNEG*%') + + +def test_OFParamStmt_dump(): + """ Test OFParamStmt to_gerber() + """ + stmt = {'param': 'OF', 'a': '0.1234567', 'b': '0.1234567'} + of = OFParamStmt.from_dict(stmt) + assert_equal(of.to_gerber(), '%OFA0.123456B0.123456*%') + diff --git a/gerber/tests/test_statements.py b/gerber/tests/test_statements.py deleted file mode 100644 index 4560521..0000000 --- a/gerber/tests/test_statements.py +++ /dev/null @@ -1,97 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -# Author: Hamilton Kibbe - -from .tests import * -from ..statements import * - - -def test_FSParamStmt_factory(): - """ Test FSParamStruct factory correctly handles parameters - """ - stmt = {'param': 'FS', 'zero': 'L', 'notation': 'A', 'x': '27'} - fs = FSParamStmt.from_dict(stmt) - assert_equal(fs.param, 'FS') - assert_equal(fs.zero_suppression, 'leading') - assert_equal(fs.notation, 'absolute') - assert_equal(fs.format, (2, 7)) - - stmt = {'param': 'FS', 'zero': 'T', 'notation': 'I', 'x': '27'} - fs = FSParamStmt.from_dict(stmt) - assert_equal(fs.param, 'FS') - assert_equal(fs.zero_suppression, 'trailing') - assert_equal(fs.notation, 'incremental') - assert_equal(fs.format, (2, 7)) - - -def test_FSParamStmt_dump(): - """ Test FSParamStmt to_gerber() - """ - stmt = {'param': 'FS', 'zero': 'L', 'notation': 'A', 'x': '27'} - fs = FSParamStmt.from_dict(stmt) - assert_equal(fs.to_gerber(), '%FSLAX27Y27*%') - - stmt = {'param': 'FS', 'zero': 'T', 'notation': 'I', 'x': '25'} - fs = FSParamStmt.from_dict(stmt) - assert_equal(fs.to_gerber(), '%FSTIX25Y25*%') - - -def test_MOParamStmt_factory(): - """ Test MOParamStruct factory correctly handles parameters - """ - stmt = {'param': 'MO', 'mo': 'IN'} - mo = MOParamStmt.from_dict(stmt) - assert_equal(mo.param, 'MO') - assert_equal(mo.mode, 'inch') - - stmt = {'param': 'MO', 'mo': 'MM'} - mo = MOParamStmt.from_dict(stmt) - assert_equal(mo.param, 'MO') - assert_equal(mo.mode, 'metric') - - -def test_MOParamStmt_dump(): - """ Test MOParamStmt to_gerber() - """ - stmt = {'param': 'MO', 'mo': 'IN'} - mo = MOParamStmt.from_dict(stmt) - assert_equal(mo.to_gerber(), '%MOIN*%') - - stmt = {'param': 'MO', 'mo': 'MM'} - mo = MOParamStmt.from_dict(stmt) - assert_equal(mo.to_gerber(), '%MOMM*%') - - -def test_IPParamStmt_factory(): - """ Test IPParamStruct factory correctly handles parameters - """ - stmt = {'param': 'IP', 'ip': 'POS'} - ip = IPParamStmt.from_dict(stmt) - assert_equal(ip.ip, 'positive') - - stmt = {'param': 'IP', 'ip': 'NEG'} - ip = IPParamStmt.from_dict(stmt) - assert_equal(ip.ip, 'negative') - - -def test_IPParamStmt_dump(): - """ Test IPParamStmt to_gerber() - """ - stmt = {'param': 'IP', 'ip': 'POS'} - ip = IPParamStmt.from_dict(stmt) - assert_equal(ip.to_gerber(), '%IPPOS*%') - - stmt = {'param': 'IP', 'ip': 'NEG'} - ip = IPParamStmt.from_dict(stmt) - assert_equal(ip.to_gerber(), '%IPNEG*%') - - -def test_OFParamStmt_dump(): - """ Test OFParamStmt to_gerber() - """ - stmt = {'param': 'OF', 'a': '0.1234567', 'b': '0.1234567'} - of = OFParamStmt.from_dict(stmt) - assert_equal(of.to_gerber(), '%OFA0.123456B0.123456*%') - - -- cgit