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
|
"""
motion_capture/camera_panel.py
CameraPanel for AddDeviceDialog.
Camera discovery and simulation toggle are handled by AddDeviceDialog.
CameraScanThread is kept here and used by the unified scanner.
"""
from __future__ import annotations
from typing import Optional, Tuple
from PyQt6.QtCore import QThread, pyqtSignal
from PyQt6.QtWidgets import QComboBox, QFormLayout, QLabel, QVBoxLayout, QWidget
_RESOLUTIONS = {
"Default": None,
"640 × 480": (640, 480),
"1280 × 720": (1280, 720),
"1920 × 1080": (1920, 1080),
"2560 × 1440": (2560, 1440),
}
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._selected_index: int = 0
self._simulate: bool = False
lay = QVBoxLayout(self)
lay.setContentsMargins(0, 4, 0, 4)
lay.setSpacing(8)
self._selected_lbl = QLabel("No camera selected — scan above and click a camera")
self._selected_lbl.setObjectName("traceSource")
self._selected_lbl.setWordWrap(True)
lay.addWidget(self._selected_lbl)
form = QFormLayout()
form.setContentsMargins(0, 0, 0, 0)
self.res_cb = QComboBox()
self.res_cb.addItems(list(_RESOLUTIONS.keys()))
form.addRow("Resolution:", self.res_cb)
lay.addLayout(form)
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()
def is_valid(self) -> bool:
return True
def set_simulate(self, simulate: bool):
self._simulate = simulate
def set_camera_index(self, index: int):
self._selected_index = index
self._selected_lbl.setText(f"Selected: Camera {index}")
def build_device(self, device_id: str):
from device import CameraDevice
resolution: Optional[Tuple[int, int]] = _RESOLUTIONS.get(
self.res_cb.currentText()
)
return CameraDevice(
device_id=device_id,
camera_index=self._selected_index,
simulate=self._simulate,
resolution=resolution,
)
|