ecadlib 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ecadlib/__init__.py +3 -0
- ecadlib/__main__.py +5 -0
- ecadlib/a365_sync.py +637 -0
- ecadlib/agent.py +1407 -0
- ecadlib/agent_queue.py +780 -0
- ecadlib/agent_watch.py +732 -0
- ecadlib/altium365.py +385 -0
- ecadlib/altium_dblib_sync.py +530 -0
- ecadlib/altium_export.py +209 -0
- ecadlib/altium_forms.py +768 -0
- ecadlib/altium_lib.py +1089 -0
- ecadlib/altium_menu.py +1751 -0
- ecadlib/altium_pkg.py +396 -0
- ecadlib/altium_project.py +1095 -0
- ecadlib/app.py +1458 -0
- ecadlib/approvals.py +395 -0
- ecadlib/assets/demo_bom.csv +8 -0
- ecadlib/assets/kicad_action_plugin.py +607 -0
- ecadlib/assets/kicad_reread.py +60 -0
- ecadlib/assets/pcblib_template.json.gz +0 -0
- ecadlib/bom_inputs.py +339 -0
- ecadlib/bom_triage.py +519 -0
- ecadlib/cad_model.py +528 -0
- ecadlib/cfb.py +171 -0
- ecadlib/cli.py +185 -0
- ecadlib/clihelp.py +149 -0
- ecadlib/cliutil.py +119 -0
- ecadlib/completion.py +173 -0
- ecadlib/costs.py +276 -0
- ecadlib/crosscheck.py +412 -0
- ecadlib/datasheet.py +2168 -0
- ecadlib/dedupe.py +572 -0
- ecadlib/detect.py +170 -0
- ecadlib/doctor.py +450 -0
- ecadlib/ds_route.py +541 -0
- ecadlib/eagle_export.py +1409 -0
- ecadlib/easy_setup.py +338 -0
- ecadlib/easyeda_convert.py +386 -0
- ecadlib/easyeda_pro_export.py +972 -0
- ecadlib/eval/__init__.py +0 -0
- ecadlib/eval/baselines/kicad-v1.json +404 -0
- ecadlib/eval/build_set.py +299 -0
- ecadlib/eval/cli.py +119 -0
- ecadlib/eval/gate.py +358 -0
- ecadlib/eval/golden/demo.txt +379 -0
- ecadlib/eval/golden/specs.txt +3120 -0
- ecadlib/eval/golden/testpack.txt +463 -0
- ecadlib/eval/golden.py +446 -0
- ecadlib/eval/replay/kicad-v1.json.gz +0 -0
- ecadlib/eval/runner.py +456 -0
- ecadlib/eval/score.py +179 -0
- ecadlib/eval/sets/kicad-v1.json +630 -0
- ecadlib/firstrun.py +400 -0
- ecadlib/footprint_adv.py +1006 -0
- ecadlib/footprint_bench.py +218 -0
- ecadlib/footprint_check.py +562 -0
- ecadlib/footprint_gen.py +582 -0
- ecadlib/footprint_rules.py +1092 -0
- ecadlib/forge.py +267 -0
- ecadlib/fp_share.py +95 -0
- ecadlib/fp_tidy.py +389 -0
- ecadlib/fsutil.py +62 -0
- ecadlib/house_style.py +1051 -0
- ecadlib/httpcache.py +124 -0
- ecadlib/hub.py +1490 -0
- ecadlib/hub_copy.py +161 -0
- ecadlib/hub_tools.py +79 -0
- ecadlib/kicad_dblib.py +257 -0
- ecadlib/kicad_export.py +837 -0
- ecadlib/kicad_import.py +913 -0
- ecadlib/kicad_index.py +160 -0
- ecadlib/kicad_menu.py +193 -0
- ecadlib/kicad_pcm.py +475 -0
- ecadlib/launcher.py +156 -0
- ecadlib/librarian.py +963 -0
- ecadlib/lifecycle.py +691 -0
- ecadlib/llm/__init__.py +276 -0
- ecadlib/llm/cli.py +70 -0
- ecadlib/llm/openai_compat.py +214 -0
- ecadlib/llm/profiles.py +245 -0
- ecadlib/llm/rails.py +570 -0
- ecadlib/llm/run.py +126 -0
- ecadlib/llm/sampling.py +49 -0
- ecadlib/mcp_server.py +723 -0
- ecadlib/messages.py +419 -0
- ecadlib/migrate.py +407 -0
- ecadlib/notify.py +329 -0
- ecadlib/package_schema.json +1238 -0
- ecadlib/package_specs/CP_EIA-3216-18_Kemet-A.json +27 -0
- ecadlib/package_specs/C_0402_1005Metric.json +22 -0
- ecadlib/package_specs/C_0805_2012Metric.json +22 -0
- ecadlib/package_specs/DFN-8-1EP_3x3mm_P0.5mm_EP1.66x2.38mm.json +40 -0
- ecadlib/package_specs/D_SMA.json +27 -0
- ecadlib/package_specs/D_SMB.json +27 -0
- ecadlib/package_specs/D_SOD-123.json +43 -0
- ecadlib/package_specs/D_SOD-323.json +43 -0
- ecadlib/package_specs/LED_0603_1608Metric.json +23 -0
- ecadlib/package_specs/LED_0805_2012Metric.json +23 -0
- ecadlib/package_specs/LQFP-48_7x7mm_P0.5mm.json +31 -0
- ecadlib/package_specs/L_0805_2012Metric.json +22 -0
- ecadlib/package_specs/MSOP-8_3x3mm_P0.65mm.json +31 -0
- ecadlib/package_specs/QFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm.json +39 -0
- ecadlib/package_specs/QFN-32-1EP_5x5mm_P0.5mm_EP3.45x3.45mm.json +37 -0
- ecadlib/package_specs/R_0402_1005Metric.json +22 -0
- ecadlib/package_specs/R_0603_1608Metric.json +22 -0
- ecadlib/package_specs/R_0805_2012Metric.json +22 -0
- ecadlib/package_specs/R_1206_3216Metric.json +22 -0
- ecadlib/package_specs/SOIC-16_3.9x9.9mm_P1.27mm.json +32 -0
- ecadlib/package_specs/SOIC-8_3.9x4.9mm_P1.27mm.json +32 -0
- ecadlib/package_specs/SOT-223-3_TabPin2.json +56 -0
- ecadlib/package_specs/SOT-23-5.json +57 -0
- ecadlib/package_specs/SOT-23-6.json +62 -0
- ecadlib/package_specs/SOT-23.json +48 -0
- ecadlib/package_specs/TSSOP-16_4.4x5mm_P0.65mm.json +31 -0
- ecadlib/package_specs/VQFN-16-1EP_3x3mm_P0.5mm_EP1.68x1.68mm.json +39 -0
- ecadlib/package_specs/advanced/BGA-100_11.0x11.0mm_Layout10x10_P1.0mm_Ball0.5mm_Pad0.4mm_NSMD.json +26 -0
- ecadlib/package_specs/advanced/BGA-121_9.0x9.0mm_Layout11x11_P0.8mm_Ball0.4mm_Pad0.35mm_NSMD.json +26 -0
- ecadlib/package_specs/advanced/BGA-144_7.0x7.0mm_Layout13x13_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD.json +29 -0
- ecadlib/package_specs/advanced/BGA-169_11.0x11.0mm_Layout13x13_P0.8mm_Ball0.5mm_Pad0.4mm_NSMD.json +23 -0
- ecadlib/package_specs/advanced/BGA-256_11.0x11.0mm_Layout20x20_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD.json +26 -0
- ecadlib/package_specs/advanced/BGA-256_17.0x17.0mm_Layout16x16_P1.0mm_Ball0.5mm_Pad0.4mm_NSMD.json +26 -0
- ecadlib/package_specs/advanced/BGA-324_15.0x15.0mm_Layout18x18_P0.8mm_Ball0.5mm_Pad0.4mm_NSMD.json +26 -0
- ecadlib/package_specs/advanced/BGA-36_3.396x3.466mm_Layout6x6_P0.4mm_Ball0.25mm_Pad0.2mm_NSMD.json +24 -0
- ecadlib/package_specs/advanced/BGA-672_27.0x27.0mm_Layout26x26_P1.0mm_Ball0.6mm_Pad0.5mm_NSMD.json +32 -0
- ecadlib/package_specs/advanced/BGA-68_5.0x5.0mm_Layout9x9_P0.5mm_Ball0.3mm_Pad0.25mm_NSMD.json +30 -0
- ecadlib/package_specs/advanced/BGA-90_8.0x13.0mm_Layout2x3x15_P0.8mm.json +26 -0
- ecadlib/package_specs/advanced/DFN-8-1EP_3x3mm_P0.5mm_EP1.65x2.38mm_ThermalVias.json +60 -0
- ecadlib/package_specs/advanced/Example_Custom_LPad_Slot.json +24 -0
- ecadlib/package_specs/advanced/JST_GH_SM04B-GHS-TB_1x04-1MP_P1.25mm_Horizontal.json +49 -0
- ecadlib/package_specs/advanced/JST_PH_B4B-PH-K_1x04_P2.00mm_Vertical.json +33 -0
- ecadlib/package_specs/advanced/JST_PH_S4B-PH-SM4-TB_1x04-1MP_P2.00mm_Horizontal.json +55 -0
- ecadlib/package_specs/advanced/JST_SH_SM04B-SRSS-TB_1x04-1MP_P1.00mm_Horizontal.json +49 -0
- ecadlib/package_specs/advanced/JST_XH_B2B-XH-A_1x02_P2.50mm_Vertical.json +34 -0
- ecadlib/package_specs/advanced/JST_XH_B4B-XH-A_1x04_P2.50mm_Vertical.json +34 -0
- ecadlib/package_specs/advanced/JST_XH_B6B-XH-A_1x06_P2.50mm_Vertical.json +33 -0
- ecadlib/package_specs/advanced/LFBGA-144_10x10mm_Layout12x12_P0.8mm.json +23 -0
- ecadlib/package_specs/advanced/Molex_KK-254_AE-6410-04A_1x04_P2.54mm_Vertical.json +34 -0
- ecadlib/package_specs/advanced/Molex_PicoBlade_53398-0471_1x04-1MP_P1.25mm_Vertical.json +54 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P1.00mm_Vertical_SMD_Pin1Left.json +14 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P1.27mm_Vertical_SMD_Pin1Left.json +14 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P2.00mm_Vertical.json +12 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P2.00mm_Vertical_SMD_Pin1Left.json +14 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P2.54mm_Vertical.json +12 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P2.54mm_Vertical_SMD_Pin1Left.json +25 -0
- ecadlib/package_specs/advanced/PinHeader_1x05_P2.54mm_Vertical_SMD_Pin1Right.json +14 -0
- ecadlib/package_specs/advanced/PinHeader_1x10_P1.00mm_Vertical.json +15 -0
- ecadlib/package_specs/advanced/PinHeader_2x05_P1.00mm_Vertical_SMD.json +13 -0
- ecadlib/package_specs/advanced/PinHeader_2x05_P1.27mm_Vertical.json +18 -0
- ecadlib/package_specs/advanced/PinHeader_2x05_P1.27mm_Vertical_SMD.json +24 -0
- ecadlib/package_specs/advanced/PinHeader_2x05_P2.00mm_Vertical_SMD.json +13 -0
- ecadlib/package_specs/advanced/PinHeader_2x05_P2.54mm_Vertical.json +12 -0
- ecadlib/package_specs/advanced/PinHeader_2x05_P2.54mm_Vertical_SMD.json +24 -0
- ecadlib/package_specs/advanced/PinHeader_2x20_P2.54mm_Vertical.json +12 -0
- ecadlib/package_specs/advanced/QFN-16-1EP_3x3mm_P0.5mm_EP1.75x1.75mm_ThermalVias.json +87 -0
- ecadlib/package_specs/advanced/UFBGA-132_7x7mm_Layout12x12_P0.5mm.json +43 -0
- ecadlib/package_specs/advanced/USB_C_Receptacle_HRO_TYPE-C-31-M-12.json +218 -0
- ecadlib/package_specs/standard.json.gz +0 -0
- ecadlib/parity.py +1204 -0
- ecadlib/parts_export.py +154 -0
- ecadlib/partview.py +215 -0
- ecadlib/plugins.py +231 -0
- ecadlib/providers.py +226 -0
- ecadlib/rebuild.py +471 -0
- ecadlib/release_check.py +437 -0
- ecadlib/reports.py +274 -0
- ecadlib/resolve.py +556 -0
- ecadlib/review_cli.py +292 -0
- ecadlib/rules.py +979 -0
- ecadlib/rules_infer.py +1205 -0
- ecadlib/sexpr.py +149 -0
- ecadlib/sheetsafe.py +65 -0
- ecadlib/sim_models.py +1087 -0
- ecadlib/sourcing.py +1161 -0
- ecadlib/stdparts.py +755 -0
- ecadlib/step_gen.py +929 -0
- ecadlib/symbol_layout.py +798 -0
- ecadlib/team.py +558 -0
- ecadlib/team_setup.py +288 -0
- ecadlib/term.py +420 -0
- ecadlib/term_views.py +64 -0
- ecadlib/testpack.py +334 -0
- ecadlib/ui/__init__.py +1 -0
- ecadlib/ui/__main__.py +3 -0
- ecadlib/ui/server.py +1029 -0
- ecadlib/ui/static/hairline.js +2 -0
- ecadlib/ui/static/index.html +2098 -0
- ecadlib/ui/static/setup.html +294 -0
- ecadlib/upkeep.py +717 -0
- ecadlib/upstream.py +573 -0
- ecadlib/userconfig.py +257 -0
- ecadlib/visual_check.py +1603 -0
- ecadlib/ws_status.py +203 -0
- ecadlib-0.3.0.dist-info/METADATA +2245 -0
- ecadlib-0.3.0.dist-info/RECORD +199 -0
- ecadlib-0.3.0.dist-info/WHEEL +5 -0
- ecadlib-0.3.0.dist-info/entry_points.txt +2 -0
- ecadlib-0.3.0.dist-info/licenses/LICENSE +21 -0
- ecadlib-0.3.0.dist-info/licenses/NOTICE +53 -0
- ecadlib-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"""Minimal native EasyEDA (std) shape-string converter: footprint -> .kicad_mod text, symbol -> .kicad_sym text.
|
|
2
|
+
|
|
3
|
+
The KiCad text then goes through kicad_import (convert_footprint / convert_symbol) to Altium, and footprint_check
|
|
4
|
+
can read the .kicad_mod. Only what a land pattern and a pin map need is converted:
|
|
5
|
+
footprint: PAD (RECT / ELLIPSE / OVAL; POLYGON as its bounding rectangle), SMD and through-hole (round or slot,
|
|
6
|
+
plated or not), HOLE (NPTH), TRACK, RECT, CIRCLE (as a 16-gon) and circular ARC on silk / fab layers, the body
|
|
7
|
+
region (SOLIDREGION on layer 99) as a fab outline; a courtyard is drawn around pads and fab (EasyEDA
|
|
8
|
+
has no courtyard layer); TEXT, VIA and other regions (layer-100 lead shapes) are dropped with a note;
|
|
9
|
+
REF** goes on silk above the courtyard and the value on fab below it (1 mm, 0.15 mm stroke); a pin-1 dot
|
|
10
|
+
inside the body is moved beside pad 1
|
|
11
|
+
symbol: pins (number, name, electrical type, position, length), rectangles, polylines, polygons, circles;
|
|
12
|
+
arcs, paths, text and multi-unit `subparts` are not converted
|
|
13
|
+
|
|
14
|
+
FORMAT ASSUMPTIONS (easyeda.com is blocked here, so this was written against hand-made fixtures shaped after
|
|
15
|
+
easyeda2kicad 1.0.1's parameter dataclasses, not real payloads):
|
|
16
|
+
* every shape is "TYPE~f1~f2~..." with fields in easyeda2kicad's order:
|
|
17
|
+
PAD~shape~cx~cy~w~h~layer~net~number~hole_r~points~rot~id~hole_len~slot_outline~plated~locked
|
|
18
|
+
TRACK~width~layer~net~"x1 y1 x2 y2 ..."~id~locked HOLE~cx~cy~r~id~locked
|
|
19
|
+
CIRCLE~cx~cy~r~width~layer~id~locked RECT~x~y~w~h~layer~id~locked~width
|
|
20
|
+
symbol pin "P~show~type~spice_num~x~y~rot~id~locked^^dot^^path~color^^name seg^^num seg^^..." with the
|
|
21
|
+
KiCad pin number at segment 4 field 4 and the name at segment 3 field 4
|
|
22
|
+
R~x~y~rx~ry~w~h~... (rx/ry may be empty) PL / PG~"x1 y1 x2 y2 ..."~... C~cx~cy~r~...
|
|
23
|
+
* footprint units are 10 mil (x 0.254 mm), relative to dataStr.head x/y, y down like KiCad
|
|
24
|
+
* symbol units are 10 mil too, relative to head x/y, y down (flipped for KiCad, whose symbols are y up)
|
|
25
|
+
* layers: 1 top copper, 2 bottom, 11 multi-layer (through-hole), 3 top silk, 13 top assembly -> F.Fab,
|
|
26
|
+
100 lead shapes -> F.Fab, 101 polarity marks -> silk; 99 (component shape) -> F.Fab here (easyeda2kicad
|
|
27
|
+
puts it on the courtyard, but a body shape is not an IPC courtyard)
|
|
28
|
+
* pin electrical type: 0 unspecified, 1 input, 2 output, 3 bidirectional, 4 power
|
|
29
|
+
* a pin's connection point is (x, y); its body end is the far end of its path; pad rotation is used as given
|
|
30
|
+
* a pad is plated unless its plated field says N / no / false / 0"""
|
|
31
|
+
import math, re
|
|
32
|
+
|
|
33
|
+
U = 0.254 # one EasyEDA unit (10 mil) in mm
|
|
34
|
+
SILK_CLEAR, SILK_HARD = 0.2, 0.1 # KLC F5.1 silk-to-pad gap; below SILK_HARD footprint_check errors
|
|
35
|
+
TEXT_H, TEXT_W, TEXT_GAP = 1.0, 0.15, 0.2 # reference / value text: height, stroke, gap to courtyard / silk (mm)
|
|
36
|
+
DOT_R = 0.5 # a silk circle this small (radius, mm) is a pin-1 dot
|
|
37
|
+
FP_LAYERS = {3: "F.SilkS", 101: "F.SilkS", 13: "F.Fab", 99: "F.Fab", 100: "F.Fab"}
|
|
38
|
+
# EasyEDA's library data asks anyone who reuses it to credit the source; every converted part carries this line
|
|
39
|
+
CREDIT = "From the JLCEDA/EasyEDA Official Library (https://easyeda.com, https://lceda.cn)"
|
|
40
|
+
PIN_TYPE = {"0": "unspecified", "1": "input", "2": "output", "3": "bidirectional", "4": "power_in"}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _f(v, d=0.0):
|
|
44
|
+
try:
|
|
45
|
+
return float(v)
|
|
46
|
+
except (TypeError, ValueError):
|
|
47
|
+
return d
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _q(s):
|
|
51
|
+
return '"' + str(s).replace("\\", "\\\\").replace('"', '\\"') + '"'
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _n(v):
|
|
55
|
+
return f"{round(v, 4) + 0:g}"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _nums(s):
|
|
59
|
+
return [float(v) for v in re.findall(r"-?\d+(?:\.\d+)?(?:[eE]-?\d+)?", s or "")]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _no(v):
|
|
63
|
+
return str(v).strip().lower() in ("n", "no", "false", "0")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _grid(v, up):
|
|
67
|
+
return (math.ceil if up else math.floor)(round(v * 100, 6)) / 100
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ---------------------------------------------------------------- footprint
|
|
71
|
+
def footprint_to_kicad(data, name=None, notes=None):
|
|
72
|
+
"""packageDetail.dataStr ({head, shape}) -> .kicad_mod text (KiCad 7 format)."""
|
|
73
|
+
notes = [] if notes is None else notes
|
|
74
|
+
head = data.get("head") or {}
|
|
75
|
+
ox, oy = _f(head.get("x")), _f(head.get("y"))
|
|
76
|
+
name = name or (head.get("c_para") or {}).get("package") or "EasyEDA"
|
|
77
|
+
X, Y = (lambda x: (_f(x) - ox) * U), (lambda y: (_f(y) - oy) * U)
|
|
78
|
+
pads, lines, circles, arcs, dropped, extent, copper = [], [], [], [], {}, [], []
|
|
79
|
+
for s in data.get("shape") or []:
|
|
80
|
+
if not isinstance(s, str):
|
|
81
|
+
continue
|
|
82
|
+
f = s.split("~")
|
|
83
|
+
kind = f[0]
|
|
84
|
+
if kind == "PAD":
|
|
85
|
+
f += [""] * (17 - len(f))
|
|
86
|
+
shape, cx, cy, w, h, layer, num, hole_r = f[1], X(f[2]), Y(f[3]), _f(f[4]) * U, _f(f[5]) * U, f[6], f[8], _f(f[9]) * U
|
|
87
|
+
rot, hole_len = _f(f[11]) % 360, _f(f[13]) * U
|
|
88
|
+
if shape == "POLYGON":
|
|
89
|
+
pts = _nums(f[10])
|
|
90
|
+
xs, ys = [X(v) for v in pts[0::2]], [Y(v) for v in pts[1::2]]
|
|
91
|
+
if xs:
|
|
92
|
+
cx, cy, w, h, rot = (min(xs) + max(xs)) / 2, (min(ys) + max(ys)) / 2, max(xs) - min(xs), max(ys) - min(ys), 0
|
|
93
|
+
notes.append(f"pad {num}: polygon pad written as its bounding rectangle {w:.3f}x{h:.3f} mm")
|
|
94
|
+
kshape = {"RECT": "rect", "OVAL": "oval", "ELLIPSE": "circle" if abs(w - h) < 1e-6 else "oval"}.get(shape, "rect")
|
|
95
|
+
at = f"(at {_n(cx)} {_n(cy)}{' ' + _n(rot) if rot else ''})"
|
|
96
|
+
if hole_r > 0:
|
|
97
|
+
d = 2 * hole_r
|
|
98
|
+
drill = f"(drill {_n(d)})"
|
|
99
|
+
if hole_len > d + 1e-6: # slot along the pad's long side (easyeda2kicad's rule)
|
|
100
|
+
drill = f"(drill oval {_n(hole_len)} {_n(d)})" if w >= h else f"(drill oval {_n(d)} {_n(hole_len)})"
|
|
101
|
+
kind_ = "np_thru_hole" if _no(f[15]) else "thru_hole"
|
|
102
|
+
pads.append(f' (pad {_q(num)} {kind_} {kshape} {at} (size {_n(w)} {_n(h)}) {drill} (layers "*.Cu" "*.Mask"))')
|
|
103
|
+
else:
|
|
104
|
+
side = "B" if str(layer) == "2" else "F"
|
|
105
|
+
pads.append(f' (pad {_q(num)} smd {kshape} {at} (size {_n(w)} {_n(h)}) (layers "{side}.Cu" "{side}.Paste" "{side}.Mask"))')
|
|
106
|
+
extent += _rect(cx, cy, w, h, rot)
|
|
107
|
+
copper.append({"num": num, "x": cx, "y": cy, "w": w, "h": h, "rot": rot, "shape": kshape})
|
|
108
|
+
elif kind == "HOLE":
|
|
109
|
+
cx, cy, d = X(f[1]), Y(f[2]), 2 * _f(f[3]) * U
|
|
110
|
+
pads.append(f' (pad "" np_thru_hole circle (at {_n(cx)} {_n(cy)}) (size {_n(d)} {_n(d)}) (drill {_n(d)}) (layers "*.Cu" "*.Mask"))')
|
|
111
|
+
extent += [(cx - d / 2, cy - d / 2), (cx + d / 2, cy + d / 2)]
|
|
112
|
+
copper.append({"num": "", "x": cx, "y": cy, "w": d, "h": d, "rot": 0, "shape": "circle"})
|
|
113
|
+
elif kind in ("TRACK", "RECT", "CIRCLE"):
|
|
114
|
+
lid = int(_f(f[2] if kind == "TRACK" else f[5] if kind in ("RECT", "CIRCLE") else 0))
|
|
115
|
+
layer = FP_LAYERS.get(lid)
|
|
116
|
+
if not layer:
|
|
117
|
+
dropped[f"{kind} on layer {lid}"] = dropped.get(f"{kind} on layer {lid}", 0) + 1
|
|
118
|
+
continue
|
|
119
|
+
if kind == "TRACK":
|
|
120
|
+
w, p = _f(f[1]) * U, _nums(f[4])
|
|
121
|
+
pts = [(X(x), Y(y)) for x, y in zip(p[0::2], p[1::2])]
|
|
122
|
+
lines += [(a, b, w, layer) for a, b in zip(pts, pts[1:])]
|
|
123
|
+
elif kind == "RECT":
|
|
124
|
+
x, y, w, h, sw = X(f[1]), Y(f[2]), _f(f[3]) * U, _f(f[4]) * U, _f(f[8] if len(f) > 8 else 0) * U or 0.1
|
|
125
|
+
c = [(x, y), (x + w, y), (x + w, y + h), (x, y + h)]
|
|
126
|
+
lines += [(a, b, sw, layer) for a, b in zip(c, c[1:] + c[:1])]
|
|
127
|
+
else:
|
|
128
|
+
circles.append((X(f[1]), Y(f[2]), _f(f[3]) * U, _f(f[4]) * U, layer))
|
|
129
|
+
elif kind == "ARC" and FP_LAYERS.get(int(_f(f[2]))) and _svg_arc(f[4]):
|
|
130
|
+
arcs.append((*_svg_arc(f[4], X, Y), _f(f[1]) * U, FP_LAYERS[int(_f(f[2]))]))
|
|
131
|
+
elif kind == "SOLIDREGION" and int(_f(f[1])) == 99 and _region(f[3]):
|
|
132
|
+
pts = [(X(x), Y(y)) for x, y in _region(f[3])] # the part body: its outline on F.Fab
|
|
133
|
+
lines += [(a, b, 0.1, "F.Fab") for a, b in zip(pts, pts[1:] + pts[:1])]
|
|
134
|
+
elif kind in ("ARC", "TEXT", "SOLIDREGION", "VIA", "SVGNODE"):
|
|
135
|
+
what = f"SOLIDREGION on layer {int(_f(f[1]))}" if kind == "SOLIDREGION" else kind
|
|
136
|
+
if kind != "SVGNODE":
|
|
137
|
+
dropped[what] = dropped.get(what, 0) + 1
|
|
138
|
+
else:
|
|
139
|
+
dropped[kind] = dropped.get(kind, 0) + 1
|
|
140
|
+
lines, circles = _clear_silk(lines, circles, copper, notes)
|
|
141
|
+
circles = _pin1_dot(circles, lines, copper, notes)
|
|
142
|
+
if dropped:
|
|
143
|
+
notes.append("EasyEDA shapes not converted: " + ", ".join(f"{k} x{v}" for k, v in sorted(dropped.items())))
|
|
144
|
+
if not pads:
|
|
145
|
+
raise ValueError("EasyEDA footprint has no pads")
|
|
146
|
+
extent += [pt for a, b, _, layer in lines if layer == "F.Fab" for pt in (a, b)]
|
|
147
|
+
extent += [(cx + s * r, cy + s * r) for cx, cy, r, _, layer in circles if layer == "F.Fab" for s in (-1, 1)]
|
|
148
|
+
xs, ys = [p[0] for p in extent], [p[1] for p in extent]
|
|
149
|
+
c = 0.25 # IPC-7351B nominal courtyard excess, on the 0.01 mm grid
|
|
150
|
+
crt = (_grid(min(xs) - c, False), _grid(min(ys) - c, False), _grid(max(xs) + c, True), _grid(max(ys) + c, True))
|
|
151
|
+
# Reference on silk just above everything (courtyard and silk), Value on fab just below: 1 mm text, 0.15 mm stroke,
|
|
152
|
+
# clear of the pads, as footprint_gen and KiCad's own library place them (EasyEDA's TEXT shapes are not converted)
|
|
153
|
+
silk_y = [v for a, b, _, layer in lines if layer == "F.SilkS" for v in (a[1], b[1])] + \
|
|
154
|
+
[cy + s * (r + w / 2) for cx, cy, r, w, layer in circles if layer == "F.SilkS" for s in (-1, 1)]
|
|
155
|
+
ref_y = round(min([crt[1]] + silk_y) - TEXT_GAP - TEXT_H / 2, 4)
|
|
156
|
+
val_y = round(max([crt[3]] + silk_y) + TEXT_GAP + TEXT_H / 2, 4)
|
|
157
|
+
cx_ = round((crt[0] + crt[2]) / 2, 4)
|
|
158
|
+
font = f"(effects (font (size {_n(TEXT_H)} {_n(TEXT_H)}) (thickness {_n(TEXT_W)})))"
|
|
159
|
+
out = [f"(footprint {_q(name)} (version 20221018) (generator ecadlib_easyeda) (layer \"F.Cu\")",
|
|
160
|
+
f" (descr {_q(CREDIT + '; converted by ecadlib.easyeda_convert; check against the datasheet')})",
|
|
161
|
+
" (attr smd)" if all(" smd " in p for p in pads) else " (attr through_hole)",
|
|
162
|
+
f' (fp_text reference "REF**" (at {_n(cx_)} {_n(ref_y)}) (layer "F.SilkS") {font})',
|
|
163
|
+
f' (fp_text value {_q(name)} (at {_n(cx_)} {_n(val_y)}) (layer "F.Fab") {font})']
|
|
164
|
+
# ${REFERENCE} on the assembly drawing (KLC F5.2), in the middle of the body, 0.5 .. 1.0 mm to fit it
|
|
165
|
+
bpts = [pt for a, b, _, layer in lines if layer in ("F.Fab", "F.SilkS") for pt in (a, b)] or [pt for pt in extent]
|
|
166
|
+
bw, bh = max(p[0] for p in bpts) - min(p[0] for p in bpts), max(p[1] for p in bpts) - min(p[1] for p in bpts)
|
|
167
|
+
fs = max(0.5, min(1.0, math.floor(100 * min(bw / (len("REF**") * 0.9), bh * 0.6)) / 100))
|
|
168
|
+
bc = (round((max(p[0] for p in bpts) + min(p[0] for p in bpts)) / 2, 4), round((max(p[1] for p in bpts) + min(p[1] for p in bpts)) / 2, 4))
|
|
169
|
+
out.append(f' (fp_text user "${{REFERENCE}}" (at {_n(bc[0])} {_n(bc[1])}) (layer "F.Fab") '
|
|
170
|
+
f'(effects (font (size {_n(fs)} {_n(fs)}) (thickness {_n(round(0.15 * fs, 3))}))))')
|
|
171
|
+
for (x1, y1), (x2, y2), w, layer in lines:
|
|
172
|
+
out.append(f' (fp_line (start {_n(x1)} {_n(y1)}) (end {_n(x2)} {_n(y2)}) (stroke (width {_n(w or 0.1)}) (type solid)) (layer "{layer}"))')
|
|
173
|
+
for cx, cy, r, w, layer in circles: # as a 16-gon: footprint_check reads fp_circle's centre from (start)
|
|
174
|
+
xy = " ".join(f"(xy {_n(cx + r * math.cos(k * math.pi / 8))} {_n(cy + r * math.sin(k * math.pi / 8))})" for k in range(16))
|
|
175
|
+
out.append(f' (fp_poly (pts {xy}) (stroke (width {_n(w or 0.1)}) (type solid)) (fill none) (layer "{layer}"))')
|
|
176
|
+
for a, m, b, w, layer in arcs:
|
|
177
|
+
out.append(f' (fp_arc (start {_n(a[0])} {_n(a[1])}) (mid {_n(m[0])} {_n(m[1])}) (end {_n(b[0])} {_n(b[1])}) '
|
|
178
|
+
f'(stroke (width {_n(w or 0.1)}) (type solid)) (layer "{layer}"))')
|
|
179
|
+
out.append(f' (fp_rect (start {_n(crt[0])} {_n(crt[1])}) '
|
|
180
|
+
f'(end {_n(crt[2])} {_n(crt[3])}) (stroke (width 0.05) (type solid)) (fill none) (layer "F.CrtYd"))')
|
|
181
|
+
notes.append("courtyard drawn by ecadlib: pads and fab outline + 0.25 mm (EasyEDA has no courtyard layer)")
|
|
182
|
+
return "\n".join(out + pads) + "\n)\n"
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _svg_arc(path, X=lambda v: v, Y=lambda v: v):
|
|
186
|
+
"""'M x1,y1 A r,r rot large sweep x2,y2' (a circular SVG arc, y down) -> (start, mid, end) through X / Y, else None."""
|
|
187
|
+
v = _nums(path)
|
|
188
|
+
if not re.match(r"\s*M[^A]*A", path or "") or len(v) != 9 or abs(v[2] - v[3]) > 1e-6 * max(1.0, abs(v[2])):
|
|
189
|
+
return None
|
|
190
|
+
(x1, y1), r, large, sweep, (x2, y2) = v[0:2], v[2], v[5], v[6], v[7:9]
|
|
191
|
+
d = math.hypot(x2 - x1, y2 - y1) / 2
|
|
192
|
+
if d < 1e-9:
|
|
193
|
+
return None
|
|
194
|
+
h = math.sqrt(max(r * r - d * d, 0.0))
|
|
195
|
+
sign = 1 if bool(large) != bool(sweep) else -1 # SVG 1.1 F.6.5, no x-axis rotation for a circle
|
|
196
|
+
cx, cy = (x1 + x2) / 2 + sign * h / d * (y1 - y2) / 2, (y1 + y2) / 2 + sign * h / d * (x2 - x1) / 2
|
|
197
|
+
a1, a2, rr = math.atan2(y1 - cy, x1 - cx), math.atan2(y2 - cy, x2 - cx), max(r, d)
|
|
198
|
+
delta = (a2 - a1) % (2 * math.pi) if sweep else -((a1 - a2) % (2 * math.pi))
|
|
199
|
+
am = a1 + delta / 2
|
|
200
|
+
mid = (cx + rr * math.cos(am), cy + rr * math.sin(am))
|
|
201
|
+
return (X(x1), Y(y1)), (X(mid[0]), Y(mid[1])), (X(x2), Y(y2))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _region(path):
|
|
205
|
+
"""A SOLIDREGION path of straight segments 'M x y L x y ... Z' -> its points, else None (curves are not read)."""
|
|
206
|
+
if not path or re.search(r"[AaCcQqSsTtHhVv]", path):
|
|
207
|
+
return None
|
|
208
|
+
v = _nums(path)
|
|
209
|
+
pts = list(zip(v[0::2], v[1::2]))
|
|
210
|
+
return pts if len(pts) >= 3 and len(v) % 2 == 0 else None
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _clear_silk(lines, circles, copper, notes):
|
|
214
|
+
"""Silk kept off the copper, as KiCad's own footprints and footprint_check want it (KLC F5.1): EasyEDA's library
|
|
215
|
+
draws silk outlines up to, or over, the pads (the fab cuts silk from copper when it makes the board). Silk lines are
|
|
216
|
+
cut back to SILK_CLEAR from every pad's outline (pieces shorter than 0.2 mm go); a silk circle (a pin-1 dot) is kept
|
|
217
|
+
whole unless it comes within SILK_HARD of a pad, and then left out. Returns (lines, circles)."""
|
|
218
|
+
from .footprint_adv import _clip_any, _pad_dist
|
|
219
|
+
if not copper:
|
|
220
|
+
return lines, circles
|
|
221
|
+
near = lambda pts, c: min(_pad_dist(x, y, p) for x, y in pts for p in copper) < c
|
|
222
|
+
out, cut = [], 0
|
|
223
|
+
for a, b, w, layer in lines:
|
|
224
|
+
sw = w or 0.1
|
|
225
|
+
c = SILK_CLEAR + sw / 2 + 0.005
|
|
226
|
+
L = math.hypot(b[0] - a[0], b[1] - a[1])
|
|
227
|
+
n = max(2, int(L / 0.01))
|
|
228
|
+
if layer != "F.SilkS" or not near([(a[0] + (b[0] - a[0]) * k / n, a[1] + (b[1] - a[1]) * k / n) for k in range(n + 1)], c):
|
|
229
|
+
out.append((a, b, w, layer))
|
|
230
|
+
continue
|
|
231
|
+
cut += 1
|
|
232
|
+
out += [(p, q, w, layer) for p, q in _clip_any(a, b, copper, c, min_len=min(0.2, L))]
|
|
233
|
+
keep, gone = [], 0
|
|
234
|
+
for cx, cy, r, w, layer in circles:
|
|
235
|
+
ring = [(cx + r * math.cos(k * math.pi / 16), cy + r * math.sin(k * math.pi / 16)) for k in range(32)] + [(cx, cy)]
|
|
236
|
+
if layer == "F.SilkS" and near(ring, SILK_HARD + (w or 0.1) / 2 + 0.005):
|
|
237
|
+
gone += 1
|
|
238
|
+
continue
|
|
239
|
+
keep.append((cx, cy, r, w, layer))
|
|
240
|
+
if cut:
|
|
241
|
+
notes.append(f"silk: {cut} line(s) cut back to {SILK_CLEAR} mm from the pads (the EasyEDA outline ran up to or over the copper)")
|
|
242
|
+
if gone:
|
|
243
|
+
notes.append(f"silk: {gone} circle(s) left out: within {SILK_HARD} mm of a pad")
|
|
244
|
+
return out, keep
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _pin1_dot(circles, lines, copper, notes):
|
|
248
|
+
"""EasyEDA's pin-1 dot usually sits inside the body outline, where the part covers it once placed. A small silk
|
|
249
|
+
circle (a dot, r <= DOT_R) inside the body is moved next to pad 1, outside the body and the pads: the spot beside
|
|
250
|
+
pad 1 (left / right / above / below) closest to it that keeps SILK_CLEAR from every pad and from the body outline."""
|
|
251
|
+
p1 = next((p for p in copper if p["num"] == "1"), None)
|
|
252
|
+
if p1 is None or len([p for p in copper if p["num"]]) < 3:
|
|
253
|
+
return circles
|
|
254
|
+
outline = [pt for a, b, _, layer in lines if layer in ("F.SilkS", "F.Fab") for pt in (a, b)]
|
|
255
|
+
pads = [pt for p in copper for pt in _rect(p["x"], p["y"], p["w"], p["h"], p["rot"])]
|
|
256
|
+
pts = outline or pads
|
|
257
|
+
body = (min(x for x, _ in pts), min(y for _, y in pts), max(x for x, _ in pts), max(y for _, y in pts))
|
|
258
|
+
pb = (min(x for x, _ in pads), min(y for _, y in pads), max(x for x, _ in pads), max(y for _, y in pads))
|
|
259
|
+
from .footprint_adv import _pad_dist
|
|
260
|
+
out = []
|
|
261
|
+
for cx, cy, r, w, layer in circles:
|
|
262
|
+
inside = body[0] < cx < body[2] and body[1] < cy < body[3] or pb[0] < cx < pb[2] and pb[1] < cy < pb[3]
|
|
263
|
+
if layer != "F.SilkS" or r > DOT_R or not inside:
|
|
264
|
+
out.append((cx, cy, r, w, layer))
|
|
265
|
+
continue
|
|
266
|
+
ext = r + (w or 0.1) / 2
|
|
267
|
+
gap = SILK_CLEAR + ext
|
|
268
|
+
x1 = _rect(p1["x"], p1["y"], p1["w"], p1["h"], p1["rot"])
|
|
269
|
+
hx, hy = (max(x for x, _ in x1) - min(x for x, _ in x1)) / 2, (max(y for _, y in x1) - min(y for _, y in x1)) / 2
|
|
270
|
+
best = None
|
|
271
|
+
for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)):
|
|
272
|
+
x, y = p1["x"] + dx * (hx + gap), p1["y"] + dy * (hy + gap)
|
|
273
|
+
if dx and body[0] - gap < x < body[2] + gap and body[1] < y < body[3]: # still on the body: past its edge
|
|
274
|
+
x = body[0] - gap if dx < 0 else body[2] + gap
|
|
275
|
+
if dy and body[1] - gap < y < body[3] + gap and body[0] < x < body[2]:
|
|
276
|
+
y = body[1] - gap if dy < 0 else body[3] + gap
|
|
277
|
+
if min(_pad_dist(x, y, p) for p in copper) < gap - 1e-6:
|
|
278
|
+
continue
|
|
279
|
+
d = math.hypot(x - p1["x"], y - p1["y"])
|
|
280
|
+
if best is None or d < best[0]:
|
|
281
|
+
best = (d, round(x, 4), round(y, 4))
|
|
282
|
+
if best is None:
|
|
283
|
+
out.append((cx, cy, r, w, layer))
|
|
284
|
+
continue
|
|
285
|
+
out.append((best[1], best[2], r, w, layer))
|
|
286
|
+
notes.append(f"silk: pin-1 dot moved from ({cx:.2f}, {cy:.2f}) inside the body to ({best[1]:.2f}, {best[2]:.2f}) "
|
|
287
|
+
f"beside pad 1, {SILK_CLEAR} mm clear of the pads")
|
|
288
|
+
return out
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _rect(cx, cy, w, h, rot):
|
|
292
|
+
a = math.radians(rot)
|
|
293
|
+
return [(cx + x * math.cos(a) - y * math.sin(a), cy + x * math.sin(a) + y * math.cos(a))
|
|
294
|
+
for x, y in ((-w / 2, -h / 2), (w / 2, -h / 2), (w / 2, h / 2), (-w / 2, h / 2))]
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
# ---------------------------------------------------------------- symbol
|
|
298
|
+
def _path_ends(path):
|
|
299
|
+
"""'M 370 300 h 10' / 'M370,300L380,300' / 'v -10' -> (start, end) in canvas units."""
|
|
300
|
+
toks = re.findall(r"[MLHVmlhv]|-?\d+(?:\.\d+)?", path or "")
|
|
301
|
+
x = y = 0.0
|
|
302
|
+
start, cmd, i = None, None, 0
|
|
303
|
+
while i < len(toks):
|
|
304
|
+
t = toks[i]
|
|
305
|
+
if t.isalpha():
|
|
306
|
+
cmd, i = t, i + 1
|
|
307
|
+
continue
|
|
308
|
+
if cmd in ("M", "L", "m", "l"):
|
|
309
|
+
dx, dy = float(toks[i]), float(toks[i + 1]); i += 2
|
|
310
|
+
x, y = (x + dx, y + dy) if cmd.islower() else (dx, dy)
|
|
311
|
+
elif cmd in ("H", "h"):
|
|
312
|
+
x = x + float(t) if cmd == "h" else float(t); i += 1
|
|
313
|
+
elif cmd in ("V", "v"):
|
|
314
|
+
y = y + float(t) if cmd == "v" else float(t); i += 1
|
|
315
|
+
else:
|
|
316
|
+
i += 1
|
|
317
|
+
if start is None:
|
|
318
|
+
start = (x, y)
|
|
319
|
+
return start, (x, y)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def symbol_to_kicad(data, name, footprint="", props=None, notes=None):
|
|
323
|
+
"""dataStr ({head, shape}) of an EasyEDA symbol -> .kicad_sym library text with one symbol `name`."""
|
|
324
|
+
notes = [] if notes is None else notes
|
|
325
|
+
head = data.get("head") or {}
|
|
326
|
+
cp = head.get("c_para") or {}
|
|
327
|
+
ox, oy = _f(head.get("x")), _f(head.get("y"))
|
|
328
|
+
P = lambda x, y: (_n((_f(x) - ox) * U), _n(-(_f(y) - oy) * U))
|
|
329
|
+
body, pins, dropped = [], [], {}
|
|
330
|
+
for s in data.get("shape") or []:
|
|
331
|
+
if not isinstance(s, str):
|
|
332
|
+
continue
|
|
333
|
+
kind = s.split("~", 1)[0]
|
|
334
|
+
if kind == "P":
|
|
335
|
+
seg = [g.split("~") for g in s.split("^^")]
|
|
336
|
+
st = seg[0] + [""] * (9 - len(seg[0]))
|
|
337
|
+
num = (seg[4][4] if len(seg) > 4 and len(seg[4]) > 4 else "") or st[3]
|
|
338
|
+
nm = seg[3][4] if len(seg) > 3 and len(seg[3]) > 4 else ""
|
|
339
|
+
cx, cy = _f(st[4]), _f(st[5])
|
|
340
|
+
a, b = _path_ends(seg[2][0] if len(seg) > 2 else "")
|
|
341
|
+
if a is None:
|
|
342
|
+
notes.append(f"pin {num}: no pin path; length 10 assumed"); a = b = (cx - 10, cy)
|
|
343
|
+
far = max((a, b), key=lambda p: math.hypot(p[0] - cx, p[1] - cy))
|
|
344
|
+
dx, dy = far[0] - cx, -(far[1] - cy) # connection point -> body, KiCad y up
|
|
345
|
+
raw = math.degrees(math.atan2(dy, dx)) if (dx or dy) else 0.0
|
|
346
|
+
ang = round(raw / 90) % 4 * 90 # KiCad only takes 0, 90, 180, 270: anything else and
|
|
347
|
+
if abs((raw - ang + 180) % 360 - 180) > 0.5: # the whole library fails to load
|
|
348
|
+
notes.append(f"pin {num}: slanted pin path ({raw % 360:.0f} deg) drawn at {ang} deg")
|
|
349
|
+
length = (abs(dx) if ang in (0, 180) else abs(dy)) * U or 2.54
|
|
350
|
+
x, y = P(cx, cy)
|
|
351
|
+
pins.append(f' (pin {PIN_TYPE.get(st[2], "unspecified")} line (at {x} {y} {ang}) (length {_n(length)})'
|
|
352
|
+
f' (name {_q(nm or "~")} (effects (font (size 1.27 1.27)))) (number {_q(num)} (effects (font (size 1.27 1.27)))))')
|
|
353
|
+
elif kind == "R":
|
|
354
|
+
f = s.split("~") + [""] * 8
|
|
355
|
+
x, y, w, h = _f(f[1]), _f(f[2]), _f(f[5]), _f(f[6])
|
|
356
|
+
(x1, y1), (x2, y2) = P(x, y), P(x + w, y + h)
|
|
357
|
+
body.append(f" (rectangle (start {x1} {y1}) (end {x2} {y2}) (stroke (width 0.254) (type default)) (fill (type background)))")
|
|
358
|
+
elif kind in ("PL", "PG"):
|
|
359
|
+
f = s.split("~")
|
|
360
|
+
p = _nums(f[1] if len(f) > 1 else "")
|
|
361
|
+
pts = [P(x, y) for x, y in zip(p[0::2], p[1::2])]
|
|
362
|
+
if kind == "PG" and pts:
|
|
363
|
+
pts.append(pts[0])
|
|
364
|
+
if len(pts) >= 2:
|
|
365
|
+
xy = " ".join(f"(xy {x} {y})" for x, y in pts)
|
|
366
|
+
body.append(f" (polyline (pts {xy}) (stroke (width 0.254) (type default)) (fill (type {'background' if kind == 'PG' else 'none'})))")
|
|
367
|
+
elif kind in ("C", "E"):
|
|
368
|
+
f = s.split("~") + [""] * 5
|
|
369
|
+
rx, ry = _f(f[3]), _f(f[4]) if kind == "E" else _f(f[3])
|
|
370
|
+
if kind == "E" and abs(rx - ry) > 1e-6:
|
|
371
|
+
dropped["E (ellipse)"] = dropped.get("E (ellipse)", 0) + 1; continue
|
|
372
|
+
x, y = P(f[1], f[2])
|
|
373
|
+
body.append(f" (circle (center {x} {y}) (radius {_n(rx * U)}) (stroke (width 0.254) (type default)) (fill (type none)))")
|
|
374
|
+
else:
|
|
375
|
+
dropped[kind] = dropped.get(kind, 0) + 1
|
|
376
|
+
if dropped:
|
|
377
|
+
notes.append("EasyEDA symbol shapes not converted: " + ", ".join(f"{k} x{v}" for k, v in sorted(dropped.items())))
|
|
378
|
+
if not pins:
|
|
379
|
+
raise ValueError("EasyEDA symbol has no pins")
|
|
380
|
+
pr = {"Reference": (cp.get("pre") or "U?").rstrip("?") or "U", "Value": name, "Footprint": footprint,
|
|
381
|
+
"Datasheet": "~", "Description": "converted from EasyEDA by ecadlib.easyeda_convert", "Credit": CREDIT}
|
|
382
|
+
pr.update({k: v for k, v in (props or {}).items() if v})
|
|
383
|
+
out = ["(kicad_symbol_lib (version 20231120) (generator ecadlib_easyeda)", f" (symbol {_q(name)} (in_bom yes) (on_board yes)"]
|
|
384
|
+
out += [f" (property {_q(k)} {_q(v)} (at 0 0 0) (effects (font (size 1.27 1.27)){'' if k in ('Reference', 'Value') else ' hide'}))" for k, v in pr.items()]
|
|
385
|
+
out += [f" (symbol {_q(name + '_1_1')}"] + body + pins + [" )", " )", ")"]
|
|
386
|
+
return "\n".join(out) + "\n"
|