收紧绘图设置为两页,并把 FN 牛顿下限固定为 0。
手指 2D 方向改到 ArrowPanel 设置对话框;归零阈值不变。已用 display_prefs、finger_2d_orientation 与绘图对话框相关 smoke 测试验证。
This commit is contained in:
@@ -17,17 +17,19 @@ from orisys_app_shared.acquisition.image_backend import (
|
||||
ImagePlan,
|
||||
)
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtCore import QSize, Qt, Signal
|
||||
from PySide6.QtGui import QImage, QPainter, QPixmap, QTransform
|
||||
from PySide6.QtWidgets import (
|
||||
QComboBox,
|
||||
QFrame,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
from orisys_studio.ui import icons
|
||||
from orisys_studio.ui.fit_text import ShrinkableLabel
|
||||
|
||||
VIEW_MODES = (ARROW_PURE, ARROW_STITCHED, DEPTH)
|
||||
@@ -149,6 +151,8 @@ class ColorBarLabel(QLabel):
|
||||
|
||||
class ArrowPanel(QFrame):
|
||||
view_mode_changed = Signal(str)
|
||||
#: Finger 2D presentation (orientation). The window opens the small dialog.
|
||||
settings_requested = Signal()
|
||||
|
||||
def __init__(self, parent=None, *, theme: str = "dark") -> None:
|
||||
super().__init__(parent)
|
||||
@@ -178,9 +182,15 @@ class ArrowPanel(QFrame):
|
||||
self.view_combo = QComboBox()
|
||||
self.view_combo.setMinimumWidth(140)
|
||||
self.view_combo.currentIndexChanged.connect(self._on_view_index_changed)
|
||||
self.btn_settings = QPushButton()
|
||||
self.btn_settings.setProperty("compact", "true")
|
||||
self.btn_settings.setProperty("iconOnly", "true")
|
||||
self.btn_settings.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.btn_settings.clicked.connect(self.settings_requested.emit)
|
||||
header.addWidget(self.title)
|
||||
header.addStretch(1)
|
||||
header.addWidget(self.view_combo)
|
||||
header.addWidget(self.btn_settings)
|
||||
layout.addLayout(header)
|
||||
|
||||
self.image_label = AspectImageLabel()
|
||||
@@ -197,6 +207,7 @@ class ArrowPanel(QFrame):
|
||||
|
||||
self._placeholder = ""
|
||||
self._populate_view_combo()
|
||||
self._apply_icons()
|
||||
self.retranslate()
|
||||
self._refresh_footer_visibility()
|
||||
self._sync_aspect()
|
||||
@@ -382,6 +393,12 @@ class ArrowPanel(QFrame):
|
||||
def set_theme(self, theme: str) -> None:
|
||||
self._theme = str(theme)
|
||||
self.colorbar.set_theme(self._theme)
|
||||
self._apply_icons()
|
||||
|
||||
def _apply_icons(self) -> None:
|
||||
size = QSize(icons.ICON_SIZE, icons.ICON_SIZE)
|
||||
self.btn_settings.setIcon(icons.icon("settings", self._theme))
|
||||
self.btn_settings.setIconSize(size)
|
||||
|
||||
def set_depth_scale(
|
||||
self, text: str | None, *, tip_key: str = "panels.depth_scale_tip"
|
||||
@@ -458,6 +475,7 @@ class ArrowPanel(QFrame):
|
||||
if i < self.view_combo.count():
|
||||
self.view_combo.setItemText(i, t(_VIEW_LABELS.get(mode, mode)))
|
||||
self.view_combo.setToolTip(t("panels.view_mode_tip"))
|
||||
self.btn_settings.setToolTip(t("panels.arrow_settings_tip"))
|
||||
self.image_label.setToolTip(t("panels.arrow_empty_tip"))
|
||||
self._refresh_pipeline_label()
|
||||
self._refresh_scale_label()
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Finger 2D canvas presentation: how the unrolled domain is shown.
|
||||
|
||||
Display only — the record and stored arrays keep the domain's own orientation.
|
||||
The 3D view is untouched (its frame is the design export's).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from orisys_app_shared.acquisition.display_orientation import (
|
||||
DEFAULT_FINGER_ORIENTATION,
|
||||
ORIENTATIONS,
|
||||
normalise_orientation,
|
||||
)
|
||||
from orisys_app_shared.i18n import t
|
||||
from PySide6.QtWidgets import (
|
||||
QComboBox,
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFormLayout,
|
||||
QLabel,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
|
||||
class FingerOrientationDialog(QDialog):
|
||||
def __init__(
|
||||
self,
|
||||
parent=None,
|
||||
*,
|
||||
finger_orientation: str = DEFAULT_FINGER_ORIENTATION,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self.setModal(True)
|
||||
self.resize(420, 200)
|
||||
|
||||
root = QVBoxLayout(self)
|
||||
form = QFormLayout()
|
||||
|
||||
self.orientation_label = QLabel()
|
||||
self.orientation_combo = QComboBox()
|
||||
for name in ORIENTATIONS:
|
||||
self.orientation_combo.addItem(t(f"dialog.orientation_{name}"), name)
|
||||
index = self.orientation_combo.findData(
|
||||
normalise_orientation(finger_orientation)
|
||||
)
|
||||
self.orientation_combo.setCurrentIndex(max(index, 0))
|
||||
self.orientation_note = QLabel()
|
||||
self.orientation_note.setObjectName("hintText")
|
||||
self.orientation_note.setWordWrap(True)
|
||||
|
||||
form.addRow(self.orientation_label, self.orientation_combo)
|
||||
form.addRow(self.orientation_note)
|
||||
root.addLayout(form)
|
||||
|
||||
self.button_box = QDialogButtonBox(
|
||||
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
root.addWidget(self.button_box)
|
||||
|
||||
self.retranslate()
|
||||
|
||||
def orientation(self) -> str:
|
||||
return normalise_orientation(self.orientation_combo.currentData())
|
||||
|
||||
def retranslate(self) -> None:
|
||||
self.setWindowTitle(t("dialog.plot_orientation_group"))
|
||||
self.orientation_label.setText(t("dialog.plot_orientation"))
|
||||
self.orientation_combo.setToolTip(t("dialog.plot_orientation_tip"))
|
||||
self.orientation_note.setText(t("dialog.plot_orientation_note"))
|
||||
for index in range(self.orientation_combo.count()):
|
||||
name = str(self.orientation_combo.itemData(index) or "")
|
||||
self.orientation_combo.setItemText(index, t(f"dialog.orientation_{name}"))
|
||||
ok_btn = self.button_box.button(QDialogButtonBox.StandardButton.Ok)
|
||||
cancel_btn = self.button_box.button(QDialogButtonBox.StandardButton.Cancel)
|
||||
if ok_btn is not None:
|
||||
ok_btn.setText(t("dialog.ok"))
|
||||
if cancel_btn is not None:
|
||||
cancel_btn.setText(t("dialog.cancel"))
|
||||
@@ -6,16 +6,14 @@ a resting channel at zero), and what the acquisition is allowed to filter.
|
||||
The filter half is a *request*: it is handed to the SDK through
|
||||
``FilterSettings.apply_to`` at the start of the next session, and what the device
|
||||
reports as done is what the record will say (see ``force/filters.py``).
|
||||
|
||||
The FN Newton lower bound is fixed at zero (see :data:`DEFAULT_PLOT_FN_MIN`); it
|
||||
is not offered as a control. Finger 2D presentation lives on the 2D panel.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from orisys_app_shared.acquisition.constants import FORCE_SAMPLE_RATE_HZ
|
||||
from orisys_app_shared.acquisition.display_orientation import (
|
||||
DEFAULT_FINGER_ORIENTATION,
|
||||
ORIENTATIONS,
|
||||
normalise_orientation,
|
||||
)
|
||||
from orisys_app_shared.force.filters import (
|
||||
FilterSettings,
|
||||
available_presets,
|
||||
@@ -33,13 +31,15 @@ from PySide6.QtWidgets import (
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QSpinBox,
|
||||
QTabWidget,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
DEFAULT_PLOT_WINDOW_SEC = 4.0
|
||||
#: Lower bounds. FN is allowed below zero: a resting normal force has no reason
|
||||
#: to sit exactly on the frame, and with the lower bound at 0 the idle curve is
|
||||
#: drawn along the subplot border that abuts the FX frame (see force_plots).
|
||||
#: FN Newton Y lower bound is always zero. A negative floor used to keep the
|
||||
#: idle curve off the FX frame; that control was removed so the axis always
|
||||
#: starts at rest = 0.
|
||||
DEFAULT_PLOT_FN_MIN = 0.0
|
||||
DEFAULT_PLOT_FN_MAX = 20.0
|
||||
DEFAULT_PLOT_SHEAR_MAX = 20.0
|
||||
@@ -75,20 +75,23 @@ class PlotSettingsDialog(QDialog):
|
||||
filters: FilterSettings | None = None,
|
||||
filters_locked: bool = False,
|
||||
sample_rate_hz: float = FORCE_SAMPLE_RATE_HZ,
|
||||
finger_orientation: str = DEFAULT_FINGER_ORIENTATION,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
self.setModal(True)
|
||||
# The layout's own minimum (618 px with the 2D presentation group) keeps the
|
||||
# buttons on screen; this is the height it opens at.
|
||||
self.resize(460, 640)
|
||||
self.resize(460, 420)
|
||||
self._sample_rate_hz = float(sample_rate_hz)
|
||||
# Callers may still pass a remembered lower bound; Newton FN floor is fixed.
|
||||
_ = fn_min
|
||||
#: True while the widgets are being filled in from a preset, so the
|
||||
#: "edited -> custom" handler does not fire on our own writes.
|
||||
self._syncing_filters = False
|
||||
|
||||
root = QVBoxLayout(self)
|
||||
form = QFormLayout()
|
||||
self.tabs = QTabWidget(self)
|
||||
root.addWidget(self.tabs, 1)
|
||||
|
||||
curves = QWidget(self.tabs)
|
||||
form = QFormLayout(curves)
|
||||
|
||||
self.window_label = QLabel()
|
||||
self.window_spin = QDoubleSpinBox()
|
||||
@@ -98,15 +101,6 @@ class PlotSettingsDialog(QDialog):
|
||||
self.window_spin.setSuffix(" s")
|
||||
self.window_spin.setValue(float(window_sec))
|
||||
|
||||
# FN is asymmetric: a lower bound that may be negative, and an upper one.
|
||||
self.fn_min_label = QLabel()
|
||||
self.fn_min_spin = QDoubleSpinBox()
|
||||
self.fn_min_spin.setRange(-1000.0, 1000.0)
|
||||
self.fn_min_spin.setSingleStep(1.0)
|
||||
self.fn_min_spin.setDecimals(1)
|
||||
self.fn_min_spin.setSuffix(" N")
|
||||
self.fn_min_spin.setValue(float(fn_min))
|
||||
|
||||
self.fn_max_label = QLabel()
|
||||
self.fn_max_spin = QDoubleSpinBox()
|
||||
self.fn_max_spin.setRange(1.0, 100.0)
|
||||
@@ -173,24 +167,7 @@ class PlotSettingsDialog(QDialog):
|
||||
precision_row.addWidget(QLabel("raw"))
|
||||
precision_row.addWidget(self.raw_decimals_spin)
|
||||
|
||||
# How the finger's 2D canvas is turned for display. A *presentation* choice, so
|
||||
# it lives with the other display ones (this dialog), not with the acquisition
|
||||
# filters below it, and it is applied at display time only — see
|
||||
# ``acquisition.display_orientation`` for why the shipped default is a half turn.
|
||||
self.orientation_label = QLabel()
|
||||
self.orientation_combo = QComboBox()
|
||||
for name in ORIENTATIONS:
|
||||
self.orientation_combo.addItem(t(f"dialog.orientation_{name}"), name)
|
||||
index = self.orientation_combo.findData(
|
||||
normalise_orientation(finger_orientation)
|
||||
)
|
||||
self.orientation_combo.setCurrentIndex(max(index, 0))
|
||||
self.orientation_note = QLabel()
|
||||
self.orientation_note.setObjectName("hintText")
|
||||
self.orientation_note.setWordWrap(True)
|
||||
|
||||
form.addRow(self.window_label, self.window_spin)
|
||||
form.addRow(self.fn_min_label, self.fn_min_spin)
|
||||
form.addRow(self.fn_max_label, self.fn_max_spin)
|
||||
form.addRow(self.shear_max_label, self.shear_max_spin)
|
||||
form.addRow(self.fn_raw_min_label, self.fn_raw_min_spin)
|
||||
@@ -200,13 +177,15 @@ class PlotSettingsDialog(QDialog):
|
||||
form.addRow(self.deadband_raw_label, self.deadband_raw_spin)
|
||||
self.precision_label = QLabel()
|
||||
form.addRow(self.precision_label, precision_row)
|
||||
root.addLayout(form)
|
||||
self.tabs.addTab(curves, "")
|
||||
|
||||
self.orientation_group = QGroupBox()
|
||||
orientation_form = QFormLayout(self.orientation_group)
|
||||
orientation_form.addRow(self.orientation_label, self.orientation_combo)
|
||||
orientation_form.addRow(self.orientation_note)
|
||||
root.addWidget(self.orientation_group)
|
||||
filters_page = QWidget(self.tabs)
|
||||
filters_layout = QVBoxLayout(filters_page)
|
||||
filters_layout.setContentsMargins(0, 8, 0, 0)
|
||||
self._build_filter_section(
|
||||
filters_layout, filters or FilterSettings(), filters_locked
|
||||
)
|
||||
self.tabs.addTab(filters_page, "")
|
||||
|
||||
newton = str(units or "newton").lower().startswith("n")
|
||||
raw_widgets = (
|
||||
@@ -219,21 +198,26 @@ class PlotSettingsDialog(QDialog):
|
||||
self.shear_raw_max_label,
|
||||
self.deadband_raw_label,
|
||||
)
|
||||
for widget in (self.fn_min_spin, self.fn_max_spin, self.shear_max_spin,
|
||||
self.deadband_n_spin, self.fn_min_label, self.fn_max_label,
|
||||
self.shear_max_label, self.deadband_n_label):
|
||||
for widget in (
|
||||
self.fn_max_spin,
|
||||
self.shear_max_spin,
|
||||
self.deadband_n_spin,
|
||||
self.fn_max_label,
|
||||
self.shear_max_label,
|
||||
self.deadband_n_label,
|
||||
):
|
||||
widget.setEnabled(newton)
|
||||
for widget in raw_widgets:
|
||||
widget.setEnabled(not newton)
|
||||
|
||||
self._build_filter_section(root, filters or FilterSettings(), filters_locked)
|
||||
|
||||
buttons_row = QHBoxLayout()
|
||||
self.btn_reset = QPushButton()
|
||||
self.btn_reset.clicked.connect(self._reset_defaults)
|
||||
buttons_row.addWidget(self.btn_reset)
|
||||
buttons_row.addStretch(1)
|
||||
self.button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||
self.button_box = QDialogButtonBox(
|
||||
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
||||
)
|
||||
self.button_box.accepted.connect(self.accept)
|
||||
self.button_box.rejected.connect(self.reject)
|
||||
buttons_row.addWidget(self.button_box)
|
||||
@@ -310,8 +294,13 @@ class PlotSettingsDialog(QDialog):
|
||||
group_form.addRow(self.lowpass_order_label, self.lowpass_order_spin)
|
||||
group_form.addRow(self.rate_auto_check, self.rate_spin)
|
||||
|
||||
for widget in (self.flow_ema_spin, self.median_spin, self.lowpass_spin,
|
||||
self.rate_spin, self.rate_auto_check):
|
||||
for widget in (
|
||||
self.flow_ema_spin,
|
||||
self.median_spin,
|
||||
self.lowpass_spin,
|
||||
self.rate_spin,
|
||||
self.rate_auto_check,
|
||||
):
|
||||
widget.setToolTip("") # filled in by retranslate()
|
||||
self.flow_ema_spin.valueChanged.connect(self._on_filter_value_edited)
|
||||
self.median_spin.valueChanged.connect(self._on_filter_value_edited)
|
||||
@@ -337,6 +326,7 @@ class PlotSettingsDialog(QDialog):
|
||||
root.addWidget(self.filters_group)
|
||||
root.addWidget(self.filter_note)
|
||||
root.addWidget(self.filter_warning)
|
||||
root.addStretch(1)
|
||||
|
||||
self._filters_locked = bool(locked)
|
||||
self.filters_group.setEnabled(not self._filters_locked)
|
||||
@@ -443,7 +433,7 @@ class PlotSettingsDialog(QDialog):
|
||||
"""Every axis limit, keyed the way the preference names it."""
|
||||
return {
|
||||
"window_sec": float(self.window_spin.value()),
|
||||
"fn_min": float(self.fn_min_spin.value()),
|
||||
"fn_min": DEFAULT_PLOT_FN_MIN,
|
||||
"fn_max": float(self.fn_max_spin.value()),
|
||||
"shear_max": float(self.shear_max_spin.value()),
|
||||
"fn_raw_min": float(self.fn_raw_min_spin.value()),
|
||||
@@ -461,27 +451,19 @@ class PlotSettingsDialog(QDialog):
|
||||
def settings(self) -> dict[str, float]:
|
||||
"""Everything the plot half of the dialog decides, in one mapping."""
|
||||
return {
|
||||
**self.ranges(), **self.deadbands(),
|
||||
**self.ranges(),
|
||||
**self.deadbands(),
|
||||
"newton_decimals": int(self.newton_decimals_spin.value()),
|
||||
"raw_decimals": int(self.raw_decimals_spin.value()),
|
||||
}
|
||||
|
||||
def orientation(self) -> str:
|
||||
"""The 2D presentation the dialog is showing (see ``display_orientation``)."""
|
||||
return normalise_orientation(self.orientation_combo.currentData())
|
||||
|
||||
def _reset_defaults(self) -> None:
|
||||
self.window_spin.setValue(DEFAULT_PLOT_WINDOW_SEC)
|
||||
self.fn_min_spin.setValue(DEFAULT_PLOT_FN_MIN)
|
||||
self.fn_max_spin.setValue(DEFAULT_PLOT_FN_MAX)
|
||||
self.shear_max_spin.setValue(DEFAULT_PLOT_SHEAR_MAX)
|
||||
self.fn_raw_min_spin.setValue(DEFAULT_PLOT_FN_RAW_MIN)
|
||||
self.fn_raw_max_spin.setValue(DEFAULT_PLOT_FN_RAW_MAX)
|
||||
self.shear_raw_max_spin.setValue(DEFAULT_PLOT_SHEAR_RAW_MAX)
|
||||
# "Reset" means the shipped picture as well: the finger's derived default.
|
||||
self.orientation_combo.setCurrentIndex(
|
||||
max(self.orientation_combo.findData(DEFAULT_FINGER_ORIENTATION), 0)
|
||||
)
|
||||
self.deadband_n_spin.setValue(DEFAULT_PLOT_DEADBAND_N)
|
||||
self.deadband_raw_spin.setValue(DEFAULT_PLOT_DEADBAND_RAW)
|
||||
self.newton_decimals_spin.setValue(DEFAULT_PLOT_NEWTON_DECIMALS)
|
||||
@@ -498,10 +480,10 @@ class PlotSettingsDialog(QDialog):
|
||||
|
||||
def retranslate(self) -> None:
|
||||
self.setWindowTitle(t("dialog.plot_settings_title"))
|
||||
self.tabs.setTabText(0, t("dialog.plot_tab_curves"))
|
||||
self.tabs.setTabText(1, t("dialog.plot_tab_filters"))
|
||||
self.window_label.setText(t("dialog.plot_window"))
|
||||
self.window_spin.setToolTip(t("dialog.plot_window_tip"))
|
||||
self.fn_min_label.setText(t("dialog.plot_fn_min"))
|
||||
self.fn_min_spin.setToolTip(t("dialog.plot_fn_min_tip"))
|
||||
self.fn_max_label.setText(t("dialog.plot_fn_max"))
|
||||
self.fn_max_spin.setToolTip(t("dialog.plot_fn_max_tip"))
|
||||
self.shear_max_label.setText(t("dialog.plot_shear_max"))
|
||||
@@ -518,17 +500,12 @@ class PlotSettingsDialog(QDialog):
|
||||
self.deadband_raw_spin.setToolTip(t("dialog.plot_deadband_raw_tip"))
|
||||
self.precision_label.setText(t("dialog.plot_value_decimals"))
|
||||
self.btn_reset.setText(t("dialog.reset_defaults"))
|
||||
self.orientation_group.setTitle(t("dialog.plot_orientation_group"))
|
||||
self.orientation_label.setText(t("dialog.plot_orientation"))
|
||||
self.orientation_combo.setToolTip(t("dialog.plot_orientation_tip"))
|
||||
self.orientation_note.setText(t("dialog.plot_orientation_note"))
|
||||
for index in range(self.orientation_combo.count()):
|
||||
name = str(self.orientation_combo.itemData(index) or "")
|
||||
self.orientation_combo.setItemText(index, t(f"dialog.orientation_{name}"))
|
||||
self.filters_group.setTitle(t("dialog.plot_filters"))
|
||||
self.filter_preset_label.setText(t("dialog.filter_preset"))
|
||||
self.preset_combo.setToolTip(t("dialog.filter_preset_tip"))
|
||||
self.preset_combo.setItemText(self.preset_combo.count() - 1, t("dialog.filter_preset_custom"))
|
||||
self.preset_combo.setItemText(
|
||||
self.preset_combo.count() - 1, t("dialog.filter_preset_custom")
|
||||
)
|
||||
self.flow_ema_label.setText(t("dialog.filter_flow_ema"))
|
||||
self.flow_ema_spin.setToolTip(t("dialog.filter_flow_ema_tip"))
|
||||
self.median_label.setText(t("dialog.filter_median"))
|
||||
|
||||
Reference in New Issue
Block a user