fix the finger 2D canvas orientation and the 3D arrow size
The unrolled canvas put the finger's +x where the 3D default view puts screen-left, and the 3D flow arrows rasterised as 0.23 px hairlines because their stroke was 2 % of their own sub-millimetre length. The 2D presentation is now the derived half turn (persisted, set from the plot-settings dialog) and the 3D stroke is one canvas pixel — the width the 2D canvas draws — verified numerically and with real-GL snapshots.
This commit is contained in:
@@ -10,6 +10,11 @@ from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from orisys_app_shared.acquisition.constants import FORCE_SAMPLE_RATE_HZ
|
||||
from orisys_app_shared.acquisition.display_orientation import (
|
||||
DEFAULT_FINGER_ORIENTATION,
|
||||
effective_orientation,
|
||||
normalise_orientation,
|
||||
)
|
||||
from orisys_app_shared.acquisition.image_backend import (
|
||||
DEPTH,
|
||||
SOURCE_LIVE,
|
||||
@@ -841,6 +846,12 @@ class StudioMainWindow(QMainWindow):
|
||||
self.toolbar.set_units(str(self.prefs.get("units") or "newton"))
|
||||
self.arrow_panel.set_view_mode(str(self.prefs.get("image_view_mode") or "arrows_pure"))
|
||||
self.arrow_panel.set_rotation_deg(self.prefs.get("arrow_rotation_deg", 90))
|
||||
# The finger's 2D presentation: a *display* convention for the unrolled canvas
|
||||
# (see ``acquisition.display_orientation``), stored as a preference and applied
|
||||
# where the picture is drawn, never to the frame's own arrays.
|
||||
self._finger_orientation = normalise_orientation(
|
||||
self.prefs.get("finger_2d_orientation", DEFAULT_FINGER_ORIENTATION)
|
||||
)
|
||||
self.toolbar.set_settings_expanded(bool(self.prefs.get("settings_expanded", False)))
|
||||
self._sync_settings_action()
|
||||
self._apply_plot_prefs_to_panel()
|
||||
@@ -1521,6 +1532,30 @@ class StudioMainWindow(QMainWindow):
|
||||
def _draw_style(self) -> str:
|
||||
return self.display_ctl.draw_style()
|
||||
|
||||
def finger_orientation(self) -> str:
|
||||
"""How the finger's 2D canvas is presented (a display convention only)."""
|
||||
return normalise_orientation(
|
||||
getattr(self, "_finger_orientation", DEFAULT_FINGER_ORIENTATION)
|
||||
)
|
||||
|
||||
def set_finger_orientation(self, orientation: str) -> str:
|
||||
"""Adopt a 2D presentation for the finger and redraw the frame on screen.
|
||||
|
||||
Takes effect at once — the next render of the frame the panel is already
|
||||
showing uses it, and a running session repaints immediately — and is stored in
|
||||
the preferences, so it also survives a restart. The 3D pane is untouched by
|
||||
design: its frame is the design export's, and the 2D orientation exists to make
|
||||
the *canvas* agree with it.
|
||||
"""
|
||||
wanted = normalise_orientation(orientation)
|
||||
if wanted == self.finger_orientation():
|
||||
return wanted
|
||||
self._finger_orientation = wanted
|
||||
self.prefs["finger_2d_orientation"] = wanted
|
||||
self._persist_prefs()
|
||||
self._repaint_live_frame()
|
||||
return wanted
|
||||
|
||||
def _arrow_policy(self):
|
||||
"""The one flow-arrow policy the 2D canvas and the 3D surface both sample with.
|
||||
|
||||
@@ -1580,12 +1615,17 @@ class StudioMainWindow(QMainWindow):
|
||||
# dialog is the place that has to say "next session, not this one".
|
||||
filters_locked=bool(self._running or self.recorder.is_recording),
|
||||
sample_rate_hz=self.display_ctl.device_sample_rate(),
|
||||
finger_orientation=self._finger_orientation,
|
||||
)
|
||||
if dialog.exec() != PlotSettingsDialog.Accepted:
|
||||
return
|
||||
# One call for the whole picture: the dialog owns the schema, the controller
|
||||
# writes what it was given and re-reads the rest.
|
||||
self.display_ctl.apply_plot_settings(dialog.settings())
|
||||
# The 2D presentation is applied here rather than in the controller: it changes
|
||||
# the picture the *window* draws (``_show_image_frame``), and it must take
|
||||
# effect without a restart.
|
||||
self.set_finger_orientation(dialog.orientation())
|
||||
filters = dialog.filter_values()
|
||||
# The same keys ``_filter_settings()`` reads back, so what the dialog
|
||||
# shows next time is what the session will actually be given.
|
||||
@@ -2585,6 +2625,12 @@ class StudioMainWindow(QMainWindow):
|
||||
canvas_bgr=hex_to_bgr(color(self._theme, "arrow_canvas", "#0B1830")),
|
||||
domain_mask=boundary,
|
||||
arrow_policy=self._arrow_policy(),
|
||||
# The 2D presentation of the finger's unrolled canvas. Display only: the
|
||||
# frame's own flow/mask/image are handed over untouched, and the 3D pane is
|
||||
# not involved at all (its frame is the design export's).
|
||||
orientation=effective_orientation(
|
||||
self._finger_orientation, draw_style=draw_style
|
||||
),
|
||||
)
|
||||
if rendered is None:
|
||||
# Never substitute another picture here: a missing depth map that showed
|
||||
@@ -2619,6 +2665,7 @@ class StudioMainWindow(QMainWindow):
|
||||
self.prefs["units"] = self.toolbar.units()
|
||||
self.prefs["image_view_mode"] = self.arrow_panel.view_mode()
|
||||
self.prefs["arrow_rotation_deg"] = self.arrow_panel.rotation_deg()
|
||||
self.prefs["finger_2d_orientation"] = self.finger_orientation()
|
||||
self.prefs.setdefault("plot_window_sec", DEFAULT_PLOT_WINDOW_SEC)
|
||||
self.prefs.setdefault("plot_fn_max", DEFAULT_PLOT_FN_MAX)
|
||||
self.prefs.setdefault("plot_shear_max", DEFAULT_PLOT_SHEAR_MAX)
|
||||
|
||||
@@ -11,6 +11,11 @@ reports as done is what the record will say (see ``force/filters.py``).
|
||||
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,
|
||||
@@ -69,9 +74,12 @@ 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._sample_rate_hz = float(sample_rate_hz)
|
||||
#: True while the widgets are being filled in from a preset, so the
|
||||
@@ -153,6 +161,22 @@ class PlotSettingsDialog(QDialog):
|
||||
self.deadband_raw_spin.setDecimals(0)
|
||||
self.deadband_raw_spin.setValue(float(deadband_raw))
|
||||
|
||||
# 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)
|
||||
@@ -164,6 +188,12 @@ class PlotSettingsDialog(QDialog):
|
||||
form.addRow(self.deadband_raw_label, self.deadband_raw_spin)
|
||||
root.addLayout(form)
|
||||
|
||||
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)
|
||||
|
||||
newton = str(units or "newton").lower().startswith("n")
|
||||
raw_widgets = (
|
||||
self.fn_raw_min_spin,
|
||||
@@ -418,6 +448,10 @@ class PlotSettingsDialog(QDialog):
|
||||
"""Everything the plot half of the dialog decides, in one mapping."""
|
||||
return {**self.ranges(), **self.deadbands()}
|
||||
|
||||
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)
|
||||
@@ -426,6 +460,10 @@ class PlotSettingsDialog(QDialog):
|
||||
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)
|
||||
)
|
||||
# "Auto" is the shipped answer for the deadband: the estimate from the
|
||||
# quiet startup window, with the layer's own floor under it.
|
||||
self.deadband_n_spin.setValue(DEFAULT_PLOT_DEADBAND_N)
|
||||
@@ -461,6 +499,13 @@ class PlotSettingsDialog(QDialog):
|
||||
self.deadband_raw_label.setText(t("dialog.plot_deadband_raw"))
|
||||
self.deadband_raw_spin.setToolTip(t("dialog.plot_deadband_raw_tip"))
|
||||
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"))
|
||||
|
||||
Reference in New Issue
Block a user