dd0ee2c71e
- What:
1. `docs/architecture/studio-architecture.md`:分层视图(Mermaid + 纯文本版)、运行时/线程视图、
二维/三维显示子系统视图、边界规则与证据表、指标表(模块行数 × 扇出、稳定核心 fan-in、验证面规模)、
可维护性评估(5 条优点 / 7 条风险,逐条带证据)、6 条建议(按性价比排序)。
2. `docs/architecture/studio-architecture.svg`(浏览器直接打开)与同名 `.png`(由 SVG 经 QtSvg 栅格化,
给没有 SVG 渲染器的场景):分层海报,层间流动用箭头表示,跨模块关系写在框内。
3. 数据来源(在 `.repro/`,可重跑):`studio_arch_scan.py` 扫 89 个模块的 import 图并给出 fan-in/fan-out
与**模块级环**;`studio_arch_imports.py` 分别统计"import 时就拉 Qt/GL/SDK"与"函数内惰性 import",
并在**新解释器**里逐个验证"导入 X 是否会拉进 PyQt5/OpenGL/SDK";`make_studio_svg.py`/`svg_to_png.py`
生成图。原始数据 `studio_arch.json`、`studio_arch_imports.json`。
- Why: 用户要看架构图来判断"软件组织是否合理、是否容易维护"。因此所有框与数字都从源码实际 import 关系
生成,而不是画一个理想分层:报告里同时给出**成立**的边界规则(无环、共享层 import 时不依赖 Qt/GL、
三维调度与相机无 Qt 可脱离 GUI 测试)与**不成立**的一条(`force/calibration_io` 在 import 时 import
orisys,于是力相关单测必须装 SDK),以及最贵的单点 `app_window.py`(3044 行 / 158 方法 / fan-out 36)。
- Verification:
· 扫描结果:89 个模块(studio 32 + shared 57)模块级**环 0 个**;import 时拉 PyQt5 的 21 个模块**全部**
在 `orisys_studio.*`;OpenGL 只在 `gl_backend`/`widget` 函数内惰性加载;新解释器验证
`scene3d.contracts/composer`、`acquisition.arrows/sinks`、`record.playback`、`theme`、
`studio.scene3d.coordinator/camera` 均为"干净"(不含 PyQt5/OpenGL/SDK),而 `force.history` 会拉进 SDK。
· 规模:studio src 13 376 行(三维 4 326)、shared 14 820 行(三维 4 576 / 记录 2 673 / 力 2 645 /
采集 2 343)、测试 11 633 行、工具 5 272 行;`scene3d.contracts` fan-in 22 为全仓最高。
· 图的自检:SVG 由 `ET.parse` 验证为良构 XML,并栅格化成 PNG 后逐版目视检查过(第一版箭头穿框,已改为
层间箭头 + 框内关系文字)。
- Not in this change: 不改任何产品代码;建议 1(拆 `app_window`)等 6 条尚未实施,它们属于下一轮工作,
每条都写了收益、成本与回归门。
141 lines
14 KiB
XML
141 lines
14 KiB
XML
<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"/>
|
||
<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"/>
|
||
<text x="46" y="111" fill="#7dd3fc" font-size="15.5" font-weight="600">① 入口</text>
|
||
<rect x="30" y="176" width="1500" height="116" rx="10" fill="#161d2c" stroke="#27324a" stroke-width="1"/>
|
||
<text x="46" y="203" fill="#7dd3fc" font-size="15.5" font-weight="600">② 窗口外壳</text>
|
||
<text x="46" y="223" fill="#9fb0c8" font-size="11.5">只有它能看见全部 UI</text>
|
||
<rect x="30" y="322" width="1500" height="148" rx="10" fill="#161d2c" stroke="#27324a" stroke-width="1"/>
|
||
<text x="46" y="349" fill="#7dd3fc" font-size="15.5" font-weight="600">③ 面板</text>
|
||
<text x="46" y="369" fill="#9fb0c8" font-size="11.5">各自的画布与控件</text>
|
||
<rect x="30" y="500" width="1500" height="190" rx="10" fill="#161d2c" stroke="#27324a" stroke-width="1"/>
|
||
<text x="46" y="527" fill="#7dd3fc" font-size="15.5" font-weight="600">④ Studio 领域服务</text>
|
||
<text x="46" y="547" fill="#9fb0c8" font-size="11.5">有 Qt 的与无 Qt 的分开</text>
|
||
<rect x="30" y="720" width="1500" height="210" rx="10" fill="#161d2c" stroke="#27324a" stroke-width="1"/>
|
||
<text x="46" y="747" fill="#7dd3fc" font-size="15.5" font-weight="600">⑤ 共享领域层</text>
|
||
<text x="46" y="767" fill="#9fb0c8" font-size="11.5">采集 / 记录 / 力 / 三维契约 —— import 时不拉 Qt</text>
|
||
<rect x="30" y="960" width="1500" height="150" rx="10" fill="#161d2c" stroke="#27324a" stroke-width="1"/>
|
||
<text x="46" y="987" fill="#7dd3fc" font-size="15.5" font-weight="600">⑥ 验证面</text>
|
||
<text x="46" y="1007" fill="#9fb0c8" font-size="11.5">为什么这个架构敢改</text>
|
||
<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"/>
|
||
<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>
|
||
<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="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="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="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="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>
|
||
<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="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>
|
||
<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="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>
|
||
<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="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="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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<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="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>
|
||
<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>
|
||
<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>
|
||
<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>
|
||
<line x1="300" y1="146" x2="300" y2="166" stroke="#7dd3fc" stroke-width="2.2"/>
|
||
<path d="M 293 164 L 300 176 L 307 164 Z" fill="#7dd3fc"/>
|
||
<text x="314" y="165" fill="#9fb0c8" font-size="11.5">启动</text>
|
||
<line x1="300" y1="292" x2="300" y2="312" stroke="#7dd3fc" stroke-width="2.2"/>
|
||
<path d="M 293 310 L 300 322 L 307 310 Z" fill="#7dd3fc"/>
|
||
<text x="314" y="311" fill="#9fb0c8" font-size="11.5">装配窗口 + 面板 + 信号转发</text>
|
||
<line x1="880" y1="292" x2="880" y2="312" stroke="#7dd3fc" stroke-width="2.2"/>
|
||
<path d="M 873 310 L 880 322 L 887 310 Z" fill="#7dd3fc"/>
|
||
<line x1="300" y1="470" x2="300" y2="490" stroke="#7dd3fc" stroke-width="2.2"/>
|
||
<path d="M 293 488 L 300 500 L 307 488 Z" fill="#7dd3fc"/>
|
||
<text x="314" y="489" fill="#9fb0c8" font-size="11.5">领域服务:会话 / 记录 / 回放 / 标定</text>
|
||
<line x1="880" y1="470" x2="880" y2="490" stroke="#7dd3fc" stroke-width="2.2"/>
|
||
<path d="M 873 488 L 880 500 L 887 488 Z" fill="#7dd3fc"/>
|
||
<text x="894" y="489" fill="#9fb0c8" font-size="11.5">三维:view → coordinator / GL / 加载线程</text>
|
||
<line x1="300" y1="690" x2="300" y2="710" stroke="#7dd3fc" stroke-width="2.2"/>
|
||
<path d="M 293 708 L 300 720 L 307 708 Z" fill="#7dd3fc"/>
|
||
<text x="314" y="709" fill="#9fb0c8" font-size="11.5">契约 · packet · mailbox(无 Qt 的共享层)</text>
|
||
<line x1="880" y1="690" x2="880" y2="710" stroke="#7dd3fc" stroke-width="2.2"/>
|
||
<path d="M 873 708 L 880 720 L 887 708 Z" fill="#7dd3fc"/>
|
||
<line x1="300" y1="930" x2="300" y2="950" stroke="#7dd3fc" stroke-width="2.2"/>
|
||
<path d="M 293 948 L 300 960 L 307 948 Z" fill="#7dd3fc"/>
|
||
<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="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>
|
||
</svg> |