sync: HP_BOWEI 2026-10-15
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user