把 archify 出的交互架构图落回仓内可复现形态,避免 docs/ 里出现「没有生成器的 SVG」: - 新生成器 tools/arch_diagram/gen_arch_endtoend_svg.py → docs/images/架构总览_ 端到端链路.svg(42 框 / 37 边 / 5 条纵带,线型分数据·命令·屏障心跳三类); 数据全部内嵌在 .py 里(generated-fresh 只把单个 gen_*.py 拷进临时目录重跑, 外部 JSON 输入会 rc≠0 假红),SVG 内不含时间戳/commit/绝对路径 - docs/架构设计/项目架构总览.md 新增 §1.3 挂图并写清与 §1 分层图的分工 (那张答「谁在哪一层」,这张答「一条数据走到底穿过谁」;纵带只是列分组, 依赖方向仍以 §1.1 表与 layer-deps 门禁为准),版本 v1.2 → v1.3 - 两处生成器清单登记:tools/arch_diagram/README.md 脚本一览、 docs/架构设计/图形绘制流程.md 的选型表与产物对照表 - draw-diagram skill 新增 §三b(大图碰撞审计判据 + 两条几何纪律)+ reference/svg-collision-audit.py(拷进 ci-artifacts 用,刻意不进 tools/) 本窗实测(都已写进 skill):42 框 37 边的图缩略目检看着干净,解析式审计一眼 报出 23 条——边标签文字盒压到节点框会被后画的节点白底吃掉半个字(「串行执行」 渲染成「行执行」);根因是同行两框走了「同轴加一段走廊」的通用规则,产出退化 四点折线,把标签落点推到第一半中点。修法:同轴且已对齐直接返回两点线,标签 落点改按总弧长取 50%。 零余量第二遍(用生成器自己的 Box/route/label_at 真值扫,框不膨胀不内缩)又抓到 两处近失:「TRGO 触发」「帧 B 下发」与相邻框只剩 0.7px / 1.5px,缩成「TRGO」 「帧 B」后最紧余量升到 3.5px;同一遍还纠正了审计器自己的错——`hi > y0` 误写成 `hi > x0` 凭空报 3 条穿框,差点重排三条边的走线,所以判据里加了「先验审计器 本身再信 0 条」。 自查:连跑两次产物 sha1 不变;scripts/check_generated_fresh.py rc=0(25 生成器 / 21 一致 / 4 在只减不增基线内);碰撞审计 rc=0(0 条);生成器无 OVERFLOW; doc-links --sections-strict 逐篇 rc=0;mermaid-syntax / nested-comment / skill-size / bom-drift 无新发;改动文件字节体检干净;/keil-build 0 错 0 警。
18 KiB
name, description, whenToUse
| name | description | whenToUse |
|---|---|---|
| draw-diagram | docs/ 文档技术配图绘制流程(精确布局图用 tools/arch_diagram/ 的纯标准库 SVG 生成器 / 拓扑图用 Mermaid):挑脚本或照抄骨架新建生成器 → 改顶部数据 → 跑生成并把 check_fit/OVERFLOW 溢出预检清零 → Windows 无渲染器时用 headless Chrome/Edge 截图 + read_image 回读目检 → 回改生成器重跑 → 过 generated-fresh 逐字节幂等 → md 相对路径引用 → 脚本+产物一并提交,产物统一放 docs/images/。 | 需要新增/修改 docs/ 文档的技术配图(协议帧、分层架构、依赖/就绪屏障、台阶/泳道对照、刻度尺、流程图、时序、状态机),从零新写一个 gen_*_svg.py 生成器,在 Windows 上想"目检"一张 SVG,或用户要求"画个图 / 生成图 / 更新架构图 / 帧格式图"时。 |
文档配图绘制(SVG / Mermaid)
docs/ 文档技术配图的统一流程;完整选型依据 / 约定 / 检查清单见 docs/架构设计/图形绘制流程.md。产物统一放 docs/images/(专门配图目录,.svg 入库;PNG 预览属本地派生物、不入库,.gitignore 已含 docs/images/*.png 与 ci-artifacts/)。
本窗实测(2026-10-29,新建 2 张图):"浏览器打开 SVG 目检"这一步在 Windows 上不可直接执行——没有现成 SVG 栅格化工具,只能走 headless Chrome/Edge 截图 + 图片回读;且
generated-fresh是逐字节门禁,生成器自己的控制台打印也会成为失败源(见 §五)。
工具选型
| 图形类型 | 工具 |
|---|---|
| 协议帧 / 字节布局(字段宽 ∝ 字节数、偏移标尺) | SVG:tools/arch_diagram/gen_frame_svg.py |
| 分层架构 / 模块依赖 | SVG:tools/arch_diagram/gen_arch_svg.py |
| 就绪屏障 / 生产者-消费者 | SVG:tools/arch_diagram/gen_barrier_svg.py |
| 台阶 / 泳道对照 / 刻度尺等"高度差、并列对照"类 | SVG:新写一个 tools/arch_diagram/gen_<主题>_svg.py(照抄最近生成器的骨架,见 §二) |
| 其他精确布局图 | 复制最接近的生成器改顶部数据(build() 与数据解耦,通用) |
| 流程 / 时序 / 状态机 / ER(只表达拓扑关系) | Markdown 内嵌 ```mermaid 代码块(flowchart / sequenceDiagram / stateDiagram-v2 / erDiagram),无需生成图片 |
Mermaid 盒子按文本自适应宽、自动加有向边,只适合"关系"类图;凡需精确像素 / 比例 / 高度差的布局图一律走 SVG 生成器(实测教训:五级"台阶"用 Mermaid 会被压成同一高度,"还差一级"就看不出来了)。
一、改既有图(最短路线)
-
改顶部数据 / 常量区(字段、节点、标题、配色),不改
build()结构。配色沿用既有调色板(主色 / 底色):语义 主色 底色 蓝 #2563eb#eff6ff青 #0d9488#f0fdfa绿 #16a34a#f0fdf4琥珀 #d97706#fffbeb紫 #7c3aed#f5f3ff灰 #475569#f1f5f9玫红 #e11d48#fff1f2 -
重跑(仓库根;脚本内置
ET.fromstring自检,XML 不合法直接抛错):& '.venv\Scripts\python.exe' tools/arch_diagram/gen_xxx.py # 期望:OK <字节> bytes, canvas <W>x<H> -> <路径>,且没有 OVERFLOW WARN / overflow warnings 行 -
目检(§三)→ 引用(§四)→ 幂等与门禁(§五)→ 提交(§六)。
二、从零新写生成器(先抄骨架,别裸写)
-
先读同目录 1~2 个最近的脚本(如
tools/arch_diagram/gen_adc_frontend_svg.py、gen_adc_code_scale_svg.py),抄它们的三条约定:- 文件头 docstring 写清四件事:产物路径 + 这张图回答什么问题 + 「为什么不用 Mermaid」 + 用法("仓库根目录,任意 python3")与"改内容 vs 调坐标"的边界;
- 顶部集中常量块:画布与几何(
W/H/PAD/RIGHT)→FONT→ 各级字号 → 调色板 → 文案(TITLE/SUB*/ 逐条目 /FOOTNOTE);坐标一律由常量推导,别在build()里塞魔法数; - 纯标准库:
import html / os / sys / xml.etree.ElementTree as ET,helpers 只有esc/text_w/txt/line/rect/wrap/check_fit/emit/build。
-
照抄预检三件套(
gen_adc_frontend_svg.py里还没有;从tools/arch_diagram/gen_data_trust_ladder_svg.py抄):OVERFLOW = []+check_fit(s, fs, avail, where)+wrap(s, fs, avail, max_lines, where),标题 / 副标题 / 盒内标题走check_fit,盒内正文与脚注走wrap(wrap按估宽折行、超行数用…收尾并记一条 OVERFLOW)。build()末尾:emit("OK ...")之后if OVERFLOW: emit("!! %d overflow warnings" % len(OVERFLOW))。估宽口径不变:CJK / 全角 / 圈码 / 箭头 ≈1.0em,其余 ≈0.58em(宁大勿小)。 -
照抄
emit()(幂等门禁的地基,见 §五):def emit(s): """控制台打印按目标编码替换,绝不因 GBK 终端遇上 ²/− 之类字符而崩 (generated-fresh 钩子会把本脚本拷进临时目录重跑,崩了就是一片假红)。""" enc = getattr(sys.stdout, "encoding", None) or "ascii" sys.stdout.write(s.encode(enc, "replace").decode(enc, "replace") + "\n") -
路径锚在脚本自身位置,别用 cwd(临时目录重跑才不会漂):
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) OUT = os.path.join(ROOT, "docs", "images", "<主题>.svg") -
跑
& '.venv\Scripts\python.exe' tools/arch_diagram/gen_<主题>_svg.py,把 OVERFLOW 告警打到 0 再进目检——预检能在渲染前就报出foot: 974 > 968这类溢出(本窗实测两张图首跑各报一条脚注超宽),比靠眼睛反复量便宜一个数量级。 -
新脚本落库时同批把这一行加进
tools/arch_diagram/README.md的「脚本一览」表。
三b、大图(几十框几十边)加一道碰撞审计,别只靠眼睛
边数一多,像素回读就查不全(本窗实测:42 框 37 边的端到端链路图,缩略 PNG 看着干净,
审计器一眼报出 23 条:边标签压节点、走线穿框、标签出画布)。做法是解析自己生成的 SVG
而不是解析渲染结果——ET.parse 产物,按 font-size/fill 分诊文字,估宽口径直接复用生成器的
text_w()(两边同一把尺子才不会假绿),然后判三件事:
- 边标签的「文字盒」vs 节点框(不是白片盒):交集 > 6 px² 就算问题——节点在边之后画, 压上去的文字会被节点白底吃掉半个字(本窗症状:「串行执行」渲染成「行执行」)。 白片本身压到框边角是无害的(白片先画、节点后画,只是盖住一点白底,不改字)。 除「有没有压上」,还要打印文字与最近框的最小水平余量并按 < 3px 报问题: 本窗「TRGO 触发」余量 0.7px、「帧 B 下发」1.5px,都没压上却随时会被真实字宽吃笔画, 缩短标签后升到 3.5px 以上才算安全。
- 走线段 vs 节点框(内缩 2px 再判,排除贴边端点):线穿框是硬伤,改边的出入框方位或独占通道。
- 标签互压 / 出画布:白片两两求交;
box[3] > H-2查脚注贴底。 - 零余量复扫(生成器侧,别只跑 SVG 侧那一遍):SVG 反推不出哪两个框是本边端点,
走线只能内缩着查 → 1~3px 的切边照不到。用生成器自己的
Box/route()/label_at()真值再扫一遍:框不膨胀不内缩、只跳本边两端。跑 SVG 侧与真值侧两遍都归零才算收工。
脚本骨架与判据见 reference/svg-collision-audit.py(可直接拷进 ci-artifacts/ 当一次性工具用,
别放 tools/arch_diagram/——那里只放生成器,非 gen_*.py 的脚本混进去会让人以为在册)。
配套的两条几何纪律(都是本窗踩出来的):
- 同轴直连不要插退化折点:同行两框若按「同轴 → 加一段 gx 走廊」的通用规则生成,会产出
[(195,176),(219,176),(219,176),(243,176)]这种点集——线还是直的,但标签落点算成第一半的中点, 整格偏移半个通道 → 压框。正解:出框轴与入框轴同轴且已对齐时直接返回两点线。 - 标签落点用弧长中点,不用「最长段中点」:多段折线里最长段可能有多条(对称 Z 字),
取第一条会把标签推到拐角上;沿总弧长取 50% 再定位,配合
seg/t旋钮微调。
先验审计器本身,再信它的「0 条」:本窗把 hi > y0 复制粘贴成 hi > x0,零余量那一遍
凭空报出 3 条「穿框」,差点为此重排三条边的走线。判据脚本的假阳性比漏报更贵——它会让你
去改一张本来没问题的图。规矩:写完后挑一条已知干净的边(例如两框正对的直连边)单独跑一遍,
确认它不报,再跑全量。
三、目检(Windows:本机没有任何现成 SVG 渲染器)
先探测,别照抄 cairosvg 命令:本仓 .venv 无 cairosvg(ModuleNotFoundError: No module named 'cairosvg'),rsvg-convert 不存在,只有 Edge / Chrome:
# ops_bash(git-bash)里探测;哪个存在用哪个
ls -1 "/c/Program Files/Google/Chrome/Application/chrome.exe" \
"/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe" 2>/dev/null
command -v rsvg-convert inkscape magick convert 2>/dev/null || echo "no CLI rasterizer"
Test-Path "C:\Program Files\Google\Chrome\Application\chrome.exe"
Test-Path "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
截图(--screenshot= 必须给绝对 Windows 路径 + 绝对 file:/// 输入,并且固定用
--headless=new:本窗用旧 --headless 同一套命令时成时不成——一次写出了 168 KB,
下一次静默不写文件;也别把 stderr 丢进 /dev/null,成功标志就是它自己打印的那行字数):
mkdir -p ci-artifacts # Chrome 没有建目录的参数,先自己建
OUT="D:\\Code\\DTU Code\\<工作树名>\\ci-artifacts\\<主题>.png" # 绝对路径、反斜杠
"/c/Program Files/Google/Chrome/Application/chrome.exe" \
--headless=new --disable-gpu --hide-scrollbars --force-device-scale-factor=1 \
--window-size=1060,712 \
--screenshot="$OUT" \
"file:///D:/Code/DTU Code/<工作树名>/docs/images/<主题>.svg" 2>&1 | tail -2
成功标志是 Chrome 自己打印的 <N> bytes written to file D:\...。回读 PNG 才算目检完成:
const r = await tools.read_image({ file_path: "D:\\Code\\DTU Code\\<工作树名>\\ci-artifacts\\<主题>.png" });
console.log(r.image.width + "x" + r.image.height, "bytes=" + r.image.bytes); // 应等于 --window-size / 画布
看什么:文字压框 / 溢出盒外、标签互撞(本窗实测两轮才干净:台阶图首轮"文字压到盒下方 + 标签互撞",第二、三轮才修完)、wrap 收尾的省略号 …(有 = 文案超长)、虚线与盒边线重叠、配色是否还在调色板里。发现问题回改生成器常量并重跑预检,然后重新截图(PNG 是派生物,不会自己更新),--window-size 跟着 canvas WxH 改。
四、md 引用(相对路径)
docs/ 根的 md:;docs/任务规划/、docs/UI设计/ 等子目录 md:(本窗实例:docs/任务规划/数据可信.md 写 )。alt 文本写成"这张图的结论是什么",别只重复文件名。
五、generated-fresh:逐字节幂等门禁(新图长期不假红的前提)
generated-fresh 把 tools/arch_diagram/gen_*.py(docs-svg 项:per_script: true、pattern: gen_*.py、mode: exact)逐个拷进系统临时目录重跑,与工作区产物逐字节比对:内容不同 = DIFF(生成物比生成器旧)、临时目录多出一个工作区没有的文件 = NEW、rc≠0 = 「生成器报错」。由此推出四条硬约束:
- 生成器只能写那一个
.svg:绝不在脚本里顺手导出 PNG 预览(预览写ci-artifacts/,不进docs/images/)。 - stdout 不许崩:GBK 终端遇上
²/−/ CJK 路径会UnicodeEncodeError→ 钩子rc≠0,一片假红。所以打印一律走emit(),脚本里不留裸print()(本窗实测:两份新生成器只改了一份,另一份照旧崩)。 - 产物内容零不确定:SVG 里不写时间戳 / 绝对路径 / 主机名;浮点坐标一律
%g/%.1f定形;字典与os.listdir结果参与布局时先sorted()。 gen_*.py命名即自动在册(pattern 就是gen_*.py)——故意不叫这个名字等于躲开门禁,门禁最大盲区正是"未登记的生成器",别利用它。
自查(仓库根):
.venv/Scripts/python.exe tools/arch_diagram/gen_<主题>_svg.py
sha1sum docs/images/<主题>.svg
.venv/Scripts/python.exe tools/arch_diagram/gen_<主题>_svg.py # 重跑后 sha1 必须不变
.venv/Scripts/python.exe scripts/check_generated_fresh.py # 期望 rc=0,无 DIFF/NEW,[BASELINE] 不增
& '.venv\Scripts\python.exe' scripts/check_generated_fresh.py # 单独跑(不 stash 别人在飞的改动)
& '.venv\Scripts\python.exe' scripts/check_generated_fresh.py --list # 只看在册声明
& '.venv\Scripts\python.exe' -m pre_commit run generated-fresh --files tools/arch_diagram/gen_<主题>_svg.py docs/images/<主题>.svg
⚠ 别跑不带 --files 也不带 --all-files 的裸 pre_commit run(会 stash 掉别人在飞的改动);判读口径 → docs/门禁说明/generated-fresh.md §4。
六、提交
生成器脚本(源)+ docs/images/<主题>.svg(产物)+ 引用它的 .md + tools/arch_diagram/README.md 那一行同一笔提交;PNG 预览不入库。
& '.venv\Scripts\python.exe' -m pre_commit run --files <脚本> <svg> <md> <README.md>
陷阱(本窗实测)
- 照着现文 /
docs/架构设计/图形绘制流程.md§四 跑pip install cairosvg或rsvg-convert:这台机器上没有(前者ModuleNotFoundError、后者根本没有这个命令)→ 先按 §三探测,改用 headless Chrome/Edge。别在没渲染出 PNG 的情况下声称"已目检"。 --screenshot=ci-artifacts/ladder.png(相对路径) →EXIT 2+Failed to write file ci-artifacts/ladder.png: 系统找不到指定的路径。 (0x3),PNG 压根没落盘;换绝对 Windows 路径后写出 95549 bytes。这条报错不区分"相对路径"与"目录不存在"两种成因(实测mkdir -p ci-artifacts已先跑过、仍报同一句),排查时两条都排。- 只做预检 + 缩略目检就收工(大图):
check_fit归零、PNG 看着干净,仍可能有二十来条标签压框——节点白底后画会吃掉压上去的标签;大图必须走 §三b 的解析式审计。 - 只做预检就收工:
check_fit/OVERFLOW归零也不代表干净——本窗实测"门标签压第 5 级盒、蓝底标签挤、多余虚线"三类碰撞只有 PNG 回读看得见;反过来只目检不预检,脚注溢出就得靠眼睛反复量。两者都要 0。 - 手改 SVG 成品:等于制造「不是我改的」diff,且
generated-fresh立刻 DIFF;也不要退化成 ASCII 框图(AGENTS.md 明令勿手绘)。一切修改都落在生成器常量上再重跑。 - 批量改生成器时
old_string不匹配:一批edit会在中途抛错,但前面的编辑已经落盘 → 半成品生成器(本窗实测:emithelper 缺失却仍打印 OK,几轮之后才查出)。正解:改前先read出精确原文,逐条改,末尾统一复验(grep -n "def emit"、grep -c print、再重跑看 rc 与 OK 行)。 - PNG 尺寸 ≠ 画布:
--window-size忘了跟着W/H改 → 截出来带大片空白或裁掉底部脚注;回读r.image.width/height与canvas WxH对一下再判定。 docs/images/下手写.svg或放 PNG 预览:前者没有生成器(NEW / 躲过门禁),后者污染"临时目录多了文件"判定。
验证清单
- 生成器输出
OK … bytes, canvas WxH,无OVERFLOW WARN/!! N overflow warnings;grep -n "def emit"命中、grep -c print= 0。 - 绝对路径 PNG 存在、尺寸 = 画布,
read_image回读确认:无文字压框 / 溢出、无标签互撞、无…截断收尾、配色在调色板内。 - 连跑两次产物 sha1 不变;
scripts/check_generated_fresh.pyrc=0(无 DIFF / NEW,[BASELINE]未增加)。 - 大图(≥20 边):跑一遍 §三b 的碰撞审计,报数归零(标签压框 / 走线穿框 / 标签出画布)。
- 引用它的
.md相对路径正确(根目录images/,子目录../images/);tools/arch_diagram/README.md已加脚本行。 - 脚本 + 产物
.svg+.md+ README 行一并暂存;pre-commit run --files …全过(无乱码 / 行尾空白 / 编码 OK)。
TODO(口径未与权威文档同步):
docs/架构设计/图形绘制流程.md§3.1 第 4 步「目检:浏览器打开 SVG」与 §四「给.venv装 cairosvg 转 PNG」在 Windows 上按本 skill 的实测是走不通的。该文档是规范归属(AGENTS.md:同一事实只在一处详述),采纳 §三 / §五 时应把这两段同步过去;目前两处不一致,做图以本 skill 的实测命令为准,规范条文以该文档为归属。