From 35f00638765d5c2d0e5162bb47b7a12b61136a72 Mon Sep 17 00:00:00 2001 From: Paulo Henrique Silva Date: Wed, 18 Dec 2013 06:16:32 -0200 Subject: Significantly improved parsing. * Parsing complete for most of the non-deprecated gerber specification. * Initial evaluation machine in place, but no useful result yet. --- gerber/gerber.py | 345 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 302 insertions(+), 43 deletions(-) diff --git a/gerber/gerber.py b/gerber/gerber.py index 882b2e8..339c054 100644 --- a/gerber/gerber.py +++ b/gerber/gerber.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- import re -import string def red(s): @@ -15,18 +14,114 @@ class Statement: class ParamStmt(Statement): - def __init__(self): - pass + def __init__(self, type): + self.type = type + + +class FSParamStmt(ParamStmt): + def __init__(self, type, zero, notation, x, y): + ParamStmt.__init__(self, type) + self.zero = zero + self.notation = notation + self.x = x + self.y = y + + +class MOParamStmt(ParamStmt): + def __init__(self, type, mo): + ParamStmt.__init__(self, type) + self.mo = mo + + +class IPParamStmt(ParamStmt): + def __init__(self, type, ip): + ParamStmt.__init__(self, type) + self.ip = ip + + +class OFParamStmt(ParamStmt): + def __init__(self, type, a, b): + ParamStmt.__init__(self, type) + self.a = a + self.b = b + + +class LPParamStmt(ParamStmt): + def __init__(self, type, lp): + ParamStmt.__init__(self, type) + self.lp = lp + + +class ADParamStmt(ParamStmt): + def __init__(self, type, d, shape): + ParamStmt.__init__(self, type) + self.d = d + self.shape = shape + + +class ADCircleParamStmt(ADParamStmt): + def __init__(self, type, d, shape, definition): + ADParamStmt.__init__(self, type, d, shape) + self.definition = definition + + +class ADRectParamStmt(ADParamStmt): + def __init__(self, type, d, shape, definition): + ADParamStmt.__init__(self, type, d, shape) + self.definition = definition + + +class ADObroundParamStmt(ADParamStmt): + def __init__(self, type, d, shape, definition): + ADParamStmt.__init__(self, type, d, shape) + self.definition = definition + + +class ADPolyParamStmt(ADParamStmt): + def __init__(self, type, d, shape, definition): + ADParamStmt.__init__(self, type, d, shape) + self.definition = definition + + +class ADMacroParamStmt(ADParamStmt): + def __init__(self, type, d, name, definition): + ADParamStmt.__init__(self, type, d, "M") + self.name = name + self.definition = definition + + +class AMParamStmt(ParamStmt): + def __init__(self, type, name, macro): + ParamStmt.__init__(self, type) + self.name = name + self.macro = macro + + +class INParamStmt(ParamStmt): + def __init__(self, type, name): + ParamStmt.__init__(self, type) + self.name = name + + +class LNParamStmt(ParamStmt): + def __init__(self, type, name): + ParamStmt.__init__(self, type) + self.name = name class CoordStmt(Statement): - def __init__(self): - pass + def __init__(self, function, x, y, i, j, op): + self.function = function + self.x = x + self.y = y + self.i = i + self.j = j + self.op = op class ApertureStmt(Statement): - def __init__(self): - pass + def __init__(self, d): + self.d = int(d) class CommentStmt(Statement): @@ -43,62 +138,129 @@ class UnknownStmt(Statement): self.line = line +IMAGE_POLARITY_POSITIVE = 1 +IMAGE_POLARITY_NEGATIVE = 2 + +LEVEL_POLARITY_DARK = 1 +LEVEL_POLARITY_CLEAR = 2 + +NOTATION_ABSOLUTE = 1 +NOTATION_INCREMENTAL = 2 + + +class GerberCoordFormat: + + def __init__(self, zeroes, x, y): + self.omit_leading_zeroes = True if zeroes == "L" else False + self.omit_trailing_zeroes = True if zeroes == "T" else False + self.x_int_digits, self.x_dec_digits = [int(d) for d in x] + self.y_int_digits, self.y_dec_digits = [int(d) for d in y] + + def resolve(self, x, y): + return x, y + + +class GerberContext: + coord_format = None + coord_notation = NOTATION_ABSOLUTE + + unit = None + + x = 0 + y = 0 + + current_aperture = 0 + interpolation = None + + region_mode = False + quadrant_mode = False + + image_polarity = IMAGE_POLARITY_POSITIVE + level_polarity = LEVEL_POLARITY_DARK + + steps = (1, 1) + repeat = (None, None) + + def __init__(self): + pass + + def set_coord_format(self, zeroes, x, y): + self.coord_format = GerberCoordFormat(zeroes, x, y) + + def set_coord_notation(self, notation): + self.coord_notation = NOTATION_ABSOLUTE if notation == "A" else NOTATION_INCREMENTAL + + def set_image_polarity(self, polarity): + self.image_polarity = IMAGE_POLARITY_POSITIVE if polarity == "POS" else IMAGE_POLARITY_NEGATIVE + + def set_level_polarity(self, polarity): + self.level_polarity = LEVEL_POLARITY_DARK if polarity == "D" else LEVEL_POLARITY_CLEAR + + def move(self, x, y): + self.x = x + self.y = y + + def aperture(self, d): + self.current_aperture = d + + class Gerber: NUMBER = r"[\+-]?\d+" FUNCTION = r"G\d{2}" STRING = r"[a-zA-Z0-9_+\-/!?<>”’(){}.\|&@# :]+" + NAME = "[a-zA-Z_$][a-zA-Z_$0-9]+" COORD_OP = r"D[0]?[123]" - PARAM_STMT = re.compile(r"%.*%") + FS = r"(?PFS)(?P(L|T))(?P(A|I))X(?P[0-7][0-7])Y(?P[0-7][0-7])" + MO = r"(?PMO)(?P(MM|IN))" + IP = r"(?PIP)(?P(POS|NEG))" + LP = r"(?PLP)(?P(D|C))" + AD_CIRCLE = r"(?PADD)(?P\d+)(?PC)(?P.*)" + AD_RECT = r"(?PADD)(?P\d+)(?PR)(?P.*)" + AD_OBROUND = r"(?PADD)(?P\d+)(?PO)(?P.*)" + AD_POLY = r"(?PADD)(?P\d+)(?PP)(?P.*)" + AD_MACRO = r"(?PADD)(?P\d+)(?P[^CROP,].*)(?P.*)".format(name=NAME) + AM = r"(?PAM)(?P{name})\*(?P.*)".format(name=NAME) + + # begin deprecated + OF = r"(?POF)(A(?P[+-]?\d+(.?\d*)))?(B(?P[+-]?\d+(.?\d*)))?" + IN = r"(?PIN)(?P.*)" + LN = r"(?PLN)(?P.*)" + # end deprecated + + PARAMS = (FS, MO, IP, LP, AD_CIRCLE, AD_RECT, AD_OBROUND, AD_MACRO, AD_POLY, AM, OF, IN, LN) + PARAM_STMT = [re.compile(r"%{0}\*%".format(p)) for p in PARAMS] COORD_STMT = re.compile(( - r"(?P{f})?" + r"(?P{function})?" r"(X(?P{number}))?(Y(?P{number}))?" r"(I(?P{number}))?(J(?P{number}))?" - r"(?P{op})?\*".format(number=NUMBER, f=FUNCTION, op=COORD_OP))) + r"(?P{op})?\*".format(number=NUMBER, function=FUNCTION, op=COORD_OP))) - APERTURE_STMT = re.compile(r"(G54)?D\d+\*") + APERTURE_STMT = re.compile(r"(G54)?D(?P\d+)\*") COMMENT_STMT = re.compile(r"G04(?P{string})(\*)?".format(string=STRING)) - EOF_STMT = re.compile(r"M02\*") + EOF_STMT = re.compile(r"(?PM02)\*") def __init__(self): self.tokens = [] + 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, UnknownStmt): - print filename - print red("[INVALID TOKEN]") - print "'%s'" % token.line - - def _match_one(self, expr, data): - match = expr.match(data) - if match is None: - return {} - else: - return match.groupdict() - - def _match_many(self, expr, data): - matches = expr.finditer(data) - if not matches: - return [] - else: - return [match.groupdict() for match in matches] + for token in self._tokenize(data): + self._evaluate(token) - def tokenize(self, data): + def _tokenize(self, data): multiline = None for i, line in enumerate(data): # remove EOL - if multiline: + if multiline: line = multiline + line.strip() else: line = line.strip() @@ -114,23 +276,17 @@ class Gerber: else: multiline = None - # parameter - param = self._match_one(self.PARAM_STMT, line) - if param: - yield ParamStmt() - continue - # coord coords = self._match_many(self.COORD_STMT, line) if coords: for coord in coords: - yield CoordStmt() + yield CoordStmt(**coord) continue # aperture selection aperture = self._match_one(self.APERTURE_STMT, line) if aperture: - yield ApertureStmt() + yield ApertureStmt(**aperture) continue # comment @@ -139,17 +295,120 @@ class Gerber: yield CommentStmt(comment["comment"]) continue + # parameter + param = self._match_one_from_many(self.PARAM_STMT, line) + if param: + if param["type"] == "FS": + yield FSParamStmt(**param) + elif param["type"] == "MO": + yield MOParamStmt(**param) + elif param["type"] == "IP": + yield IPParamStmt(**param) + elif param["type"] == "LP": + yield LPParamStmt(**param) + elif param["type"] == "AD": + if param["shape"] == "C": + yield ADCircleParamStmt(**param) + elif param["shape"] == "R": + yield ADRectParamStmt(**param) + elif param["shape"] == "O": + yield ADObroundParamStmt(**param) + elif param["shape"] == "P": + yield ADPolyParamStmt(**param) + else: + yield ADMacroParamStmt(**param) + elif param["type"] == "AM": + yield AMParamStmt(**param) + elif param["type"] == "OF": + yield OFParamStmt(**param) + elif param["type"] == "IN": + yield INParamStmt(**param) + elif param["type"] == "LN": + yield LNParamStmt(**param) + else: + yield UnknownStmt(line) + + continue + # eof eof = self._match_one(self.EOF_STMT, line) if eof: yield EofStmt() continue + print red("UNKNOWN TOKEN") + print "{0}:'{1}'".format(red(str(i+1)), line) + + if False: + print self.COORD_STMT.pattern + print self.APERTURE_STMT.pattern + print self.COMMENT_STMT.pattern + print self.EOF_STMT.pattern + for i in self.PARAM_STMT: + print i.pattern + yield UnknownStmt(line) + def _match_one(self, expr, data): + match = expr.match(data) + if match is None: + return {} + else: + return match.groupdict() + + def _match_one_from_many(self, exprs, data): + for expr in exprs: + match = expr.match(data) + if match: + return match.groupdict() + + return {} + + def _match_many(self, expr, data): + result = [] + pos = 0 + while True: + match = expr.match(data, pos) + if match: + result.append(match.groupdict()) + pos = match.endpos + else: + break + + return result + + def _evaluate(self, token): + if isinstance(token, (CommentStmt, UnknownStmt, EofStmt)): + return + + elif isinstance(token, ParamStmt): + self._evaluate_param(token) + + elif isinstance(token, CoordStmt): + self._evaluate_coord(token) + + elif isinstance(token, ApertureStmt): + self._evaluate_aperture(token) + + else: + raise Exception("Invalid token to evaluate") + + def _evaluate_param(self, param): + if param.type == "FS": + self.ctx.set_coord_format(param.zero, param.x, param.y) + self.ctx.set_coord_notation(param.notation) + + def _evaluate_coord(self, coord): + self.ctx.move(coord.x, coord.y) + + def _evaluate_aperture(self, aperture): + self.ctx.aperture(aperture.d) + + if __name__ == "__main__": import sys for f in sys.argv[1:]: + print f g = Gerber() g.parse(f) -- cgit