文档:刷新架构报告的指标与结论(app_window 拆分已落地)

- What: `docs/architecture/studio-architecture.md` 的数字与结论按当前源码重扫后刷新:
  - 分层图/纯文本图:`app_window` 3044 → **2309 行 / fan-out 37**,并新增一层
    "②b Studio 控制器"(session_controller 437 / replay_controller 641 / view_layout 398 /
    display_prefs 227);领域服务与共享层的行数一并更新(replay 291、calibration_service 194、
    coordinator 619、camera 238、view 757、asset_source 256、gl_backend 870;
    shared scene3d 4565 / acquisition 2627 / record 3008 / force 3057 / 基础设施 1507)。
  - 边界规则:模块数 89(studio 32 + shared 57)→ **94(studio 37 + shared 57)**,环 0 个不变。
  - 指标表:新增 `replay_controller`(641) 与 `session_controller`(437) 两行,并写明
    "app_window 的 fan-out 仍是最高(37),因为它本来就是装配器——但规则已经不在它里面"。
  - 生产面/验证面:studio 生产 14 655 行、shared 14 764、测试 15 639(shared 7226/334 项 +
    studio 8413/310 项)、工具 4046(ui_check 3401);验证面 19.7k > 生产面 14.7k。
  - 评估第 1 条与"一句话结论"改写为:上帝对象已按职责拆开,并**诚实记账**留下的东西——
    约 40 个过渡转发(给 tests/`ui_check` 的桥,属过渡层)、仍留在窗口的预览与 2D 渲染
    (它们同时属于面板与相机),以及 fan-out 从 36 升到 37 的原因。
  - 建议表第 1 条标记为已完成(写清五个去处与新增 41 项控制器单测),新增第 1b 条
    "下次改动顺手删掉对应转发",并补第 7 条"真机与 ARM 验收"(本机无设备/板卡,是当前最大空白)。
- What: `studio-architecture.svg` / `.png` 由 `.repro/make_studio_svg.py` 重新生成:
  窗口框改为"2309 行 · 158 方法 · fan-out 37 · 装配 + 信号转发",右侧四个控制器小框,
  面板/服务/共享层/验证面的行数与测试项数同步;底部结论改为"已按职责拆开 + 剩下的胶水"。
  顺带修好 `svg_to_png.py` 里写死的 1560×1180(现在读 SVG 自己的尺寸),否则海报加高后会被裁掉。
- Why: 这份报告是"判断组织是否合理"的依据,数字或结论一旦过期就会误导决策;
  拆分改变最大的一处正是它第 1 条风险与第 1 条建议,必须同步。
- Verification: 图表脚本重跑无异常(SVG well-formed,PNG 1560×1210 已目视检查);
  同批源码的验证为 pytest 644 passed / 0 failed / 15 skipped(offscreen)、
  tools/ui_check.py 587 项 0 失败、.repro/locale_check.py 0 缺失。
