feat(studio-3d): 历史回放里的三维面板 —— 由「重算」通道喂它,并标注来源

- What: `VIEW_SHOWS_3D` 加入 `replay`;`app_window` 新增 `_shows_3d()`(可见性与布局共用一个判据)
  与 `_splitter_sizes_for()`(预设表里的 3D 槽是"二维布局"的描述,面板打开时从最宽的面板借
  `DEFAULT_3D_PANE_WIDTH=340`);`ThreeDPanel` 新增第三个来源标签(实时采集 / 回放:录像不含三维场 /
  重算通道:由录像重算,非记录值)与 `set_source_kind()`;`ThreeDCoordinator.show_rest()` 把参考模型
  放回屏幕并停止主张测量值(重算停止时调用,否则最后一帧会留在屏幕上);`Mesh3dPanel` 转发这两者,
  并新增"加载中"状态的修正:切换传感器再切回来时若该包已加载,直接显示已就绪的画面而不是一条永远
  不会被替换的"正在加载"。
- Why: 录像里没有三维场(raw_*.mp4 是原始相机图像,`depth.stored=false`,CSV 只有力值),所以纯回放只能
  显示参考模型;但「从视频重算力值」本来就把同一个运行路径指向录像的原始视频,而采集线程上的三维 sink
  早已注册——因此重算一开始面板就会收到真实的形变帧。这两件事必须由界面区分:一个是"没有测量",
  一个是"当前算法对这段录像会说什么",把后者当成记录值展示就是另一种 `display_illustrative`。
- Verification: apps/studio 全量通过(test_scene3d_view.py 32 项,含"回放里面板可见且标注正确""重算时
  标注切换""停止重算后清回参考模型""实时视图标为实时";test_scene3d_coordinator.py 15 项,含 show_rest
  与"停止时在飞的帧不会随后被显示");ui_check 570 项 0 失败(新增"已安装包必须被加载并标注来源");
  locale_check 0 缺失;仓库内工程包经 validate_scene 全部 ok。
- Not in this change: 逐帧保存展开场/深度(记录格式变更,设计文档明确否掉);重算通道本身(已存在,本次
  只是把它的帧接到面板上);示意位移(仍需 SDK lift + 已确认尺度)。
