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(),
)
|