Files
ykcheungab ff5f089dc1 autocal: 序列页阵列预览工具、2D 图点可见度,标签顺序与送到标定页
- 标签顺序:几何与安装 放到 测试序列 之前
- 序列点增加可选 uv_px(向后兼容),点表新增 U/V 列;加入所选点、删除所选、
  送到标定页(按 U/V 经同一套求解加入标定队列并切换页面)
- 已选点结果改显示在选点区状态行,不再写入全局顶部状态条
- 序列页左上区改紧凑:名称与备注同一行,分“阵列”“运动参数”两页;
  运动参数每行三组,输入框改用 compactField
- 新增阵列工具(array_plan.py、ui/array_tool.py):UV 规则网格(可蛇形遍历)与
  阶段预设,逐点检查是否在曲面内、是否留有边距,候选点实时预览在 2D 图上,
  只有可用点才能加入,已在序列里的点不重复加入
- 2D 平面图:标记改为缩放后再绘制并加深色描边,修复缩小后点不清;
  底图可选 原图/灰度/变暗/仅轮廓,点色可选 黄/白/青/橙/灰(默认灰度+黄,写入偏好);
  序列点带编号并连线显示遍历顺序,选中行高亮
- 新增测试 test_sequence_uv、test_sequence_to_capture、test_array_plan、test_array_tool
2026-10-19 11:39:09 +08:00

128 lines
4.8 KiB
Python

"""The array tool and the 2D map's look: what the operator sees and what the tool offers."""
from __future__ import annotations
import numpy as np
from orisys_app_shared.i18n import t
from orisys_autocal.array_plan import Candidate
from orisys_autocal.ui.sensor_panel import FlatSurfaceView
from PySide6.QtGui import QColor
def _synthetic_flat(size: int = 64):
from orisys_autocal.sensor.uv_flat import FlatSurface
image = np.zeros((size, size, 3), dtype=np.uint8)
image[..., 2] = 200 # a plain blue map: any yellow on it is a mark
return FlatSurface(
size_hw=(size, size),
image_rgb=image,
positions_mm=np.zeros((size, size, 3)),
normals=np.tile(np.array([0.0, 0.0, 1.0]), (size, size, 1)),
face_index=np.zeros((size, size), dtype=np.int32),
uv_min=(0.0, 0.0),
uv_max=(1.0, 1.0),
)
def test_marks_stay_visible_when_the_map_is_scaled_down(qt_app):
view = FlatSurfaceView()
view.resize(32, 32) # half the map's size
view.set_flat(_synthetic_flat(64))
view.set_marker_colour("#ffd60a")
view.set_sequence_points([(32, 32, "", False)])
shown = view.pixmap().toImage()
centre = QColor(shown.pixel(shown.width() // 2, shown.height() // 2))
# The mark is drawn after the scale, so its colour is at the centre of the
# shown image instead of being smeared into the blue by the downscale.
assert centre.red() > 220 and centre.green() > 180 and centre.blue() < 80
def test_the_background_can_be_changed_and_marks_survive_it(qt_app):
view = FlatSurfaceView()
view.resize(64, 64)
view.set_flat(_synthetic_flat(64))
before = QColor(view.pixmap().toImage().pixel(2, 2))
for mode in ("gray", "dim", "outline"):
view.set_background(mode)
after = QColor(view.pixmap().toImage().pixel(2, 2))
assert after != before, mode
assert view.background == mode
view.set_background("nonsense")
assert view.background == "original"
def test_unusable_candidates_are_drawn_apart_from_usable_ones(qt_app):
view = FlatSurfaceView()
view.resize(64, 64)
view.set_flat(_synthetic_flat(64))
view.set_marker_colour("#ffd60a")
view.set_candidates([(32, 32, False)])
shown = view.pixmap().toImage()
red_pixels = sum(
1
for x in range(24, 41)
for y in range(24, 41)
if QColor(shown.pixel(x, y)).red() > 200 and QColor(shown.pixel(x, y)).green() < 110
)
assert red_pixels > 0
def test_the_look_is_chosen_on_the_page_and_reported(make_window):
window = make_window()
panel = window.finger_panel
seen: list[tuple[str, str]] = []
panel.viewStyleChanged.connect(lambda mode, colour: seen.append((mode, colour)))
panel.bg_combo.setCurrentIndex(panel.bg_combo.findData("dim"))
panel.colour_combo.setCurrentIndex(panel.colour_combo.findData("white"))
assert panel.view_style() == ("dim", "white")
assert seen[-1] == ("dim", "white")
assert window.prefs["finger_view_background"] == "dim"
assert panel.view.marker_colour == QColor("#ffffff")
window.close()
def test_the_grid_has_rows_times_columns_candidates_and_follows_its_fields(make_window):
window = make_window()
tool = window.sequence_panel.array_tool
assert len(tool.candidates()) == 9
tool.spin("rows").setValue(2)
tool.spin("cols").setValue(5)
assert len(tool.candidates()) == 10
tool.mode_combo.setCurrentIndex(tool.mode_combo.findData("preset"))
assert tool.mode == "preset"
tool.preset_combo.setCurrentIndex(tool.preset_combo.findData("stage0"))
assert tool.candidates() == [("C13", 0, 470)]
window.close()
def test_only_usable_candidates_can_be_added(make_window):
window = make_window()
tool = window.sequence_panel.array_tool
added: list = []
tool.addRequested.connect(added.append)
tool.set_checked([Candidate(0, 0, False, "off_surface")])
assert not tool.add_button.isEnabled()
tool.set_checked([Candidate(0, 0, True), Candidate(5, 5, False, "near_edge")])
assert tool.add_button.isEnabled()
assert tool.add_button.text() == t("array.btn_add", count=1)
tool.add_button.click()
assert [c.u for c in added[0]] == [0]
window.close()
def test_adding_candidates_skips_a_point_already_in_the_sequence(make_window, monkeypatch):
window = make_window()
panel = window.sequence_panel
monkeypatch.setattr(window, "_map_origin", lambda: (100, 150))
monkeypatch.setattr(
window.finger_panel,
"solve_pixel_row",
lambda x, y: {"X": 1.0, "Y": 2.0, "Z": 3.0, "u_px": x - 100.0, "v_px": 150.0 - y},
)
before = panel.point_row_count()
window._on_array_add([Candidate(10, 20), Candidate(30, 40)]) # noqa: SLF001
window._on_array_add([Candidate(10, 20), Candidate(50, 60)]) # noqa: SLF001
assert panel.point_row_count() == before + 3
window.close()