引入非阻塞主线
This commit is contained in:
@@ -405,6 +405,72 @@ def _find_profile_exact(profile_list, cfg: dict):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────────────────────────
|
||||||
|
# Refresh Devices Worker Thread
|
||||||
|
# ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class RefreshDevicesWorker(QThread):
|
||||||
|
done = pyqtSignal(dict) # result dict on success
|
||||||
|
error = pyqtSignal(str) # error message on failure
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
from pyorbbecsdk import Pipeline, OBSensorType
|
||||||
|
p = Pipeline()
|
||||||
|
|
||||||
|
sn = "unknown"
|
||||||
|
try:
|
||||||
|
sn = p.get_device().get_device_info().get_serial_number()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
color_items = []
|
||||||
|
color_list = p.get_stream_profile_list(OBSensorType.COLOR_SENSOR)
|
||||||
|
for i in range(color_list.get_count()):
|
||||||
|
vp = color_list.get_stream_profile_by_index(i).as_video_stream_profile()
|
||||||
|
if vp is None:
|
||||||
|
continue
|
||||||
|
fmt = str(vp.get_format()).split(".")[-1]
|
||||||
|
cfg = {"width": vp.get_width(), "height": vp.get_height(),
|
||||||
|
"fps": vp.get_fps(), "format": fmt}
|
||||||
|
label = f"{vp.get_width()}×{vp.get_height()} @ {vp.get_fps()} fps [{fmt}]"
|
||||||
|
color_items.append((label, cfg))
|
||||||
|
|
||||||
|
depth_items = []
|
||||||
|
depth_list = p.get_stream_profile_list(OBSensorType.DEPTH_SENSOR)
|
||||||
|
for i in range(depth_list.get_count()):
|
||||||
|
vp = depth_list.get_stream_profile_by_index(i).as_video_stream_profile()
|
||||||
|
if vp is None:
|
||||||
|
continue
|
||||||
|
fmt = str(vp.get_format()).split(".")[-1]
|
||||||
|
cfg = {"width": vp.get_width(), "height": vp.get_height(),
|
||||||
|
"fps": vp.get_fps(), "format": fmt}
|
||||||
|
label = f"{vp.get_width()}×{vp.get_height()} @ {vp.get_fps()} fps [{fmt}]"
|
||||||
|
depth_items.append((label, cfg))
|
||||||
|
|
||||||
|
preset_items = []
|
||||||
|
current_preset = None
|
||||||
|
try:
|
||||||
|
preset_list = p.get_device().get_available_preset_list()
|
||||||
|
current_preset = p.get_device().get_current_preset_name()
|
||||||
|
if preset_list and len(preset_list) > 0:
|
||||||
|
preset_items = [("── 不切换 ──", None)] + [
|
||||||
|
(preset_list[i], preset_list[i]) for i in range(len(preset_list))
|
||||||
|
]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.done.emit({
|
||||||
|
"sn": sn,
|
||||||
|
"color_items": color_items,
|
||||||
|
"depth_items": depth_items,
|
||||||
|
"preset_items": preset_items,
|
||||||
|
"current_preset": current_preset,
|
||||||
|
})
|
||||||
|
except Exception as exc:
|
||||||
|
self.error.emit(str(exc))
|
||||||
|
|
||||||
|
|
||||||
# ─────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────
|
||||||
# Camera Worker Thread
|
# Camera Worker Thread
|
||||||
# ─────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────
|
||||||
@@ -460,6 +526,9 @@ class CameraWorker(QThread):
|
|||||||
self._running = False
|
self._running = False
|
||||||
self.wait(6000)
|
self.wait(6000)
|
||||||
|
|
||||||
|
def request_stop(self):
|
||||||
|
self._running = False
|
||||||
|
|
||||||
# ── Thread body ──
|
# ── Thread body ──
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
@@ -1031,6 +1100,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._worker: CameraWorker | None = None
|
self._worker: CameraWorker | None = None
|
||||||
self._batch_worker: BatchD2CWorker | None = None
|
self._batch_worker: BatchD2CWorker | None = None
|
||||||
self._fetch_worker: FetchParamsWorker | None = None
|
self._fetch_worker: FetchParamsWorker | None = None
|
||||||
|
self._refresh_worker: RefreshDevicesWorker | None = None
|
||||||
self._session_dir: Path | None = None
|
self._session_dir: Path | None = None
|
||||||
self._params_filename = "camera_params.json"
|
self._params_filename = "camera_params.json"
|
||||||
self._color_profiles_data: list[dict] = []
|
self._color_profiles_data: list[dict] = []
|
||||||
@@ -1145,10 +1215,10 @@ class MainWindow(QMainWindow):
|
|||||||
grp1 = self._step_group(1, "刷新设备")
|
grp1 = self._step_group(1, "刷新设备")
|
||||||
g1 = QHBoxLayout(grp1)
|
g1 = QHBoxLayout(grp1)
|
||||||
g1.setContentsMargins(8, 4, 8, 6)
|
g1.setContentsMargins(8, 4, 8, 6)
|
||||||
refresh_btn = QPushButton("刷新设备")
|
self.refresh_btn = QPushButton("刷新设备")
|
||||||
refresh_btn.setFixedHeight(_sz(28))
|
self.refresh_btn.setFixedHeight(_sz(28))
|
||||||
refresh_btn.clicked.connect(self._refresh_devices)
|
self.refresh_btn.clicked.connect(self._refresh_devices)
|
||||||
g1.addWidget(refresh_btn)
|
g1.addWidget(self.refresh_btn)
|
||||||
self.device_status = QLabel("未连接设备,请点击「刷新设备」")
|
self.device_status = QLabel("未连接设备,请点击「刷新设备」")
|
||||||
self.device_status.setStyleSheet(f"color:#aaa; font-size:{_fs(12)}px;")
|
self.device_status.setStyleSheet(f"color:#aaa; font-size:{_fs(12)}px;")
|
||||||
g1.addWidget(self.device_status)
|
g1.addWidget(self.device_status)
|
||||||
@@ -1375,10 +1445,10 @@ class MainWindow(QMainWindow):
|
|||||||
grp1 = self._step_group(1, "刷新设备")
|
grp1 = self._step_group(1, "刷新设备")
|
||||||
g1 = QHBoxLayout(grp1)
|
g1 = QHBoxLayout(grp1)
|
||||||
g1.setContentsMargins(8, 4, 8, 6)
|
g1.setContentsMargins(8, 4, 8, 6)
|
||||||
d2c_refresh_btn = QPushButton("刷新设备")
|
self.d2c_refresh_btn = QPushButton("刷新设备")
|
||||||
d2c_refresh_btn.setFixedHeight(_sz(28))
|
self.d2c_refresh_btn.setFixedHeight(_sz(28))
|
||||||
d2c_refresh_btn.clicked.connect(self._refresh_devices)
|
self.d2c_refresh_btn.clicked.connect(self._refresh_devices)
|
||||||
g1.addWidget(d2c_refresh_btn)
|
g1.addWidget(self.d2c_refresh_btn)
|
||||||
self.d2c_device_status = QLabel("未连接设备,请点击「刷新设备」")
|
self.d2c_device_status = QLabel("未连接设备,请点击「刷新设备」")
|
||||||
self.d2c_device_status.setStyleSheet(f"color:#aaa; font-size:{_fs(12)}px;")
|
self.d2c_device_status.setStyleSheet(f"color:#aaa; font-size:{_fs(12)}px;")
|
||||||
g1.addWidget(self.d2c_device_status)
|
g1.addWidget(self.d2c_device_status)
|
||||||
@@ -1500,10 +1570,8 @@ class MainWindow(QMainWindow):
|
|||||||
# ─────────────────────────────────
|
# ─────────────────────────────────
|
||||||
|
|
||||||
def _refresh_devices(self):
|
def _refresh_devices(self):
|
||||||
"""连接相机,枚举并填充所有分辨率下拉框(Tab 1 + Tab 2)。"""
|
if self._refresh_worker and self._refresh_worker.isRunning():
|
||||||
self.device_status.setText("正在扫描设备…")
|
return
|
||||||
QApplication.processEvents()
|
|
||||||
|
|
||||||
self.color_combo.clear()
|
self.color_combo.clear()
|
||||||
self.depth_combo.clear()
|
self.depth_combo.clear()
|
||||||
self.preset_combo.clear()
|
self.preset_combo.clear()
|
||||||
@@ -1511,84 +1579,66 @@ class MainWindow(QMainWindow):
|
|||||||
self.d2c_depth_combo.clear()
|
self.d2c_depth_combo.clear()
|
||||||
self._color_profiles_data.clear()
|
self._color_profiles_data.clear()
|
||||||
self._depth_profiles_data.clear()
|
self._depth_profiles_data.clear()
|
||||||
|
self.refresh_btn.setEnabled(False)
|
||||||
|
self.d2c_refresh_btn.setEnabled(False)
|
||||||
|
for lbl in (self.device_status, self.d2c_device_status):
|
||||||
|
lbl.setText("正在扫描设备…")
|
||||||
|
lbl.setStyleSheet(f"color:#aaa; font-size:{_fs(12)}px;")
|
||||||
|
self._refresh_worker = RefreshDevicesWorker()
|
||||||
|
self._refresh_worker.done.connect(self._on_refresh_done)
|
||||||
|
self._refresh_worker.error.connect(self._on_refresh_error)
|
||||||
|
self._refresh_worker.start()
|
||||||
|
|
||||||
try:
|
def _on_refresh_done(self, result: dict):
|
||||||
from pyorbbecsdk import Pipeline, OBSensorType
|
self.refresh_btn.setEnabled(True)
|
||||||
p = Pipeline()
|
self.d2c_refresh_btn.setEnabled(True)
|
||||||
|
for label, cfg in result["color_items"]:
|
||||||
|
self._color_profiles_data.append(cfg)
|
||||||
|
self.color_combo.addItem(label, cfg)
|
||||||
|
self.d2c_color_combo.addItem(label, cfg)
|
||||||
|
for label, cfg in result["depth_items"]:
|
||||||
|
self._depth_profiles_data.append(cfg)
|
||||||
|
self.depth_combo.addItem(label, cfg)
|
||||||
|
self.d2c_depth_combo.addItem(label, cfg)
|
||||||
|
preset_items = result.get("preset_items", [])
|
||||||
|
current_preset = result.get("current_preset")
|
||||||
|
if preset_items:
|
||||||
|
for label, data in preset_items:
|
||||||
|
self.preset_combo.addItem(label, data)
|
||||||
|
idx = self.preset_combo.findData(current_preset)
|
||||||
|
if idx >= 0:
|
||||||
|
self.preset_combo.setCurrentIndex(idx)
|
||||||
|
self.preset_combo.setEnabled(True)
|
||||||
|
fm = self.preset_combo.fontMetrics()
|
||||||
|
max_w = max(fm.horizontalAdvance(self.preset_combo.itemText(i))
|
||||||
|
for i in range(self.preset_combo.count()))
|
||||||
|
padded = max_w + _sz(48)
|
||||||
|
self.preset_combo.view().setMinimumWidth(padded)
|
||||||
|
self.preset_combo.setMinimumWidth(padded)
|
||||||
|
self.preset_combo.updateGeometry()
|
||||||
|
else:
|
||||||
|
self.preset_combo.addItem("(设备不支持)", None)
|
||||||
|
self.preset_combo.setEnabled(False)
|
||||||
|
sn = result.get("sn", "unknown")
|
||||||
|
nc, nd = len(self._color_profiles_data), len(self._depth_profiles_data)
|
||||||
|
msg = f"已连接 SN: {sn} 彩色 {nc} 项 / 深度 {nd} 项"
|
||||||
|
style = f"color:#4aaa4a; font-size:{_fs(12)}px;"
|
||||||
|
self.device_status.setText(msg)
|
||||||
|
self.device_status.setStyleSheet(style)
|
||||||
|
self.d2c_device_status.setText(msg)
|
||||||
|
self.d2c_device_status.setStyleSheet(style)
|
||||||
|
self._refresh_worker = None
|
||||||
|
|
||||||
sn = "unknown"
|
def _on_refresh_error(self, msg: str):
|
||||||
try:
|
self.refresh_btn.setEnabled(True)
|
||||||
sn = p.get_device().get_device_info().get_serial_number()
|
self.d2c_refresh_btn.setEnabled(True)
|
||||||
except Exception:
|
full = f"未找到设备:{msg}"
|
||||||
pass
|
style = f"color:#cc6666; font-size:{_fs(12)}px;"
|
||||||
|
self.device_status.setText(full)
|
||||||
color_list = p.get_stream_profile_list(OBSensorType.COLOR_SENSOR)
|
self.device_status.setStyleSheet(style)
|
||||||
for i in range(color_list.get_count()):
|
self.d2c_device_status.setText(full)
|
||||||
vp = color_list.get_stream_profile_by_index(i).as_video_stream_profile()
|
self.d2c_device_status.setStyleSheet(style)
|
||||||
if vp is None:
|
self._refresh_worker = None
|
||||||
continue
|
|
||||||
fmt = str(vp.get_format()).split(".")[-1]
|
|
||||||
cfg = {"width": vp.get_width(), "height": vp.get_height(),
|
|
||||||
"fps": vp.get_fps(), "format": fmt}
|
|
||||||
label = f"{vp.get_width()}×{vp.get_height()} @ {vp.get_fps()} fps [{fmt}]"
|
|
||||||
self._color_profiles_data.append(cfg)
|
|
||||||
self.color_combo.addItem(label, cfg)
|
|
||||||
self.d2c_color_combo.addItem(label, cfg)
|
|
||||||
|
|
||||||
depth_list = p.get_stream_profile_list(OBSensorType.DEPTH_SENSOR)
|
|
||||||
for i in range(depth_list.get_count()):
|
|
||||||
vp = depth_list.get_stream_profile_by_index(i).as_video_stream_profile()
|
|
||||||
if vp is None:
|
|
||||||
continue
|
|
||||||
fmt = str(vp.get_format()).split(".")[-1]
|
|
||||||
cfg = {"width": vp.get_width(), "height": vp.get_height(),
|
|
||||||
"fps": vp.get_fps(), "format": fmt}
|
|
||||||
label = f"{vp.get_width()}×{vp.get_height()} @ {vp.get_fps()} fps [{fmt}]"
|
|
||||||
self._depth_profiles_data.append(cfg)
|
|
||||||
self.depth_combo.addItem(label, cfg)
|
|
||||||
self.d2c_depth_combo.addItem(label, cfg)
|
|
||||||
|
|
||||||
# 枚举 preset
|
|
||||||
try:
|
|
||||||
preset_list = p.get_device().get_available_preset_list()
|
|
||||||
current_preset = p.get_device().get_current_preset_name()
|
|
||||||
if preset_list and len(preset_list) > 0:
|
|
||||||
self.preset_combo.addItem("── 不切换 ──", None)
|
|
||||||
for i in range(len(preset_list)):
|
|
||||||
name = preset_list[i]
|
|
||||||
self.preset_combo.addItem(name, name)
|
|
||||||
# 选中当前 preset
|
|
||||||
idx = self.preset_combo.findData(current_preset)
|
|
||||||
if idx >= 0:
|
|
||||||
self.preset_combo.setCurrentIndex(idx)
|
|
||||||
self.preset_combo.setEnabled(True)
|
|
||||||
fm = self.preset_combo.fontMetrics()
|
|
||||||
max_w = max(fm.horizontalAdvance(self.preset_combo.itemText(i))
|
|
||||||
for i in range(self.preset_combo.count()))
|
|
||||||
padded = max_w + _sz(48)
|
|
||||||
self.preset_combo.view().setMinimumWidth(padded)
|
|
||||||
self.preset_combo.setMinimumWidth(padded)
|
|
||||||
self.preset_combo.updateGeometry()
|
|
||||||
else:
|
|
||||||
self.preset_combo.addItem("(设备不支持)", None)
|
|
||||||
self.preset_combo.setEnabled(False)
|
|
||||||
except Exception as exc:
|
|
||||||
self.preset_combo.addItem(f"(不支持: {exc})", None)
|
|
||||||
self.preset_combo.setEnabled(False)
|
|
||||||
|
|
||||||
nc, nd = len(self._color_profiles_data), len(self._depth_profiles_data)
|
|
||||||
msg = f"已连接 SN: {sn} 彩色 {nc} 项 / 深度 {nd} 项"
|
|
||||||
self.device_status.setText(msg)
|
|
||||||
self.device_status.setStyleSheet(f"color:#4aaa4a; font-size:{_fs(12)}px;")
|
|
||||||
self.d2c_device_status.setText(msg)
|
|
||||||
self.d2c_device_status.setStyleSheet(f"color:#4aaa4a; font-size:{_fs(12)}px;")
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
msg = f"未找到设备:{exc}"
|
|
||||||
self.device_status.setText(msg)
|
|
||||||
self.device_status.setStyleSheet(f"color:#cc6666; font-size:{_fs(12)}px;")
|
|
||||||
self.d2c_device_status.setText(msg)
|
|
||||||
self.d2c_device_status.setStyleSheet(f"color:#cc6666; font-size:{_fs(12)}px;")
|
|
||||||
|
|
||||||
# ─────────────────────────────────
|
# ─────────────────────────────────
|
||||||
# Stream Toggle
|
# Stream Toggle
|
||||||
@@ -1611,8 +1661,24 @@ class MainWindow(QMainWindow):
|
|||||||
self._stop_stream()
|
self._stop_stream()
|
||||||
|
|
||||||
def _start_stream(self, color_cfg: dict, depth_cfg: dict):
|
def _start_stream(self, color_cfg: dict, depth_cfg: dict):
|
||||||
self._stop_stream() # 确保旧 worker 已停止
|
old = self._worker
|
||||||
preset = self.preset_combo.currentData() # None 表示不切换
|
self._worker = None
|
||||||
|
if old and old.isRunning():
|
||||||
|
# 异步等旧 pipeline 释放后再启动新的
|
||||||
|
old.request_stop()
|
||||||
|
old.finished.connect(old.deleteLater)
|
||||||
|
old.finished.connect(lambda: self._do_start_stream(color_cfg, depth_cfg))
|
||||||
|
self.capture_btn.setEnabled(False)
|
||||||
|
self.color_combo.setEnabled(False)
|
||||||
|
self.depth_combo.setEnabled(False)
|
||||||
|
self.stream_btn.setText("关闭")
|
||||||
|
return
|
||||||
|
if old:
|
||||||
|
old.deleteLater()
|
||||||
|
self._do_start_stream(color_cfg, depth_cfg)
|
||||||
|
|
||||||
|
def _do_start_stream(self, color_cfg: dict, depth_cfg: dict):
|
||||||
|
preset = self.preset_combo.currentData()
|
||||||
self._worker = CameraWorker(color_cfg, depth_cfg, preset=preset)
|
self._worker = CameraWorker(color_cfg, depth_cfg, preset=preset)
|
||||||
self._worker.set_d2c(self.d2c_check.isChecked())
|
self._worker.set_d2c(self.d2c_check.isChecked())
|
||||||
self._worker.set_d2c_method(self.capture_d2c_method_combo.currentData())
|
self._worker.set_d2c_method(self.capture_d2c_method_combo.currentData())
|
||||||
@@ -1630,13 +1696,14 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def _stop_stream(self):
|
def _stop_stream(self):
|
||||||
if self._worker:
|
if self._worker:
|
||||||
self._worker.stop()
|
w = self._worker
|
||||||
self._worker = None
|
self._worker = None
|
||||||
|
w.request_stop()
|
||||||
|
w.finished.connect(w.deleteLater)
|
||||||
self.capture_btn.setEnabled(False)
|
self.capture_btn.setEnabled(False)
|
||||||
self.color_combo.setEnabled(True)
|
self.color_combo.setEnabled(True)
|
||||||
self.depth_combo.setEnabled(True)
|
self.depth_combo.setEnabled(True)
|
||||||
self.stream_btn.setText("开启")
|
self.stream_btn.setText("开启")
|
||||||
# 清空预览
|
|
||||||
for lbl in (self.color_lbl, self.depth_lbl):
|
for lbl in (self.color_lbl, self.depth_lbl):
|
||||||
lbl.setText("等待画面…")
|
lbl.setText("等待画面…")
|
||||||
lbl.setPixmap(QPixmap())
|
lbl.setPixmap(QPixmap())
|
||||||
|
|||||||
Reference in New Issue
Block a user