重构 Autocal 采集页:单一队列与控制器驱动的主按钮

选点、对准、加载与录像分散在两个面板,状态靠隐藏相位判断。现改由不依赖 Qt 的采集控制器驱动,并加入录像选项、汇总平铺副本、保持到时自动结束、会话结束回到空闲,以及无设备时的模拟采集。已运行 Autocal 非 Qt 测试并通过范围内门禁;界面测试待 Windows 复核,未做实机验收。
This commit is contained in:
ykcheungab
2026-10-18 16:04:32 +08:00
parent 85d0b396e3
commit 06e06126ca
44 changed files with 7769 additions and 2218 deletions
+64 -136
View File
@@ -1,7 +1,9 @@
"""The finger debugger's panel: picking, refusing, and what a row contains.
"""The geometry-and-mounting screen: the single-point trial.
Interaction tests use a small dome. Geometry and native-resolution checks keep
the real export and skip individually when that optional export is unavailable.
The point queue and its operation-sheet export live on the capture page and are
covered by ``test_capture_page.py``.
"""
from __future__ import annotations
@@ -398,9 +400,9 @@ def test_the_play_button_needs_a_picked_point(panel):
assert panel.sim_button.isEnabled()
def test_clearing_the_points_disables_the_simulation_again(panel):
def test_clearing_the_point_disables_the_simulation_again(panel):
panel.handle_pick(*_first_surface_pixel(panel.view.flat))
panel.clear_points()
panel.clear_point()
assert not panel.preview.can_simulate
assert not panel.sim_button.isEnabled()
@@ -539,155 +541,81 @@ def test_the_simulation_cannot_start_without_a_pose(bundle, preview):
assert preview._sim_transform() is None # noqa: SLF001
# ------------------------------------------------------------- the point table
# ------------------------------------------------------------- the single pick
def _pick_three(panel) -> list[dict]:
"""Fill the table with three real picks, spread over the surface."""
flat = panel.view.flat
picked: list[dict] = []
step = max(1, flat.width // 7)
for y in range(step, flat.height, step):
for x in range(step, flat.width, step):
if flat.sample(x, y) is None:
def _another_surface_pixel(flat, first) -> tuple[int, int]:
"""A second surface pixel, so a replacement pick is visibly different."""
for y in range(0, flat.normals.shape[0], 5):
for x in range(0, flat.normals.shape[1], 5):
if (x, y) == first or flat.sample(x, y) is None:
continue
row = panel.handle_pick(x, y)
if row is not None:
picked.append(row)
if len(picked) == 3:
break
if len(picked) == 3:
break
assert len(picked) == 3, f"only picked {len(picked)} points"
return picked
return x, y
raise AssertionError("the flat raster has no second surface pixel")
def test_the_table_has_a_select_column_and_an_editable_body(panel):
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QCheckBox
def test_the_window_names_the_screen_geometry_and_mounting(make_window):
"""P5: the debug screen is now the geometry and mounting page, in both places."""
from orisys_app_shared.i18n import t
_pick_three(panel)
assert panel.table.columnCount() == len(panel.COLUMNS) + 1
for row in range(panel.table.rowCount()):
holder = panel.table.cellWidget(row, panel.SELECT_COLUMN)
assert holder is not None and holder.findChild(QCheckBox) is not None
# The six input columns are editable; the solved ones are not.
for offset in range(len(panel.COLUMNS)):
item = panel.table.item(0, offset + 1)
editable = bool(item.flags() & Qt.ItemFlag.ItemIsEditable)
assert editable == (offset < panel.INPUT_COLUMNS), panel.COLUMNS[offset]
window = make_window()
try:
title = t("finger.title")
assert title == t("workspace.tab_finger")
assert title == "几何与安装", title # the default locale is zh_CN
assert window.finger_panel.title() == title
index = window.workspace.page_keys().index("finger")
assert window.workspace.tabText(index) == title
finally:
window.close()
def test_editing_a_coordinate_resolves_the_row_again(panel):
"""Typing a new sensor coordinate must re-solve, not leave a stale pose."""
_pick_three(panel)
before = panel.points()[0]
def test_the_screen_no_longer_keeps_a_point_table(panel):
"""P5: the queue owns points; this screen is the single-point trial.
item = panel.table.item(0, 1) # the x column
item.setText(f"{before['x'] + 1.0:.3f}")
after = panel.points()[0]
assert after["x"] == pytest.approx(before["x"] + 1.0, abs=1e-3)
# The solved columns moved with it, which is the point of re-resolving.
assert after["X"] != pytest.approx(before["X"], abs=1e-9)
assert after["residual"] < 1.0
The list, its editor and its export moved to the capture page's queue, so the
old widgets must not exist here any more.
"""
assert not hasattr(panel, "table")
assert not hasattr(panel, "export_sites")
assert not hasattr(panel, "clear_points")
assert not hasattr(panel, "acquisition_points")
assert not hasattr(panel, "export_sites_button")
def test_a_non_numeric_edit_is_refused_and_the_old_value_kept(panel):
_pick_three(panel)
before = panel.points()[0]["x"]
panel.table.item(0, 1).setText("not a number")
assert panel.points()[0]["x"] == pytest.approx(before)
assert panel.status_label.text()
def test_ticking_a_row_selects_it_and_clears_again(panel):
from PySide6.QtWidgets import QCheckBox
_pick_three(panel)
first = panel.table.cellWidget(0, panel.SELECT_COLUMN).findChild(QCheckBox)
third = panel.table.cellWidget(2, panel.SELECT_COLUMN).findChild(QCheckBox)
first.setChecked(True)
third.setChecked(True)
assert panel.selected_rows() == [0, 2]
first.setChecked(False)
assert panel.selected_rows() == [2]
def test_removing_selected_drops_exactly_those_rows(panel):
from PySide6.QtWidgets import QCheckBox
_pick_three(panel)
panel.table.cellWidget(1, panel.SELECT_COLUMN).findChild(QCheckBox).setChecked(True)
removed = panel.remove_selected()
assert removed == 1
assert panel.point_count() == 2
assert not panel.selected_rows()
def test_removing_with_nothing_ticked_drops_the_current_row(panel):
"""The obvious fallback: no tick means 'the row I am looking at'."""
_pick_three(panel)
panel.table.setCurrentCell(1, 1)
assert panel.remove_selected() == 1
assert panel.point_count() == 2
def test_a_row_can_be_moved_up_and_down(panel):
_pick_three(panel)
before = [row["x"] for row in panel.points()]
panel.table.setCurrentCell(0, 1)
assert panel.move_selected(1)
assert [row["x"] for row in panel.points()] == [before[1], before[0], before[2]]
assert panel.move_selected(-1)
assert [row["x"] for row in panel.points()] == before
def test_a_row_cannot_be_moved_off_the_end(panel):
_pick_three(panel)
before = [row["x"] for row in panel.points()]
panel.table.setCurrentCell(0, 1)
assert not panel.move_selected(-1)
assert [row["x"] for row in panel.points()] == before
def test_selection_marks_the_point_in_both_views(panel):
"""The red dot: the whole reason for selecting a row."""
_pick_three(panel)
panel.table.setCurrentCell(2, 1)
record = panel.points()[2]
assert panel.view.selected_pixel is not None
# And it is the right pixel: the one showing this row's 3D point.
pixel = panel.view.selected_pixel
point, _normal, _face = panel.view.flat.sample(*pixel)
assert point == pytest.approx([record["x"], record["y"], record["z"]], abs=0.05)
assert panel.preview._highlight_mm is not None # noqa: SLF001
def test_a_pick_marks_its_own_row_as_selected(panel):
def test_a_pick_keeps_exactly_one_point(panel):
flat = panel.view.flat
x, y = _first_surface_pixel(flat)
first = _first_surface_pixel(flat)
second = _another_surface_pixel(flat, first)
first_row = panel.handle_pick(*first)
assert first_row is not None
assert panel.point_count() == 1
assert panel.points()[0]["u_px"] == first_row["u_px"]
second_row = panel.handle_pick(*second)
assert second_row is not None
# A second click replaces the first point rather than adding a row.
assert panel.point_count() == 1
stored = panel.points()[0]
assert (stored["u_px"], stored["v_px"]) == (second_row["u_px"], second_row["v_px"])
assert stored["u_px"] != first_row["u_px"]
def test_a_pick_marks_the_point_in_both_views(panel):
x, y = _first_surface_pixel(panel.view.flat)
panel.handle_pick(x, y)
assert panel.view.selected_pixel is not None
assert panel.preview._highlight_mm is not None # noqa: SLF001
def test_clearing_the_points_removes_the_markers(panel):
_pick_three(panel)
panel.clear_points()
def test_clearing_the_point_removes_the_markers(panel):
panel.handle_pick(*_first_surface_pixel(panel.view.flat))
panel.clear_point()
assert panel.point_count() == 0
assert panel.points() == []
assert panel.view.selected_pixel is None
assert panel.preview._highlight_mm is None # noqa: SLF001
@@ -1042,13 +970,13 @@ def test_picking_with_no_bundle_says_so(qt_app):
widget.deleteLater()
def test_clearing_points_empties_the_table(panel):
def test_clearing_the_point_empties_the_screen(panel):
flat = panel.view.flat
x, y = _first_surface_pixel(flat)
panel.handle_pick(x, y)
assert panel.point_count() == 1
panel.clear_points()
panel.clear_point()
assert panel.point_count() == 0
assert panel.points() == []
@@ -1078,10 +1006,10 @@ def test_the_sensor_z_offset_reaches_the_solve(panel):
assert 0.0 < thick.contact_z_mm - thin.contact_z_mm < 4.0
assert thick.target_xy_mm == pytest.approx(thin.target_xy_mm)
# Picking through the panel uses the same fields, so the two must agree.
panel.clear_points()
panel.clear_point()
panel.set_sensor_setup((0.0, 0.0, 0.0), 0.0)
thin_row = panel.handle_pick(x, y)
panel.clear_points()
panel.clear_point()
panel.set_sensor_setup((0.0, 0.0, 4.0), 0.0)
thick_row = panel.handle_pick(x, y)
assert thin_row is not None and thick_row is not None