feat(studio-2d): 回放可一键重算;实时界面不再有任何回放入口
- What:
1. **回放里一键重算**:`ui/toolbar.py` 的 replay 组新增 `btn_recompute`(可选中的"由视频重算…",
信号 `recompute_clicked`、`set_recompute_enabled/checked`、tooltip 说明"2D 面板会显示处理后的图像,
录像本身只存了原始相机图像");`app_window` 把它接到 `act_recompute`(同一个开关,所以菜单、rail 与
`_on_recompute_toggled` 不会各走一套),并在 `_refresh_replay_state`/`_sync_replay_actions` 里同步
可用与选中状态。
2. **实时界面禁止回放入口**:新增 `_sync_replay_actions()`——`act_open_video`/`act_playback`/
`act_playback_latest` 只在 `_view_preset == "replay"` 时可用,否则禁用并把原因写进 tooltip
(`toolbar.replay_view_only` = "请在「历史回放」视图里打开录像");`_load_replay_video()` 与
`_open_latest_recording()` 在非 replay 预设下**直接拒绝并提示**,不再改动 `source_mode`/`video_path`
(原来打开一段没有 forces.csv 的视频会走到 `_load_replay_video` 的裸视频分支,把采集源切成文件而 2D
面板什么都没变)。rail 的回放按钮本来就在 replay 分组(`toolbar.py:476/598`),不改动;空格键原本就按
`run_role == "playback"` 收口,加了断言。
3. locale:新增 `toolbar.recompute`/`toolbar.recompute_tip`/`toolbar.replay_view_only`(中英各一套)。
- Why: 用户报的第二个 bug——「实时检测界面还是可以回放视频,建议回放会自动切到历史回放上面」,并在两个
选项里选了"实时界面禁止回放入口(反向做法)"。核对确认:`_on_browse_video`/`_load_replay_video` 在实时
预设下可用且只设 `source_mode="video"` + `video_path`,不切视图预设;随后 `_render_replay_frame()` 因
`_view_preset != "replay"` 直接返回、`_can_recompute()` 同因返回 False,于是界面停在"实时"却已经把采集源
换成了视频——按运行键就"在实时界面跑视频"。配套地,回放里"看不到处理后的图像"这一个用户期待,由
「由视频重算」给出:它现在就在回放视图的 rail 上,点一下即可,2D 面板与实时共用同一个后端(见上一提交)。
注意区分:实时视图用视频**当采集源**(`source_mode == "video"` 显示"回到摄像机")是另一项既有能力,保留
不变,只是不再由回放路径隐式设置。
- Verification:
· `test_ui_smoke.py` +1(`test_the_replay_entries_exist_only_in_the_replay_view`):实时预设下三个文件
菜单动作禁用且 tooltip 带原因、rail 无回放/重算按钮、`_load_replay_video()` 返回 False 且
`replay.has_video()`/`source_mode()`/播放定时器都不变、空格不启动回放;切到历史回放后三者可用、裸视频
仍能打开(`plan_modes() == ("recorded",)`)、rail 的重算按钮可点并同步 `act_recompute`。
· 既有用例继续通过:`test_a_bare_video_plays_even_though_it_is_not_a_recording`(先切到历史回放再打开,
播放与逐帧解码不受影响)、`test_depth_view_...`、三维与记录相关用例。
· 全量:apps/studio 237 项 0 失败 14 跳过;ui_check 587 项 0 失败;locale_check 0 缺失(zh/en 键集合
仍一致)。
- Not in this change: 没有改成"打开视频时自动切视图"(按用户选择,改为禁止 + 说明);`source_mode == "video"`
在实时视图的既有行为未动("由视频当相机"仍可用,只是入口不再是回放路径);阶段 8(界面与有效控制)与
阶段 9(限速/统计/保护包)未开始。
This commit is contained in:
@@ -2247,6 +2247,64 @@ def test_the_application_converts_bgr_to_qimage_in_one_place():
|
||||
assert others == [], f"another BGR→QImage conversion exists: {others}"
|
||||
|
||||
|
||||
def test_the_replay_entries_exist_only_in_the_replay_view(make_window, record_root):
|
||||
"""Stage 7: the live view has no replay entry, and says where they live.
|
||||
|
||||
Opening a video from the live view used to set the acquisition source to a file
|
||||
while the 2D panel kept showing the camera and Run acquired from the video — a state
|
||||
nothing on screen described.
|
||||
"""
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from orisys_app_shared.i18n import t
|
||||
|
||||
bare = Path(record_root) / "gated_video.mp4"
|
||||
writer = cv2.VideoWriter(str(bare), cv2.VideoWriter_fourcc(*"mp4v"), 30.0, (64, 48))
|
||||
for index in range(10):
|
||||
writer.write(np.full((48, 64, 3), index * 8, dtype=np.uint8))
|
||||
writer.release()
|
||||
|
||||
window = _show(make_window(), (1440, 880))
|
||||
try:
|
||||
window._apply_view_preset("live")
|
||||
QApplication.processEvents()
|
||||
for action in (window.act_open_video, window.act_playback, window.act_playback_latest):
|
||||
assert not action.isEnabled(), "a replay entry is offered in the live view"
|
||||
assert t("toolbar.replay_view_only") in action.toolTip()
|
||||
assert not window.toolbar.btn_replay_video.isVisible()
|
||||
assert not window.toolbar.btn_recompute.isEnabled()
|
||||
|
||||
# Programmatic use is refused too, and leaves nothing half-changed.
|
||||
assert window._load_replay_video(bare) is False
|
||||
QApplication.processEvents()
|
||||
assert not window.replay.has_video()
|
||||
assert window.toolbar.source_mode() == "camera"
|
||||
assert not window._play_timer.isActive()
|
||||
window._on_play_shortcut()
|
||||
assert not window._play_timer.isActive(), "Space started a replay in the live view"
|
||||
|
||||
# In the replay view they are offered again, and the recompute entry follows
|
||||
# whether there is anything to recompute from.
|
||||
window._apply_view_preset("replay")
|
||||
QApplication.processEvents()
|
||||
for action in (window.act_open_video, window.act_playback, window.act_playback_latest):
|
||||
assert action.isEnabled()
|
||||
assert window._load_replay_video(bare) is True
|
||||
QApplication.processEvents()
|
||||
assert window.arrow_panel.plan_modes() == ("recorded",)
|
||||
assert window.toolbar.btn_recompute.isEnabled(), (
|
||||
"a video with no recording beside it can still be recomputed"
|
||||
)
|
||||
window.toolbar.btn_recompute.click()
|
||||
QApplication.processEvents()
|
||||
assert window.act_recompute.isChecked(), "the rail's recompute entry did not toggle"
|
||||
window.act_recompute.setChecked(False)
|
||||
finally:
|
||||
window.close()
|
||||
|
||||
|
||||
def test_a_file_that_is_not_a_video_at_all_is_refused_with_a_reason(
|
||||
make_window, record_root
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user