summaryrefslogtreecommitdiff
path: root/plugins/motion_capture/camera_panel.py
blob: 611c942afba674282064496974d7cc2693029cc2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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(),
        )