update exe GUI build
This commit is contained in:
+73
-103
@@ -4,17 +4,13 @@ from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.QtCore import QSize, Qt, QTimer
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import (
|
||||
QApplication,
|
||||
QFileDialog,
|
||||
QHBoxLayout,
|
||||
QLineEdit,
|
||||
QMainWindow,
|
||||
QPushButton,
|
||||
QSplitter,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
@@ -23,6 +19,7 @@ from PyQt5.QtWidgets import (
|
||||
from pipeline.config import PipelineConfig
|
||||
from pipeline.service import FingerPipeline
|
||||
|
||||
from app_paths import resource_path
|
||||
from .layout import (
|
||||
DashboardLayout,
|
||||
LAYOUT,
|
||||
@@ -30,27 +27,19 @@ from .layout import (
|
||||
PanelSetup,
|
||||
top_row_splitter_sizes,
|
||||
)
|
||||
from .mesh_view_state import MeshViewState
|
||||
from .styles import STYLE
|
||||
from .transport_icons import transport_icon
|
||||
from .widgets import (
|
||||
ArrowPanelWidget,
|
||||
ForceChartWidget,
|
||||
MeshViewportWidget,
|
||||
SettingsPanelWidget,
|
||||
TeeStream,
|
||||
WidgetLogHandler,
|
||||
make_log_widget,
|
||||
)
|
||||
|
||||
|
||||
_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."""
|
||||
|
||||
@@ -73,7 +62,7 @@ class FingerDashboardWindow(QMainWindow):
|
||||
self.setWindowTitle(layout.window_title)
|
||||
self.setStyleSheet(STYLE)
|
||||
|
||||
logo_path = Path(__file__).resolve().parents[2] / "logo.png"
|
||||
logo_path = resource_path("logo.png")
|
||||
if logo_path.is_file():
|
||||
self.setWindowIcon(QIcon(str(logo_path)))
|
||||
|
||||
@@ -83,10 +72,6 @@ class FingerDashboardWindow(QMainWindow):
|
||||
self.timer = QTimer()
|
||||
self.timer.timeout.connect(self._on_timer)
|
||||
|
||||
self.camera_edit.setText(str(config.video))
|
||||
self.config_edit.setText(config.config)
|
||||
self.cal_edit.setText(config.calib or "")
|
||||
|
||||
def _setup_ui(self) -> None:
|
||||
layout = self.layout
|
||||
central = QWidget()
|
||||
@@ -95,42 +80,10 @@ class FingerDashboardWindow(QMainWindow):
|
||||
root_layout.setContentsMargins(layout.root_margin_px, layout.root_margin_px, layout.root_margin_px, layout.root_margin_px)
|
||||
root_layout.setSpacing(layout.root_spacing_px)
|
||||
|
||||
bar = QHBoxLayout()
|
||||
bar.setSpacing(layout.toolbar_spacing_px)
|
||||
|
||||
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("Camera ID or Video Path")
|
||||
bar.addWidget(self.camera_edit, 1)
|
||||
|
||||
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")
|
||||
bar.addWidget(self.config_edit, 1)
|
||||
|
||||
self.cal_edit = QLineEdit("")
|
||||
bar.addWidget(self.cal_edit, 1)
|
||||
|
||||
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_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.mesh_viewport = MeshViewportWidget(layout=layout)
|
||||
self._log_panel_visible = setup.log_panel.enabled
|
||||
self.mesh_state = MeshViewState.from_layout(layout)
|
||||
self.mesh_viewport = MeshViewportWidget(layout=layout, mesh_state=self.mesh_state)
|
||||
arrows_slot = layout.slot("arrows")
|
||||
self.arrow_panel: ArrowPanelWidget | None = None
|
||||
if arrows_slot is not None and arrows_slot.enabled:
|
||||
@@ -141,53 +94,91 @@ class FingerDashboardWindow(QMainWindow):
|
||||
)
|
||||
self.force_charts = ForceChartWidget(layout=layout, lowpass_cutoff_hz=self.config.lowpass_cutoff_hz)
|
||||
self.log_widget = make_log_widget(layout.log_config)
|
||||
self.settings_panel = SettingsPanelWidget(
|
||||
layout=layout,
|
||||
mesh_state=self.mesh_state,
|
||||
show_log=self._log_panel_visible,
|
||||
)
|
||||
self.settings_panel.set_video_source(str(self.config.video))
|
||||
self.settings_panel.set_config_path(self.config.config)
|
||||
self.settings_panel.set_calibration_path(self.config.calib or "")
|
||||
|
||||
self.settings_panel.log_visibility_changed.connect(self._set_log_panel_visible)
|
||||
self.settings_panel.free_rotation_changed.connect(self.mesh_viewport.set_free_rotation)
|
||||
self.settings_panel.view_reset_requested.connect(self.mesh_viewport.reset_view)
|
||||
self.settings_panel.browse_video_requested.connect(self._select_camera)
|
||||
self.settings_panel.browse_config_requested.connect(self._select_config)
|
||||
self.settings_panel.btn_play_pause.clicked.connect(self._on_play_pause_clicked)
|
||||
self.settings_panel.btn_reset_tracker.clicked.connect(self._on_reset)
|
||||
self.settings_panel.set_play_pause_state("run")
|
||||
|
||||
self._panel_widgets: dict[str, QWidget] = {}
|
||||
self._panel_widgets["mesh"] = self.mesh_viewport
|
||||
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["settings"] = self.settings_panel
|
||||
self._panel_widgets["log"] = self.log_widget
|
||||
|
||||
# Top row: mesh | arrows | charts (order from layout.PANEL_SETUP.top_row)
|
||||
self.charts_column = QSplitter(Qt.Vertical)
|
||||
self.charts_column.addWidget(self.force_charts.widget)
|
||||
if setup.log_panel.enabled:
|
||||
self.charts_column.addWidget(self.log_widget)
|
||||
self.log_widget.setVisible(self._log_panel_visible)
|
||||
self.charts_column.setStretchFactor(0, setup.charts_log_stretch)
|
||||
self.charts_column.setStretchFactor(1, setup.log_stretch)
|
||||
self.charts_column.setChildrenCollapsible(False)
|
||||
|
||||
h_splitter = QSplitter(Qt.Horizontal)
|
||||
for slot in setup.top_row.slots:
|
||||
if not slot.enabled:
|
||||
continue
|
||||
h_splitter.addWidget(self._panel_widgets[slot.id])
|
||||
if slot.id == "charts":
|
||||
h_splitter.addWidget(self.charts_column)
|
||||
else:
|
||||
h_splitter.addWidget(self._panel_widgets[slot.id])
|
||||
h_splitter.setStretchFactor(h_splitter.count() - 1, slot.stretch)
|
||||
h_splitter.setChildrenCollapsible(setup.top_row.collapsible)
|
||||
self.h_splitter = h_splitter
|
||||
|
||||
# Vertical: top panel | log panel
|
||||
v_splitter = QSplitter(Qt.Vertical)
|
||||
v_splitter.addWidget(h_splitter)
|
||||
if setup.log_panel.enabled:
|
||||
v_splitter.addWidget(self.log_widget)
|
||||
v_splitter.setStretchFactor(0, setup.top_stretch)
|
||||
v_splitter.setStretchFactor(1, setup.log_stretch)
|
||||
else:
|
||||
v_splitter.setStretchFactor(0, 1)
|
||||
self.v_splitter = v_splitter
|
||||
|
||||
root_layout.addWidget(v_splitter)
|
||||
root_layout.addWidget(h_splitter)
|
||||
|
||||
self.resize(layout.window_width, layout.window_height)
|
||||
QTimer.singleShot(0, self._apply_panel_layout)
|
||||
|
||||
def _set_log_panel_visible(self, visible: bool) -> None:
|
||||
self._log_panel_visible = bool(visible)
|
||||
self.log_widget.setVisible(self._log_panel_visible)
|
||||
QTimer.singleShot(0, self._apply_panel_layout)
|
||||
|
||||
def _apply_panel_layout(self) -> None:
|
||||
setup = self.layout.panel_setup
|
||||
column_height = self.layout.charts_column_height_px()
|
||||
charts_slot = self.layout.top_slot("charts")
|
||||
log_slot = setup.log_panel
|
||||
|
||||
for slot in setup.top_row.slots:
|
||||
if not slot.enabled:
|
||||
continue
|
||||
if slot.id in ("mesh", "arrows"):
|
||||
self._panel_widgets[slot.id].set_fixed_size(slot.width_px, slot.height_px)
|
||||
elif slot.id == "charts":
|
||||
self.force_charts.set_panel_size(slot.width_px, slot.height_px)
|
||||
self._panel_widgets[slot.id].set_fixed_size(slot.width_px, column_height)
|
||||
elif slot.id == "settings":
|
||||
settings_height = slot.height_px if slot.height_px > 0 else column_height
|
||||
self.settings_panel.set_panel_size(slot.width_px, settings_height)
|
||||
|
||||
if setup.log_panel.enabled and setup.log_panel.height_px > 0:
|
||||
self.log_widget.setFixedHeight(setup.log_panel.height_px)
|
||||
if (
|
||||
self._log_panel_visible
|
||||
and log_slot.enabled
|
||||
and log_slot.height_px > 0
|
||||
):
|
||||
self.log_widget.show()
|
||||
self.log_widget.setFixedHeight(log_slot.height_px)
|
||||
self.force_charts.set_panel_size(charts_slot.width_px, charts_slot.height_px)
|
||||
self.charts_column.setSizes([charts_slot.height_px, log_slot.height_px])
|
||||
else:
|
||||
self.log_widget.hide()
|
||||
self.force_charts.set_panel_size(charts_slot.width_px, column_height)
|
||||
self.charts_column.setSizes([column_height, 0])
|
||||
|
||||
self.h_splitter.setSizes(list(top_row_splitter_sizes(self.layout)))
|
||||
|
||||
@@ -209,28 +200,7 @@ class FingerDashboardWindow(QMainWindow):
|
||||
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()
|
||||
self.settings_panel.set_play_pause_state(state)
|
||||
|
||||
def _on_play_pause_clicked(self) -> None:
|
||||
if self._running:
|
||||
@@ -244,9 +214,9 @@ class FingerDashboardWindow(QMainWindow):
|
||||
self._set_play_pause_button("starting")
|
||||
QApplication.processEvents()
|
||||
|
||||
self.config.video = self.camera_edit.text().strip()
|
||||
self.config.config = self.config_edit.text().strip()
|
||||
cal = self.cal_edit.text().strip()
|
||||
self.config.video = self.settings_panel.video_source()
|
||||
self.config.config = self.settings_panel.config_path()
|
||||
cal = self.settings_panel.calibration_path()
|
||||
self.config.calib = cal or None
|
||||
|
||||
try:
|
||||
@@ -266,7 +236,7 @@ class FingerDashboardWindow(QMainWindow):
|
||||
self._wall_fps = 0.0
|
||||
self.timer.start(self.layout.timer_interval_ms)
|
||||
self._set_play_pause_button("pause")
|
||||
self.btn_reset.setEnabled(True)
|
||||
self.settings_panel.btn_reset_tracker.setEnabled(True)
|
||||
print(
|
||||
f"▶ 启动 — 摄像机={self.config.video}, 配置={self.config.config}, "
|
||||
f"标定={self.config.calib or '(none)'}"
|
||||
@@ -285,7 +255,7 @@ class FingerDashboardWindow(QMainWindow):
|
||||
self.pipeline.close()
|
||||
|
||||
self._set_play_pause_button("run")
|
||||
self.btn_reset.setEnabled(False)
|
||||
self.settings_panel.btn_reset_tracker.setEnabled(False)
|
||||
|
||||
def _on_reset(self) -> None:
|
||||
if not self._running:
|
||||
@@ -371,7 +341,7 @@ class FingerDashboardWindow(QMainWindow):
|
||||
"视频 (*.mp4 *.avi *.mov);;所有文件 (*)",
|
||||
)
|
||||
if path:
|
||||
self.camera_edit.setText(path)
|
||||
self.settings_panel.set_video_source(path)
|
||||
|
||||
def _select_config(self) -> None:
|
||||
path, _ = QFileDialog.getOpenFileName(
|
||||
@@ -381,7 +351,7 @@ class FingerDashboardWindow(QMainWindow):
|
||||
"JSON (*.json);;所有文件 (*)",
|
||||
)
|
||||
if path:
|
||||
self.config_edit.setText(path)
|
||||
self.settings_panel.set_config_path(path)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
key = event.key()
|
||||
|
||||
Reference in New Issue
Block a user