- Not in this change: 不改代码;不改 `.repro` 下的扫描脚本口径(仍以扫描输出为准)。
This commit is contained in:
ykcheungab
2026-09-15 10:02:37 +08:00
parent 81e39ca868
commit b37c861254
3 changed files with 128 additions and 80 deletions
+45 -36
View File
@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1560" height="1180" viewBox="0 0 1560 1180" font-family="Segoe UI, sans-serif">
<rect width="1560" height="1180" fill="#0f1420"/>
<svg xmlns="http://www.w3.org/2000/svg" width="1560" height="1210" viewBox="0 0 1560 1210" font-family="Segoe UI, sans-serif">
<rect width="1560" height="1210" fill="#0f1420"/>
<text x="40" y="42" fill="#e8edf7" font-size="23" font-weight="700">Orisys Studio 架构(按源码实际 import 关系生成)</text>
<text x="40" y="66" fill="#9fb0c8" font-size="12.5">绿 = import 时不拉 Qt/OpenGL 蓝 = 需要 Qt 橙 = GL 仅运行时惰性加载 紫 = 验证面 数字为行数(含空行)</text>
<rect x="30" y="84" width="1500" height="62" rx="10" fill="#161d2c" stroke="#27324a" stroke-width="1"/>
@@ -22,92 +22,100 @@
<rect x="220" y="100" width="520" height="34" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="231" y="121" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">main.py</text>
<text x="231" y="139" fill="#9fb0c8" font-size="11">110 行 · QApplication · 主题 · 首选项 · 单实例</text>
<rect x="220" y="200" width="700" height="76" rx="7" fill="#1d2b45" stroke="#f87171" stroke-width="1.3"/>
<rect x="220" y="200" width="700" height="76" rx="7" fill="#1d2b45" stroke="#7dd3fc" stroke-width="1.3"/>
<text x="231" y="221" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">app_window.py · StudioMainWindow</text>
<text x="231" y="239" fill="#9fb0c8" font-size="11">3044 行 · 158 个方法 · 50 个 _on_* 处理器 · fan-out 36</text>
<text x="231" y="255" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">唯一 fan-in = 1 的中心:改任何一处都会碰它</text>
<rect x="950" y="216" width="170" height="44" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="961" y="237" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">brand_bar</text>
<text x="961" y="255" fill="#9fb0c8" font-size="11">146 行</text>
<rect x="1140" y="216" width="170" height="44" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="1151" y="237" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">toolbar</text>
<text x="1151" y="255" fill="#9fb0c8" font-size="11">760 行</text>
<rect x="1330" y="216" width="170" height="44" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="1341" y="237" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">record_status</text>
<text x="1341" y="255" fill="#9fb0c8" font-size="11">205 行</text>
<text x="231" y="239" fill="#9fb0c8" font-size="11">2309 行 · 158 个方法 · fan-out 37(含四个控制器)</text>
<text x="231" y="255" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">装配 + 信号转发;状态与规则在四个控制器里(右侧)</text>
<rect x="950" y="200" width="270" height="44" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="961" y="221" fill="#e8edf7" font-size="12" font-family="Consolas, monospace">session_controller.py</text>
<text x="961" y="239" fill="#9fb0c8" font-size="11">会话 · 相位 · 437 行</text>
<text x="961" y="255" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">worker / 遥测 / 结束原因</text>
<rect x="1235" y="200" width="270" height="44" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="1246" y="221" fill="#e8edf7" font-size="12" font-family="Consolas, monospace">replay_controller.py</text>
<text x="1246" y="239" fill="#9fb0c8" font-size="11">回放策略 · 641 行</text>
<text x="1246" y="255" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">播放 · 打开 · 重算</text>
<rect x="950" y="250" width="270" height="44" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="961" y="271" fill="#e8edf7" font-size="12" font-family="Consolas, monospace">view_layout.py</text>
<text x="961" y="289" fill="#9fb0c8" font-size="11">面板布局 · 398 行</text>
<text x="961" y="305" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">可见性 · 预设 · 3D 开关</text>
<rect x="1235" y="250" width="270" height="44" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="1246" y="271" fill="#e8edf7" font-size="12" font-family="Consolas, monospace">display_prefs.py</text>
<text x="1246" y="289" fill="#9fb0c8" font-size="11">显示偏好 · 227 行</text>
<text x="1246" y="305" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">单位 · 量程 · 箭头 · fps</text>
<rect x="220" y="352" width="238" height="92" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="231" y="373" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">ui/arrow_panel</text>
<text x="231" y="391" fill="#9fb0c8" font-size="11">2D 图像 · 519 行</text>
<text x="231" y="391" fill="#9fb0c8" font-size="11">2D 图像 · 518 行</text>
<text x="231" y="407" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">→ image_backend(共享)</text>
<rect x="478" y="352" width="238" height="92" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="489" y="373" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">ui/mesh3d_panel</text>
<text x="489" y="391" fill="#9fb0c8" font-size="11">3D 宿主容器 · 405 行</text>
<text x="489" y="391" fill="#9fb0c8" font-size="11">3D 宿主容器 · 404 行</text>
<text x="489" y="407" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">→ scene3d/view</text>
<rect x="736" y="352" width="238" height="92" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="747" y="373" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">ui/force_plots</text>
<text x="747" y="391" fill="#9fb0c8" font-size="11">三轴曲线 · 1243 行</text>
<text x="747" y="391" fill="#9fb0c8" font-size="11">三轴曲线 · 1242 行</text>
<text x="747" y="407" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">→ force.history</text>
<rect x="994" y="352" width="238" height="92" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="1005" y="373" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">ui/cal_panel</text>
<text x="1005" y="391" fill="#9fb0c8" font-size="11">标定表 · 793 行</text>
<text x="1005" y="391" fill="#9fb0c8" font-size="11">标定表 · 792 行</text>
<text x="1005" y="407" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">→ force 标定</text>
<rect x="1252" y="352" width="238" height="92" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="1263" y="373" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">对话框 ×3</text>
<text x="1263" y="391" fill="#9fb0c8" font-size="11">sensor / plot / cal_point</text>
<rect x="220" y="524" width="296" height="56" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="231" y="545" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">recording.py</text>
<text x="231" y="563" fill="#9fb0c8" font-size="11">记录协调 · 268 行</text>
<text x="231" y="563" fill="#9fb0c8" font-size="11">记录协调 · 267 行</text>
<rect x="538" y="524" width="296" height="56" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="549" y="545" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">replay.py</text>
<text x="549" y="563" fill="#9fb0c8" font-size="11">回放引擎 · 286 行(QObject)</text>
<text x="549" y="563" fill="#9fb0c8" font-size="11">回放引擎 · 291 行(QObject)</text>
<text x="549" y="579" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">→ record 读取</text>
<rect x="856" y="524" width="296" height="56" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="867" y="545" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">calibration_service.py</text>
<text x="867" y="563" fill="#9fb0c8" font-size="11">标定 · 131 行</text>
<text x="867" y="563" fill="#9fb0c8" font-size="11">标定文件与归属 · 194 行</text>
<text x="867" y="579" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">无 Qt</text>
<rect x="1174" y="524" width="296" height="56" rx="7" fill="#152b25" stroke="#4a5a76" stroke-width="1.3"/>
<text x="1185" y="545" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">scene3d/coordinator.py</text>
<text x="1185" y="563" fill="#9fb0c8" font-size="11">调度 · 560 行 · 无 Qt</text>
<text x="1185" y="563" fill="#9fb0c8" font-size="11">调度 · 619 行 · 无 Qt</text>
<text x="1185" y="579" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">mailbox + epoch</text>
<rect x="220" y="594" width="296" height="56" rx="7" fill="#152b25" stroke="#4a5a76" stroke-width="1.3"/>
<text x="231" y="615" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">scene3d/camera.py</text>
<text x="231" y="633" fill="#9fb0c8" font-size="11">相机 · 210 行 · 无 Qt</text>
<text x="231" y="633" fill="#9fb0c8" font-size="11">相机 · 238 行 · 无 Qt</text>
<rect x="538" y="594" width="296" height="56" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="549" y="615" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">scene3d/view.py</text>
<text x="549" y="633" fill="#9fb0c8" font-size="11">3D 唯一入口 · 758 行</text>
<text x="549" y="633" fill="#9fb0c8" font-size="11">3D 唯一入口 · 757 行</text>
<text x="549" y="649" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">describe() / render_totals()</text>
<rect x="856" y="594" width="296" height="56" rx="7" fill="#1d2b45" stroke="#4a5a76" stroke-width="1.3"/>
<text x="867" y="615" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">scene3d/asset_source.py</text>
<text x="867" y="633" fill="#9fb0c8" font-size="11">加载线程 · 257 行</text>
<text x="867" y="633" fill="#9fb0c8" font-size="11">加载线程 · 256 行</text>
<text x="867" y="649" fill="#7dd3fc" font-size="11" font-family="Consolas, monospace">验签 + 设备几何校验</text>
<rect x="1174" y="594" width="296" height="56" rx="7" fill="#1e273a" stroke="#fbbf24" stroke-width="1.3" stroke-dasharray="4 3"/>
<text x="1185" y="615" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">scene3d/gl_backend.py</text>
<text x="1185" y="633" fill="#9fb0c8" font-size="11">GL 惰性 · 871 行</text>
<text x="1185" y="633" fill="#9fb0c8" font-size="11">GL 惰性 · 870 行</text>
<rect x="220" y="744" width="406" height="54" rx="7" fill="#152b25" stroke="#4a5a76" stroke-width="1.3"/>
<text x="231" y="765" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">orisys_app_shared.scene3d</text>
<text x="231" y="783" fill="#9fb0c8" font-size="11">契约 / 采样 / 合成 / 包 / 签名 · 4057 行</text>
<text x="231" y="783" fill="#9fb0c8" font-size="11">契约 / 采样 / 合成 / 包 / 签名 · 4565 行</text>
<rect x="648" y="744" width="406" height="54" rx="7" fill="#152b25" stroke="#4a5a76" stroke-width="1.3"/>
<text x="659" y="765" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">orisys_app_shared.acquisition</text>
<text x="659" y="783" fill="#9fb0c8" font-size="11">worker / packet / sinks / 图像后端 · 2343 行</text>
<text x="659" y="783" fill="#9fb0c8" font-size="11">worker / packet / sinks / 图像后端 · 2627 行</text>
<rect x="1076" y="744" width="406" height="54" rx="7" fill="#152b25" stroke="#4a5a76" stroke-width="1.3"/>
<text x="1087" y="765" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">orisys_app_shared.record</text>
<text x="1087" y="783" fill="#9fb0c8" font-size="11">录像读写 / CSV / session.json · 2673 行</text>
<text x="1087" y="783" fill="#9fb0c8" font-size="11">录像读写 / CSV / session.json · 3008 行</text>
<rect x="220" y="810" width="406" height="54" rx="7" fill="#3a2a18" stroke="#4a5a76" stroke-width="1.3"/>
<text x="231" y="831" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">orisys_app_shared.force</text>
<text x="231" y="849" fill="#9fb0c8" font-size="11">力源 / 标定 / 滤波 · 2645 行</text>
<text x="231" y="849" fill="#9fb0c8" font-size="11">力源 / 标定 / 滤波 · 3057 行</text>
<rect x="648" y="810" width="406" height="54" rx="7" fill="#152b25" stroke="#4a5a76" stroke-width="1.3"/>
<text x="659" y="831" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">camera · prefs · paths · i18n · theme · logging</text>
<text x="659" y="849" fill="#9fb0c8" font-size="11">基础设施 · 1268 行</text>
<text x="659" y="849" fill="#9fb0c8" font-size="11">基础设施 · 1507 行</text>
<text x="1076" y="828" fill="#fbbf24" font-size="11.5">⚠ 唯一的例外:force.calibration_io 在 import 时</text>
<text x="1076" y="846" fill="#fbbf24" font-size="11.5">  import orisys(力相关单测需要装 SDK)</text>
<rect x="220" y="984" width="616" height="44" rx="7" fill="#251d33" stroke="#7c5cbf" stroke-width="1.3"/>
<text x="231" y="1005" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">shared/tests</text>
<text x="231" y="1023" fill="#9fb0c8" font-size="11">22 文件 · 5992 行 · 316 项</text>
<text x="231" y="1023" fill="#9fb0c8" font-size="11">22 文件 · 7226 行 · 334 项</text>
<rect x="860" y="984" width="616" height="44" rx="7" fill="#251d33" stroke="#7c5cbf" stroke-width="1.3"/>
<text x="871" y="1005" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">apps/studio/tests</text>
<text x="871" y="1023" fill="#9fb0c8" font-size="11">10 文件 · 5641 行 · 240 项 + 真 GL 14 项</text>
<text x="871" y="1023" fill="#9fb0c8" font-size="11">15 文件 · 8413 行 · 310 项 + 真 GL 14 项</text>
<rect x="220" y="1036" width="616" height="44" rx="7" fill="#251d33" stroke="#7c5cbf" stroke-width="1.3"/>
<text x="231" y="1057" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">tools/ui_check.py</text>
<text x="231" y="1075" fill="#9fb0c8" font-size="11">3168 行 · 587 项 UI 断言</text>
<text x="231" y="1075" fill="#9fb0c8" font-size="11">3401 行 · 587 项 UI 断言</text>
<rect x="860" y="1036" width="616" height="44" rx="7" fill="#251d33" stroke="#7c5cbf" stroke-width="1.3"/>
<text x="871" y="1057" fill="#e8edf7" font-size="13" font-family="Consolas, monospace">tools/bench_scene.py · soak_scene.py · probe_gl.py</text>
<text x="871" y="1075" fill="#9fb0c8" font-size="11">性能 / 长时 / 平台报告</text>
@@ -135,7 +143,8 @@
<text x="314" y="949" fill="#9fb0c8" font-size="11.5">验证:单测 · 真 GL · UI 检查 · 性能长时报告</text>
<line x1="880" y1="930" x2="880" y2="950" stroke="#7dd3fc" stroke-width="2.2"/>
<path d="M 873 948 L 880 960 L 887 948 Z" fill="#7dd3fc"/>
<text x="40" y="1128" fill="#9fb0c8" font-size="11.5">依赖方向只有向下:扫全部 89 个模块(studio 32 + shared 57),模块级环 0 个;新增模块只要不反向 import 就不会破坏分层。</text>
<text x="40" y="1128" fill="#9fb0c8" font-size="11.5">依赖方向只有向下:扫全部 94 个模块(studio 37 + shared 57),模块级环 0 个;新增模块只要不反向 import 就不会破坏分层。</text>
<text x="40" y="1146" fill="#9fb0c8" font-size="11.5">2D 与 3D 是两条独立流水线,共同上游只有同一个 SdkPacket;任一失败只降级自己(2D/记录/曲线不受影响)。</text>
<text x="40" y="1164" fill="#9fb0c8" font-size="11.5">最贵的一处是 app_window.py(3044 行 / fan-out 36):视图装配、会话生命周期、业务决策、持久化四个角色目前都在它里面。</text>
<text x="40" y="1164" fill="#9fb0c8" font-size="11.5">app_window 已按职责拆开(会话 / 回放 / 布局 / 显示偏好),158 个方法里约 40 个是给测试与 ui_check 的一层转发;</text>
<text x="40" y="1182" fill="#9fb0c8" font-size="11.5">剩下的胶水是预览(_start_preview/_stop_preview)与 2D 渲染(_show_image_frame/_depth_range),它们同时属于面板与相机。</text>
</svg>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB