在单位回退时就近提示缺少可用标定

让实时和回放的原始单位回退原因出现在力曲线单位按钮旁。相关定向测试与 lint gate 通过。
This commit is contained in:
ykcheungab
2026-10-10 15:05:09 +08:00
parent cf86bd0702
commit 48de5b74e6
7 changed files with 42 additions and 3 deletions
+1 -1
View File
@@ -504,7 +504,7 @@
"finger_run_unavailable": "Run is not available for this sensor type", "finger_run_unavailable": "Run is not available for this sensor type",
"video_required": "Select a video file first", "video_required": "Select a video file first",
"config_missing": "Sensor config not found for {type}", "config_missing": "Sensor config not found for {type}",
"units_raw_fallback": "Calibration unavailable — showing raw units", "units_raw_fallback": "No usable calibration; showing raw for now",
"reveal_failed": "Could not reveal config: {error}" "reveal_failed": "Could not reveal config: {error}"
}, },
"scene3d": { "scene3d": {
+1 -1
View File
@@ -504,7 +504,7 @@
"finger_run_unavailable": "此传感器类型暂不支持运行", "finger_run_unavailable": "此传感器类型暂不支持运行",
"video_required": "请先选择视频文件", "video_required": "请先选择视频文件",
"config_missing": "未找到传感器配置: {type}", "config_missing": "未找到传感器配置: {type}",
"units_raw_fallback": "无标定数据 — 显示原始单位", "units_raw_fallback": "无可用标定,暂显示原始单位",
"reveal_failed": "无法打开配置位置: {error}" "reveal_failed": "无法打开配置位置: {error}"
}, },
"scene3d": { "scene3d": {
@@ -152,6 +152,7 @@ class AcquirePresenter(QObject):
if not self._host.session.raw_fallback_notified: if not self._host.session.raw_fallback_notified:
self._host.session.raw_fallback_notified = True self._host.session.raw_fallback_notified = True
self._host.notify(t("status.units_raw_fallback")) self._host.notify(t("status.units_raw_fallback"))
self._charts.show_unit_fallback()
else: else:
self._host.session.raw_fallback_notified = False self._host.session.raw_fallback_notified = False
self._host.session.last_sample_valid = True self._host.session.last_sample_valid = True
@@ -1411,6 +1411,7 @@ class ReplayController(QObject):
self._host.set_display_unit(unit) self._host.set_display_unit(unit)
if unit != wanted: if unit != wanted:
self._host.notify(t("status.units_raw_fallback")) self._host.notify(t("status.units_raw_fallback"))
self._charts.show_unit_fallback()
self._charts.show_all() self._charts.show_all()
self._image_panel.set_source_kind("replay") self._image_panel.set_source_kind("replay")
return return
@@ -1483,6 +1484,7 @@ class ReplayController(QObject):
wanted = "N" if self._charts.unit() == "N" else "raw" wanted = "N" if self._charts.unit() == "N" else "raw"
if wanted == "N" and not source.unit_available("N"): if wanted == "N" and not source.unit_available("N"):
self._host.notify(t("status.units_raw_fallback")) self._host.notify(t("status.units_raw_fallback"))
self._charts.show_unit_fallback()
return "raw" return "raw"
return wanted return wanted
@@ -16,7 +16,7 @@ from orisys_app_shared.force.source import StoreCurveSource
from orisys_app_shared.i18n import t from orisys_app_shared.i18n import t
from orisys_app_shared.record.sample import plottable_axis_mask from orisys_app_shared.record.sample import plottable_axis_mask
from orisys_app_shared.theme import color from orisys_app_shared.theme import color
from PySide6.QtCore import QSize, Qt, Signal from PySide6.QtCore import QSize, Qt, QTimer, Signal
from PySide6.QtGui import QColor, QFont, QFontDatabase, QFontMetrics from PySide6.QtGui import QColor, QFont, QFontDatabase, QFontMetrics
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QFrame, QFrame,
@@ -252,6 +252,13 @@ class ForcePlotsPanel(QFrame):
self.btn_settings = self._header_button("settings") self.btn_settings = self._header_button("settings")
self.btn_settings.clicked.connect(self.plot_settings_requested.emit) self.btn_settings.clicked.connect(self.plot_settings_requested.emit)
header.addWidget(self.btn_settings) header.addWidget(self.btn_settings)
self.unit_notice = QLabel()
self.unit_notice.setObjectName("hintText")
self.unit_notice.hide()
header.addWidget(self.unit_notice)
self._unit_notice_timer = QTimer(self)
self._unit_notice_timer.setSingleShot(True)
self._unit_notice_timer.timeout.connect(self.unit_notice.hide)
# The unit chip is a button: switching between newton and raw is a chart # The unit chip is a button: switching between newton and raw is a chart
# concern, so it lives on the chart. # concern, so it lives on the chart.
self.unit_badge = QPushButton() self.unit_badge = QPushButton()
@@ -733,6 +740,12 @@ class ForcePlotsPanel(QFrame):
self._last_overview_at = now self._last_overview_at = now
self._sync_overview() self._sync_overview()
def show_unit_fallback(self) -> None:
"""Explain a failed Newton switch beside the unit control."""
self.unit_notice.setText(t("status.units_raw_fallback"))
self.unit_notice.show()
self._unit_notice_timer.start(6000)
def set_units(self, unit: str) -> None: def set_units(self, unit: str) -> None:
"""Switch display units without mixing units in the curve buffer. """Switch display units without mixing units in the curve buffer.
@@ -742,6 +755,9 @@ class ForcePlotsPanel(QFrame):
cleared rather than reinterpreted. cleared rather than reinterpreted.
""" """
unit = "N" if str(unit).upper().startswith("N") else "raw" unit = "N" if str(unit).upper().startswith("N") else "raw"
if unit == "N":
self._unit_notice_timer.stop()
self.unit_notice.hide()
if unit == self._unit: if unit == self._unit:
return return
self._unit = unit self._unit = unit
@@ -1450,6 +1466,7 @@ class ForcePlotsPanel(QFrame):
self.btn_settings.setAccessibleName(t("chart.settings")) self.btn_settings.setAccessibleName(t("chart.settings"))
self.unit_badge.setToolTip(t("chart.unit_toggle_tip")) self.unit_badge.setToolTip(t("chart.unit_toggle_tip"))
self.unit_badge.setAccessibleName(t("chart.unit_toggle")) self.unit_badge.setAccessibleName(t("chart.unit_toggle"))
self.unit_notice.setText(t("status.units_raw_fallback"))
self.window_caption.setText(t("chart.overview")) self.window_caption.setText(t("chart.overview"))
self.window_caption.setToolTip(t("chart.window_tip")) self.window_caption.setToolTip(t("chart.window_tip"))
self._refresh_mode_label() self._refresh_mode_label()
@@ -3,6 +3,7 @@
from __future__ import annotations from __future__ import annotations
import numpy as np import numpy as np
from orisys_app_shared.i18n import t
from orisys_studio.ui.force_plots import ForcePlotsPanel, _nice_ticks from orisys_studio.ui.force_plots import ForcePlotsPanel, _nice_ticks
@@ -28,6 +29,19 @@ def _curve(panel: ForcePlotsPanel, channel: int) -> np.ndarray:
# ------------------------------------------------------------- force output # ------------------------------------------------------------- force output
def test_missing_calibration_notice_sits_beside_unit_button(qt_app):
panel = _panel("raw")
assert not panel.unit_notice.isVisible()
panel.show_unit_fallback()
assert panel.unit_notice.text() == t("status.units_raw_fallback")
assert not panel.unit_notice.isHidden()
header = panel.layout().itemAt(0).layout()
assert header.itemAt(header.count() - 2).widget() is panel.unit_notice
assert header.itemAt(header.count() - 1).widget() is panel.unit_badge
panel.set_units("N")
assert panel.unit_notice.isHidden()
def test_raw_threshold_and_precision_match_cards_and_curve(qt_app): def test_raw_threshold_and_precision_match_cards_and_curve(qt_app):
panel = _panel("raw") panel = _panel("raw")
panel.set_deadband_floors(raw=50.0) panel.set_deadband_floors(raw=50.0)
@@ -111,6 +111,7 @@ class _Charts:
self.followed = 0 self.followed = 0
self.shown = 0 self.shown = 0
self.cleared = 0 self.cleared = 0
self.unit_fallbacks = 0
def unit(self) -> str: def unit(self) -> str:
return self._unit return self._unit
@@ -134,6 +135,9 @@ class _Charts:
def show_all(self) -> None: def show_all(self) -> None:
self.shown += 1 self.shown += 1
def show_unit_fallback(self) -> None:
self.unit_fallbacks += 1
class _ImagePanel: class _ImagePanel:
def __init__(self) -> None: def __init__(self) -> None:
@@ -584,6 +588,7 @@ def test_the_charts_fall_back_to_raw_when_a_recording_has_no_newtons(qt_app):
assert engine.units[-1] == "raw" assert engine.units[-1] == "raw"
assert host.units[-1] == "raw" assert host.units[-1] == "raw"
assert t("status.units_raw_fallback") in host.notes assert t("status.units_raw_fallback") in host.notes
assert parts[1].unit_fallbacks == 1
engine._reader = _Reader(newtons=[1.0, 2.0, 3.0]) engine._reader = _Reader(newtons=[1.0, 2.0, 3.0])
controller.apply_source(True) controller.apply_source(True)