v
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user