From 6d2db67e6d0973ce26ce3a6700ca44295f73fea7 Mon Sep 17 00:00:00 2001 From: Hamilton Kibbe Date: Sat, 18 Oct 2014 01:44:51 -0400 Subject: Refactor rendering --- gerber/rs274x.py | 189 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 149 insertions(+), 40 deletions(-) (limited to 'gerber/rs274x.py') diff --git a/gerber/rs274x.py b/gerber/rs274x.py index 4076f77..39693c9 100644 --- a/gerber/rs274x.py +++ b/gerber/rs274x.py @@ -19,14 +19,13 @@ """ -import re +import copy import json +import re from .gerber_statements import * +from .primitives import * from .cam import CamFile, FileSettings - - - def read(filename): """ Read data from filename and return a GerberFile @@ -72,8 +71,9 @@ class GerberFile(CamFile): `bounds` is stored as ((min x, max x), (min y, max y)) """ - def __init__(self, statements, settings, filename=None): - super(GerberFile, self).__init__(statements, settings, filename) + def __init__(self, statements, settings, primitives, filename=None): + super(GerberFile, self).__init__(statements, settings, primitives, filename) + @property def comments(self): @@ -111,22 +111,7 @@ class GerberFile(CamFile): for statement in self.statements: f.write(statement.to_gerber()) - def render(self, ctx, filename=None): - """ Generate image of layer. - Parameters - ---------- - ctx : :class:`GerberContext` - GerberContext subclass used for rendering the image - - filename : string - If provided, the rendered image will be saved to `filename` - """ - ctx.set_bounds(self.bounds) - for statement in self.statements: - ctx.evaluate(statement) - if filename is not None: - ctx.dump(filename) class GerberParser(object): @@ -178,15 +163,31 @@ class GerberParser(object): def __init__(self): self.settings = FileSettings() self.statements = [] + self.primitives = [] + self.apertures = {} + self.current_region = None + self.x = 0 + self.y = 0 + + self.aperture = 0 + self.interpolation = 'linear' + self.direction = 'clockwise' + self.image_polarity = 'positive' + self.level_polarity = 'dark' + self.region_mode = 'off' + self.quadrant_mode = 'multi-quadrant' + self.step_and_repeat = (1, 1, 0, 0) + def parse(self, filename): fp = open(filename, "r") data = fp.readlines() for stmt in self._parse(data): + self.evaluate(stmt) self.statements.append(stmt) - return GerberFile(self.statements, self.settings, filename) + return GerberFile(self.statements, self.settings, self.primitives, filename) def dump_json(self): stmts = {"statements": [stmt.__dict__ for stmt in self.statements]} @@ -218,7 +219,7 @@ class GerberParser(object): did_something = False # Region Mode - (mode, r) = self._match_one(self.REGION_MODE_STMT, line) + (mode, r) = _match_one(self.REGION_MODE_STMT, line) if mode: yield RegionModeStmt.from_gerber(line) line = r @@ -226,7 +227,7 @@ class GerberParser(object): continue # Quadrant Mode - (mode, r) = self._match_one(self.QUAD_MODE_STMT, line) + (mode, r) = _match_one(self.QUAD_MODE_STMT, line) if mode: yield QuadrantModeStmt.from_gerber(line) line = r @@ -234,7 +235,7 @@ class GerberParser(object): continue # coord - (coord, r) = self._match_one(self.COORD_STMT, line) + (coord, r) = _match_one(self.COORD_STMT, line) if coord: yield CoordStmt.from_dict(coord, self.settings) line = r @@ -242,7 +243,7 @@ class GerberParser(object): continue # aperture selection - (aperture, r) = self._match_one(self.APERTURE_STMT, line) + (aperture, r) = _match_one(self.APERTURE_STMT, line) if aperture: yield ApertureStmt(**aperture) @@ -251,7 +252,7 @@ class GerberParser(object): continue # comment - (comment, r) = self._match_one(self.COMMENT_STMT, line) + (comment, r) = _match_one(self.COMMENT_STMT, line) if comment: yield CommentStmt(comment["comment"]) did_something = True @@ -259,7 +260,7 @@ class GerberParser(object): continue # parameter - (param, r) = self._match_one_from_many(self.PARAM_STMT, line) + (param, r) = _match_one_from_many(self.PARAM_STMT, line) if param: if param["param"] == "FS": stmt = FSParamStmt.from_dict(param) @@ -292,7 +293,7 @@ class GerberParser(object): continue # eof - (eof, r) = self._match_one(self.EOF_STMT, line) + (eof, r) = _match_one(self.EOF_STMT, line) if eof: yield EofStmt() did_something = True @@ -311,17 +312,125 @@ class GerberParser(object): yield UnknownStmt(line) oldline = line - def _match_one(self, expr, data): - match = expr.match(data) - if match is None: - return ({}, None) - else: - return (match.groupdict(), data[match.end(0):]) + def evaluate(self, stmt): + """ Evaluate Gerber statement and update image accordingly. - def _match_one_from_many(self, exprs, data): - for expr in exprs: - match = expr.match(data) - if match: - return (match.groupdict(), data[match.end(0):]) + This method is called once for each statement in the file as it + is parsed. + Parameters + ---------- + statement : Statement + Gerber/Excellon statement to evaluate. + + """ + if isinstance(stmt, (CommentStmt, UnknownStmt, EofStmt)): + return + + elif isinstance(stmt, ParamStmt): + self._evaluate_param(stmt) + + elif isinstance(stmt, CoordStmt): + self._evaluate_coord(stmt) + + elif isinstance(stmt, ApertureStmt): + self._evaluate_aperture(stmt) + + elif isinstance(stmt, (RegionModeStmt, QuadrantModeStmt)): + self._evaluate_mode(stmt) + + else: + raise Exception("Invalid statement to evaluate") + + + def _define_aperture(self, d, shape, modifiers): + aperture = None + if shape == 'C': + diameter = float(modifiers[0][0]) + aperture = Circle(position=None, diameter=diameter) + elif shape == 'R': + width = float(modifiers[0][0]) + height = float(modifiers[0][1]) + aperture = Rectangle(position=None, width=width, height=height) + elif shape == 'O': + width = float(modifiers[0][0]) + height = float(modifiers[0][1]) + aperture = Obround(position=None, width=width, height=height) + self.apertures[d] = aperture + + def _evaluate_mode(self, stmt): + if stmt.type == 'RegionMode': + if self.region_mode == 'on' and stmt.mode == 'off': + self.primitives.append(Region(self.current_region, self.level_polarity)) + self.current_region = None + self.region_mode = stmt.mode + elif stmt.type == 'QuadrantMode': + self.quadrant_mode = stmt.mode + + def _evaluate_param(self, stmt): + if stmt.param == "FS": + self.settings.zero_suppression = stmt.zero_suppression + self.settings.format = stmt.format + self.settings.notation = stmt.notation + elif stmt.param == "MO": + self.settings.units = stmt.mode + elif stmt.param == "IP": + self.image_polarity = stmt.ip + elif stmt.param == "LP": + self.level_polarity = stmt.lp + elif stmt.param == "AD": + self._define_aperture(stmt.d, stmt.shape, stmt.modifiers) + + def _evaluate_coord(self, stmt): + x = self.x if stmt.x is None else stmt.x + y = self.y if stmt.y is None else stmt.y + if stmt.function in ("G01", "G1"): + self.interpolation = 'linear' + elif stmt.function in ('G02', 'G2', 'G03', 'G3'): + self.interpolation = 'arc' + self.direction = ('clockwise' if stmt.function in ('G02', 'G2') + else 'counterclockwise') + if stmt.op == "D01": + if self.region_mode == 'on': + if self.current_region is None: + self.current_region = [(self.x, self.y), ] + self.current_region.append((x, y,)) + else: + start = (self.x, self.y) + end = (x, y) + width = self.apertures[self.aperture].stroke_width + if self.interpolation == 'linear': + self.primitives.append(Line(start, end, width, self.level_polarity)) + else: + center = (start[0] + stmt.i, start[1] + stmt.j) + self.primitives.append(Arc(start, end, center, self.direction, width, self.level_polarity)) + + elif stmt.op == "D02": + pass + + elif stmt.op == "D03": + primitive = copy.deepcopy(self.apertures[self.aperture]) + primitive.position = (x, y) + primitive.level_polarity = self.level_polarity + self.primitives.append(primitive) + self.x, self.y = x, y + + def _evaluate_aperture(self, stmt): + self.aperture = stmt.d + + +def _match_one(expr, data): + match = expr.match(data) + if match is None: return ({}, None) + else: + return (match.groupdict(), data[match.end(0):]) + + +def _match_one_from_many(exprs, data): + for expr in exprs: + match = expr.match(data) + if match: + return (match.groupdict(), data[match.end(0):]) + + return ({}, None) -- cgit