From 2ddc4de70ad18c5a1cb66358af4700eff7d684a0 Mon Sep 17 00:00:00 2001 From: Arkylin Date: Wed, 22 Apr 2026 16:46:47 +0800 Subject: [PATCH] v --- main.py | 109 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 19 deletions(-) diff --git a/main.py b/main.py index 35757ed..8f699e0 100644 --- a/main.py +++ b/main.py @@ -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)