绘图流程沉淀为 draw-diagram skill,SVG 收拢到 docs/images/
@@ -98,9 +98,8 @@ simulator/SDL2/
|
||||
# 内容页黄金参考(ref/*.bin)与目检 PNG 均为本地生成物,不入库:
|
||||
# 首次/换机后先跑 check_content_pages.py --update 生成 ref/*.bin,再跑 regen_ref_png.py 出 PNG
|
||||
simulator/tests/ref/
|
||||
# 架构图 PNG 预览(源为 docs/*.svg,PNG 本地渲染生成,不入库)
|
||||
docs/项目架构总览.png
|
||||
docs/就绪屏障.png
|
||||
# 架构图 PNG 预览(源为 docs/images/*.svg,PNG 本地渲染生成,不入库)
|
||||
docs/images/*.png
|
||||
*.pyc
|
||||
|
||||
# Python venv (unified env, see AGENTS.md "Python 环境"; rebuild via requirements.txt)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: draw-diagram
|
||||
description: docs/ 文档技术配图绘制流程(精确布局图用 SVG 生成器 / 拓扑图用 Mermaid):挑脚本 → 改顶部数据 → 重跑生成 → 目检 → md 相对路径引用 → 脚本+产物一并提交,产物统一放 docs/images/。Use when 需要新增/修改 docs/ 文档的技术配图(协议帧、分层架构、依赖/就绪屏障、流程图、时序、状态机),或用户要求"画个图 / 生成图 / 更新架构图 / 帧格式图"。
|
||||
---
|
||||
|
||||
# 文档配图绘制(SVG / Mermaid)
|
||||
|
||||
`docs/` 文档技术配图的统一流程;完整选型依据 / 约定 / 检查清单见 `docs/图形绘制流程.md`。**产物统一放 `docs/images/`**(专门配图目录,`.svg` 入库;PNG 预览属本地派生物、不入库)。
|
||||
|
||||
## 工具选型
|
||||
|
||||
| 图形类型 | 工具 |
|
||||
|---|---|
|
||||
| 协议帧 / 字节布局(字段宽 ∝ 字节数、偏移标尺) | SVG:`tools/arch_diagram/gen_frame_svg.py` |
|
||||
| 分层架构 / 模块依赖 | SVG:`tools/arch_diagram/gen_arch_svg.py` |
|
||||
| 就绪屏障 / 生产者-消费者 | SVG:`tools/arch_diagram/gen_barrier_svg.py` |
|
||||
| 其他精确布局图 | **复制**最接近的生成器改顶部数据(`build()` 与数据解耦,通用) |
|
||||
| 流程 / 时序 / 状态机 / ER(只表达拓扑关系) | Markdown 内嵌 ` ```mermaid ` 代码块(`flowchart` / `sequenceDiagram` / `stateDiagram-v2` / `erDiagram`),无需生成图片 |
|
||||
|
||||
> Mermaid 盒子按文本自适应宽、自动加有向边,**只适合"关系"类图**;凡需精确像素 / 比例的布局图一律走 SVG 生成器。
|
||||
|
||||
## SVG 生成流程(仓库根目录执行)
|
||||
|
||||
1. **挑 / 复制脚本**:新类型图复制最接近的生成器,只改顶部数据区。
|
||||
2. **改顶部数据**:字段 / 节点 / 标题 / 配色。沿用既有调色板(主色 / 底色):
|
||||
|
||||
| 语义 | 主色 | 底色 |
|
||||
|---|---|---|
|
||||
| 蓝 | `#2563eb` | `#eff6ff` |
|
||||
| 青 | `#0d9488` | `#f0fdfa` |
|
||||
| 绿 | `#16a34a` | `#f0fdf4` |
|
||||
| 琥珀 | `#d97706` | `#fffbeb` |
|
||||
| 紫 | `#7c3aed` | `#f5f3ff` |
|
||||
| 灰 | `#475569` | `#f1f5f9` |
|
||||
| 玫红 | `#e11d48` | `#fff1f2` |
|
||||
|
||||
3. **重跑**(脚本内置 `ElementTree` 自检,XML 不合法直接抛错):
|
||||
|
||||
```powershell
|
||||
& '.venv\Scripts\python.exe' tools/arch_diagram/gen_xxx.py
|
||||
# 成功输出:OK <字节> bytes, canvas <W>x<H> -> <路径>
|
||||
```
|
||||
|
||||
4. **目检**:浏览器打开 `docs/images/*.svg`——文字不溢出盒外、框 / 线 / 标签对齐、配色符合调色板。
|
||||
5. **md 引用**(相对路径):`docs/` 根的 md 写 ``;`docs/UI 设计/` 等子目录 md 写 ``。
|
||||
6. **提交**:生成器脚本(源)+ 生成的 `.svg`(产物)**一并**提交。
|
||||
|
||||
## SVG 约定(手写 / 新增生成器必须遵循)
|
||||
|
||||
- **纯标准库**拼 SVG(只用 `html` / `os`(+ `math`)),不引第三方库(SVG 由浏览器用系统字体渲染)
|
||||
- 所有文本经 `html.escape(s, quote=True)`,防止破坏 XML
|
||||
- 字体栈:`Microsoft YaHei, Segoe UI, PingFang SC, sans-serif`
|
||||
- 文本宽估算(自适应盒宽):CJK / 全角 / 圈码 / 箭头 ≈ `1.0em`,其余 ≈ `0.58em`(**宁大勿小**防溢出)
|
||||
- 落盘:`open(OUT, "w", encoding="utf-8", newline="\n")`(UTF-8 无 BOM + LF)
|
||||
- 写盘后 `xml.etree.ElementTree.fromstring(svg)` 自检
|
||||
- 产物放 `docs/images/`,命名 `<主题>.svg`(可与同名 `.md` 对应)
|
||||
|
||||
## SVG → PNG(可选,本地派生物不入库)
|
||||
|
||||
- 最简单:浏览器打开 `.svg` → 另存为图片
|
||||
- 脚本化:`& '.venv\Scripts\python.exe' -m pip install cairosvg` 后
|
||||
`& '.venv\Scripts\python.exe' -c "import cairosvg; cairosvg.svg2png(url='docs/images/xxx.svg', write_to='x.png', scale=2)"`
|
||||
- `.gitignore` 已含 `docs/images/*.png`
|
||||
|
||||
## 提交前检查清单
|
||||
|
||||
- [ ] 生成器输出 `OK … bytes`(无异常 = XML 合法)
|
||||
- [ ] 浏览器目检:文字不溢出、框 / 线 / 标签对齐、配色符合调色板
|
||||
- [ ] 引用它的 `.md` 里图片**相对路径**正确(能显示)
|
||||
- [ ] 脚本 + 产物 `.svg` **一并**暂存
|
||||
- [ ] `pre-commit run --files <脚本> <svg> <md>` 全过(无乱码 / 行尾空白 / 编码 OK)
|
||||
@@ -149,6 +149,13 @@ cmake --build build --config Debug
|
||||
- 详见 `docs/UI 设计/LVGL模拟器部署与使用说明.md`
|
||||
- 中文字体:`ui_cn_chars.txt`(字符集)+ `tools/font_gen/` 生成 `Drivers/BSP/UI/ui_font_cn_12.c`,见 `docs/UI 设计/UI_中文字体生成与配置.md`
|
||||
|
||||
## 图形绘制(文档配图)
|
||||
|
||||
文档里的技术图形统一用 **SVG 生成器**(`tools/arch_diagram/`,纯标准库)或 **Mermaid**,**勿手绘 ASCII 框图**:
|
||||
- **精确布局图**(协议帧 / 分层架构 / 依赖 / 就绪屏障)→ `tools/arch_diagram/gen_*.py`:改脚本顶部数据 → `& '.venv\Scripts\python.exe' tools/arch_diagram/gen_xxx.py` 重新生成 `docs/images/*.svg`(脚本内置 XML 自检)。
|
||||
- **拓扑类图**(流程 / 时序 / 状态机)→ Markdown 内嵌 `mermaid` 代码块。
|
||||
- 全流程 / 选型 / 约定 / 检查清单见 **`docs/图形绘制流程.md`**(已沉淀为 `draw-diagram` skill);产物统一放 **`docs/images/`** 专门目录;脚本与生成的 `.svg` 一并提交。
|
||||
|
||||
## 注意
|
||||
|
||||
- `Inc/` 和 `Src/` 中 CubeMX 生成的文件带有 `USER CODE BEGIN`/`END` 标记,自定义代码写在这些区域之间
|
||||
|
||||
@@ -436,7 +436,7 @@ python tools/menu_gen/gen_menu.py
|
||||
|
||||
### 4.1 总览
|
||||
|
||||

|
||||

|
||||
|
||||
- 每级一个**框**,框 = 该级条目列表(紧凑列表,**无逐框页头/返回按钮**,返回靠 `←`);
|
||||
- **无滑动动画**(1bpp 推流);
|
||||
@@ -494,7 +494,7 @@ python tools/menu_gen/gen_menu.py
|
||||
|
||||
`frame_build` 拿到 (depth, node, sel, scroll) 后,先**算三个数**——框宽 w、位置 (x, y)、框高 h——再照数建框体、注册行数据、应用选中/滚动。四步各配一格图(下图 ①~④):
|
||||
|
||||

|
||||

|
||||
|
||||
**① 框宽 w = 最宽行 + 2 条边框**(图①)
|
||||
|
||||
@@ -894,11 +894,11 @@ bool ui_content_page_is_open(void); /* 查询是否有内容页
|
||||
| 2026-09-07 | **全文结构重排(理清三层架构与协作)**:旧版三层描述分散在 §六(数据结构)/§七(运行时数据)/§八(实现架构) 三处、§四 又混装导航逻辑与显示模型,读者难以看清各层职责与层间配合;重排为十章——**一 架构总览**(1.1 为什么拆三层 / 1.2 职责速览+依赖图 / 1.3 调用矩阵 / 1.4 一次按键的旅程 / 1.5 初始化时序 / 1.6 层间不变量)→ **二 菜单内容** → **三 数据层**(含 3.7 改菜单流程)→ **四 显示规范**(自 §四⑧ 抽出,含 4.5 参考图偏差)→ **五 显示层**(5.1 职责边界 / 5.5 公开 API 8 接口表)→ **六 逻辑层**(6.2 17 B 状态 / 6.3 导航规则含完整走查 / 6.4 键语义与分发)→ **七 层间配合细节**(7.1 逐键分层动作 / 7.2 框生命周期 / 7.3 存vs算 / 7.4 读写矩阵 / 7.5 内存预算带层归属)→ **八 验证与陷阱** → **九 迁移历史**(结构/运行时/内容三表收拢)→ **十 变更记录**;**修正归属错误**:`s_scroll[]` 实为**逻辑层** `ui_menu.c` 字段(17 B 导航状态之一,见 ui_menu.c),旧 §7.2 误划给显示层,新文 §6.2/§7.5 已归正;全部交叉引用同步到新章号;技术内容零删减 |
|
||||
| 2026-09-07 | §1.2/§1.4/§1.5 三幅架构图由 ASCII 转 Mermaid(1 张 flowchart + 2 张 sequenceDiagram,初始化时序含表自检 alt/else 分支);键源措辞统一为“远端键”(与 `UI_设计架构.md`“远端客户端”一致:MCU 端按键经 TCP 7003 远端客户端 → ui_net_sink → keypad);职责表/调用矩阵/“两个要点”文字保留 |
|
||||
| 2026-09-07 | 正文现状化:删除正文过渡性历史叙述(“设计阶段拟名…/实现采用…/见变更记录”等,旧文见 git 历史),各节改写为现状描述;移除 §九“迁移历史”整章(旧→新对照保留于本变更记录),变更记录重编号为 §九;§8 去日期注记,陷阱 3/4 改写为现状约束 |
|
||||
| 2026-09-07 | §4.1 总览图由 ASCII 框线图升级为 SVG 精确几何图(新增 `images/级联框总览.svg`,按 §5.2 常量 / §5.3 公式逐 px 绘制,含根框装饰 / 状态行 / 选中行反色 / 共享边框列标注 + ①~⑦ 位置标注);ASCII 版删除(旧图见 git 历史) |
|
||||
| 2026-09-07 | §4.1 总览图由 ASCII 框线图升级为 SVG 精确几何图(新增 `docs/images/级联框总览.svg`,按 §5.2 常量 / §5.3 公式逐 px 绘制,含根框装饰 / 状态行 / 选中行反色 / 共享边框列标注 + ①~⑦ 位置标注);ASCII 版删除(旧图见 git 历史) |
|
||||
| 2026-09-07 | 删除 §4.5“参考图与实现偏差”表(偏差数据已在 §8.2 注 5“参考图保真度”完整记载);正文两处引用(§四 引言、§8.1 验证表)改指 §8.2 注 5 |
|
||||
| 2026-09-07 | §5.1 职责边界表去掉“不做”列、改为列表(负向约束已由章首“绝不做”行与 §1.6 层间不变量表承载,无信息损失) |
|
||||
| 2026-09-07 | §6.1 职责边界表同样去掉“不做”列、改为列表(负向约束由章首“绝不做”行、§1.3 调用矩阵与 §1.6 层间不变量表承载,无信息损失) |
|
||||
| 2026-09-08 | §5.3 几何计算重写为图文并茂:新增 `images/级联框几何计算.svg` 四格图(① 行剖面定框宽 ② X 共享边框列 ③ Y 顶对齐 ④ Y 溢出上移,含虚线幽灵框 + 超屏红区示意),正文改为四步叙述 + 实测框宽表 + 公式汇总(五式内容不变) |
|
||||
| 2026-09-08 | §5.3 几何计算重写为图文并茂:新增 `docs/images/级联框几何计算.svg` 四格图(① 行剖面定框宽 ② X 共享边框列 ③ Y 顶对齐 ④ Y 溢出上移,含虚线幽灵框 + 超屏红区示意),正文改为四步叙述 + 实测框宽表 + 公式汇总(五式内容不变) |
|
||||
| 2026-09-08 | `menu_frame_t` 瘦身:`built` bool→`uint8_t`(与系统存储类型一致);`x/y/w/h` `int32_t`→`int16_t`(值域 0..160 / ≤242,16 位够;y 需有符号——钳制前可为负);`s_frame[4]` 96 B→48 B,省 48 B RAM(§5.4 对象模型表 / §7.5 内存预算表同步) |
|
||||
| 2026-09-08 | 行 label 指针合并:删 `s_row_text` / `s_row_arrow` 两数组(省 512 B);文本/箭头颜色改在行容器 `s_row` 上设置、子 label 继承(LVGL `text_color` 为可继承属性,`lv_style.c` 属性标志表证实);§5.4 对象模型表 / §7.5 内存预算表同步(768 B→256 B) |
|
||||
| 2026-09-08 | **行对象 → 自绘重写(方案1,省 LVGL 池"每行对象机制税")**:行不再是 LVGL 对象——行数据改 `s_row_node[4][16]`(`uint8_t` 节点下标,256 B→64 B),框体 `LV_EVENT_DRAW_MAIN` 自绘(选中行白带 `lv_draw_rect` + 每行 `lv_draw_label` 文本/箭头,按滚动偏移枚举可见行、重绘区外行 culling);框内滚动改 `LV_EVENT_GET_SELF_SIZE` 虚报内容高 + 手动跟随滚动(`view_scroll_to_sel`,替代 `lv_obj_scroll_to_view`);删 `s_style_selected`、`view_add_row` 与 flex 堆行;`frame_build/destroy/refresh` 改行数据注册/清空/重绘。§5.4/§5.6/§6.2/§7.1~7.5 同步。验证:`@build` 0 错 0 警 + 代码规范 PASS + 模拟器 8 状态像素回归(白带/边框/根框不变性 + 3 组往返态逐像素一致)全 PASS |
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="246" viewBox="0 0 600 246" font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">
|
||||
<rect x="0" y="0" width="600" height="246" fill="#ffffff"/>
|
||||
<text x="40" y="40" font-size="20" font-weight="700" fill="#0f172a">RemoDispBus 协议帧格式</text>
|
||||
<text x="40" y="64" font-size="13" fill="#64748b">整帧 = 4 + LEN + 1 字节 · 大端(网络字节序)· CRC = DATA 段逐字节异或(不含 TAG/CMD/LEN)</text>
|
||||
<line x1="40" y1="96" x2="560" y2="96" stroke="#cbd5e1" stroke-width="1"/>
|
||||
<line x1="40" y1="92" x2="40" y2="100" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="40" y="88" font-size="12" fill="#94a3b8" text-anchor="middle">0</text>
|
||||
<line x1="92" y1="92" x2="92" y2="100" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="92" y="88" font-size="12" fill="#94a3b8" text-anchor="middle">1</text>
|
||||
<line x1="144" y1="92" x2="144" y2="100" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="144" y="88" font-size="12" fill="#94a3b8" text-anchor="middle">2</text>
|
||||
<line x1="196" y1="92" x2="196" y2="100" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="196" y="88" font-size="12" fill="#94a3b8" text-anchor="middle">3</text>
|
||||
<line x1="248" y1="92" x2="248" y2="100" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="248" y="88" font-size="12" fill="#94a3b8" text-anchor="middle">4</text>
|
||||
<line x1="508" y1="92" x2="508" y2="100" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="508" y="88" font-size="12" fill="#94a3b8" text-anchor="middle">4+LEN</text>
|
||||
<line x1="560" y1="92" x2="560" y2="100" stroke="#94a3b8" stroke-width="1.5"/>
|
||||
<text x="560" y="88" font-size="12" fill="#94a3b8" text-anchor="middle">4+LEN+1</text>
|
||||
<rect x="40" y="116" width="52" height="74" fill="#eff6ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="66.0" y="146" font-size="15" font-weight="700" fill="#0f172a" text-anchor="middle">TAG</text>
|
||||
<text x="66.0" y="170" font-size="12" fill="#2563eb" text-anchor="middle">1 B</text>
|
||||
<rect x="92" y="116" width="52" height="74" fill="#eff6ff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="118.0" y="146" font-size="15" font-weight="700" fill="#0f172a" text-anchor="middle">CMD</text>
|
||||
<text x="118.0" y="170" font-size="12" fill="#2563eb" text-anchor="middle">1 B</text>
|
||||
<rect x="144" y="116" width="52" height="74" fill="#f0fdfa" stroke="#0d9488" stroke-width="1.5"/>
|
||||
<text x="170.0" y="146" font-size="15" font-weight="700" fill="#0f172a" text-anchor="middle">LEN_H</text>
|
||||
<text x="170.0" y="170" font-size="12" fill="#0d9488" text-anchor="middle">1 B</text>
|
||||
<rect x="196" y="116" width="52" height="74" fill="#f0fdfa" stroke="#0d9488" stroke-width="1.5"/>
|
||||
<text x="222.0" y="146" font-size="15" font-weight="700" fill="#0f172a" text-anchor="middle">LEN_L</text>
|
||||
<text x="222.0" y="170" font-size="12" fill="#0d9488" text-anchor="middle">1 B</text>
|
||||
<rect x="248" y="116" width="260" height="74" fill="#fffbeb" stroke="#d97706" stroke-width="1.5"/>
|
||||
<text x="378.0" y="146" font-size="15" font-weight="700" fill="#0f172a" text-anchor="middle">DATA</text>
|
||||
<text x="378.0" y="170" font-size="12" fill="#d97706" text-anchor="middle">LEN B · 变长</text>
|
||||
<rect x="508" y="116" width="52" height="74" fill="#f0fdf4" stroke="#16a34a" stroke-width="1.5"/>
|
||||
<text x="534.0" y="146" font-size="15" font-weight="700" fill="#0f172a" text-anchor="middle">CRC</text>
|
||||
<text x="534.0" y="170" font-size="12" fill="#16a34a" text-anchor="middle">1 B</text>
|
||||
<text x="40" y="230" font-size="12" fill="#94a3b8">TAG:0xAA=客户端帧 / 0xBB=服务端帧 · DATA 为变长段(LEN 字节,0–255,图中按 5 字节示意)· 详见 docs/RemoDispBus协议与接收设计说明.md §1</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,133 @@
|
||||
# 图形绘制流程(SVG / Mermaid)
|
||||
|
||||
> **范围**:本仓库文档(`docs/`)里的技术配图——协议帧 / 字节布局、分层架构、依赖 / 就绪屏障、流程 / 时序 / 状态机等。
|
||||
> **目标**:统一绘图方法与工具链,保证图形**精确、可复现、可 diff、可过 pre-commit**,并与既有 `tools/arch_diagram/` 生成器一致。
|
||||
> **沉淀**:本流程已沉淀为 `draw-diagram` skill(`.opencode/skills/draw-diagram/SKILL.md`)——画图时直接加载该 skill 即可。
|
||||
> **产物目录**:生成的 `.svg` 统一放 **`docs/images/`**(专门配图目录,产物入库)。
|
||||
|
||||
---
|
||||
|
||||
## 一、选型:什么图用什么工具
|
||||
|
||||
| 图形类型 | 工具 | 说明 |
|
||||
|---|---|---|
|
||||
| 协议帧 / 字节布局(精确比例) | **SVG**(`gen_frame_svg.py`) | 字段宽须严格 ∝ 字节数 + 逐字段标注 + 偏移标尺 |
|
||||
| 分层架构 / 模块依赖 | **SVG**(`gen_arch_svg.py`) | 多节点跨层依赖,Mermaid 会画成"意大利面",手工排布更清晰 |
|
||||
| 就绪屏障 / 生产者-消费者 | **SVG**(`gen_barrier_svg.py`) | 三栏对齐 + 正交连线,避免交叉 |
|
||||
| 流程图 / 时序 / 状态机 / ER | **Mermaid**(Markdown 内嵌) | 拓扑关系类图,节点少、强调连接;渲染器原生支持 |
|
||||
| UI 界面 / 像素级样机 | `simulator/` 截图、`tools/ui_mock/` | 非本流程范围 |
|
||||
|
||||
**为什么"精确布局图"用 SVG,不用 Mermaid / HTML:**
|
||||
|
||||
| 维度 | SVG | Mermaid | HTML |
|
||||
|---|---|---|---|
|
||||
| 字段宽 ∝ 字节数 | ✅ 像素精确定位 | ❌ 盒子按文本自适应 | ⚠️ 可但繁琐 |
|
||||
| 连续字节流(无假箭头) | ✅ 分段矩形 | ❌ 自动加有向边 | ✅ |
|
||||
| 纯文本(可 diff / lint / pre-commit) | ✅ | ✅(代码块) | ⚠️ |
|
||||
| 直接是图片(免截图) | ✅ | ⚠️ 需渲染器 | ❌ 需截图 |
|
||||
| 矢量无损(任意 DPI) | ✅ | ✅(渲染后) | ⚠️ |
|
||||
| 依赖 | 无(浏览器用系统字体渲染) | 渲染器 | 浏览器 |
|
||||
|
||||
> 结论:**要精确像素 / 比例的布局图 → SVG 生成器**;**只表达拓扑关系的图 → Mermaid**。
|
||||
|
||||
---
|
||||
|
||||
## 二、既有工具(`tools/arch_diagram/`)
|
||||
|
||||
三个生成器共享同一套约定(§3.2),均为**纯标准库**拼 SVG:
|
||||
|
||||
| 脚本 | 产物(入库) | 顶部数据区(改这里 → 重跑) |
|
||||
|---|---|---|
|
||||
| `gen_arch_svg.py` | `docs/images/项目架构总览.svg` | `LAYERS` / `ROWS` / `FOOT` |
|
||||
| `gen_barrier_svg.py` | `docs/images/就绪屏障.svg` | `BITS` / `PRODUCERS` / `CONSUMERS` |
|
||||
| `gen_frame_svg.py` | `docs/images/RemoDispBus帧格式.svg` | `FIELDS` / `TITLE` / `SUB` / `FOOT` |
|
||||
|
||||
---
|
||||
|
||||
## 三、绘制流程
|
||||
|
||||
### 3.1 精确布局图(协议帧 / 架构 / 依赖)——用生成器
|
||||
|
||||
1. **挑脚本**:协议帧 → `gen_frame_svg.py`;分层架构 → `gen_arch_svg.py`;就绪屏障 → `gen_barrier_svg.py`。
|
||||
2. **改数据**:编辑脚本顶部数据区。配色沿用既有调色板:
|
||||
|
||||
| 语义 | 主色 | 底色 |
|
||||
|---|---|---|
|
||||
| 蓝 | `#2563eb` | `#eff6ff` |
|
||||
| 青 | `#0d9488` | `#f0fdfa` |
|
||||
| 绿 | `#16a34a` | `#f0fdf4` |
|
||||
| 琥珀 | `#d97706` | `#fffbeb` |
|
||||
| 紫 | `#7c3aed` | `#f5f3ff` |
|
||||
| 灰 | `#475569` | `#f1f5f9` |
|
||||
| 玫红 | `#e11d48` | `#fff1f2` |
|
||||
|
||||
3. **重跑**(仓库根目录;纯标准库,任意 python3,统一用 `.venv` 亦可):
|
||||
```powershell
|
||||
& '.venv\Scripts\python.exe' tools/arch_diagram/gen_frame_svg.py
|
||||
```
|
||||
成功输出 `OK <字节> bytes, canvas <W>x<H> -> <路径>`。脚本内置 `ElementTree.fromstring` 自检——**XML 不合法会直接抛错**,不会产出坏图。
|
||||
4. **目检**:浏览器打开 `docs/images/*.svg`,确认文字不溢出盒外、框 / 线 / 标签对齐、配色正确。
|
||||
5. **引用**:在对应 `.md` 里用**相对路径** ``——`docs/` 根目录下的 md 写 ``;`docs/UI 设计/` 等子目录的 md 写 ``。
|
||||
6. **提交**:脚本(源)+ 生成的 `.svg`(产物)**一起**提交(产物入库,类比生成物 `ui_menu_tab.c`)。
|
||||
|
||||
### 3.2 SVG 约定(手写 / 新增生成器时遵循)
|
||||
|
||||
- **纯标准库**:只用 `html` / `os`(+ 需要时 `math`)拼 SVG 字符串;**不引第三方库**(SVG 由浏览器用系统字体渲染,无需 Pillow / 字体文件)。
|
||||
- **文本转义**:所有文本经 `html.escape(s, quote=True)`(`& < > " '` → 实体),防止破坏 XML。
|
||||
- **字体**:根 `<svg font-family="Microsoft YaHei, Segoe UI, PingFang SC, sans-serif">`(系统字体栈,Win / mac 通用)。
|
||||
- **文本宽估算**(自适应盒宽时):`text_w(s, fs)`——CJK / 全角 / 圈码 / 箭头 ≈ `1.0em`,其余 ≈ `0.58em`(**宁大勿小**,防溢出)。
|
||||
- **落盘**:`open(OUT, "w", encoding="utf-8", newline="\n")`(UTF-8 无 BOM + LF,符合仓库编码铁律)。
|
||||
- **自检**:写盘后 `xml.etree.ElementTree.fromstring(svg)`。
|
||||
- **产物位置**:`docs/images/`(专门配图目录;引用它的 `.md` 用相对路径指向)。
|
||||
- **命名**:`docs/images/<主题>.svg`(可与同名 `.md` 对应,如 `images/RemoDispBus帧格式.svg` ↔ `RemoDispBus协议与接收设计说明.md` §1.2)。
|
||||
|
||||
### 3.3 拓扑类图(流程 / 时序 / 状态机)——用 Mermaid
|
||||
|
||||
在 Markdown 里直接写 ` ```mermaid ` 代码块(GitHub 与多数渲染器原生渲染,**无需生成图片**):
|
||||
|
||||
````markdown
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as 客户端
|
||||
participant S as 服务端
|
||||
C->>S: LCDMEM 取帧
|
||||
S-->>C: 1bpp 位图(3200 B)
|
||||
```
|
||||
````
|
||||
|
||||
常用:`flowchart`(流程 / 框图)、`sequenceDiagram`(时序)、`stateDiagram-v2`(状态机)、`erDiagram`(实体关系)。
|
||||
> ⚠️ Mermaid 盒子宽度自适应、会自动连线,**只适合"关系"类图**;凡需精确像素 / 比例的布局图(协议帧、分层框)一律走 §3.1 的 SVG 生成器。
|
||||
|
||||
---
|
||||
|
||||
## 四、SVG → PNG(可选)
|
||||
|
||||
默认交付 **SVG**(矢量、可缩放、可 diff、可过门禁)。仅当某个**不渲染 SVG 的查看器**需要位图时再转 PNG:
|
||||
|
||||
- **最简单**:浏览器打开 `.svg` → 截图 / 另存为图片。
|
||||
- **脚本化**:给 `.venv` 装 `cairosvg` 后转(`scale=2` 提高清晰度):
|
||||
```powershell
|
||||
& '.venv\Scripts\python.exe' -m pip install cairosvg
|
||||
& '.venv\Scripts\python.exe' -c "import cairosvg; cairosvg.svg2png(url='docs/images/RemoDispBus帧格式.svg', write_to='remo.png', scale=2)"
|
||||
```
|
||||
|
||||
> PNG 属**本地派生物、不入库**(与 `simulator/tests/ref/` 的参考图一致,`.gitignore` 已含 `*.png`)。
|
||||
|
||||
---
|
||||
|
||||
## 五、检查清单(提交前)
|
||||
|
||||
- [ ] 生成器输出 `OK … bytes`(无异常 = XML 合法)。
|
||||
- [ ] 浏览器目检:文字不溢出、框 / 线 / 标签对齐、配色符合调色板。
|
||||
- [ ] 引用它的 `.md` 里图片**相对路径**正确(能显示)。
|
||||
- [ ] 脚本 + 产物 `.svg` **一并**暂存(产物入库)。
|
||||
- [ ] `pre-commit run --files <脚本> <svg> <md>` 全过(无乱码 / 行尾空白 / 编码 OK)。
|
||||
|
||||
---
|
||||
|
||||
## 六、变更记录
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-09-11 | 创建:统一图形绘制流程(选型 / SVG 生成器 / Mermaid / 约定 / 检查清单);新增 `tools/arch_diagram/gen_frame_svg.py`(通用协议帧图)+ `docs/RemoDispBus帧格式.svg` |
|
||||
| 2026-09-11 | 新增专门配图目录 `docs/images/`:收拢 `docs/` 根与 `docs/UI 设计/images/` 下 5 张 SVG;同步更新 3 个生成器 OUT 路径、4 处文档图片引用、`AGENTS.md` 与 `.gitignore`(PNG 预览规则);流程沉淀为 `draw-diagram` skill |
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
这与 CubeMX"一次性创建全部任务"的模式完全兼容:任务仍由 `MX_FREERTOS_Init` 统一创建,但各自在入口 `wait`,相当于"创建即挂起、就绪才放行"。
|
||||
|
||||

|
||||

|
||||
|
||||
> 上图:**左 = 生产者(set)· 中 = 事件组 `sys_evtHandle` · 右 = 消费者(wait)**;任务卡与事件位对齐同一行,`bit1 → ftpTask` 的额外依赖用 **All 汇合点**(`osFlagsWaitAll`)表达。
|
||||
> 源码 `tools/arch_diagram/gen_barrier_svg.py`(改数据重跑即可),PNG 预览本地渲染、不入库。
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
## 1. 全景分层架构
|
||||
|
||||

|
||||

|
||||
|
||||
> 上图由 `tools/arch_diagram/gen_arch_svg.py` 生成(**手绘 SVG**,布局确定、零连线交叉);改架构后编辑脚本顶部 `ROWS` 数据重跑即可,源数据与 §1.1 表一致。
|
||||
> 需要 PNG 预览(PPT/聊天分享):用 Edge 无头渲染 `docs/项目架构总览.svg` 即可(PNG 不入库)。
|
||||
> 需要 PNG 预览(PPT/聊天分享):用 Edge 无头渲染 `docs/images/项目架构总览.svg` 即可(PNG 不入库)。
|
||||
|
||||
> 分层原则:**上层只依赖相邻下层(单向)**;**① 业务层为顶层**(仅 `net_cfg`,被 UI 与基础服务共用),**② UI 层与 ③ 基础服务层同级并列**。跨层的具体调用关系见 §1.2 表。
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
|
||||
### 2.2 就绪屏障(Event Flags,取代魔法延时)
|
||||
|
||||

|
||||

|
||||
|
||||
> 三栏式:**左 = 生产者(set)· 中 = 事件组 `sys_evtHandle` · 右 = 消费者(wait)**。
|
||||
> 任务卡与事件位对齐同一行,箭头 = `set` / `wait` 关系;`bit1 → ftpTask` 的额外依赖用 **All 汇合点**表达(`osFlagsWaitAll`)。
|
||||
|
||||
@@ -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()
|
||||