Files
STM32F4-Base/.dsh/skills/draw-diagram/SKILL.md
T
wandering1 045ca7f688 docs(配图): 端到端链路鸟瞰进生成器体系,补大图碰撞审计判据
把 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 警。
2026-10-30 15:59:24 +08:00

18 KiB
Raw Blame History

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 会被压成同一高度,"还差一级"就看不出来了)。

一、改既有图(最短路线)

  1. 改顶部数据 / 常量区(字段、节点、标题、配色),不改 build() 结构。配色沿用既有调色板(主色 / 底色):

    语义 主色 底色
    蓝 #2563eb #eff6ff
    青 #0d9488 #f0fdfa
    绿 #16a34a #f0fdf4
    琥珀 #d97706 #fffbeb
    紫 #7c3aed #f5f3ff
    灰 #475569 #f1f5f9
    玫红 #e11d48 #fff1f2
  2. 重跑(仓库根;脚本内置 ET.fromstring 自检,XML 不合法直接抛错):

    & '.venv\Scripts\python.exe' tools/arch_diagram/gen_xxx.py
    # 期望:OK <字节> bytes, canvas <W>x<H> -> <路径>,且没有 OVERFLOW WARN / overflow warnings 行
    
  3. 目检(§三)→ 引用(§四)→ 幂等与门禁(§五)→ 提交(§六)。

二、从零新写生成器(先抄骨架,别裸写)

  1. 先读同目录 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。
  2. 照抄预检三件套(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(宁大勿小)。

  3. 照抄 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")
    
  4. 路径锚在脚本自身位置,别用 cwd(临时目录重跑才不会漂):

    ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
    OUT = os.path.join(ROOT, "docs", "images", "<主题>.svg")
    
  5. 跑 & '.venv\Scripts\python.exe' tools/arch_diagram/gen_<主题>_svg.py,把 OVERFLOW 告警打到 0 再进目检——预检能在渲染前就报出 foot: 974 > 968 这类溢出(本窗实测两张图首跑各报一条脚注超宽),比靠眼睛反复量便宜一个数量级。

  6. 新脚本落库时同批把这一行加进 tools/arch_diagram/README.md 的「脚本一览」表。

三b、大图(几十框几十边)加一道碰撞审计,别只靠眼睛

边数一多,像素回读就查不全(本窗实测:42 框 37 边的端到端链路图,缩略 PNG 看着干净, 审计器一眼报出 23 条:边标签压节点、走线穿框、标签出画布)。做法是解析自己生成的 SVG 而不是解析渲染结果——ET.parse 产物,按 font-size/fill 分诊文字,估宽口径直接复用生成器的 text_w()(两边同一把尺子才不会假绿),然后判三件事:

  1. 边标签的「文字盒」vs 节点框(不是白片盒):交集 > 6 px² 就算问题——节点在边之后画, 压上去的文字会被节点白底吃掉半个字(本窗症状:「串行执行」渲染成「行执行」)。 白片本身压到框边角是无害的(白片先画、节点后画,只是盖住一点白底,不改字)。 除「有没有压上」,还要打印文字与最近框的最小水平余量并按 < 3px 报问题: 本窗「TRGO 触发」余量 0.7px、「帧 B 下发」1.5px,都没压上却随时会被真实字宽吃笔画, 缩短标签后升到 3.5px 以上才算安全。
  2. 走线段 vs 节点框(内缩 2px 再判,排除贴边端点):线穿框是硬伤,改边的出入框方位或独占通道。
  3. 标签互压 / 出画布:白片两两求交;box[3] > H-2 查脚注贴底。
  4. 零余量复扫(生成器侧,别只跑 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:![标题](images/xxx.svg);docs/任务规划/、docs/UI设计/ 等子目录 md:![…](../images/xxx.svg)(本窗实例:docs/任务规划/数据可信.md 写 ![数据可信是五级台阶,现在踩在第 2 级](../images/数据可信台阶.svg))。alt 文本写成"这张图的结论是什么",别只重复文件名。

五、generated-fresh:逐字节幂等门禁(新图长期不假红的前提)

generated-fresh 把 tools/arch_diagram/gen_*.py(docs-svg 项:per_script: true、pattern: gen_*.py、mode: exact)逐个拷进系统临时目录重跑,与工作区产物逐字节比对:内容不同 = DIFF(生成物比生成器旧)、临时目录多出一个工作区没有的文件 = NEW、rc≠0 = 「生成器报错」。由此推出四条硬约束:

  1. 生成器只能写那一个 .svg:绝不在脚本里顺手导出 PNG 预览(预览写 ci-artifacts/,不进 docs/images/)。
  2. stdout 不许崩:GBK 终端遇上 ² / − / CJK 路径会 UnicodeEncodeError → 钩子 rc≠0,一片假红。所以打印一律走 emit(),脚本里不留裸 print()(本窗实测:两份新生成器只改了一份,另一份照旧崩)。
  3. 产物内容零不确定:SVG 里不写时间戳 / 绝对路径 / 主机名;浮点坐标一律 %g / %.1f 定形;字典与 os.listdir 结果参与布局时先 sorted()。
  4. 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 会在中途抛错,但前面的编辑已经落盘 → 半成品生成器(本窗实测:emit helper 缺失却仍打印 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.py rc=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 的实测命令为准,规范条文以该文档为归属。