绘图流程沉淀为 draw-diagram skill,SVG 收拢到 docs/images/
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""生成 docs/项目架构总览.svg —— 分层架构图(纯标准库,无需第三方依赖)。
|
||||
"""生成 docs/images/项目架构总览.svg —— 分层架构图(纯标准库,无需第三方依赖)。
|
||||
|
||||
为什么不用 Mermaid:自动布局不可控,多节点/跨层依赖会画成交叉的"意大利面"。
|
||||
本脚本手工排布:每层一个圆角框(标题在框内顶部 + 模块芯片自动换行),
|
||||
@@ -9,14 +9,14 @@
|
||||
用法(仓库根目录,任意 python3):
|
||||
python tools/arch_diagram/gen_arch_svg.py
|
||||
输出:
|
||||
docs/项目架构总览.svg
|
||||
docs/images/项目架构总览.svg
|
||||
改架构时:编辑本文件顶部的 LAYERS / ROWS 数据后重跑即可。
|
||||
"""
|
||||
import html
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
OUT = os.path.join(ROOT, "docs", "项目架构总览.svg")
|
||||
OUT = os.path.join(ROOT, "docs", "images", "项目架构总览.svg")
|
||||
|
||||
# ----------------------------- 画布与尺寸参数 -----------------------------
|
||||
W = 1560
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""生成 docs/就绪屏障.svg —— 启动就绪屏障(Event Flags)依赖图(纯标准库)。
|
||||
"""生成 docs/images/就绪屏障.svg —— 启动就绪屏障(Event Flags)依赖图(纯标准库)。
|
||||
|
||||
为什么不用 Mermaid:原图 9 条边全挤在单一中心节点上,画成交叉的"意大利面"。
|
||||
本脚本手工排布为三栏:
|
||||
@@ -11,7 +11,7 @@
|
||||
用法(仓库根目录,任意 python3):
|
||||
python tools/arch_diagram/gen_barrier_svg.py
|
||||
输出:
|
||||
docs/就绪屏障.svg
|
||||
docs/images/就绪屏障.svg
|
||||
改依赖时:编辑本文件顶部的 BITS / PRODUCERS / CONSUMERS 数据后重跑。
|
||||
"""
|
||||
import html
|
||||
@@ -19,7 +19,7 @@ import math
|
||||
import os
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
OUT = os.path.join(ROOT, "docs", "就绪屏障.svg")
|
||||
OUT = os.path.join(ROOT, "docs", "images", "就绪屏障.svg")
|
||||
|
||||
# ----------------------------- 画布与尺寸 -----------------------------
|
||||
W = 1400
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
#!/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/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()
|
||||
Reference in New Issue
Block a user