From 4b930aceb9130341e453f06bc513b60eb3634ce1 Mon Sep 17 00:00:00 2001 From: Miklos Marton Date: Sat, 15 Jul 2023 11:13:26 +0200 Subject: [PATCH] Implement GenCAD import Fixes #392 --- InteractiveHtmlBom/ecad/__init__.py | 7 + InteractiveHtmlBom/ecad/gencad.py | 584 ++++++++++++++++++++++++++++ 2 files changed, 591 insertions(+) create mode 100644 InteractiveHtmlBom/ecad/gencad.py diff --git a/InteractiveHtmlBom/ecad/__init__.py b/InteractiveHtmlBom/ecad/__init__.py index ecaef5ee..fdb79f68 100644 --- a/InteractiveHtmlBom/ecad/__init__.py +++ b/InteractiveHtmlBom/ecad/__init__.py @@ -17,6 +17,8 @@ def get_parser_by_extension(file_name, config, logger): return get_easyeda_parser(file_name, config, logger) elif ext in ['.fbrd', '.brd']: return get_fusion_eagle_parser(file_name, config, logger) + elif ext in ['.cad']: + return get_gencad_parser(file_name, config, logger) else: return None @@ -39,3 +41,8 @@ def get_generic_json_parser(file_name, config, logger): def get_fusion_eagle_parser(file_name, config, logger): from .fusion_eagle import FusionEagleParser return FusionEagleParser(file_name, config, logger) + + +def get_gencad_parser(file_name, config, logger): + from .gencad import GenCadParser + return GenCadParser(file_name, config, logger) \ No newline at end of file diff --git a/InteractiveHtmlBom/ecad/gencad.py b/InteractiveHtmlBom/ecad/gencad.py new file mode 100644 index 00000000..98b9e2d7 --- /dev/null +++ b/InteractiveHtmlBom/ecad/gencad.py @@ -0,0 +1,584 @@ +import codecs +from parsimonious.grammar import Grammar +from parsimonious.nodes import NodeVisitor +from .common import EcadParser, Component, BoundingBox +import math +import os + +class MyVisitor(NodeVisitor): + def trimQuotationMark(self, inputStr): + if inputStr.startswith('"') and inputStr.endswith('"'): + return inputStr[1:-1] + return inputStr + + def rotate(self, origin, point, angle): + """ + Rotate a point counterclockwise by a given angle around a given origin. + + The angle should be given in radians. + """ + ox, oy = origin + px, py = point + + qx = ox + math.cos(angle) * (px - ox) - math.sin(angle) * (py - oy) + qy = oy + math.sin(angle) * (px - ox) + math.cos(angle) * (py - oy) + return qx, qy + + def distance(self, x1, x2, y1, y2): + return math.sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2)) +class GenCadPadVisitor(MyVisitor): + def __init__(self): + self.pads = {} + self.gencad_shape_to_myshape = { + 'FINGER': '', + 'ROUND': 'circle', + 'ANNULAR': 'circle', + 'BULLET': 'chamfrect', + 'RECTANGULAR': 'rect', + 'HEXAGON': 'chamfrect', + 'OCTAGON': 'chamfrect', + 'POLYGON': 'custom', + 'UNKNOWN': '', + } + def visit_pad_(self, pad, visited_children): + shape = 'custom' + if pad.children[4].text in self.gencad_shape_to_myshape.keys(): + shape = self.gencad_shape_to_myshape[pad.children[4].text] + pad2 = { + "shape": shape + } + + for subshape in pad.children[8].children: + if pad.children[4].text == "RECTANGULAR" and subshape.children[0].children[0].text == "RECTANGLE": + pad2['size'] = [ + float(subshape.children[0].children[6].text), + float(subshape.children[0].children[8].text) + ] + break + elif pad.children[4].text == "ROUND" and subshape.children[0].children[0].text == "CIRCLE": + pad2['size'] = [ + # radius + float(subshape.children[0].children[6].text) * 2, + float(subshape.children[0].children[6].text) * 2 + ] + break + elif pad.children[4].text == "POLYGON": + if not ('svgpath' in pad2): + pad2['svgpath'] = '' + if subshape.children[0].children[0].text == 'LINE': + pad2['svgpath'] += 'M {:f} {:f}\n'.format(float(1), float(1)) + pad2['svgpath'] += 'L {:f} {:f}\n'.format(float(1), float(1)) + elif subshape.children[0].children[0].text == 'ARC': + startx = float(subshape.children[0].children[2].children[0].children[0].text) + starty = float(subshape.children[0].children[2].children[0].children[2].text) + + endx = float(subshape.children[0].children[2].children[2].children[0].text) + endy = float(subshape.children[0].children[2].children[2].children[2].text) + + centerx = float(subshape.children[0].children[2].children[4].children[0].text) + centery = float(subshape.children[0].children[2].children[4].children[2].text) + + dx = startx - endx + dy = starty - endy + + startAngle = math.atan2(starty - centery, startx - centerx) + endAngle = math.atan2(endy - centery, endx - centerx) + if endAngle < startAngle: + endAngle += 2.0 * math.pi + + angle = math.degrees(endAngle - startAngle) + + pad2['svgpath'] += 'M {:f} {:f}\n'.format(startx, starty) + pad2['svgpath'] += 'A {:f} {:f} {:f} 1 1 {:f}\n'.format(dx, dy, angle, endx, endy) + self.pads[pad.children[2].text] = pad2 + def visit_gencad_file(self, file, visited_children): + return self.pads + + def generic_visit(self, node, visited_children): + """ The generic visit method. """ + return visited_children or node + +class GenCadPadstackVisitor(MyVisitor): + def __init__(self, pads): + self.pads = pads + self.padstacks = {} + def visit_padstack(self, padstack, visited_children): + newPadstack = { + "layers": [], + "pos": [], + "size": [], + "angle": 0, + } + + if padstack.children[4].text == '' or float(padstack.children[4].text) == 0: + newPadstack['type'] = 'smd' + else: + newPadstack['type'] = 'th' + newPadstack['drillshape'] = 'circle' + drillsize = float(padstack.children[4].text) + newPadstack['drillsize'] = [drillsize / 2.0, drillsize / 2.0] + + for pad in padstack.children[6]: + if pad.children[0].children[0].text == 'PAD' and (pad.children[0].children[4].text == 'TOP' or pad.children[0].children[4].text == 'BOTTOM'): + if pad.children[0].children[4].text == 'TOP': + newPadstack["layers"].append("F") + padName = pad.children[0].children[2].text + if not padName in self.pads: + print("PAD {} in PADSTACK {} not found in the $PADS section".format(padName, pad.children[0].children[0].text)) + else: + newPadstack = newPadstack | self.pads[padName] + + + elif pad.children[0].children[4].text == 'BOTTOM': + newPadstack["layers"].append("B") + + self.padstacks[padstack.children[2].text] = newPadstack + + def visit_gencad_file(self, file, visited_children): + return self.padstacks + + def generic_visit(self, node, visited_children): + """ The generic visit method. """ + return visited_children or node + +class GenCadShapeAndDeviceVisitor(MyVisitor): + def __init__(self): + self.shapes = {} + self.devices = {} + def visit_gencad_file(self, file, visited_children): + return self.shapes, self.devices + def visit_device(self, device, visited_children): + newDevice = {'name': ''} + for prop in device.children[4].children: + if prop.children[0].children[0].text == "DESC": + newDevice['name'] = self.trimQuotationMark(prop.children[0].children[2].text) + self.devices[device.children[2].text] = newDevice + + def visit_shape(self, shape, visited_children): + shapeOut = { + "pads": [], + "drawings": [] + } + for prim in shape.children[4].children: + # line/arc/circle/rectangle/fiducial/insert/height_/attribute/shape_artwork + if prim.children[0].children[0].text == 'PIN': + # PIN 1 "REC_1.20x1.40_TOP" -1.000000 0.000000 TOP 0.000000 0 + pad = { + "name": prim.children[0].children[2].text, + "type": prim.children[0].children[4].text, + "x": float(prim.children[0].children[6].text), + "y": -float(prim.children[0].children[8].text), + "angle": float(prim.children[0].children[12].text), + "layer": 'F' if prim.children[0].children[10].text == 'TOP' else 'B', + "pin1": prim.children[0].children[2].text == '1' + } + shapeOut["pads"].append(pad) + # highlighting pin1 of single pin (most likely TP) does not makes sense + if len(shapeOut["pads"]) == 1: + shapeOut["pads"][0]["pin1"] = False + shapeName = self.trimQuotationMark(shape.children[2].text) + self.shapes[shapeName] = shapeOut + def generic_visit(self, node, visited_children): + """ The generic visit method. """ + return visited_children or node +class GenCadVisitor(MyVisitor): + def __init__(self, shapes, padstacks, devices): + self.shapes = shapes + self.padstacks = padstacks + self.devices = devices + self.contour_width = 0.1 + self.board_outline_bbox = BoundingBox() + self.pcbdata = { + 'drawings': { + 'silkscreen': { + 'F': [], + 'B': [] + }, + 'fabrication': { + 'F': [], + 'B': [] + } + }, + 'edges': [], + 'footprints': [], + 'font_data': {}, + 'metadata': { + "title": "", + "revision": "", + "company": "", + "date": "", + }, + 'bom': { + "both": [], + "F": [], + "B": [], + } + } + self.components = [] + + def visit_cont_line(self, line, visited_children): + x1 = float(line.children[0].children[2].children[0].text) + y1 = -float(line.children[0].children[2].children[2].text) + + x2 = float(line.children[0].children[4].children[0].text) + y2 = -float(line.children[0].children[4].children[2].text) + self.board_outline_bbox.add_point(x1, y1) + self.board_outline_bbox.add_point(x2, y2) + + line2 = { + "type": "segment", + "start": [x1, y1], + "end": [x2, y2], + "width": self.contour_width, + } + self.pcbdata['edges'].append(line2) + + def addPadToBbox(self, pad, bbox): + if pad["shape"] == "circle": + bbox.add_circle(pad["pos"][0], pad["pos"][1], pad["size"][0] / 2) + elif pad["shape"] in ["rect", "oval", "roundrect", "chamfrect"]: + bbox.add_rectangle(pad["pos"][0], pad["pos"][1], pad["size"][0], pad["size"][1], pad["angle"]) + elif pad["shape"] == "custom": + bbox.add_svgpath(pad["svgpath"], self.contour_width) + return bbox + def visit_component(self, component, visited_children): + fp = { + 'ref': component.children[2].text, + 'pads': [], + 'bbox': {} + } + shapeName = '' + shape = {} + layer = 'F' + deviceName = '' + bbox = BoundingBox() + angle = 0.0 + + for property in component.children[4].children: + propertyType = property.children[0].children[0].text + if propertyType == 'PLACE': + x = float(property.children[0].children[2].text) + y = -float(property.children[0].children[4].text) + fp['center'] = [x, y] + elif propertyType == 'ROTATION': + angle= float(property.children[0].children[2].text) + elif propertyType == 'LAYER': + fp['layer'] = 'F' if property.children[0].children[2].text == 'TOP' else 'B' + if property.children[0].children[2].text == 'TOP': + self.pcbdata["bom"]["F"].append([component.children[2].text, '1']) + else: + self.pcbdata["bom"]["B"].append([component.children[2].text, '1']) + layer = 'B' + elif propertyType == 'DEVICE': + deviceName = property.children[0].children[2].text + elif propertyType == 'SHAPE': + shapeName = self.trimQuotationMark(property.children[0].children[2].text) + + if shapeName in self.shapes: + shape = self.shapes[shapeName] + fp["drawings"] = shape["drawings"] + for pad in shape["pads"]: + if not pad["type"] in self.padstacks: + print("The pad type {} of the SHAPE {} not found within PADSTACKS".format(pad.type, shapeName)) + continue + newPad = self.padstacks[pad["type"]].copy() + newPad["pos"] = [pad["x"], pad["y"]] + newPad["angle"] = pad["angle"] + newPad["layer"] = pad["layer"] + newPad["pin1"] = pad["pin1"] + bbox = self.addPadToBbox(newPad, bbox) + fp['pads'].append(newPad) + else: + print("SHAPE {} for COMPONENT {}} not found!".format(shapeName, fp['ref'])) + + for pad in fp["pads"]: + rotatedPos = self.rotate((0,0), (pad["pos"][0],pad["pos"][1]), -math.radians(angle)) + pad["pos"][0] = fp["center"][0] + rotatedPos[0] + pad["pos"][1] = fp["center"][1] + rotatedPos[1] + pad["angle"] += angle + pad["angle"] = pad["angle"] % 360 + if fp["layer"] == 'B' and len(pad["layers"]) == 1: + if pad["layers"] == ["F"]: + pad["layers"] = ["B"] + else: + pad["layers"] = ["F"] + + bbox = bbox.to_dict() + fp['bbox'] = { + 'angle': angle, + "pos": fp["center"], + "relpos": [bbox["minx"], bbox["miny"]], + "size": [bbox["maxx"] - bbox["minx"], bbox["maxy"] - bbox["miny"]], + } + self.pcbdata['footprints'].append(fp) + self.pcbdata["bom"]["both"].append([component.children[2].text, '1']) + + device = self.devices[deviceName] + comp = Component(ref=component.children[2].text, + val=device['name'], + footprint=shapeName, + layer=layer, + attr=None, + extra_fields=[]) + self.components.append(comp) + def visit_cont_rect(self, rect, visited_children): + startx = float(rect.children[0].children[2].text) + starty = -float(rect.children[0].children[4].text) + + endx = float(rect.children[0].children[6].text) + endy = -float(rect.children[0].children[8].text) + self.board_outline_bbox.add_rectangle(startx, starty, endx, endy, 0) + + rect2 = { + "type": "rect", + "width": self.contour_width, + "start": [startx, starty], + "end": [endx, endy], + } + self.pcbdata['edges'].append(rect2) + + def visit_cont_circle(self, circle, visited_children): + centerx = float(circle.children[0].children[2].text) + centery = -float(circle.children[0].children[4].text) + radius = float(circle.children[0].children[6].text) + self.board_outline_bbox.add_circle(centerx, centery, radius) + + circle2 = { + "type": "circle", + "width": self.contour_width, + "start": [centerx, centery], + "radius": radius, + "filled": 0 + } + self.pcbdata['edges'].append(circle2) + + def visit_cont_arc(self, arc, visited_children): + startx = float(arc.children[0].children[2].children[0].children[0].text) + starty = -float(arc.children[0].children[2].children[0].children[2].text) + + endx = float(arc.children[0].children[2].children[2].children[0].text) + endy = -float(arc.children[0].children[2].children[2].children[2].text) + + centerx = float(arc.children[0].children[2].children[4].children[0].text) + centery = -float(arc.children[0].children[2].children[4].children[2].text) + + radius = self.distance(startx, centerx, starty, centery) + self.board_outline_bbox.add_circle(centerx, centery, radius) + + startAngle = math.atan2(starty - centery, startx - centerx) + endAngle = math.atan2(endy - centery, endx - centerx) + if endAngle < startAngle: + endAngle += 2.0 * math.pi + + arc2 = { + "type": "arc", + "width": self.contour_width, + "start": [centerx, centery], + "radius": radius, + "startangle": math.degrees(endAngle), + "endangle": math.degrees(startAngle), + } + self.pcbdata['edges'].append(arc2) + + def visit_revision(self, revision, visited_children): + self.pcbdata["metadata"]["revision"] = revision.children[2].text + + def visit_drawing(self, drawing, visited_children): + path = drawing.children[2].text + path = path.replace("\\", "/").replace('"', '') + self.pcbdata["metadata"]["title"] = os.path.basename(path) + def visit_gencad_file(self, file, visited_children): + if self.board_outline_bbox.initialized(): + self.pcbdata['edges_bbox'] = self.board_outline_bbox.to_dict() + return self.pcbdata, self.components + + def generic_visit(self, node, visited_children): + """ The generic visit method. """ + return visited_children or node +class GenCadParser(EcadParser): + def __init__(self, file_name, config, logger): + self.grammar = Grammar(r""" + gencad_file = header (board/pads/padstacks/artworks/shapes/components/devices/signals/tracks/layers/routes/mech/testpins/powerpins/pseudos/changes)* + header = "$HEADER" n+ gencad_version? user? drawing? revision? units? origin? intertrack? attribute* "$ENDHEADER" n* + p_integer = sign? ~r"[0-9]+" + string = '"' ~r'[^"]*' '"' + nonquoted_string = ~r"[^\"\s\r\n]+" + string_to_end = ~r"[^\r\n]*" + wrapper_to_end = (string/string_to_end) + major = p_integer + minor = p_integer + gencad_version = "GENCAD" s major ("." minor)? n+ + user = "USER" s wrapper_to_end n+ + drawing = "DRAWING" s wrapper_to_end n+ + revision = "REVISION" s wrapper_to_end n+ + unit = dimension + units = "UNITS" s unit n+ + origin = "ORIGIN" s x s y n+ + intertrack = "INTERTRACK" s number n+ + attribute_category = nonquoted_string + attribute_name = (nonquoted_string / string) + attribute_data = wrapper_to_end + dimension = ("INCH" / "THOU" / "MM" / "MM100" / ("USER" s p_integer) / ("USERM" s p_integer) / ("USERMM" s p_integer)) + attribute = "ATTRIBUTE" s attribute_category s attribute_name s attribute_data n+ + thickness = "THICKNESS" s number n+ + cutout = "CUTOUT" s name s n (line/arc/circle/rectangle/n)* + mask = "MASK" s name s layer n (line/arc/circle/rectangle)* n* + cont_line = line "" + cont_arc = arc "" + cont_circle = circle "" + cont_rect = rectangle "" + board = "$BOARD" n+ thickness? (cont_line/cont_arc/cont_circle/cont_rect/cutout/mask/artwork)* n* "$ENDBOARD" n* + arc = "ARC" s arc_ref + s = ~r"[\s|\t]*" + n = "\r"? "\n" + sign = ~r"['+'|'-']" + number = ~r"[-|+]?[0-9]+(\.[0-9]+)?([e|E][-+]?[0-9]+)?" + x = number + y = number + line_start = x s y + line_end = x s y + line = "LINE" s line_start s line_end n+ + arc_start = x s y + arc_end = x s y + arc_center = x s y + arc_p1 = number + arc_p2 = number + arc_ref = arc_start s arc_end s arc_center s arc_p1? s arc_p2? + name = ~r"[^\s\t\r\n]*" + filename = string + filled_ref = ("0" / "YES") + flip = ("0" / "FLIP") + height = number + width = number + layer_index = p_integer + layer = ("TOP"/"BOTTOM"/"SOLDERMASK_TOP"/"SOLDERMASK_BOTTOM"/"SILKSCREEN_TOP"/"SILKSCREEN_BOTTOM"/"SOLDERPASTE_TOP"/"SOLDERPASTE_BOTTOM"/("POWER" layer_index)/("GROUND" layer_index)/("INNER" layer_index?)/"ALL"/("LAYER" layer_index)/("LAYER_" layer_index)/("LAYERSET" layer_index)) + type = "TYPE" s (nonquoted_string/string) n+ + filled = "FILLED" s filled_ref n* + artwork = "ARTWORK" s name s layer n (line/arc/circle/rectangle/type/filled)* + radius = number + circle = "CIRCLE" s x s y s radius n+ + rectangle = "RECTANGLE" s x s y s width s height n+ + pad_name = (nonquoted_string/string) + pad_type = ("FINGER"/"ROUND"/"ANNULAR"/"BULLET"/"RECTANGULAR"/"HEXAGON"/"OCTAGON"/"POLYGON"/"UNKNOWN") + drill_size = number + pad_ = "PAD" s pad_name s pad_type s drill_size n+ (line/arc/circle/rectangle/attribute)* n* + pads = "$PADS" n+ pad_* "$ENDPADS" n* + rot = number + mirror = ("0"/"MIRRORX"/"MIRRORY") + padstacks_pad = "PAD" s pad_name s layer s rot s mirror n+ + padstack = "PADSTACK" s pad_name s drill_size n* (padstacks_pad/attribute)* + padstacks = "$PADSTACKS" n+ padstack* "$ENDPADSTACKS" n* + named_layer = "LAYER" s nonquoted_string n* + track_ = "TRACK" s nonquoted_string n+ + text_size = number + text_text = string + rectangle_ref = x s y s width s height + text_par = text_size s rot s mirror s layer s text_text s rectangle_ref + text = "TEXT" s x s y s text_par n* + artworks_artwork = "ARTWORK" string n (named_layer/track_/filled/text/line/arc/circle/rectangle/attribute)* n* + artworks = "$ARTWORKS" n* artworks_artwork* "$ENDARTWORKS" n* + shape_artwork = "ARTWORK" s string s x s y s rot s mirror n attribute* + fid_name = (nonquoted_string/string) + fid = "FID" s fid_name s pad_name s x s y s layer s rot s mirror n+ attribute* + shape_pin_name = (nonquoted_string/string) + shapes_pin = "PIN" s shape_pin_name s pad_name s x s y s layer s rot s mirror n+ + shape_name = (nonquoted_string/string) + fiducial = "FIDUCIAL" s x s y n+ + insert = "INSERT" s nonquoted_string n+ + height_ = "HEIGHT" s height n+ + shape = "SHAPE" s shape_name n (line/arc/circle/rectangle/fiducial/insert/height_/attribute/shape_artwork/fid/shapes_pin)* n* + shapes = "$SHAPES" n* shape* "$ENDSHAPES" n* + sheet = "SHEET" s string n* + component_name = (nonquoted_string/string) + device_name = wrapper_to_end + device_ = "DEVICE" s device_name n* + place = "PLACE" s x s y n+ + rotation = "ROTATION" s rot n+ + shape_ = "SHAPE" s shape_name s mirror s flip n+ + value = ("VALUE"/"Value") s wrapper_to_end n+ + partnumber = "PartNumber" s nonquoted_string n+ + artwork_ = "ARTWORK" s string s x s y s rot s mirror s flip n+ attribute* + component = "COMPONENT" s component_name n* (device_/place/named_layer/rotation/shape_/value/partnumber/artwork_/fid/text/sheet/attribute)* n* + components = "$COMPONENTS" n* component* "$ENDCOMPONENTS" n* + style = "STYLE" s string n* + package = "PACKAGE" s string n* + pin_name = (nonquoted_string/string) + pindesc = "PINDESC" s pin_name s string n* + pinfunct = "PINFUNCT" s pin_name s string n* + pincount = "PINCOUT" s number n* + tol = "TOL" s nonquoted_string n+ + ptol = "PTOL" s nonquoted_string n+ + ntol = "NTOL" s nonquoted_string n+ + volts = "VOLTS" s string n+ + desc = "DESC" s string n+ + value = ("VALUE"/"Value") s wrapper_to_end n+ + device = "DEVICE" s wrapper_to_end n+ (part/type/style/package/pindesc/pinfunct/pincount/value/tol/ntol/ptol/volts/desc/attribute)* n* + part = "PART" s wrapper_to_end n+ + devices = "$DEVICES" n* device* "$ENDDEVICES" n* + node = "NODE" s component_name s pin_name s n* + tan = string + tin = string + probe = string + nailloc = "NAILLOC" s component_name s pin_name s string s x s y s tan s tin s probe s layer n* + signal = "SIGNAL" s wrapper_to_end n* (node/nailloc/attribute)* n* + signals = "$SIGNALS" n* signal* "$ENDSIGNALS" n* + track_width = number + track = "TRACK" s nonquoted_string s track_width n* + tracks = "$TRACKS" n* track* "$ENDTRACKS" n* + define = "DEFINE" s layer s (nonquoted_string/string) n* + layerset = "LAYERSET" s layer n+ layer_* + layer_ = "LAYER" s layer n* + layers = "$LAYERS" n* (define/layerset)* "$ENDLAYERS" n* + via_name = nonquoted_string + tp_name = string + nailloc_ = "NAILLOC" s via_name s tp_name s x s y s tan s tin probe s layer n* + via = "VIA" s pad_name s x s y s layer s drill_size s via_name n+ (nailloc_/attribute)* + testpad_name = string + testpad = "TESTPAD" s pad_name s x s y s rot s mirror s testpad_name n+ + plane = "PLANE" s nonquoted_string n (line/arc/circle/rectangle/attribute)* + route = "ROUTE" s wrapper_to_end n+ (track_/layer_/line/arc/circle/rectangle/via/testpad/plane/text/attribute)* + routes = "$ROUTES" n* route* "$ENDROUTES" n* + mechanical = "MECHANICAL" s part_name s? string? n (place/layer_/rotation/shape_)* + hole = "HOLE" s x s y s drill_size n+ + fhole = "FHOLE" s x s y s drill_size n+ + mech = "$MECH" n+ (hole/fhole/mechanical/attribute/artwork_/fid)* "$ENDMECH" n* + tin = string + sig_name = (nonquoted_string/string) + testpin = "TESTPIN" s tp_name s x s y s sig_name s tan s tin s probe s layer n* + testpins = "$TESTPINS" n+ (testpin/text/attribute)* "$ENDTESTPINS" n* + powerpin = "POWERPIN" s tp_name s x s y s sig_name s tan s tin s probe s layer n* + powerpins = "$POWERPINS" n+ (powerpin/text/attribute)* "$ENDPOWERPINS" n* + pseudo_signal = "SIGNAL" s sig_name s string n+ + part_name = (nonquoted_string/string) + pseudo_device = "DEVICE" s part_name s string n+ + pseudo_part = "PART" s part_name s string n+ + pseudo_shape = "SHAPE" s shape_name s string n+ + pseudo_component = "COMPONENT" s component_name s string n+ + pseudo_testpad = "TESTPAD" s pad_name s string n+ + pseudo_padstack = "PADSTACK" s pad_name s string n+ + pseudo_pin = "PIN" s pin_name s string n+ + pseudo_testpin = "TESTPIN" s tp_name s string n+ + pseudo_powerpin = "POWERPIN" s tp_name s string n+ + pseudo_via = "VIA" s via_name s string n+ + pseudos = "$PSEUDOS" n+ (pseudo_signal/pseudo_device/pseudo_part/pseudo_shape/pseudo_component/pseudo_testpad/pseudo_padstack/pseudo_pin/pseudo_testpin/pseudo_powerpin/pseudo_via/pseudo_testpad)* "$ENDPSEUDOS" n* + sig_change = "SIGNAL" s sig_name s sig_name n+ + dev_change = "DEVICE" s part_name s part_name n+ + change = "CHANGE" s string n+ (sig_change/dev_change)* + changes = "$CHANGES" n+ change* "$ENDCHANGES" + """) + self.file_name = file_name + self.config = config + self.logger = logger + + + def parse(self): + with codecs.open(self.file_name, 'r', encoding='utf-8', + errors='ignore') as fdata: + content = fdata.read() + tree = self.grammar.parse(content) + + pads = GenCadPadVisitor().visit(tree) + padstacks = GenCadPadstackVisitor(pads).visit(tree) + shapes, devices = GenCadShapeAndDeviceVisitor().visit(tree) + ret = GenCadVisitor(shapes, padstacks, devices).visit(tree) + return ret