This commit is contained in:
i
2026-04-22 16:46:47 +08:00
parent 8941616a22
commit 2ddc4de70a
+90 -19
View File
@@ -117,7 +117,8 @@ from d2c_batch import batch_convert, software_d2c, software_d2c_inv
PARAMS_FILE = "camera_params.json"
MIN_DEPTH_MM, MAX_DEPTH_MM = 200, 5000
_DISCONNECT_TIMEOUTS = 6 # 6 × 500 ms = 3 s 无帧 → 断线
_DISCONNECT_STARTUP_GRACE_SEC = 5.0
_DISCONNECT_NO_FRAME_SEC = 3.0
_UI_SCALE: float = 1.0 # initialized in main() before window creation
@@ -729,18 +730,23 @@ class CameraWorker(QThread):
align_filter = AlignFilter(align_to_stream=OBStreamType.COLOR_STREAM)
point_cloud_filter = PointCloudFilter()
stream_started_at = time.monotonic()
last_frame_at = stream_started_at
while self._running:
try:
frames = pipeline.wait_for_frames(100)
if frames is None:
self._consec_timeouts += 1
if (self._consec_timeouts >= _DISCONNECT_TIMEOUTS
now = time.monotonic()
if now - stream_started_at < _DISCONNECT_STARTUP_GRACE_SEC:
continue
if (now - last_frame_at >= _DISCONNECT_NO_FRAME_SEC
and not self._disconnect_emitted):
self._disconnect_emitted = True
self._running = False
self.disconnected.emit()
continue
last_frame_at = time.monotonic()
self._consec_timeouts = 0
with self._lock:
@@ -876,7 +882,6 @@ class CameraWorker(QThread):
except Exception as exc:
if self._running:
self.log_msg.emit(f"相机错误:{exc}")
self._consec_timeouts += _DISCONNECT_TIMEOUTS
if not self._disconnect_emitted:
self._disconnect_emitted = True
self._running = False
@@ -1239,8 +1244,12 @@ class MainWindow(QMainWindow):
self._batch_worker: BatchD2CWorker | None = None
self._fetch_worker: FetchParamsWorker | None = None
self._refresh_worker: RefreshDevicesWorker | None = None
self._refresh_proc: QProcess | None = None
self._refresh_tmp: str | None = None
self._ob_context = None
self._hotplug_pending = False
self._pending_stream_cfg: tuple[dict, dict] | None = None
self._closing = False
self._session_dir: Path | None = None
self._params_filename = "camera_params.json"
self._color_profiles_data: list[dict] = []
@@ -1718,6 +1727,37 @@ class MainWindow(QMainWindow):
# Device Refresh
# ─────────────────────────────────
def _stop_refresh_process(self):
proc = self._refresh_proc
self._refresh_proc = None
tmp = self._refresh_tmp
self._refresh_tmp = None
if proc is None:
if tmp:
try:
Path(tmp).unlink(missing_ok=True)
except Exception:
pass
return
try:
proc.finished.disconnect(self._on_refresh_proc_done)
except Exception:
pass
try:
if proc.state() != QProcess.NotRunning:
proc.terminate()
if not proc.waitForFinished(1200):
proc.kill()
proc.waitForFinished(1200)
except Exception:
pass
proc.deleteLater()
if tmp:
try:
Path(tmp).unlink(missing_ok=True)
except Exception:
pass
def _setup_hotplug_monitor(self):
try:
from pyorbbecsdk import Context
@@ -1783,6 +1823,7 @@ class MainWindow(QMainWindow):
def _refresh_devices(self):
if getattr(self, '_refreshing', False):
return
self._stop_refresh_process()
self._refreshing = True
self.color_combo.clear()
self.depth_combo.clear()
@@ -1815,7 +1856,12 @@ class MainWindow(QMainWindow):
proc.start(sys.executable, [str(Path(__file__).resolve()), _ENUM_FLAG, self._refresh_tmp])
def _on_refresh_proc_done(self):
if self._closing:
self._refreshing = False
return
tmp = getattr(self, '_refresh_tmp', None)
if self._refresh_proc is not None:
self._refresh_proc.deleteLater()
self._refresh_proc = None
self._refresh_tmp = None
@@ -1900,31 +1946,52 @@ class MainWindow(QMainWindow):
def _retire_worker(self, w: "CameraWorker"):
"""请求 worker 停止,持有引用直到线程真正结束再销毁。"""
if w in self._stopping_workers:
return
self._stopping_workers.append(w)
w.request_stop()
w.finished.connect(lambda: self._on_worker_finished(w))
w.request_stop()
# 避免已结束线程错过 finished 信号导致队列残留,进而卡死后续启动
if not w.isRunning():
QTimer.singleShot(0, lambda: self._on_worker_finished(w))
def _on_worker_finished(self, w: "CameraWorker"):
removed = False
if w in self._stopping_workers:
self._stopping_workers.remove(w)
w.deleteLater()
removed = True
if removed:
w.deleteLater()
self._try_start_pending_stream()
def _try_start_pending_stream(self):
# 清理已经退出但残留的 worker,避免阻塞新的启动请求
for old in list(self._stopping_workers):
if not old.isRunning():
self._stopping_workers.remove(old)
old.deleteLater()
if self._closing:
return
if self._worker is not None:
return
if self._stopping_workers:
return
if not self._pending_stream_cfg:
return
color_cfg, depth_cfg = self._pending_stream_cfg
self._pending_stream_cfg = None
self._do_start_stream(color_cfg, depth_cfg)
def _start_stream(self, color_cfg: dict, depth_cfg: dict):
self._pending_stream_cfg = (color_cfg, depth_cfg)
old = self._worker
self._worker = None
if old:
self._retire_worker(old)
pending = self._stopping_workers
if pending:
# 等最后一个停止中的 worker 退出后再开新 pipeline
last = pending[-1]
last.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)
else:
self._do_start_stream(color_cfg, depth_cfg)
self.capture_btn.setEnabled(False)
self.color_combo.setEnabled(False)
self.depth_combo.setEnabled(False)
self._try_start_pending_stream()
def _do_start_stream(self, color_cfg: dict, depth_cfg: dict):
preset = self.preset_combo.currentData()
@@ -1946,6 +2013,7 @@ class MainWindow(QMainWindow):
self.capture_btn.setEnabled(True)
def _stop_stream(self):
self._pending_stream_cfg = None
if self._worker:
self._retire_worker(self._worker)
self._worker = None
@@ -1966,15 +2034,16 @@ class MainWindow(QMainWindow):
def _on_camera_disconnected(self):
"""相机断开:静默关闭流,不弹窗。"""
self._pending_stream_cfg = None
if self._worker:
self._worker.deleteLater()
self._retire_worker(self._worker)
self._worker = None
self._set_stream_btn(False)
self.capture_btn.setEnabled(False)
self.color_combo.setEnabled(True)
self.depth_combo.setEnabled(True)
for lbl in (self.color_lbl, self.depth_lbl):
lbl.setText("设备已断开,请刷新后重新开启")
lbl.setText("设备已断开,请加载后重新开启")
lbl.setPixmap(QPixmap())
self.device_status.setText("设备已断开连接,请重新插入后点击「加载设备」")
self.device_status.setStyleSheet(f"color:#cc8844; font-size:{_fs(12)}px;")
@@ -2203,6 +2272,8 @@ class MainWindow(QMainWindow):
# ─────────────────────────────────
def closeEvent(self, event):
self._closing = True
self._stop_refresh_process()
if self._ob_context is not None:
try:
self._ob_context.set_device_changed_callback(None)