Files
STM32F4-Base/tools/arch_diagram/gen_frame_svg.py
T
wandering1 a6cdebde62 文档体系重组与 D-7 一致性门禁落地,清理芯片手册孤儿图片
- D-6 目录规范化:docs 根目录散落文档归入 UI设计/子系统设计/架构设计/审查记录/
  环境与部署/网络 等子目录,去掉目录名空格,重命名 嵌入式C语言代码规范_V1.0.md
- D-7 三档一致性门禁:新增 scripts/check_doc_links.py(链接存在性)、
  scripts/gen_task_table.py(FreeRTOS 任务表数值)、scripts/check_mem_numbers.py
  (内存/Flash 体积),均支持 --check/--write/--diff,pre-commit 钩子同步注册
- D-5 文档索引:新建 docs/README.md,新建 docs/文档规范.md 约束目录命名与链接写法
- D-1 芯片手册瘦身:删除 1069 张无引用孤儿图片(77.06 MB),
  docs/芯片手册 120 MB → 41 MB,已引用图片零断链
- 同步 C 源码注释中的文档路径引用(D-6 重组后),构建 0 错误 0 警告
2026-09-26 00:51:53 +08:00

156 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""生成 docs/images/RemoDispBus帧格式.svg —— 协议帧结构图(纯标准库,无需第三方依赖)。
为什么不用 Mermaid / HTML:协议"帧"是等宽字节的连续分段,需要"字段宽 ∝ 字节数"的
精确比例与逐字段标注。Mermaid 盒子按内容自适应宽度、还会自动加箭头(把连续字节流画成
有向边,失真);HTML 要截图才能变成可移植图片。SVG 可精确定位(像素 ∝ 字节)、矢量无损、
纯文本可 diff / 可 lint、浏览器直接用系统字体渲染、Markdown 里 ![]() 即可嵌入。
本脚本沿用 tools/arch_diagram/ 既有约定(纯标准库拼 SVG + ElementTree 校验 +
UTF-8/LF 落盘 + 顶部数据改后重跑)。
用法(仓库根目录,任意 python3):
python tools/arch_diagram/gen_frame_svg.py
输出:
docs/images/RemoDispBus帧格式.svg
改字段 / 配色:编辑本文件顶部的 FIELDS / TITLE / SUB / FOOT 后重跑。
画别的帧:复制本文件,改 FIELDS 与 OUT / TITLE(build() 与 FIELDS 解耦,通用)。
"""
import html
import os
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
OUT = os.path.join(ROOT, "docs", "images", "RemoDispBus帧格式.svg")
# ----------------------------- 画布与尺寸参数 -----------------------------
PAD = 40 # 左右留白
PX_PER_BYTE = 52 # 每字节像素宽(1B 字段 = 52px;须足以容纳最长字段名 / 标注)
DATA_UNITS = 5 # 变长 DATA 段的"代表宽度"(按 N 字节画,仅示意,标注 LEN B)
BAR_Y = 116 # 字段盒顶 y(标题 / 副标题 / 偏移标尺在其上方)
BAR_H = 74 # 字段盒高
NAME_DY = 30 # 字段名在盒内的 y 偏移
SIZE_DY = 54 # 尺寸标注在盒内的 y 偏移
RULER_DY = 20 # 偏移标尺线在盒顶上方
TITLE_FONT = 20
SUB_FONT = 13
NAME_FONT = 15
SIZE_FONT = 12
RULER_FONT = 12
FOOT_FONT = 12
FONT = "Microsoft YaHei, Segoe UI, PingFang SC, sans-serif"
# ----------------------------- 帧字段(改这里) -----------------------------
# name=字段名 bytes=字节数(None=变长段,按 DATA_UNITS 画) label=尺寸标注 color/bg=配色
FIELDS = [
dict(name="TAG", bytes=1, label="1 B", color="#2563eb", bg="#eff6ff"),
dict(name="CMD", bytes=1, label="1 B", color="#2563eb", bg="#eff6ff"),
dict(name="LEN_H", bytes=1, label="1 B", color="#0d9488", bg="#f0fdfa"),
dict(name="LEN_L", bytes=1, label="1 B", color="#0d9488", bg="#f0fdfa"),
dict(name="DATA", bytes=None, label="LEN B · 变长", color="#d97706", bg="#fffbeb"),
dict(name="CRC", bytes=1, label="1 B", color="#16a34a", bg="#f0fdf4"),
]
TITLE = "RemoDispBus 协议帧格式"
SUB = "整帧 = 4 + LEN + 1 字节 · 大端(网络字节序)· CRC = DATA 段逐字节异或(不含 TAG/CMD/LEN)"
FOOT = ("TAG:0xAA=客户端帧 / 0xBB=服务端帧 · DATA 为变长段(LEN 字节,0–255,图中按 5 字节示意)· "
"详见 docs/UI设计/RemoDispBus协议与接收设计说明.md §1")
# ----------------------------- 工具函数 -----------------------------
def esc(s):
return html.escape(s, quote=True)
def field_w(f):
return (f["bytes"] if f["bytes"] is not None else DATA_UNITS) * PX_PER_BYTE
def offset_labels(fields):
"""按字段字节数累计边界偏移;遇变长段后,其后偏移记为 '<前缀>+LEN[+n]'。"""
labs, off, expr = ["0"], 0, None
for f in fields:
if f["bytes"] is None:
expr = ("%d+LEN" % off) if off is not None else ("%s+LEN" % (expr or "0"))
off = None
labs.append(expr)
elif off is not None:
off += f["bytes"]
labs.append(str(off))
else:
expr = "%s+%d" % (expr, f["bytes"])
labs.append(expr)
return labs
# ----------------------------- 生成 -----------------------------
def build():
out = []
add = out.append
frame_w = sum(field_w(f) for f in FIELDS)
W = frame_w + 2 * PAD
# 预计算各字段 x / 宽
segs, x = [], PAD
for f in FIELDS:
w = field_w(f)
segs.append([f, x, w])
x += w
labels = offset_labels(FIELDS)
total_h = BAR_Y + BAR_H + 56
add('<rect x="0" y="0" width="%d" height="%d" fill="#ffffff"/>' % (W, total_h))
# 标题 + 副标题
add('<text x="%d" y="40" font-size="%d" font-weight="700" fill="#0f172a">%s</text>'
% (PAD, TITLE_FONT, esc(TITLE)))
add('<text x="%d" y="64" font-size="%d" fill="#64748b">%s</text>'
% (PAD, SUB_FONT, esc(SUB)))
# 偏移标尺(每个字段边界一个刻度 + 偏移标注)
ruler_y = BAR_Y - RULER_DY
add('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="#cbd5e1" stroke-width="1"/>'
% (PAD, ruler_y, PAD + frame_w, ruler_y))
bounds = [PAD] + [s[1] + s[2] for s in segs]
for i, bx in enumerate(bounds):
add('<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="#94a3b8" stroke-width="1.5"/>'
% (bx, ruler_y - 4, bx, ruler_y + 4))
lab = labels[i] if i < len(labels) else "?"
add('<text x="%d" y="%d" font-size="%d" fill="#94a3b8" text-anchor="middle">%s</text>'
% (bx, ruler_y - 8, RULER_FONT, esc(lab)))
# 字段盒(名在上、尺寸在下)
for (f, bx, bw) in segs:
add('<rect x="%d" y="%d" width="%d" height="%d" fill="%s" stroke="%s" stroke-width="1.5"/>'
% (bx, BAR_Y, bw, BAR_H, f["bg"], f["color"]))
cx = bx + bw / 2.0
add('<text x="%.1f" y="%d" font-size="%d" font-weight="700" fill="#0f172a" '
'text-anchor="middle">%s</text>'
% (cx, BAR_Y + NAME_DY, NAME_FONT, esc(f["name"])))
add('<text x="%.1f" y="%d" font-size="%d" fill="%s" text-anchor="middle">%s</text>'
% (cx, BAR_Y + SIZE_DY, SIZE_FONT, f["color"], esc(f["label"])))
# 脚注
add('<text x="%d" y="%d" font-size="%d" fill="#94a3b8">%s</text>'
% (PAD, total_h - 16, FOOT_FONT, esc(FOOT)))
svg = ('<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" '
'viewBox="0 0 %d %d" font-family="%s">\n%s\n</svg>\n'
% (W, total_h, W, total_h, FONT, "\n".join(out)))
return svg, W, total_h
def main():
svg, W, h = build()
with open(OUT, "w", encoding="utf-8", newline="\n") as f:
f.write(svg)
import xml.etree.ElementTree as ET
ET.fromstring(svg)
print("OK %d bytes, canvas %dx%d -> %s" % (len(svg.encode("utf-8")), W, h, OUT))
if __name__ == "__main__":
main()