from PyQt5.QtWidgets import ( QDialog, QFormLayout, QVBoxLayout, QHBoxLayout, QPushButton, QDoubleSpinBox, QSpinBox, QMessageBox, QLabel ) from PyQt5.QtCore import Qt # (레이블, 셀주소, 위젯종류, min, max, 기본값, 소수점자리) # D3(노출)은 AcqExposure() 출력 셀 — 읽기 전용, 목록에서 제외 _PARAMS = [ ("최대 노출 시간", "F3", "double", 0.01, 1000.0, 950.0, 3), ("목표 이미지 밝기", "G3", "double", 0.0, 255.0, 50.0, 1), ("조명 강도", "D6", "int", 0, 100, 70, 0), ("초점 위치", "D14", "int", 0, 999, 139, 0), ] class ImageSettingsDialog(QDialog): def __init__(self, insight_cam, parent=None): super().__init__(parent) self._cam = insight_cam self.setWindowTitle("이미지 설정 — In-Sight 2000C") self.setMinimumWidth(360) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self._widgets = {} # 셀주소 → 위젯 self._originals = {} # 셀주소 → 로드 시 원본값 self._build_ui() self._load_values() # ------------------------------------------------------------------ # # UI 구성 # ------------------------------------------------------------------ # def _build_ui(self): form = QFormLayout() form.setLabelAlignment(Qt.AlignRight) form.setRowWrapPolicy(QFormLayout.DontWrapRows) form.setHorizontalSpacing(16) form.setVerticalSpacing(10) # 노출(D3)은 읽기 전용 표시 self._exposure_label = QLabel("—") self._exposure_label.setStyleSheet("color: gray;") form.addRow("노출 (밀리초, 읽기 전용):", self._exposure_label) for label, cell, kind, lo, hi, default, decimals in _PARAMS: if kind == "double": w = QDoubleSpinBox() w.setRange(lo, hi) w.setDecimals(decimals) w.setValue(default) w.setSingleStep(0.1) else: w = QSpinBox() w.setRange(int(lo), int(hi)) w.setValue(int(default)) w.setMinimumWidth(120) self._widgets[cell] = w form.addRow(label, w) btn_ok = QPushButton("확인") btn_ok.setDefault(True) btn_ok.clicked.connect(self._on_ok) btn_cancel = QPushButton("취소") btn_cancel.clicked.connect(self.reject) btn_row = QHBoxLayout() btn_row.addStretch() btn_row.addWidget(btn_ok) btn_row.addWidget(btn_cancel) root = QVBoxLayout(self) root.addLayout(form) root.addSpacing(8) root.addLayout(btn_row) # ------------------------------------------------------------------ # # 값 로드 — GV{cell} 명령, 응답 2줄(상태코드 + 값) # ------------------------------------------------------------------ # def _load_values(self): # D3: 읽기 전용 표시 exp = self._gv("D3") print(f"[설정창] GVD3 → {exp!r}") if exp is not None: self._exposure_label.setText(f"{exp:.3f} ms") for _, cell, kind, _, _, _, _ in _PARAMS: val = self._gv(cell) print(f"[설정창] GV{cell} → {val!r}") w = self._widgets[cell] if val is not None: if kind == "double": w.setValue(val) else: w.setValue(int(round(val))) self._originals[cell] = val else: self._originals[cell] = None def _gv(self, cell: str): """GV{cell} 전송 → 숫자 반환, 실패 시 None""" try: self._cam._send(f"GV{cell}") status = self._cam._read_line() # "1" 또는 "0" if status != "1": print(f"[설정창] GV{cell} 상태코드: {status!r}") return None raw = self._cam._read_line() # 실제 값 return float(raw) except Exception as e: print(f"[설정창] GV{cell} 오류: {e}") return None # ------------------------------------------------------------------ # # 확인 버튼 — SV{cell} {value} 명령 # ------------------------------------------------------------------ # def _on_ok(self): import time # 변경 항목 수집 changes = [] for _, cell, kind, _, _, _, _ in _PARAMS: w = self._widgets[cell] current = float(w.value()) original = self._originals.get(cell) if original is None or abs(current - original) >= 1e-9: changes.append((cell, current, kind)) if not changes: self.accept() return # 오프라인 전환 self._cam._send("SO0") resp = self._cam._read_line() print(f"[설정창] SO0 (오프라인) → {resp!r}") if resp.strip() != "1": QMessageBox.critical(self, "오류", f"오프라인 전환 실패 (응답: {resp!r})") return time.sleep(0.3) # 카메라가 오프라인 전환 완료 대기 # 값 설정 errors = [] for cell, value, kind in changes: if not self._sv(cell, value, kind): errors.append(cell) time.sleep(0.2) # SV 처리 완료 대기 # 온라인 복귀 — 최대 3회 재시도 online_ok = False for attempt in range(3): self._cam._send("SO1") resp = self._cam._read_line() print(f"[설정창] SO1 (온라인) 시도 {attempt+1} → {resp!r}") if resp.strip() == "1": online_ok = True break time.sleep(0.5) if not online_ok: QMessageBox.warning( self, "온라인 복귀 실패", f"SO1 명령이 실패했습니다 (마지막 응답: {resp!r})\n" "카메라를 수동으로 재시작하거나 In-Sight Explorer에서 Online으로 전환하세요." ) if errors: QMessageBox.critical( self, "설정 실패", f"다음 셀 설정 실패:\n{chr(10).join(errors)}\n\n" "셀이 읽기 전용이거나 값 범위를 벗어났을 수 있습니다." ) return self.accept() def _sv(self, cell: str, value: float, kind: str) -> bool: """SV{cell} {value} 전송 → 응답 '1'이면 True""" try: formatted = str(int(round(value))) if kind == "int" else f"{value:.6g}" self._cam._send(f"SV{cell} {formatted}") resp = self._cam._read_line() print(f"[설정창] SV{cell} {formatted} → {resp!r}") return resp.strip() == "1" except Exception as e: print(f"[설정창] SV{cell} 오류: {e}") return False