diff options
Diffstat (limited to 'plugins/motion_capture/camera_panel.py')
| -rw-r--r-- | plugins/motion_capture/camera_panel.py | 132 |
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(), + ) |
