summaryrefslogtreecommitdiff
path: root/plugins/motion_capture/camera_panel.py
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/motion_capture/camera_panel.py')
-rw-r--r--plugins/motion_capture/camera_panel.py132
1 files changed, 132 insertions, 0 deletions
diff --git a/plugins/motion_capture/camera_panel.py b/plugins/motion_capture/camera_panel.py
new file mode 100644
index 0000000..611c942
--- /dev/null
+++ b/plugins/motion_capture/camera_panel.py
@@ -0,0 +1,132 @@
+"""
+motion_capture/camera_panel.py
+
+CameraPanel for AddDeviceDialog β€” scans for OpenCV cameras and builds
+a CameraDevice when the user clicks "Add Device".
+"""
+
+from __future__ import annotations
+
+from PyQt6.QtCore import Qt, QThread, pyqtSignal
+from PyQt6.QtWidgets import (
+ QCheckBox, QGroupBox, QHBoxLayout, QLabel, QListWidget,
+ QListWidgetItem, QPushButton, QVBoxLayout, QWidget,
+)
+
+
+class CameraScanThread(QThread):
+ cameras_found = pyqtSignal(list) # list of (index: int, label: str)
+
+ def run(self):
+ results = []
+ try:
+ import cv2
+ for i in range(6):
+ cap = cv2.VideoCapture(i)
+ if cap.isOpened():
+ ret, _ = cap.read()
+ if ret:
+ w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
+ h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
+ results.append((i, f"Camera {i} ({w}Γ—{h})"))
+ cap.release()
+ except ImportError:
+ pass
+ self.cameras_found.emit(results)
+
+
+class CameraPanel(QWidget):
+ """Config panel for Camera device in AddDeviceDialog."""
+
+ def __init__(self):
+ super().__init__()
+ self._cameras: list = []
+ self._selected_index: int = 0
+ self._scanner = None
+
+ lay = QVBoxLayout(self)
+ lay.setContentsMargins(0, 4, 0, 4)
+ lay.setSpacing(8)
+
+ self.sim_chk = QCheckBox("Simulation mode (Lissajous pattern, no camera needed)")
+ self.sim_chk.setChecked(False)
+ lay.addWidget(self.sim_chk)
+
+ scan_grp = QGroupBox("Available Cameras")
+ sg = QVBoxLayout(scan_grp)
+ sg.setSpacing(4)
+
+ top = QHBoxLayout()
+ self._scan_btn = QPushButton("πŸ” Scan Cameras")
+ self._scan_btn.setObjectName("addTraceBtn")
+ self._scan_btn.clicked.connect(self._scan)
+ self._status = QLabel("Click Scan to detect cameras")
+ self._status.setObjectName("traceSource")
+ top.addWidget(self._scan_btn)
+ top.addWidget(self._status, 1)
+ sg.addLayout(top)
+
+ self._list = QListWidget()
+ self._list.setMaximumHeight(120)
+ self._list.setObjectName("portList")
+ self._list.itemClicked.connect(self._on_select)
+ self._list.setToolTip("Click a camera to select it")
+ sg.addWidget(self._list)
+
+ hint = QLabel("↑ Click a camera above to select it")
+ hint.setObjectName("traceSource")
+ sg.addWidget(hint)
+
+ lay.addWidget(scan_grp)
+
+ note = QLabel(
+ "Requires OpenCV: pip install opencv-python\n"
+ "Use Simulation mode if no camera is available."
+ )
+ note.setObjectName("traceSource")
+ note.setWordWrap(True)
+ lay.addWidget(note)
+ lay.addStretch()
+
+ # ── Scan ─────────────────────────────────────────────────────────────
+
+ def _scan(self):
+ self._scan_btn.setEnabled(False)
+ self._status.setText("Scanning…")
+ self._list.clear()
+ self._scanner = CameraScanThread()
+ self._scanner.cameras_found.connect(self._on_found)
+ self._scanner.start()
+
+ def _on_found(self, cameras):
+ self._scan_btn.setEnabled(True)
+ self._cameras = cameras
+ self._list.clear()
+ if not cameras:
+ self._status.setText("No cameras found")
+ item = QListWidgetItem(" No cameras detected")
+ item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsSelectable)
+ self._list.addItem(item)
+ else:
+ self._status.setText(f"{len(cameras)} camera(s) found")
+ for idx, label in cameras:
+ item = QListWidgetItem(f" {label}")
+ item.setData(Qt.ItemDataRole.UserRole, idx)
+ self._list.addItem(item)
+ self._list.setCurrentRow(0)
+ self._selected_index = cameras[0][0]
+
+ def _on_select(self, item: QListWidgetItem):
+ idx = item.data(Qt.ItemDataRole.UserRole)
+ if idx is not None:
+ self._selected_index = idx
+
+ # ── Build ─────────────────────────────────────────────────────────────
+
+ def build_device(self, device_id: str):
+ from device import CameraDevice
+ return CameraDevice(
+ device_id=device_id,
+ camera_index=self._selected_index,
+ simulate=self.sim_chk.isChecked(),
+ )