summaryrefslogtreecommitdiff
path: root/plugins/motion_capture/window.py
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/motion_capture/window.py')
-rw-r--r--plugins/motion_capture/window.py47
1 files changed, 40 insertions, 7 deletions
diff --git a/plugins/motion_capture/window.py b/plugins/motion_capture/window.py
index 46b8850..328c61b 100644
--- a/plugins/motion_capture/window.py
+++ b/plugins/motion_capture/window.py
@@ -16,7 +16,7 @@ from typing import Optional, Tuple
import numpy as np
-from PyQt6.QtCore import Qt, QPointF, QTimer, pyqtSignal
+from PyQt6.QtCore import Qt, QPointF, QTimer, QThread, pyqtSignal
from PyQt6.QtGui import (
QCloseEvent, QColor, QFont, QImage, QMouseEvent,
QPainter, QPen, QPixmap,
@@ -27,6 +27,17 @@ from PyQt6.QtWidgets import (
)
+# ── Background camera enumeration ───────────────────────────────────────────
+
+class _CameraListThread(QThread):
+ """Runs tracker.list_cameras() off the GUI thread — see _scan_cameras."""
+ done = pyqtSignal(list)
+
+ def run(self):
+ from tracker import list_cameras
+ self.done.emit(list_cameras())
+
+
# ── Frame display + rubber-band selection widget ──────────────────────────────
class FrameSelector(QWidget):
@@ -262,7 +273,8 @@ class MotionCaptureWindow(QWidget):
self._refresh = QTimer(self)
self._refresh.timeout.connect(self._update)
- self._refresh.start(33)
+ self._refresh.start(100) # 10 Hz — a position readout doesn't need 30fps,
+ # and this is a full-frame copy + repaint on the GUI thread
# ── UI ────────────────────────────────────────────────────────────────
@@ -377,15 +389,31 @@ class MotionCaptureWindow(QWidget):
# ── Actions ───────────────────────────────────────────────────────────
def _scan_cameras(self):
- from tracker import list_cameras
- current_idx = self.get_camera_index()
+ # list_cameras() tries cv2.VideoCapture() on several indices
+ # sequentially — each one can be slow to fail (backend/driver
+ # probing), so this used to run inline here and block the whole
+ # app while the window opened. Run it on a background thread instead
+ # (same pattern camera_panel.py's CameraScanThread already uses for
+ # the Add Device dialog's scan).
+ self._pending_cam_idx = self.get_camera_index()
+ self._refresh_btn.setEnabled(False)
self._cam_combo.blockSignals(True)
self._cam_combo.clear()
- self._cameras = list_cameras()
+ self._cam_combo.addItem("Scanning…")
+ self._cam_combo.blockSignals(False)
+ self._scan_thread = _CameraListThread(self)
+ self._scan_thread.done.connect(self._on_cameras_scanned)
+ self._scan_thread.start()
+
+ def _on_cameras_scanned(self, cameras: list):
+ self._refresh_btn.setEnabled(True)
+ self._cam_combo.blockSignals(True)
+ self._cam_combo.clear()
+ self._cameras = cameras
for _, label in self._cameras:
self._cam_combo.addItem(label)
for i, (idx, _) in enumerate(self._cameras):
- if idx == current_idx:
+ if idx == self._pending_cam_idx:
self._cam_combo.setCurrentIndex(i)
break
self._cam_combo.blockSignals(False)
@@ -466,9 +494,14 @@ class MotionCaptureWindow(QWidget):
# ── Display update ─────────────────────────────────────────────────────
def _update(self):
- if not self._connected:
+ if not self._connected or not self.isVisible():
return
+ # simulated flips async now (camera opens on the tracker's background
+ # thread, see CameraTracker._open_and_run) — keep the badge in sync
+ # instead of reading a value that was still stale at Connect-time.
+ self._sim_badge.setVisible(self._tracker.simulated)
+
if not self._selecting:
frame = self._tracker.get_frame()
if frame is not None: