feat(studio): 三维视图接入产品(阶段 4:界面、生命周期与记录隔离)
- What: `Scene3DView` 装配(widget/相机/渲染器/合成器/协调器/适配器),`asset_source` 只查找并后台加载已编译的 `.oryscene` 包,`scene3d/prefs` 持久化并**修复**显示设置, `ui/mesh3d_panel` 用真实容器替换占位标签(无 GL/无包/包被拒/传感器不匹配各显示一行原因), `main.py` 在 `QApplication` 之前选定 GL profile,`app_window` 接线:3D 作为采集线程上的 第二个 sink、`_on_run_tick` 驱动 `tick()`、视图切换与 `closeEvent` 释放、preferences 落盘。 - Why: 阶段 4 的验收要求"三维开关不影响采集/记录/力曲线",并把阶段 1 留下的 "开/关三维 CSV 一致"这条补上;同时把"没有 context"从"看起来像在工作"变成可显示的终态。 - Verification: app 侧 test_scene3d_view 28 项、test_scene3d_gl_smoke 14 项(真实 GPU)、 adapters 17 项、skeleton 4 项、stage1 24 项、coordinator 13 项、ui_smoke 86 项; shared 侧 contract 26 / assets 33 / package 19 / live 25;`ui_check.py` 566 项检查 0 失败 (新增 "3D pane" 段 12 项);`locale_check.py` 302 个字面 key 0 缺失、中英各 377 key 对齐; 记录隔离:同一 96 帧会话挂/不挂 3D 消费者,`forces.csv` 逐字节相同、有效样本数与记录问题数相同; 生产者预算:合成器放慢到 50 ms 时 `submit_packet` p99 < 1 ms。 - Not in this change: 示意位移(`displacement_sensor_m`)仍未接入(需 SDK lift 与已确认尺度); ARM 板卡适配(按用户要求暂缓);包签名/AES-GCM 与长时性能报告(阶段 5); 仓库中仍未提供 `.oryscene` 包(发布产物,且其单位与变换必须由人声明)。
This commit is contained in:
@@ -2792,6 +2792,97 @@ def main() -> int:
|
||||
app.processEvents()
|
||||
stuck.release()
|
||||
|
||||
section("3D pane")
|
||||
from orisys_studio.app_window import MIN_PANEL_WIDTHS as _MIN_PANEL_WIDTHS
|
||||
|
||||
pane_window = StudioMainWindow(
|
||||
prefs={
|
||||
"language": "zh_CN",
|
||||
"theme": "dark",
|
||||
"sensor_type": "finger",
|
||||
"source_mode": "camera",
|
||||
"show_finger_3d": True,
|
||||
}
|
||||
)
|
||||
pane_window.resize(1600, 900)
|
||||
pane_window.show()
|
||||
app.processEvents()
|
||||
pane = pane_window.mesh3d_panel
|
||||
try:
|
||||
# A box sensor has no 3D scene, and the pane says so rather than showing an
|
||||
# empty picture. The view already built for the finger sensor is kept (it is
|
||||
# reused when the sensor goes back), so "no view" is checked on a fresh pane
|
||||
# and "the message is what is on screen" on this one.
|
||||
from orisys_studio.ui.mesh3d_panel import Mesh3dPanel as _Mesh3dPanel
|
||||
|
||||
fresh = _Mesh3dPanel()
|
||||
try:
|
||||
fresh.set_sensor("box")
|
||||
check(
|
||||
fresh.view is None,
|
||||
"a 3D view was built for a sensor type that has no scene",
|
||||
)
|
||||
check(fresh.showing_message, "a box sensor's 3D pane is not showing a message")
|
||||
check(fresh.message.text().strip() != "", "the 3D pane showed an empty line")
|
||||
finally:
|
||||
fresh.deleteLater()
|
||||
|
||||
pane.set_sensor("box")
|
||||
check(pane.showing_message, "the 3D pane kept showing a picture for a box sensor")
|
||||
|
||||
# A finger with no package names where the package belongs and how to build one.
|
||||
pane.set_sensor("finger")
|
||||
diagnostic = pane.last_diagnostic
|
||||
check(
|
||||
diagnostic is not None and diagnostic.code == "asset_rejected",
|
||||
f"a missing 3D scene was not reported as an asset problem: {diagnostic}",
|
||||
)
|
||||
if diagnostic is not None:
|
||||
check(
|
||||
"compile_scene.py" in diagnostic.details.get("compile", ""),
|
||||
"the missing-scene report does not say how to build one",
|
||||
)
|
||||
check(
|
||||
str(diagnostic.details.get("directory", "")) in pane.message.text()
|
||||
or pane.message.text().strip(),
|
||||
"the pane does not name the directory it looked in",
|
||||
)
|
||||
|
||||
# The pane's controls must fit the width the window reserves for it.
|
||||
view = pane.ensure_view()
|
||||
if view is not None:
|
||||
wanted = view.minimumSizeHint().width()
|
||||
check(
|
||||
wanted <= _MIN_PANEL_WIDTHS["mesh3d"],
|
||||
f"the 3D pane needs {wanted} px, the window reserves "
|
||||
f"{_MIN_PANEL_WIDTHS['mesh3d']}",
|
||||
)
|
||||
|
||||
# One state, two controls: toggling either one twenty times must not drift,
|
||||
# rebuild the view, or leave the menu checkbox disagreeing with the pane.
|
||||
first_view = pane.view
|
||||
for _ in range(20):
|
||||
pane_window.act_finger_3d.setChecked(not pane_window.act_finger_3d.isChecked())
|
||||
check(
|
||||
pane_window.act_finger_3d.isChecked() == bool(pane_window._show_finger_3d),
|
||||
"the View menu entry and the window's 3D flag disagree after 20 toggles",
|
||||
)
|
||||
check(
|
||||
pane.enabled() == bool(pane_window._show_finger_3d),
|
||||
"the 3D pane's own state drifted from the window's after 20 toggles",
|
||||
)
|
||||
check(
|
||||
pane.view is first_view,
|
||||
"toggling the 3D pane rebuilt the view (a per-toggle allocation)",
|
||||
)
|
||||
|
||||
pane.close_view()
|
||||
pane.close_view()
|
||||
check(True, "closing the 3D pane twice is safe")
|
||||
finally:
|
||||
pane_window.close()
|
||||
app.processEvents()
|
||||
|
||||
section("replay")
|
||||
replay_root = APP_ROOT / "outputs" / "_replay_check"
|
||||
_shutil.rmtree(replay_root, ignore_errors=True)
|
||||
|
||||
Reference in New Issue
Block a user