#!/usr/bin/env python3 """EasyEDA .esym 符号源码解析。 格式:每行一个 JSON 数组。实测出来的字段布局(2026-09-07,PhoneRail 工程 131 个符号): ["PART", name, {"BBOX":[x1,y1,x2,y2]}] ["PIN", id, ?, ?, x, y, length, rotation, color, ?, ?] x,y 是**连接点**(外端),rotation 是「从连接点指向本体」的方向:0=向右(引脚在左边) ["ATTR", id, parentId, key, value, ?, visible, x, y, rot, fontStyle, ?] 挂在 PIN 上的 key 有 NAME / NUMBER / Pin Type ["RECT", id, x1, y1, x2, y2, rx, ry, rot, style, ?] ["POLY", id, [x1,y1,x2,y2,...], closed, style, ?] ["CIRCLE", id, cx, cy, r, style, ?] ["ELLIPSE", id, cx, cy, rx, ry, rot, style, ?] ["ARC", id, sx, sy, mx, my, ex, ey, style, ?] ← 实测 (mx,my) 是弧中点,和 KiCad 三点式同构 ["TEXT", id, x, y, rot, text, style] 坐标单位 0.01 inch,**Y 轴向上**(和 KiCad .kicad_sym 同向,符号图形不需要翻 Y)。 """ import json MM = 0.254 # 1 EasyEDA 单位 = 0.01 inch = 0.254 mm PIN_TYPE = { # EasyEDA "Pin Type" -> KiCad 电气类型 "Undefined": "unspecified", "IN": "input", "OUT": "output", "BI": "bidirectional", "POWER": "power_in", } def parse(path): """-> {'name':str, 'bbox':[x1,y1,x2,y2], 'pins':[...], 'shapes':[...], 'texts':[...]}""" rows = [] for line in open(path, encoding="utf-8"): line = line.strip() if line: rows.append(json.loads(line)) sym = {"name": None, "bbox": None, "pins": [], "shapes": [], "texts": [], "skipped": []} pins = {} # pin id -> dict for r in rows: t = r[0] if t == "PART": sym["name"] = r[1] if len(r) > 2 and isinstance(r[2], dict): sym["bbox"] = r[2].get("BBOX") elif t == "PIN": p = {"id": r[1], "x": r[4], "y": r[5], "len": r[6], "rot": r[7], "number": None, "name": None, "type": "Undefined", "nameVisible": True, "numberVisible": True} pins[r[1]] = p sym["pins"].append(p) elif t == "ATTR": p = pins.get(r[2]) if not p: continue key, val, vis = r[3], r[4], (r[6] if len(r) > 6 else 1) if key == "NAME": p["name"] = val; p["nameVisible"] = bool(vis) elif key == "NUMBER": p["number"] = val; p["numberVisible"] = bool(vis) elif key == "Pin Type": p["type"] = val or "Undefined" elif t == "RECT": sym["shapes"].append({"k": "rect", "x1": r[2], "y1": r[3], "x2": r[4], "y2": r[5]}) elif t == "POLY": pts = r[2] sym["shapes"].append({"k": "poly", "pts": [(pts[i], pts[i + 1]) for i in range(0, len(pts) - 1, 2)], "closed": bool(r[3]) if len(r) > 3 else False}) elif t == "CIRCLE": sym["shapes"].append({"k": "circle", "cx": r[2], "cy": r[3], "r": r[4]}) elif t == "ELLIPSE": # KiCad 没有椭圆图元;长短轴相等就是圆,否则退化成外接圆(本工程里全是小圆点) sym["shapes"].append({"k": "circle", "cx": r[2], "cy": r[3], "r": max(r[4], r[5])}) elif t == "ARC": sym["shapes"].append({"k": "arc", "sx": r[2], "sy": r[3], "mx": r[4], "my": r[5], "ex": r[6], "ey": r[7]}) elif t == "TEXT": sym["texts"].append({"x": r[2], "y": r[3], "rot": r[4], "s": r[5]}) elif t in ("DOCTYPE", "HEAD", "LINESTYLE", "FONTSTYLE"): pass else: # TABLE / TABEL_CELL / OBJ / GROUP 只出现在图框符号里,不是元件 sym["skipped"].append(t) return sym