autocal: 测试序列页与几何与安装页共用 2D 平面图和 3D 预览
- FingerPanel 新增 release_views/restore_views,把表面选点图和三维预览 整体借出,不复制,已拾取的点、传感器包和 GL 视图仍是同一份状态 - SequencePanel 用共用视图替换“仿真阶段实现”的占位框(adopt_views/release_views) - 主窗口按当前标签页把两个视图挂到序列页或几何页 - 新增 tests/test_shared_views.py - 序列页借用视图时给两个框固定最小高度并隐藏长图例,修复 3D 视图下按钮与勾选框叠压
This commit is contained in:
@@ -261,6 +261,9 @@ class FingerPanel(QGroupBox):
|
||||
|
||||
body = QHBoxLayout()
|
||||
body.setSpacing(10)
|
||||
self._body = body
|
||||
#: True while the 2D picker and 3D preview are shown on the sequence page.
|
||||
self._views_lent = False
|
||||
picker_box = self._build_picker()
|
||||
readout_box = self._build_readouts()
|
||||
preview_box = self._build_preview()
|
||||
@@ -279,6 +282,44 @@ class FingerPanel(QGroupBox):
|
||||
self.set_tool_xy(*tool_xy_mm)
|
||||
self.retranslate()
|
||||
|
||||
# ------------------------------------------------------------- shared views
|
||||
|
||||
def release_views(self) -> tuple[QWidget, QWidget]:
|
||||
"""Hand the 2D picker and the 3D preview to another page.
|
||||
|
||||
The sequence page needs the same two views, and a widget has one parent, so
|
||||
they are *moved* rather than copied: one picked point, one bundle and one GL
|
||||
context stay the single source of truth. Whoever takes them must give them
|
||||
back through :meth:`restore_views`.
|
||||
"""
|
||||
if not self._views_lent:
|
||||
self._body.removeWidget(self._picker_box)
|
||||
self._body.removeWidget(self._preview_box)
|
||||
self._views_lent = True
|
||||
# Short on height elsewhere, the colour key goes to the tooltip only.
|
||||
self.legend_label.setVisible(False)
|
||||
return self._picker_box, self._preview_box
|
||||
|
||||
def restore_views(self) -> None:
|
||||
"""Put the two views back in this page, after the readouts between them."""
|
||||
if not self._views_lent:
|
||||
return
|
||||
self._body.insertWidget(0, self._picker_box, 2)
|
||||
self._body.insertWidget(2, self._preview_box, 6)
|
||||
self.legend_label.setVisible(True)
|
||||
for box in (self._picker_box, self._preview_box):
|
||||
layout = box.layout()
|
||||
if layout is not None:
|
||||
layout.invalidate()
|
||||
layout.activate()
|
||||
self._picker_box.show()
|
||||
self._preview_box.show()
|
||||
self._views_lent = False
|
||||
|
||||
@property
|
||||
def views_lent(self) -> bool:
|
||||
return self._views_lent
|
||||
|
||||
# ------------------------------------------------------------------ widgets
|
||||
|
||||
def _build_toolbar(self, bundle_path: str) -> QWidget:
|
||||
|
||||
@@ -117,6 +117,9 @@ VALIDATION_CHECKS: tuple[tuple[str, str], ...] = (
|
||||
("contact_confirmable", "sequence.check_contact"),
|
||||
)
|
||||
|
||||
#: Height the shared 2D and 3D boxes get on this page, in pixels.
|
||||
SHARED_VIEW_MIN_HEIGHT = 420
|
||||
|
||||
#: Placeholder rows, so the flow shows its shape. Marked as unmeasured: a row
|
||||
#: with no status is a design, not a measurement.
|
||||
_EXAMPLE_POINTS: tuple[tuple[float, float, float], ...] = (
|
||||
@@ -164,6 +167,8 @@ class SequencePanel(QGroupBox):
|
||||
self._check_rows: dict[str, QLabel] = {}
|
||||
self._summary: dict[str, Any] = {}
|
||||
self._dirty = False
|
||||
#: The 2D picker and 3D preview, while the geometry page has lent them.
|
||||
self._views: tuple[QWidget, QWidget] | None = None
|
||||
|
||||
outer = QVBoxLayout(self)
|
||||
outer.setContentsMargins(10, 8, 10, 10)
|
||||
@@ -267,6 +272,13 @@ class SequencePanel(QGroupBox):
|
||||
self.preview_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
column.addWidget(self.preview_label, 1)
|
||||
|
||||
# The geometry page's two views are shown here while this page is open:
|
||||
# a sequence is designed against the surface and the pose, not a table.
|
||||
self._view_row = QHBoxLayout()
|
||||
self._view_row.setContentsMargins(0, 0, 0, 0)
|
||||
self._view_row.setSpacing(8)
|
||||
column.addLayout(self._view_row, 1)
|
||||
|
||||
self.preview_hint = QLabel(box)
|
||||
self.preview_hint.setObjectName("hintLabel")
|
||||
self.preview_hint.setWordWrap(True)
|
||||
@@ -274,6 +286,47 @@ class SequencePanel(QGroupBox):
|
||||
self._preview_box = box
|
||||
return box
|
||||
|
||||
def adopt_views(self, picker: QWidget, preview: QWidget) -> None:
|
||||
"""Show the shared 2D picker and 3D preview in place of the placeholder."""
|
||||
if self._views is not None:
|
||||
return
|
||||
self._views = (picker, preview)
|
||||
self.preview_label.setVisible(False)
|
||||
# An explicit floor. Left to its contents the box was allotted less height
|
||||
# than its rows (a 3D view that will not shrink below 240, check boxes,
|
||||
# buttons) need, and the layout then drew them on top of each other.
|
||||
for widget in (picker, preview):
|
||||
widget.setMinimumHeight(SHARED_VIEW_MIN_HEIGHT)
|
||||
self._view_row.addWidget(picker, 2)
|
||||
self._view_row.addWidget(preview, 3)
|
||||
picker.show()
|
||||
preview.show()
|
||||
self._relayout(preview)
|
||||
|
||||
def release_views(self) -> bool:
|
||||
"""Stop showing the shared views; the caller puts them back where they live."""
|
||||
if self._views is None:
|
||||
return False
|
||||
for widget in self._views:
|
||||
self._view_row.removeWidget(widget)
|
||||
widget.setMinimumHeight(0)
|
||||
self._views = None
|
||||
self.preview_label.setVisible(True)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _relayout(widget: QWidget) -> None:
|
||||
"""Recompute a moved widget's inner layout for its new size."""
|
||||
layout = widget.layout()
|
||||
if layout is not None:
|
||||
layout.invalidate()
|
||||
layout.activate()
|
||||
widget.updateGeometry()
|
||||
|
||||
@property
|
||||
def has_views(self) -> bool:
|
||||
return self._views is not None
|
||||
|
||||
# --------------------------------------------------------------- validation
|
||||
|
||||
def _build_validation(self) -> QWidget:
|
||||
|
||||
@@ -328,6 +328,9 @@ class AutoCalMainWindow(QMainWindow):
|
||||
sensor_panel=self.calibration_panel,
|
||||
)
|
||||
self.calibration_panel.reviewRequested.connect(self._on_review_records)
|
||||
# The geometry page's 2D and 3D views are also the sequence page's.
|
||||
self.workspace.pageChanged.connect(self._place_shared_views)
|
||||
self._place_shared_views(self.workspace.current_key())
|
||||
|
||||
self.main_splitter = QSplitter(Qt.Orientation.Horizontal, body)
|
||||
self.main_splitter.addWidget(self.left_rail)
|
||||
@@ -405,6 +408,18 @@ class AutoCalMainWindow(QMainWindow):
|
||||
|
||||
# ----------------------------------------------------------- device setup
|
||||
|
||||
def _place_shared_views(self, key: str) -> None:
|
||||
"""Show the surface picker and 3D preview on whichever of the two pages is open.
|
||||
|
||||
One set of widgets, so a point picked on either page, the loaded bundle and
|
||||
the simulation are the same state; only the parent changes with the tab.
|
||||
"""
|
||||
if key == "sequence":
|
||||
picker, preview = self.finger_panel.release_views()
|
||||
self.sequence_panel.adopt_views(picker, preview)
|
||||
elif self.sequence_panel.release_views() or self.finger_panel.views_lent:
|
||||
self.finger_panel.restore_views()
|
||||
|
||||
def _show_device_setup(self, tab: str | None = None) -> None:
|
||||
"""Open the connection/bus dialog, optionally on one of its tabs."""
|
||||
self.device_setup.open_for(tab)
|
||||
|
||||
Reference in New Issue
Block a user