diff --git a/InteractiveHtmlBom/ecad/__init__.py b/InteractiveHtmlBom/ecad/__init__.py index ecaef5e..fdb79f6 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/common.py b/InteractiveHtmlBom/ecad/common.py index 92b3f21..85a50a0 100644 --- a/InteractiveHtmlBom/ecad/common.py +++ b/InteractiveHtmlBom/ecad/common.py @@ -131,7 +131,7 @@ def add_arc(): la = 1 if da > 180 else 0 svgpath = 'M %s %s A %s %s 0 %s 1 %s %s' % \ (x1, y1, r, r, la, x2, y2) - bbox.add_svgpath(svgpath, width, self.logger) + bbox.add_svgpath(svgpath, width, 0, 0, self.logger) { 'segment': add_segment, @@ -232,12 +232,12 @@ def add_circle(self, x, y, r): self.add_point(x, y + r) return self - def add_svgpath(self, svgpath, width, logger): + def add_svgpath(self, svgpath, width, x, y, logger): w = width / 2 for segment in parse_path(svgpath, logger): x0, x1, y0, y1 = segment.bbox() - self.add_point(x0 - w, y0 - w) - self.add_point(x1 + w, y1 + w) + self.add_point(x0 - w + x, y0 - w + y) + self.add_point(x1 + w + x, y1 + w + y) def pad(self, amount): """Add small padding to the box.""" diff --git a/InteractiveHtmlBom/ecad/gencad.py b/InteractiveHtmlBom/ecad/gencad.py new file mode 100644 index 0000000..82fb2d8 --- /dev/null +++ b/InteractiveHtmlBom/ecad/gencad.py @@ -0,0 +1,649 @@ +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_pad_shape_to_ihb_shape = { + 'FINGER': 'custom', + 'ROUND': 'circle', + 'ANNULAR': 'circle', + 'BULLET': 'custom', + 'RECTANGULAR': 'rect', + 'HEXAGON': 'custom', + 'OCTAGON': 'custom', + 'POLYGON': 'custom', + 'UNKNOWN': '', + } + def visit_pad_(self, pad, visited_children): + shape = 'custom' + if pad.children[4].text in self.gencad_pad_shape_to_ihb_shape.keys(): + shape = self.gencad_pad_shape_to_ihb_shape[pad.children[4].text] + pad2 = { + "shape": shape + } + + lastx = None + lasty = None + 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 in ["POLYGON", "FINGER", "BULLET", 'HEXAGON', 'OCTAGON']: + if not ('svgpath' in pad2): + pad2['svgpath'] = '' + if subshape.children[0].children[0].text == 'LINE': + x1 = float(subshape.children[0].children[2].children[0].text) + y1 = float(subshape.children[0].children[2].children[2].text) + x2 = float(subshape.children[0].children[4].children[0].text) + y2 = float(subshape.children[0].children[4].children[2].text) + + if pad2['svgpath'] == '': + pad2['svgpath'] += 'M {:f} {:f}\n'.format(x1, y1) + else: + if lastx == x2 and lasty == y2: + tmpx = x1 + tmpy = y1 + x1 = x2 + y1 = y2 + x2 = tmpx + y2 = tmpy + + pad2['svgpath'] += 'L {:f} {:f}\n'.format(x2, y2) + lastx = x2 + lasty = y2 + 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) + + if lastx == endx and lasty == endy: + tmpx = startx + tmpy = starty + endx = startx + endy = starty + startx = tmpx + starty = tmpy + + centerx = float(subshape.children[0].children[2].children[4].children[0].text) + centery = float(subshape.children[0].children[2].children[4].children[2].text) + + startAngle = math.atan2(starty - centery, startx - centerx) + endAngle = math.atan2(endy - centery, endx - centerx) + if endAngle < startAngle: + endAngle += 2.0 * math.pi + + radius = self.distance(startx, centerx, starty, centery) + largeArcFlag = 0 if math.degrees(endAngle - startAngle) <= 180 else 1 + + if pad2['svgpath'] == '': + pad2['svgpath'] += 'M {:f} {:f}\n'.format(startx, starty) + + #pad2['svgpath'] += 'L {:f} {:f}\n'.format(endx, endy) + pad2['svgpath'] += 'A {:f} {:f} 0 {:d} 0 {:f} {:f}\n'.format(radius, radius, largeArcFlag, endx, endy) + lastx = endx + lasty = 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 len(prop.children[0].children) == 0: + continue + if prop.children[0].children[0].text == "DESC": + newDevice['name'] = self.trimQuotationMark(prop.children[0].children[2].text.strip()) + self.devices[device.children[2].text] = newDevice + + def visit_shape(self, shape, visited_children): + shapeOut = { + "pads": {}, + "drawings": [] + } + pinIndex = '' + 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 + pinIndex = prim.children[0].children[2].text + shapeOut["pads"][pinIndex] = { + "name": pinIndex, + "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' + } + + # highlighting pin1 of single pin (most likely TP) does not makes sense + if len(shapeOut["pads"]) == 1: + shapeOut["pads"][pinIndex]["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, logger): + self.shapes = shapes + self.padstacks = padstacks + self.devices = devices + self.logger = logger + 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 'shape' not in pad: + return 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, pad["pos"][0], pad["pos"][1], self.logger) + 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"].values(): + 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'])) + + if deviceName not in self.devices: + if self.trimQuotationMark(deviceName) != "NULL": + # Mentor Expedition sometimes exports DNP components with NULL device reference + print("Device with name {} not found in $DEVICES".format(deviceName)) + return + + 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"] + + if bbox.initialized(): + 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_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) + + line = { + "type": "segment", + "start": [x1, y1], + "end": [x2, y2], + "width": self.contour_width, + } + self.pcbdata['edges'].append(line) + 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)* + arc = "ARC" s arc_ref + arc_center = x s* y + arc_end = 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? + arc_start = x s* y + artwork = "ARTWORK" s name s layer n (line/arc/circle/rectangle/type/filled)* + artwork_ = "ARTWORK" s string s x s y s rot s mirror s flip n+ attribute* + artworks = "$ARTWORKS" n* artworks_artwork* "$ENDARTWORKS" n* + artworks_artwork = "ARTWORK" string n (named_layer/track_/filled/text/line/arc/circle/rectangle/attribute)* n* + attribute = "ATTRIBUTE" s attribute_category s attribute_name s attribute_data n+ + attribute_category = nonquoted_string + attribute_data = wrapper_to_end + attribute_name = (nonquoted_string / string) + board = "$BOARD" n+ thickness? (cont_line/cont_arc/cont_circle/cont_rect/cutout/mask/artwork)* n* "$ENDBOARD" n* + change = "CHANGE" s string n+ (sig_change/dev_change)* + changes = "$CHANGES" n+ change* "$ENDCHANGES" + circle = "CIRCLE" s x s y s radius " "* n+ + component = "COMPONENT" s component_name n* (device_/place/named_layer/rotation/shape_/value/partnumber/artwork_/fid/text/sheet/attribute)* n* + component_name = (nonquoted_string/string) + components = "$COMPONENTS" n* component* "$ENDCOMPONENTS" n* + cont_arc = arc "" + cont_circle = circle "" + cont_line = line "" + cont_rect = rectangle "" + cutout = "CUTOUT" s name so n (line/arc/circle/rectangle/n)* + define = "DEFINE" s layer s (nonquoted_string/string) n* + desc_body = ~r"\".*" + desc = "DESC" s desc_body n+ + dev_change = "DEVICE" s part_name s part_name n+ + device = "DEVICE" s wrapper_to_end n+ (part/type/style/package/pindesc/pinfunct/pincount/value/tol/ntol/ptol/volts/desc/attribute/n/stale_quote)* n* + device_ = "DEVICE" s device_name n* + device_name = wrapper_to_end + devices = "$DEVICES" n* device* "$ENDDEVICES" n* + dimension = ("INCH" / "THOU" / "MM" / "MM100" / ("USER" s p_integer) / ("USERM" s p_integer) / ("USERMM" s p_integer)) + drawing = "DRAWING" s wrapper_to_end n+ + drill_size = number + fhole = "FHOLE" s x s y s drill_size n+ + fid = "FID" s fid_name s pad_name s x s y s layer s rot s mirror n+ attribute* + fid_name = (nonquoted_string/string) + fiducial = "FIDUCIAL" s x s y n+ + filename = string + filled = "FILLED" s filled_ref n* + filled_ref = ("0" / "YES") + flip = ("0" / "FLIP") + gencad_version = "GENCAD" s major ("." minor)? n+ + header = "$HEADER" n+ gencad_version? user? drawing? revision? units? origin? intertrack? attribute* "$ENDHEADER" n* + height = number + height_ = "HEIGHT" s height n+ + hole = "HOLE" s x s y s drill_size n+ + insert = "INSERT" s nonquoted_string n+ + intertrack = "INTERTRACK" s number n+ + 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)) + layer_ = "LAYER" s layer n* + layer_index = p_integer + layers = "$LAYERS" n* (define/layerset)* "$ENDLAYERS" n* + layerset = "LAYERSET" s layer n+ layer_* + line = "LINE" s* line_start s* line_end n+ + line_end = x s* y + line_start = x s* y + major = p_integer + mask = "MASK" s name s layer n (line/arc/circle/rectangle)* n* + mech = "$MECH" n+ (hole/fhole/mechanical/attribute/artwork_/fid)* n* "$ENDMECH" n* + mechanical = "MECHANICAL" (wrapper_to_end)? n (place/layer_/rotation/shape_/hole)* n* + minor = p_integer + mirror = ("0"/"MIRRORX"/"MIRRORY") + n = "\r"? "\n" + nailloc = "NAILLOC" s component_name s pin_name s string s x s y s tan s tin s probe s layer n* + nailloc_ = "NAILLOC" s via_name s tp_name s x s y s tan s tin probe s layer n* + name = ~r"[^\s\t\r\n]*" + named_layer = "LAYER" s nonquoted_string n* + node = "NODE" s component_name s pin_name s n* + nonquoted_string = ~r"[^\"\s\r\n]+" + ntol = "NTOL" s nonquoted_string n+ + number = ~r"[-|+]?[0-9]+(\.[0-9]+)?([e|E][-+]?[0-9]+)?" + origin = "ORIGIN" s x s y n+ + p_integer = sign? ~r"[0-9]+" + package = "PACKAGE" s string n* + pad_ = "PAD" s pad_name s pad_type s drill_size n+ (line/arc/circle/rectangle/attribute)* n* + pad_name = (nonquoted_string/string) + pad_type = ("FINGER"/"ROUND"/"ANNULAR"/"BULLET"/"RECTANGULAR"/"HEXAGON"/"OCTAGON"/"POLYGON"/"UNKNOWN") + pads = "$PADS" n+ pad_* "$ENDPADS" n* + padstack = "PADSTACK" s pad_name s drill_size n* (padstacks_pad/attribute)* + padstacks = "$PADSTACKS" n+ padstack* "$ENDPADSTACKS" n* + padstacks_pad = "PAD" s pad_name s layer s rot s mirror n+ + part = "PART" s wrapper_to_end n+ + part_name = (nonquoted_string/string) + partnumber = "PartNumber" s nonquoted_string n+ + pin_name = (nonquoted_string/string) + pincount = "PINCOUT" s number n* + pindesc = "PINDESC" s pin_name s string n* + pinfunct = "PINFUNCT" s pin_name s string n* + place = "PLACE" s x s y n+ + plane = "PLANE" s nonquoted_string n (line/arc/circle/rectangle/attribute)* + 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* + probe = string + pseudo_component = "COMPONENT" s component_name s string n+ + pseudo_device = "DEVICE" s part_name s string n+ + pseudo_padstack = "PADSTACK" s pad_name s string n+ + pseudo_part = "PART" s part_name s string n+ + pseudo_pin = "PIN" s pin_name s string n+ + pseudo_powerpin = "POWERPIN" s tp_name s string n+ + pseudo_shape = "SHAPE" s shape_name s string n+ + pseudo_signal = "SIGNAL" s sig_name s string n+ + pseudo_testpad = "TESTPAD" s pad_name s string n+ + pseudo_testpin = "TESTPIN" 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* + ptol = "PTOL" s nonquoted_string n+ + radius = number + rectangle = "RECTANGLE" s x s y s width s height n+ + rectangle_ref = x s y s width s height + revision = "REVISION" s wrapper_to_end n+ + rot = number + rotation = "ROTATION" s rot n+ + route = "ROUTE" s wrapper_to_end n+ (track_/layer_/line/arc/circle/rectangle/via/testpad/plane/text/attribute)* + routes = "$ROUTES" n* route* "$ENDROUTES" n* + s = ~r"[\s|\t]*" + so = ~r"[\s|\t]?" + shape = "SHAPE" s shape_name n (line/arc/circle/rectangle/fiducial/insert/height_/attribute/shape_artwork/fid/shapes_pin)* n* + shape_ = "SHAPE" s shape_name s mirror s flip n+ + shape_artwork = "ARTWORK" s string s x s y s rot s mirror n attribute* + shape_name = (nonquoted_string/string) + shape_pin_name = (nonquoted_string/string) + shapes = "$SHAPES" n* shape* "$ENDSHAPES" n* + shapes_pin = "PIN" s shape_pin_name s pad_name s x s y s layer s rot s mirror n+ + sheet = "SHEET" s string n* + sig_change = "SIGNAL" s sig_name s sig_name n+ + sig_name = (nonquoted_string/string) + sign = ~r"['+'|'-']" + signal = "SIGNAL" s wrapper_to_end n* (node/nailloc/attribute)* n* + signals = "$SIGNALS" n* signal* "$ENDSIGNALS" n* + stale_quote = ~r"\"\r?\n" + string = '"' ~r'[^"]*' '"' + string_to_end = ~r"[^\r\n]*" + style = "STYLE" s pad_name n* + tan = string + testpad = "TESTPAD" s pad_name s x s y s rot s mirror s testpad_name n+ + testpad_name = 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* + text = "TEXT" s x s y s text_par n* + text_par = text_size s rot s mirror s layer s text_text s rectangle_ref + text_size = number + text_text = string + thickness = "THICKNESS" s number n+ + tin = string + tin = string + tol = "TOL" s nonquoted_string n+ + tp_name = string + track = "TRACK" s nonquoted_string s track_width n* + track_ = "TRACK" s nonquoted_string n+ + track_width = number + tracks = "$TRACKS" n* track* "$ENDTRACKS" n* + type = "TYPE" s (nonquoted_string/string) n+ + unit = dimension + units = "UNITS" s unit n+ + user = "USER" s wrapper_to_end n+ + value = ("VALUE"/"Value") s wrapper_to_end n+ + value = ("VALUE"/"Value") s wrapper_to_end n+ + via = "VIA" s pad_name s x s y s layer s drill_size s via_name n+ (nailloc_/attribute)* + via_name = nonquoted_string + volts = "VOLTS" s string n+ + width = number + wrapper_to_end = (string/string_to_end) + x = number + y = number + """) + self.file_name = file_name + self.config = config + self.logger = logger + + + def parse(self): + with codecs.open(self.file_name, 'r', encoding='ascii', + 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, self.logger).visit(tree) + return ret diff --git a/InteractiveHtmlBom/ecad/svgpath.py b/InteractiveHtmlBom/ecad/svgpath.py index 9666502..4076c3c 100644 --- a/InteractiveHtmlBom/ecad/svgpath.py +++ b/InteractiveHtmlBom/ecad/svgpath.py @@ -128,6 +128,8 @@ def __init__(self, start, radius, rotation, large_arc, sweep, end, in some sense when viewing SVGs (as the y coordinate starts at the top of the image and increases towards the bottom). """ + if start == end: + print("sads") assert start != end assert radius.real != 0 and radius.imag != 0