sync: HP_BOWEI 2026-10-15

This commit is contained in:
Myfro9
2026-10-15 01:59:40 +08:00
parent 771acd4171
commit a0a3445bfd
21 changed files with 3042 additions and 16 deletions
+103
View File
@@ -0,0 +1,103 @@
"""把 drawio 框图渲染成 PNG,供原理图第一页当插图(n10 硬要求)。
两种 drawio 格式都吃:
* SVG 外壳(drawio MCP / 导出的 .drawio.svg):headless Chrome 直接截 SVG;
* 纯 mxfile XML(drawio 桌面版存的 .drawio):套一层 HTML,用 diagrams.net 的
viewer-static.min.js 在 headless Chrome 里渲染后截图(需要能上网取 JS)。
不用 draw.io.exe --export:G-0022 实测它在 Windows 上报 input file not found,还会挂起。
python -m schgen.diagram <in.drawio> <out.png> [--scale 2]
"""
import html
import io
import os
import re
import struct
import subprocess
import sys
import tempfile
from .env import chrome
VIEWER_JS = "https://viewer.diagrams.net/js/viewer-static.min.js"
def _png_size(p):
d = io.open(p, "rb").read(32)
return struct.unpack(">II", d[16:24])
def _page_size(xml):
m = re.search(r'pageWidth="(\d+)"[^>]*pageHeight="(\d+)"', xml)
if m:
return int(m.group(1)), int(m.group(2))
return 1600, 1200
def _bounds(xml):
"""所有顶点几何的外包框,用来裁掉页面空白。"""
xs, ys = [], []
for m in re.finditer(r'<mxGeometry\s+([^>]*?)/?>', xml):
a = dict(re.findall(r'(\w+)="([^"]*)"', m.group(1)))
if a.get("relative") == "1" or "width" not in a:
continue
x, y = float(a.get("x", 0)), float(a.get("y", 0))
w, h = float(a["width"]), float(a["height"])
xs += [x, x + w]; ys += [y, y + h]
if not xs:
return None
return min(xs), min(ys), max(xs), max(ys)
def render(src, png, scale=2, margin=20):
raw = io.open(src, encoding="utf-8", errors="replace").read()
tmpdir = tempfile.mkdtemp(prefix="kf_diag_")
if raw.lstrip().startswith("<svg") or "<svg" in raw[:500]:
m = re.search(r'<svg[^>]*\swidth="([\d.]+)(?:px)?"[^>]*\sheight="([\d.]+)', raw)
w, h = (int(float(m.group(1))), int(float(m.group(2)))) if m else (1600, 1200)
url = "file:///" + os.path.abspath(src).replace("\\", "/")
budget = []
else:
xml = raw
bb = _bounds(xml)
if bb:
w, h = int(bb[2] - bb[0] + 2 * margin), int(bb[3] - bb[1] + 2 * margin)
else:
w, h = _page_size(xml)
cfg = '{"highlight":"#0000ff","nav":false,"resize":false,"toolbar":"","edit":null,' \
'"lightbox":false,"border":%d,"xml":%s}' % (margin, _json_str(xml))
page = ('<!DOCTYPE html><html><head><meta charset="utf-8">'
'<style>html,body{margin:0;padding:0;background:#fff}</style></head><body>'
'<div class="mxgraph" style="max-width:100%%" data-mxgraph="%s"></div>'
'<script src="%s"></script></body></html>' % (html.escape(cfg, quote=True), VIEWER_JS))
hp = os.path.join(tmpdir, "d.html")
io.open(hp, "w", encoding="utf-8").write(page)
url = "file:///" + hp.replace("\\", "/")
budget = ["--virtual-time-budget=15000"]
if os.path.exists(png):
os.remove(png)
os.makedirs(os.path.dirname(os.path.abspath(png)), exist_ok=True)
cmd = [chrome(), "--headless", "--disable-gpu", "--hide-scrollbars",
"--force-device-scale-factor=%d" % scale, "--default-background-color=FFFFFFFF",
"--window-size=%d,%d" % (w, h), "--screenshot=%s" % os.path.abspath(png)] + budget + [url]
subprocess.run(cmd, check=True, capture_output=True, timeout=120)
if not os.path.exists(png):
raise SystemExit("截图失败:%s" % png)
pw, ph = _png_size(png)
print("框图 -> %s %dx%d px(KiCad 按 300DPI 解释:scale=1 时 %.0fx%.0f mm)"
% (png, pw, ph, pw / 300.0 * 25.4, ph / 300.0 * 25.4))
return png
def _json_str(s):
import json
return json.dumps(s)
if __name__ == "__main__":
a = sys.argv[1:]
sc = 2
if "--scale" in a:
i = a.index("--scale"); sc = int(a[i + 1]); del a[i:i + 2]
render(a[0], a[1], scale=sc)