重构 Autocal 采集页:单一队列与控制器驱动的主按钮
选点、对准、加载与录像分散在两个面板,状态靠隐藏相位判断。现改由不依赖 Qt 的采集控制器驱动,并加入录像选项、汇总平铺副本、保持到时自动结束、会话结束回到空闲,以及无设备时的模拟采集。已运行 Autocal 非 Qt 测试并通过范围内门禁;界面测试待 Windows 复核,未做实机验收。
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user