Commit Graph

10 Commits

Author SHA1 Message Date
ykcheungab fa2703968e added ruff test 2026-09-27 19:20:43 +08:00
ykcheungab 12781854ca fixed commetn from songxiao 2026-09-25 17:46:37 +08:00
ykcheungab ccedb9ddd9 migrate pyqt5 to pyside6 2026-09-21 09:34:56 +08:00
ykcheungab cf45e654e1 docs(studio-3d): 阶段 7–9 交付说明 + 接口骨架同步 + 保护包的应用侧配置
- 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 板卡报告
  按用户先前的意思仍是暂缓项。
2026-09-14 22:02:43 +08:00
ykcheungab 3c0424b90c feat(studio-3d): 设备几何身份接入 —— 同尺寸不同映射/不同表面被拒绝,资产加载只采用最新请求
- What:
  1. **设备侧自己说几何**(新增 `shared/.../acquisition/geometry.py`):`describe_sensor_geometry()`
     从运行时真正加载的文件推导——`manifest.json` 的 `assets_dir`、`sensor.json` 的
     `camera.resolution_stitch`/`source_scale`、以及据此命名的 unroll map(`unroll_map_uv_s0.4.npz`)。
     产出 `DeviceGeometry(expected, verified, detail)`:`field_domain_id` + `shape_hw`(映射身份,含
     flip/scale/映射内容摘要)与 **无单位** 的 `source_geometry_id`(地图里 `world_xyz` 的内容摘要)。
     米制 `geometry_id` 需要"1 源单位 = N 米"这句只有人能做的声明,设备侧**故意留空**。两类失败区分对待:
     定位不到 bundle / 没写 `assets_dir` → `verified=False`(可显示,但不声明已校验);bundle 变了
     (map 缺失、与 `resolution_stitch` 不符)→ 直接拒绝并指名两个形状/文件。
  2. **契约**:`ExpectedGeometry += source_geometry_id`;`SceneAssets += source_geometry_id,
     compatible_sensor_model, compatible_hardware_revisions`;诊断码 `geometry_unverified`(info);
     `digest.source_geometry_digest(world_xyz)`;`legacy.convert()` 写入 `source_geometry_id`;
     `package.py` 的 manifest 增 `source_geometry` 块(数组名与单位写明),读取时把 `compatible` 与
     `source_geometry` 一并还原(旧包读到空串)。`validate_assets()` 的 expected 段重写:**只比较已声明的
     字段**,`source_geometry_id` 不符即拒绝,包内未记录该身份 → 返回 `geometry_unverified` 警告而不是
     静默通过;顺带修掉 `expected.sensor_model != assets.parts[0].role` 这行毫无意义的比较,改成真正的
     型号/硬件版本比对(`*` = 任意版本)。
  3. **接线**:`app_window._sync_3d_source()` 传 `expected_provider=`(一个闭包,指向当前传感器配置);
     `SceneAssetLoader` 在**工作线程**上解析它(`_resolve_and_load`);`view.use_scene(assets, source_id,
     expected)` 改为 `validate_assets(assets, expected)` 并把警告送到面板,同时记录 `device_verified`;
     `_make_snapshot()` 用**设备**的身份构造 adapter(设备没声明的字段才回退资产),使
     "帧与场景匹配" 第一次成为独立检查而不是自证;`describe()` 记录 `scene.device_verified` 与设备身份。
  4. **资产加载请求身份(R7-a)**:`SceneAssetLoader` 增请求号与 pending 槽——忙时不再丢弃新请求,而是
     保留最新一个并在前一个结束后启动;完成信号带请求号(`loaded(assets, 描述, id, device)` /
     `failed(diag, id)`),`Mesh3dPanel` 丢弃过期完成(否则旧文件的场景会落回新选择上)。失败后可重试。
  5. **工具与包**:`compile_scene.py`/`validate_scene.py` 报告 `geometry_id`/`field_domain_id`/
     `source_geometry_id`/兼容信息;README 写明无单位身份是什么、为什么设备侧用它;**重编发布包**
     `apps/studio/config/sensors/finger/scene/finger-display-1.0.0.oryscene`(geometry_id 仍为
     `1a2e0e92…`、1032 顶点 / 1974 三角形,新增 `source_geometry_id=46a03a5f…`,因此 asset_hash 变为 `ea4beab6…`)。
