cf45e654e1
- What:
1. `docs/architecture/studio-3d-stages.md` 新增三节:**阶段 7**(二维图像后端统一与回放入口收口,含起因、
做法、验收表格)、**阶段 8 第一批**(能力控件/真实色标/复位语义/命名)、**阶段 9 上半**(限速、真实统计、
无位移复用几何、保护包密钥),并新开一节「尚未完成」把两项写清楚:阶段 8 第二批(画布布局与曲线量程)与
阶段 9 的门禁运行(GUI 2 小时长时 + 两块 ARM 板的探针/性能/长时报告),外加阶段 6 遗留的真机验证项。
文首进度与依赖关系图同步。
2. `docs/architecture/studio-3d-contracts.pyi`:新增 `SceneCapabilities`;`RenderStats` 增
`paint_calls`/`scale_lower`/`scale_upper`/`scale_mode` 并注明 `displayed_frames` 是"按帧身份的唯一帧"
而不是绘制次数;`RenderFrame` 增 `compose_ms`。
3. `tools/scene_assets/README.md`:新增「Studio 应用侧的配置」——`scene3d_verify_key`/
`scene3d_content_key_file` + `scene3d_content_key_id`/`scene3d_require_signature` 的用法、为什么没有验签
公钥就打不开签名包、以及"密钥文件缺失是状态而非崩溃、记录里只留布尔与路径"。
- Why: 规格 §13 要求每阶段同时交出实现、测试、输入样例、失败样例、兼容说明与平台诊断报告;阶段 6 的文档提交
之后又落了阶段 7/8/9 的接口改动,骨架文件必须跟上,否则下一阶段会照着过期文档实现。同时把"没做完的两项"
写在文档里(而不是留在对话里),下一个接手的人不需要猜。
- Verification: 文档改动本身不执行测试;其中的数字全部来自本仓可复现的命令:`pytest`(shared+apps 同一次
运行 588 项 0 失败 15 跳过)、真 GL 冒烟 14 项 0 失败、`ui_check.py` 587 项 0 失败、`locale_check.py`
0 缺失、`bench_scene.py --cpu-only`(20k 顶点 1.03 ms / 真实包 0.17 ms)、`validate_scene.py`(重编包
全项 ok)与两份 `.repro/probe_stage6_*.json`。
- Not in this change: 不改代码;阶段 8 第二批与阶段 9 门禁仍未做(见文档「尚未完成」一节);ARM 板卡报告
按用户先前的意思仍是暂缓项。
161 lines
7.7 KiB
Markdown
161 lines
7.7 KiB
Markdown
# 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`.
|
||
|
||
The package also records a **unit-free** `source_geometry_id` (`46a03a5f…` for the
|
||
shipped bundle). It is the only geometry identity the acquisition side can reproduce
|
||
without the unit statement above, so it is what lets the running application verify
|
||
that the scene it is drawing was built from the same surface — and the same mapping,
|
||
via `field_domain_id` — that the device is actually running. A package that predates
|
||
this record is still accepted, and reported as *unverified* rather than as a match.
|
||
|
||
## 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
|
||
```
|
||
|
||
### What the Studio application needs for a protected package
|
||
|
||
The application reads the same material from its preferences file (paths only — the
|
||
private half never leaves the build machine):
|
||
|
||
```json
|
||
{
|
||
"scene3d_verify_key": "C:\\keys\\build-ed25519.pub.pem",
|
||
"scene3d_content_key_file": "C:\\keys\\finger-display.key",
|
||
"scene3d_content_key_id": "finger-display",
|
||
"scene3d_require_signature": true
|
||
}
|
||
```
|
||
|
||
- `scene3d_verify_key` is the **public** key `--write-verify-key` produced. Without it a
|
||
signed package is refused by the container ("a verification key is required"), which is
|
||
why the application could not open a protected release at all before this existed.
|
||
- `scene3d_content_key_file` + `scene3d_content_key_id` are only needed for a **sealed**
|
||
(`aes-gcm`) package. The file may hold the key as hex, base64 or raw bytes.
|
||
- `scene3d_require_signature` is the application's own release gate: with it on, an
|
||
unsigned package is refused (`asset_auth_failed`) instead of being quietly drawn.
|
||
Leave it off for engineering builds — the shipped `finger-display-1.0.0.oryscene` is
|
||
deliberately plain.
|
||
- A missing or unreadable file is *not* a crash: the pane reports why, and the
|
||
two-dimensional application keeps running. `session.json` records which of these were
|
||
configured (booleans and paths, never key material).
|
||
|
||
## 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`.
|