added mesh surface arrows

This commit is contained in:
ykcheungab
2026-06-19 21:22:28 +08:00
parent 2e5c047d1e
commit 258948bcb7
20 changed files with 951 additions and 194 deletions
+72 -33
View File
@@ -6,7 +6,7 @@ import sys
import time
from pathlib import Path
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtCore import QSize, Qt, QTimer
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import (
QApplication,
@@ -31,6 +31,7 @@ from .layout import (
top_row_splitter_sizes,
)
from .styles import STYLE
from .transport_icons import transport_icon
from .widgets import (
ArrowPanelWidget,
ForceChartWidget,
@@ -41,6 +42,15 @@ from .widgets import (
)
_PLAY_PAUSE_BUTTON_NAME = "btnPlayPause"
_PLAY_PAUSE_ICON_PX = 18
_PLAY_PAUSE_ICON_COLORS = {
"run": "#4caf84",
"starting": "#555555",
"pause": "#ff9800",
}
class FingerDashboardWindow(QMainWindow):
"""Single-window finger demo: 3D mesh | UV arrows | force charts."""
@@ -88,14 +98,14 @@ class FingerDashboardWindow(QMainWindow):
bar = QHBoxLayout()
bar.setSpacing(layout.toolbar_spacing_px)
self.btn_camera = QPushButton("摄像机")
self.btn_camera = QPushButton("Sensor")
self.btn_camera.clicked.connect(self._select_camera)
bar.addWidget(self.btn_camera)
self.camera_edit = QLineEdit("0")
self.camera_edit.setPlaceholderText("摄像头编号 或 视频路径")
self.camera_edit.setPlaceholderText("Camera ID or Video Path")
bar.addWidget(self.camera_edit, 1)
self.btn_config = QPushButton("配置")
self.btn_config = QPushButton("Config")
self.btn_config.clicked.connect(self._select_config)
bar.addWidget(self.btn_config)
self.config_edit = QLineEdit("./config/dd02-ov.json")
@@ -104,34 +114,38 @@ class FingerDashboardWindow(QMainWindow):
self.cal_edit = QLineEdit("")
bar.addWidget(self.cal_edit, 1)
self.btn_reset = QPushButton("重置")
self.btn_reset = QPushButton("Reset")
self.btn_reset.setEnabled(False)
self.btn_reset.clicked.connect(self._on_reset)
bar.addWidget(self.btn_reset)
self.btn_start = QPushButton("启动")
self.btn_start.setObjectName("btnStart")
self.btn_start.clicked.connect(self._on_start)
bar.addWidget(self.btn_start)
self.btn_stop = QPushButton("暂停")
self.btn_stop.setObjectName("btnStop")
self.btn_stop.setEnabled(False)
self.btn_stop.clicked.connect(self._on_stop)
bar.addWidget(self.btn_stop)
self.btn_play_pause = QPushButton()
self.btn_play_pause.setObjectName(_PLAY_PAUSE_BUTTON_NAME)
self.btn_play_pause.setFixedSize(36, 32)
self.btn_play_pause.setIconSize(QSize(_PLAY_PAUSE_ICON_PX, _PLAY_PAUSE_ICON_PX))
self.btn_play_pause.clicked.connect(self._on_play_pause_clicked)
self._set_play_pause_button("run")
bar.addWidget(self.btn_play_pause)
root_layout.addLayout(bar)
setup = layout.panel_setup
self._panel_widgets: dict[str, QWidget] = {}
self.mesh_viewport = MeshViewportWidget(layout=layout)
self.arrow_panel = ArrowPanelWidget(flow_threshold=layout.arrow_flow_threshold, layout=layout)
arrows_slot = layout.slot("arrows")
self.arrow_panel: ArrowPanelWidget | None = None
if arrows_slot is not None and arrows_slot.enabled:
self.arrow_panel = ArrowPanelWidget(
flow_threshold=layout.arrow_flow_threshold,
background=layout.arrow_background,
layout=layout,
)
self.force_charts = ForceChartWidget(layout=layout, lowpass_cutoff_hz=self.config.lowpass_cutoff_hz)
self.log_widget = make_log_widget(layout.log_config)
self._panel_widgets: dict[str, QWidget] = {}
self._panel_widgets["mesh"] = self.mesh_viewport
self._panel_widgets["arrows"] = self.arrow_panel
if self.arrow_panel is not None:
self._panel_widgets["arrows"] = self.arrow_panel
self._panel_widgets["charts"] = self.force_charts.widget
self._panel_widgets["log"] = self.log_widget
@@ -194,11 +208,40 @@ class FingerDashboardWindow(QMainWindow):
sys.stdout = TeeStream(*streams)
sys.stderr = sys.stdout
def _set_play_pause_button(self, state: str) -> None:
"""``run`` = green play, ``starting`` = grey disabled play, ``pause`` = orange pause."""
btn = self.btn_play_pause
if state == "run":
btn.setToolTip("Run")
btn.setEnabled(True)
elif state == "starting":
btn.setToolTip("Starting…")
btn.setEnabled(False)
elif state == "pause":
btn.setToolTip("Pause")
btn.setEnabled(True)
else:
raise ValueError(f"unknown play/pause state: {state!r}")
kind = "pause" if state == "pause" else "play"
btn.setText("")
btn.setIcon(transport_icon(kind, _PLAY_PAUSE_ICON_COLORS[state]))
btn.setProperty("playState", state)
style = btn.style()
style.unpolish(btn)
style.polish(btn)
btn.update()
def _on_play_pause_clicked(self) -> None:
if self._running:
self._on_stop()
else:
self._on_start()
def _on_start(self) -> None:
if self._running:
return
self.btn_start.setText("启动中...")
self.btn_start.setEnabled(False)
self._set_play_pause_button("starting")
QApplication.processEvents()
self.config.video = self.camera_edit.text().strip()
@@ -214,8 +257,7 @@ class FingerDashboardWindow(QMainWindow):
QTimer.singleShot(0, self._apply_panel_layout)
except Exception as exc:
self.mesh_viewport.close_renderer()
self.btn_start.setText("启动")
self.btn_start.setEnabled(True)
self._set_play_pause_button("run")
print(f"启动失败: {exc}")
return
@@ -223,8 +265,7 @@ class FingerDashboardWindow(QMainWindow):
self._frame_index = 0
self._wall_fps = 0.0
self.timer.start(self.layout.timer_interval_ms)
self.btn_start.setText("启动")
self.btn_stop.setEnabled(True)
self._set_play_pause_button("pause")
self.btn_reset.setEnabled(True)
print(
f"▶ 启动 — 摄像机={self.config.video}, 配置={self.config.config}, "
@@ -237,17 +278,14 @@ class FingerDashboardWindow(QMainWindow):
self._running = False
self.timer.stop()
self.log_handler.clear_status_line()
self.btn_stop.setText("停止中...")
self.btn_stop.setEnabled(False)
self._set_play_pause_button("starting")
QApplication.processEvents()
self.mesh_viewport.close_renderer()
self.pipeline.close()
self.btn_start.setEnabled(True)
self.btn_stop.setText("暂停")
self._set_play_pause_button("run")
self.btn_reset.setEnabled(False)
print("⏸ 已暂停")
def _on_reset(self) -> None:
if not self._running:
@@ -278,9 +316,10 @@ class FingerDashboardWindow(QMainWindow):
profile.record("mesh", mesh_ms)
arrows_ms = 0.0
t0 = time.perf_counter()
self.arrow_panel.update_frame(frame)
arrows_ms = (time.perf_counter() - t0) * 1000.0
if self.arrow_panel is not None:
t0 = time.perf_counter()
self.arrow_panel.update_frame(frame)
arrows_ms = (time.perf_counter() - t0) * 1000.0
if profile is not None:
profile.record("arrows", arrows_ms)