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