把 Studio 主机协议改成单向端口,并让「从视频重算」走分析入口

控制器经 AcquireGate / ImageSink / ShellNotes 与信号接线,采集 tick 用 acquire_presenter 上屏;三维测试证明分析结果已喂 3D 后,重算并入 on_analysis_requested。
验证:apps/studio 下相关 pytest 与 tools/ui_check.py(634 checks)通过。
This commit is contained in:
ykcheungab
2026-10-05 14:00:24 +08:00
parent 25ccffd814
commit 28e955a817
27 changed files with 1764 additions and 745 deletions
@@ -18,6 +18,11 @@ from datetime import datetime
from pathlib import Path
from orisys_app_shared.i18n import t
from orisys_app_shared.sensors import (
SHAPE_COMPATIBILITY,
SHAPE_DESCRIPTOR,
SHAPE_UNREADABLE,
)
from PySide6.QtWidgets import (
QDialog,
QFrame,
@@ -29,6 +34,14 @@ from PySide6.QtWidgets import (
from orisys_studio.ui.fit_text import ShrinkableLabel
#: How the catalog's shape identifiers read here. One literal key per kind, so the locale scan
#: sees them and a new kind is a visible fallback rather than a printed identifier.
_SHAPE_KEYS: dict[str, str] = {
SHAPE_DESCRIPTOR: "sensor.catalog_shape_descriptor",
SHAPE_COMPATIBILITY: "sensor.catalog_shape_compatibility",
SHAPE_UNREADABLE: "sensor.catalog_shape_unreadable",
}
class SensorDialog(QDialog):
"""Sensor type / config picker plus the calibration facts that follow it."""
@@ -190,8 +203,10 @@ class SensorDialog(QDialog):
def _refresh_profile(self) -> None:
"""State the selected catalog entry, and the reason when it cannot be started.
Read from the picker rather than re-derived here, so the window and the dialog can never
say two different things about one bundle.
This is the surface that keeps the facts the picker row no longer carries: the identity
the SDK assigned, how the bundle was read and its validation. It is read from the picker
rather than re-derived here, so the window and the dialog can never say two different
things about one bundle.
"""
read_entry = getattr(self.picker, "selected_entry", None)
entry = read_entry() if callable(read_entry) else None
@@ -201,8 +216,8 @@ class SensorDialog(QDialog):
return
line = t(
"sensor.catalog_profile",
family=entry.product_family or t("sensor.family_unidentified"),
profile_id=entry.profile_id or entry.folder,
shape=t(_SHAPE_KEYS.get(entry.shape, _SHAPE_KEYS[SHAPE_UNREADABLE])),
origin=entry.origin,
state=entry.state,
)
@@ -9,9 +9,10 @@ own labels and tooltips instead of a bare "配置" combo.
Both combos are filled from the configuration catalog
(:mod:`orisys_app_shared.sensors`), not from a hard-coded product list: the type
combo offers the families bundles actually exist for, and the config combo offers
the catalog's entries for the chosen family — each labelled with its profile,
origin and the SDK's own verdict. An entry the SDK rejects stays listed, because
that is how its reason reaches the operator, and
the catalog's entries for the chosen family — each labelled with what it is, where
it came from and what the calibration selection gives, so the choice is between
products and outcomes rather than between ``profile_id`` strings. An entry the SDK
rejects stays listed, because that is how its reason reaches the operator, and
:meth:`SensorPickerWidget.start_blocked_reason` states why the current selection
must not be started (see also :meth:`selected_entry`).
"""
@@ -238,10 +239,11 @@ def _family_label(family: str) -> str:
def _entry_label(entry: CatalogEntry) -> str:
"""One combo item: the catalog's label with its family group named in this locale.
"""One combo item: the catalog's operator label, with nothing internal in it.
The catalog's own label is the data form (and is what a log or a test should compare); the
internal marker for "no product family" is replaced here, where translating belongs, so an
operator never reads a sentinel.
The catalog names an entry by its display name, so the internal marker for "no product
family" cannot reach the row today; it is still substituted here because a sentinel is
never something an operator should read, and the item text is the one place it could
surface if an entry ever had to fall back to its family.
"""
return entry.label().replace(FAMILY_UNIDENTIFIED, t("sensor.family_unidentified"))