- Why: 审查报告 P1/R3:主窗口只传 sensor_type 与 package_override,`Mesh3dPanel._expected` 永远是 None,
  `validate_assets(assets, None)` 跳过全部设备比对,而 adapter 的 geometry_id/field_domain_id/shape_hw
  全部来自**已选中的资产**——于是收到的数据被贴上"资产自己期望的身份"。结果:shape 相同但 UV 映射、
  flip_v、source_scale 或相机配置不同时无法区分。P1/R7:`SceneAssetLoader.load()` 在忙时直接 return,
  既没保留最新请求也没有 generation,`_on_loaded` 又用当前 `_source` 标记已加载路径,快速切换配置时旧
  文件的任务会回到新选择上。
- Verification:
  · 证据探针 `.repro/probe_stage6_device.py` → `probe_stage6_device.json`(真实 bundle + 真实包):
    设备描述 `verified=true`、domain `feb7f71e…`、source geometry `46a03a5f…`、shape 300x280、
    米制 id 未声明、map = `unroll_map_uv_s0.4.npz`;发布包通过该描述;**同尺寸不同映射**(只改 map 一个
    像素的坐标)→ domain 变化、shape 不变、包被 `field_domain_mismatch` 拒绝;设备什么都不声明 →
    接受 + `geometry_unverified`。
  · 新增 `shared/tests/test_scene3d_device_geometry.py` 9 项:描述来源、发布包通过、同尺寸不同映射拒绝、
    同 map 不同表面拒绝、定位不到 bundle → unverified、`resolution_stitch` 与 map 不符拒绝、map 缺失
    指名文件、无 `assets_dir` → unverified、map 命名规则。
  · `test_scene3d_contracts.py` +4(未声明身份 → 警告而非匹配、source geometry 不符拒绝/缺记录 → 警告、
    型号与硬件版本、`source_geometry_digest` 的稳定性与形状校验);`test_scene3d_package.py` +2
    (`source_geometry` 往返、旧包读成"未声明");`test_scene3d_view.py` +6(adapter 身份来自设备、
    设备 domain 不符 → 拒绝且不上屏、无描述 → `geometry_unverified` 且 `device_verified=false`、
    **最新请求才运行**、面板丢弃过期完成、失败后可重试)。
  · 全量:apps/studio 0 失败(`--junitxml`:231→236 项);shared 0 失败(含 device_geometry 9 项);
    `validate_scene.py` 对重编包全项 ok。
- Not in this change: 生产侧仍不声明米制 `geometry_id`(那句单位声明只有人能写;要设备端复算它需要把
  `meters_per_source_unit` 与刚体变换记进配置,属可选加强项,已在本提交的 `DeviceGeometry.detail` 里留位);
  `ExpectedGeometry.mapping_digest` 未加——`field_domain_id` 本身就是由映射内容摘要派生的,再加一个字段
  只是同一个事实的第二个名字;`docs/architecture/studio-3d-contracts.pyi` 与本阶段新契约的同步放在阶段 6
  的文档提交里;保护包密钥注入(R4)、限速与真实统计、二维后端统一均未动。
2026-09-14 21:10:53 +08:00
ykcheungab 981c250559 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 + 已确认尺度)。
2026-09-14 17:13:37 +08:00
ykcheungab 5cd92254e1 feat(studio-3d): 阶段 5 —— 发布保护(签名/可选加密/平台锁定)与性能长时报告
- What: 新增 `scene3d/protect.py`(封套 v2:Ed25519 签名 + 可选 AES-256-GCM,nonce/salt 每包唯一、
  元数据作 AAD、明文摘要进签名、KeyProvider 只问 get_content_key)与 `record/visualization3d.py`
  (session.json 的 visualization3d 块:写入版本化、读取时缺块=不支持);`package.py` 支持 v2
  (payload=plain 仅签名 / aes-gcm 签名+加密)并继续读 v1,新增解压比上限与"写入后自读回"校验;
  `contracts.py` 新增 PlatformLock/PlatformInfo/check_platform_lock、Sealing、platform_mismatch 诊断码;
  `Scene3DView` 在装配前查 OS/架构、拿到 capabilities 后查图形半区,违反即撤下场景并在面板显示原因;
  工具 `compile_scene.py` 新增 --sign-key/--write-verify-key/--encrypt-key-id/--key-file/--lock-from-probe,
  `validate_scene.py` 新增 --key-file/--key-id/--verify-key/--require-signature(发布门禁),
  新增 `bench_scene.py`(p50/p95 分阶段 + --cpu-only + --vertices N + --package)与
  `soak_scene.py`(RSS 四分位速率 + 队列上界 + 抽稀采样);两份报告落到 docs/architecture/。