This commit is contained in:
ykcheungab
2026-09-14 17:13:37 +08:00
parent 2226bdde68
commit 981c250559
13 changed files with 515 additions and 27 deletions
+126
View File
@@ -0,0 +1,126 @@
# Scene asset tools
Offline tools for the three-dimensional view. They never run in the application:
the app loads a **compiled** package (`.oryscene`) and nothing else.
| tool | what it is for |
|---|---|
| `compile_scene.py` | a Blender export (+ optional housing GLB) → one `.oryscene` package |
| `validate_scene.py` | the checks the loader and the compiler must agree on; the release gate |
| `probe_gl.py` | what this machine's OpenGL actually is (exit 0 GPU, 3 software, 4 no context) |
| `bench_scene.py` | per-stage p50/p95 of the running view (`--cpu-only` for the pure CPU cost) |
| `soak_scene.py` | long run: RSS trend and the queue bound |
Run them with the repository's own interpreter, from `orisys-app`:
```powershell
$py = '.venv-build\Scripts\python.exe'
```
## Compiling the shipped finger sensor
The **runtime** asset is `sensors/finger/assets/30fps-0703`: it is what
`sensors/finger/manifest.json` names (`assets_dir`), and its unroll map
(`unroll_map_uv_s0.4.npz`, out size 300×280, `source_scale` 0.4) matches
`camera.resolution_stitch` in `sensor.json`, which is the grid the live optical flow
is computed on.
Do **not** compile `assets/visualization/flatten_remap.npz`: it is a 1024×1024
domain, so every live frame would be refused with `field_domain_mismatch`
(300x280 vs 1024x1024). That map belongs to offline visualisation.
```powershell
$sdk = 'D:\orisys_ws\orisys-sdk\sensors\finger\assets\30fps-0703' # or the copy in apps/studio/config
# 1) state the unit (the importer never guesses it) and read what it measured
& $py tools\scene_assets\validate_scene.py --legacy-dir $sdk --map "$sdk\unroll_map_uv_s0.4.npz"
# 2) compile
& $py tools\scene_assets\compile_scene.py `
--legacy-dir $sdk `
--map "$sdk\unroll_map_uv_s0.4.npz" `
--meters-per-source-unit 0.001 `
--output apps\studio\config\sensors\finger\scene\finger-display-1.0.0.oryscene
# 3) verify
& $py tools\scene_assets\validate_scene.py apps\studio\config\sensors\finger\scene\finger-display-1.0.0.oryscene
```
**`--meters-per-source-unit` is a physical statement about the sensor, not a
formatting choice.** The measured mesh extent is 15.81 × 15.0 × 7.5 source units
(1032 vertices, 1974 triangles); the shipped finger surface is about 15.8 mm across,
so one source unit is one millimetre and the value is `0.001`. If the mechanical
drawing says otherwise, state that number instead — nothing downstream can tell the
difference, and a wrong factor looks plausible.
Expected result: `1032 vertices`, `1974 triangles`, `geometry_id` starting
`1a2e0e92…`, a 56 KB package, and `validate_scene.py` reporting every check `ok`.
## Where the application looks for a package
1. `preference.json` → `"scene3d_package": "<path>"` (an absolute path; also the way
to point a developer build at a freshly compiled package);
2. `<config>/sensors/<sensor>/scene/*.oryscene` — for the studio app that is
`apps/studio/config/sensors/finger/scene/`;
3. nothing: the pane reports the directory it searched and the compile command above,
and the two-dimensional views keep working.
The SDK's own `sensors/` tree is **not** searched (the app prefers its local
`config/sensors` bundles). Compile into the app's `scene/` directory, or set
`scene3d_package`.
## Release packages (signature, optional encryption, platform lock)
All three are optional and independent of the plain package above:
```powershell
& $py tools\scene_assets\compile_scene.py --legacy-dir $sdk --map "$sdk\unroll_map_uv_s0.4.npz" `
--meters-per-source-unit 0.001 `
--sign-key keys\build-ed25519.pem --sign-key-id build-2024 `
--write-verify-key keys\build-ed25519.pub.pem `
--encrypt-key-id finger-display --key-file keys\finger-display.key `
--lock-from-probe docs\architecture\gl-report-windows-dev.json `
--output apps\studio\config\sensors\finger\scene\finger-display-1.0.0.oryscene
```
- `--sign-key` signs the manifest (and therefore every entry hash, the compatibility
information and the platform lock). The private key stays on the build machine; the
client ships only `--write-verify-key`'s public half.
- `--encrypt-key-id` + `--key-file` additionally seal the content with AES-256-GCM.
Encryption never replaces signing. Reading a sealed package needs the
`protected-assets` extra (`cryptography`); a plain package does not.
- `--lock-from-probe` takes the platform lock (OS, architecture, GL profile, vendor,
renderer, minimum texture size, "no software rasteriser") from a `probe_gl.py`
report. A machine the lock excludes loses the 3D pane, with the reason, and keeps
everything else.
- `--encrypt-key-id` without `--key-file`, or with no crypto backend installed, is
**refused** rather than silently writing a plain package that looks protected.
Release gate:
```powershell
& $py tools\scene_assets\validate_scene.py <package> `
--key-file keys\finger-display.key --key-id finger-display `
--verify-key keys\build-ed25519.pub.pem --require-signature
```
## Performance and long runs
```powershell
# per-stage p50/p95 of the running view (needs a real window system)
$env:QT_QPA_PLATFORM = 'windows'
& $py tools\scene_assets\bench_scene.py --frames 600 --output perf.json
& $py tools\scene_assets\bench_scene.py --cpu-only --frames 500 --vertices 20000 --output perf-cpu.json
& $py tools\scene_assets\bench_scene.py --package <package> --key-file keys\finger-display.key `
--key-id finger-display --verify-key keys\build-ed25519.pub.pem --output perf-real.json
# long run: the release gate is two hours in the GUI mode (it is the stricter one)
& $py tools\scene_assets\soak_scene.py --minutes 120 --fps 30 --output soak.json
& $py tools\scene_assets\soak_scene.py --no-window --minutes 10 --fps 60 --output soak-cpu.json
```
The committed reports for the developer machine are
`docs/architecture/perf-report-windows-dev.json` and
`docs/architecture/soak-report-windows-dev.json`; the stage-by-stage acceptance, the
failure samples and the compatibility notes are in
`docs/architecture/studio-3d-stages.md`.