diff options
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | gerber.md | 73 | ||||
-rw-r--r-- | gerber.py | 129 |
3 files changed, 209 insertions, 0 deletions
@@ -1,2 +1,9 @@ gerber-tools ============ + +This hopefully will be a useful set of tools to handle Gerber files in Python. + +Right now we have a working parser and I am working on simple Gerber to SVG converter. + +See gerber.md for some random information regardind Gerber format. + diff --git a/gerber.md b/gerber.md new file mode 100644 index 0000000..865d177 --- /dev/null +++ b/gerber.md @@ -0,0 +1,73 @@ + +# Gerber (RS-274X or Extended Gerber) is a bilevel, resolution independent image format. + +# // graphic objects +# // draw: line segment, thickness, round or square line endings. (solid circle and rectangule apertures only) +# // arc: circular arc, thickness, round endings. (solid circle standard aperture only) +# // flash: replication of a given apertura (shape) +# // region: are defined by a countour (linear/arc segments.) +# +# // draw/arc: can have zero length (just flash the aperture) +# // flash: any aperture can be flashed +# +# // operation codes operates on coordinate data blocks. each operation code is for one coordinate data block pair and vice-versa. +# // D01: stroke an aperture from current point to coordinate pair. region mode off. lights-on move. +# // D02: move current point to this coordinate pair +# // D03: flash current aperture at this coordinate pair. +# +# // graphics state +# // all state controlled by codes and parameters, except current point +# // +# // state fixed? initial value +# // coordinate format fixed undefined +# // unit fixed undefined +# // image polarity fixed positive +# // steps/repeat variable 1,1,-,- +# // level polarity variable dark +# // region mode variable off +# // current aperture variable undefined +# // quadrant mode variable undefined +# // interpolation mode variable undefined +# // current point variable (0,0) +# +# // attributes: metadata, both standard and custom. No change on image. +# +# // G01: linear +# // G04: comment +# // M02: end of file +# // D: select aperture +# // G75: multi quadrant mode (circles) +# // G36: region begin +# // G37: region end +# +# // [G01] [Xnnfffff] [Ynnffff] D01* +# +# // ASCII 32-126, CR LF. +# // * end-of-block +# // % parameer delimiter +# // , field separator +# // <space> only in comments +# // case sensitive +# +# // int: +/- 32 bit signed +# // decimal: +/- digits +# // names: [a-zA-Z_$]{[a-zA-Z_$0-9]+} (255) +# // strings: [a-zA-Z0-9_+-/!?<>”’(){}.\|&@# ]+ (65535) +# +# // data block: end in * +# // statement: one or more data block, if contain parameters starts and end in % (parameter statement) +# // statement: [%]<Data Block>{<Data Block>}[%] +# // statements: function code, coordinate data, parameters +# +# // function code: operation codes (D01..) or code that set state. +# // function codes applies before operation codes act on coordinates +# +# // coordinate data: <Coordinate data>: [X<Number>][Y<Number>][I<Number>][J<Number>](D01|D02|D03) +# // offsets are not modal +# +# // parameter: %Parameter code<required modifiers>[optional modifiers]*% +# // code: 2 characters +# +# // parameters can have line separators: %<Parameter>{{<Line separator>}<Parameter>}% +# +# // function code: (GDM){1}[number], parameters: [AZ]{2}
\ No newline at end of file diff --git a/gerber.py b/gerber.py new file mode 100644 index 0000000..74da114 --- /dev/null +++ b/gerber.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import re + +def red(s): + return '\033[1;31m{0}\033[0;m'.format(s) + +class Statement: + pass + +class ParamStmt(Statement): + pass + +class CoordStmt(Statement): + pass + +class ApertureStmt(Statement): + pass + +class CommentStmt(Statement): + def __init__(self, comment): + self.comment = comment + +class EofStmt(Statement): + pass + +class UnexpectedStmt(Statement): + def __init__(self, line): + self.line = line + +class GerberContext: + x = 0 + y = 0 + +class Gerber: + NUMBER = r"[\+-]?\d+" + FUNCTION = r"G\d{2}" + STRING = r"[a-zA-Z0-9_+-/!?<>”’(){}.\|&@# :]+" + + COORD_OP = r"D[0]?[123]" + + PARAM_STMT = re.compile(r"%.*%") + + COORD_STMT = re.compile(( + r"(?P<f>{f})?" + r"(X(?P<x>{number}))?(Y(?P<y>{number}))?" + r"(I(?P<i>{number}))?(J(?P<j>{number}))?" + r"(?P<op>{op})?\*".format(number=NUMBER, f=FUNCTION, op=COORD_OP))) + + APERTURE_STMT = re.compile(r"(G54)?D\d+\*") + + COMMENT_STMT = re.compile(r"G04(?P<comment>{string})\*".format(string=STRING)) + + EOF_STMT = re.compile(r"M02\*") + + def __init__(self): + self.apertures = {} + self.ctx = GerberContext() + + def parse(self, filename): + + fp = open(filename, "r") + data = fp.readlines() + + self.tokens = list(self.tokenize(data)) + for token in self.tokens: + if isinstance(token, UnexpectedStmt): + print filename + print red("[UNEXPECTED TOKEN]") + print self.COORD_STMT.pattern + print token.line + + def tokenize(self, data): + multiline = None + + for i, line in enumerate(data): + # remove EOL + if multiline: + line = multiline + line.strip() + else: + line = line.strip() + + # deal with multi-line parameters + if line.startswith("%") and not line.endswith("%"): + multiline = line + continue + else: + multiline = None + + # parameter + match = self.PARAM_STMT.match(line) + if match: + yield ParamStmt() + continue + + # coord + matches = self.COORD_STMT.finditer(line) + if matches: + for match in matches: + yield CoordStmt() + continue + + # aperture selection + match = self.APERTURE_STMT.match(line) + if match: + yield ApertureStmt() + continue + + # comment + match = self.COMMENT_STMT.match(line) + if match: + yield CommentStmt(match.groupdict("comment")) + continue + + # eof + match = self.EOF_STMT.match(line) + if match: + yield EofStmt() + continue + + yield UnexpectedStmt(line) + +if __name__ == "__main__": + import sys + + for f in sys.argv[1:]: + g = Gerber() + g.parse(f) |