- Why: 规格 §5.2 的保护目标是"提高复制成本"而不是"防提取",所以要的是签名(来源)+ 可选认证加密(机密性)
  + 密钥隔离,而不是自制加密或把 ZIP 当加密;§12 的"包保护/性能/长时"三行需要可复现的数字与判定标准,
  而不是一次性的观感。平台锁定还必须只降级三维:锁定不通过时二维照常。
- Verification: shared 全量通过(新增 test_scene3d_protect.py 35 项:封套往返/篡改/密钥/锁定/记录块/
  compile→validate 端到端发布流程;test_scene3d_package.py 20 项含 v1↔v2 兼容);apps/studio 全量通过
  (test_scene3d_view.py 30 项含锁定拒绝与 session.json 块);ui_check 566 项 0 失败;locale_check 0 缺失;
  真实资产端到端:1032 顶点编译出 signed+sealed+locked 包(56 927 B 密文),validate --require-signature 退出 0,
  改一个字节退出 2;性能报告(perf-report-windows-dev.json):1032 顶点 compose p50 0.50 ms(30 fps 上限内),
  20022 顶点/39480 三角形 compose p50 6.15 ms(约 33 ms 帧的 18%),采集线程 submit p95 < 0.4 ms(合成器被人为放慢到 50 ms);
  长时报告(soak-report-windows-dev.json):无窗口 10 min/60 fps 尾段 0.354 MB/min,GUI 4 min/30 fps 尾段 0.809 MB/min,
  队列上界(mailbox=1、单飞行合成)两者均成立。过程中修掉:GUI 进程里的合成器计时被 GIL/vsync 污染(98.8 ms vs 真值 6.1 ms)、
  跨线程读 last_stats 撕裂、key id 无 provider 时会静默写明文包、soak 工具自身记时间列表会无界增长、RSS 读取失败伪装成零增长、
  平台锁定不是终态诊断。
- Not in this change: ARM 板卡的能力报告与 2 小时门禁运行(按用户要求暂缓;命令与判定标准已写入阶段 5 文档);
  GPU 显存随时间的测量(无可移植 API,报告说明渲染器缓冲估计恒定且更新路径不新建 GPU 对象);
  `cryptography` 在此环境无法联网安装,本机验收用的是本机已有的 cp310 构建(pyproject 里已声明为可选 extra
  `protected-assets`)。
2026-09-14 16:44:33 +08:00
ykcheungab d0dcac743f 阶段 2:资产合同 —— 旧 NPZ 导入、坐标/单位、绑定、包容器与工具 - What: shared/scene3d 新增四个模块与两个工具的真实现。digest.py:规范化内容摘要(C 连续小端字节 + 排序 JSON;DIGEST_SCHEMA 版本化),geometry_id(只看 part 角色/顶点/三角形,故意不含法线材质与 LOD 名——改颜色不得让标定失效)、binding_id、field_domain_id(形状、flip_v、source_scale、原点/索引约定、映射内容)、asset_hash(条目摘要 + 去自引用字段的 manifest)。legacy.py:LegacyFingerImporter 按"先单位、后刚体"两步转换(meters_per_source_unit 必填且不得猜、validate_rigid_transform 拒绝含缩放/镜像/非仿射的 4x4);split_at_uv_seams 按 (源顶点, 量化 UV) 拆点,接缝处不平均 UV;vertex_normals 面积加权并给无面顶点有限回退;uv_to_pixel 采用实测约定 x=u*(W-1)、y=(1-v)*(H-1)(flip_v 时);build_binding 生成四点双线性采样表(域内裁剪、零权 tap 丢弃并重分配、未用项 -1/0);uv_correspondence_residual 用 Procrustes 残差把"UV 是否对得上 world_xyz"变成可断言的事实;estimate_meters_per_source_unit 只报告不应用。glb.py:glTF 2.0 子集读取(GLB 头/chunk 校验、accessor/bufferView/stride、节点 TRS 与矩阵烘焙进顶点、法线用逆转置再归一化、缺法线则计算),明确拒绝 Draco/meshopt/量化扩展、稀疏 accessor、外部 buffer、骨骼、形态目标、非 TRIANGLES、越界索引、非有限坐标与超预算三角形;with_housing 把外壳作为独立 part 加入且不动感知几何 id。package.py:.oryscene(zip:envelope.json + manifest.json + sensing/housing *.npy,无 pickle 无代码执行),写入原子(先写 .partial 再替换)、读取逐一校验 magic/envelope 版本/payload 状态/条目路径/manifest 自哈希/逐条目 sha256/dtype/shape/**从包内数组重算 geometry_id 与 binding_id**,加密封套明确报"本版本只开明文包"。tools/scene_assets/validate_scene.py 与 compile_scene.py 真实现(含 --json 报告与退出码:0 通过、2 检查失败、1 参数/文件问题;未给单位时停在 needs_units 并打印实测候选尺度)。测试:shared/tests/test_scene3d_assets.py 33 项(真实 1032 导出与 1005 对比资产的输入样例、接缝/法线/绑定/单位/变换/摘要的失败样例、GLB 往返与拒绝样例)、test_scene3d_package.py 24 项(往返、外壳条目、以及篡改条目/篡改 manifest 哈希/自洽但错误的几何/非有限坐标/多余条目/缺条目/路径穿越/dtype 与 shape 不符/未来版本/加密封套/key provider 的拒绝)。docs/architecture/studio-3d-stages.md 阶段 2 记为完成并附验收结果。 - Why: 阶段 2 要回答的是"这份几何到底是什么、单位是什么、它和哪套 UV 域配套、以及这份包有没有被动过",这些一旦错了会以"三维看起来有点怪"的形式出现且事后无法追溯。因此把 UV 约定做成可测量的事实(真实资产残差 0.0064 vs 错误翻转 0.59)、把单位做成必填参数而不是默认值、把 identity 做成内容摘要并**在读取时重算**,同时用真实资产当输入样例、用篡改后的包当失败样例。 - Verification: 真实资产导入:1032 顶点/1974 三角形/域 300×280/尺寸 15.81×15.0×7.5 mm,validate_assets 通过;UV 残差 0.0064(错翻转 0.59);两套资产混用被拒(geometry_mismatch);缩放/镜像/非仿射变换被拒;compile→validate 端到端 ok(包内条目哈希与 manifest 自哈希全部通过);shared 全量 244 passed;studio 118 passed;ui_check 554 项 0 失败;.repro/verify_assets.py 22 项 0 失败。过程中修掉 5 个真实缺陷:np.load 需要真实文件对象(手写内存 stub 抛空消息异常)、包可以"自洽却错误"(改数组+改哈希后无人重算几何 id,现已重算并比对)、binding 行数不匹配被误报为域错误、np.ptp 平铺比较无意义(换算/不变性断言改为按轴)、以及合成 PoC 场景自己用字符串当 geometry_id(所有人都会抄它,等于绕过检查)。 - Not in this change: 签名与 AES-GCM(阶段 5);LOD 简化;calibration.py 侧的资产哈希登记;三维视图仍未被应用导入(阶段 4 接线)。 2026-09-14 15:08:29 +08:00
ykcheungab 5767b4bf11 阶段 1:平台 PoC —— 真实 GL 上跑通静态场景,并修掉它抓出的相机/上下文错误 - What: 按 docs/architecture/studio-3d-stages.md 阶段 1 落地图形栈。OrbitCamera 全部实现(target/distance/yaw/pitch 表示;fit 按包围球与较窄的那个视场轴定距离;orbit/pan/zoom 有界;set_preset 从 eye/target 反解角度使拖动可延续;matrices 输出只读 float32 view/projection;另加 depth_range_m() 便于断言)。gl_backend:GLDispatch.bind_current_context 真正绑定 PyOpenGL 并读 vendor/renderer/GL/GLSL/max texture、判软件渲染、profile 不达标时抛 ThreeDUnsupported 并带回能力;GLRenderer 实现 initialize(两个 program,desktop33/gles30 共用同一份着色器主体、只换头部)、upload_scene(每 part 一套 VAO/VBO/EBO,交错 layout position/normal/scalar/valid = 8 float)、update_surface(只对感知面 glBufferSubData,绝不重建索引)、draw(绑 Qt 的 defaultFramebufferObject、深度测试、按 role 选 program、housing 隐藏即跳过、scalar 经 LUT 纹理与 range uniform 上色,Python 不生成 RGB 数组)、release(幂等);纯函数 pack_part/pack_scene_part/estimate_gpu_bytes/rest_surface/shader_source/shader_header/lut_rgba/is_software_renderer/profile_from_renderable_type 都可在无 GPU 环境测试。Sensor3DWidget 实现 initializeGL/paintGL/resizeGL/release_resources/aboutToBeDestroyed/showEvent/hideEvent 与鼠标事件;加载场景时先采用资产自带的默认视角再 fit;一帧几何不匹配只"拒绝该帧"(计数 + 诊断 + 用 rest_surface 清掉上次测量)而不是把整个视图打成不可用。shared/scene3d/synthetic.py 提供 PoC 与测试共用的已知场景(20mm 感知板 + 外壳盒,米、sensor 坐标系、带默认三视角预设)。tools/scene_assets/probe_gl.py 真实现:真实窗口取 context 并输出 JSON 报告(平台/Qt/PyOpenGL/vendor/renderer/GL/GLSL/DPR/framebuffer/是否软件渲染),退出码即门禁(0 通过、3 软件渲染、4 无上下文);apps/studio/pyproject.toml 增加 [three-d] 可选依赖(仅 PyOpenGL;Open3D 不进运行时)。测试:apps/studio/tests/test_scene3d_stage1.py 24 项(相机数学、PoC 场景合规、打包布局、着色器两方言共用主体、LUT、软件渲染识别、renderableType 映射、GPU 预算、参考帧语义,全部无需 GPU)+ test_scene3d_gl_smoke.py 8 项(真实 context:能力、两个 part 都画出来、外壳可隐藏、参考帧中性 vs 色图有差异、动态更新不动 housing 缓冲、坏几何帧被拒且视图存活、resize 跟随、release 幂等且新 context 能重传场景、探针如实报告);offscreen 下后者自动 skip。docs/architecture/gl-report-windows-dev.json 为本机平台报告。 - Why: 用户要求"从骨架搭起→计划阶段→逐阶段实现与验收",并明确 ARM 暂缓。阶段 1 的产出本身就是一份能回答"这块板的图形栈能不能用"的报告,所以先把它做实、把 profile 冻结点定在真实 context 上,而不是先堆屏幕上的东西。 - Verification: 真实 context(QT_QPA_PLATFORM=windows)下 8 项 GL 验收全过;offscreen 下 24 项 CPU 项全过、GL 项按设计 skip;shared 全量 187 passed;studio 全量 116 passed(含新 32 项);ui_check 554 项 0 失败;probe_gl 输出 verdict=ok(NVIDIA RTX 4080 SUPER、GL 3.3 Core、GLSL 3.30、非软件渲染)。过程中这套验收抓出并修掉了 7 个真实缺陷:Qt renderableType 映射反了(NVIDIA 兼容 ES 着色器所以本机看不出,Intel/ARM 会直接失败)、观察相机是左手基导致整模型镜像(det=-1)、近平面过近使 NDC z 全挤在 0.99(深度精度归零、外壳与感知面会 z-fighting)、手动 makeCurrent 早于 Qt 首次绘制会把 GPU 对象建在被丢弃的 context 里(glBindVertexArray 报 INVALID_OPERATION)、context 重建后不重传场景、一帧几何不匹配把整个视图打死、以及 grabFramebuffer 是 BGRA+stride 导致颜色断言全部读错通道。 - Not in this change: 未接入 app_window/main.py(三维对现有应用仍完全惰性,故"开/关三维 CSV 行序一致"这条验收随阶段 4 做);阶段 2 的资产导入/坐标/包格式;ARM 平台实测(probe 已可在板上直接运行)。 2026-09-14 14:55:45 +08:00
ykcheungab 0b309e7edf 三维显示骨架:可执行合同 + 模块签名 + 分阶段计划 - What: 按 docs/architecture/studio-3d-design.md 与 studio-3d-contracts.pyi 搭建骨架(阶段 0)。shared/src/orisys_app_shared/scene3d/:contracts.py 把 .pyi 的规格变成可执行代码——全部 DTO/枚举(FrameIdentity/FieldDescriptor/SurfaceFieldFrame/MeshPart/SurfaceBinding/CameraPreset/ExpectedGeometry/SceneAssets/ColorScale/DisplaySettings/VertexFields/RenderFrame/CameraMatrices/GLCapabilities/RenderStats/CompileOptions/CompileReport)、标准诊断码表(DIAGNOSTIC_SEVERITY,含 stale_frame=info、field_unavailable=warning 等)、ThreeDError、freeze()(拥有数据 + C 连续 + 只读)、五个校验器(validate_surface_frame/validate_binding/validate_mesh_part/validate_assets/validate_display_settings/validate_render_frame)、LatestFrameMailbox(容量 1、替换计数、锁内只换引用)、UNAVAILABLE_DESCRIPTOR/rest_descriptor() 与 summarize_parts(),以及全部 Protocol;该模块不 import Qt/OpenGL/SDK。assets.py/sampling.py/composer.py 为签名骨架(未实现处抛 NotImplementedError 并写明归属阶段)。apps/studio/src/orisys_studio/scene3d/:camera/adapters/coordinator/gl_backend/widget/panel 签名骨架,其中 configure_surface_format() 已实现(QApplication 之前调用,desktop33=GL3.3 Core / gles30=GLES3.0,深度 24、无 MSAA);模块级一律不 import GL 绑定,PyOpenGL 只在 GLDispatch.bind_current_context 里加载。tools/scene_assets/{probe_gl,validate_scene,compile_scene}.py 提供入口与参数,未实现时输出 not_implemented 并返回 2(避免被 CI 当成通过)。docs/architecture/studio-3d-stages.md:阶段 0-5 的产出、每阶段的验收矩阵、并行度与回归门。测试:shared/tests/test_scene3d_contracts.py 26 项(freeze 拥有数据、可写/非连续数组被拒、scalar 与 valid 形状一致、位移缺 quality 被拒、非有限 scalar 标为 valid 只报警不静默、绑定越界/-1 约定/权重和为 1、三角形索引越界、感知法线单位长度、恰好一个 sensing part、binding 必须属于 sensing part、设备几何/域/尺寸不符被拒、色标 lower<upper、邮箱替换计数与多线程不阻塞生产者、身份区分 source/generation、骨架可导入且不引入 PyOpenGL);apps/studio/tests/test_scene3d_skeleton.py 4 项(骨架不 import OpenGL、surface format 两个 profile、widget 构造不需要 context、各 stub 报出归属阶段)。 - Why: 用户要求"先从骨架搭起,然后计划功能实现顺序,最后逐阶段实现与验收"。骨架阶段的价值在于把接口变成可执行的(.pyi 本身无法被测试),并把"数组只读、单位与质量必须声明、近似位移不得当毫米、参考帧不得冒充测量、显示丢帧不得计入记录丢帧"这些容易在实现时被绕过的规则先固定下来;同时明确不 import Qt/OpenGL 的边界,使采集线程与 CI 能在无 GPU 环境导入它。 - Verification: shared 全量 187 passed(原 161 + 26);studio 95 passed(原 91 + 4);ui_check 554 项检查 0 失败;compileall 通过;probe_gl --profile desktop33 在未实现时返回 2 并说明将产出什么报告。 - Not in this change: 三维功能本身(阶段 1 起);app_window/main.py 未接线,骨架对现有应用完全惰性(无任何模块被应用导入)。 2026-09-14 14:32:38 +08:00