# 이미지 설정 다이얼로그 — In-Sight 2000C Telnet 셀 직접 GV/SV from PyQt5.QtWidgets import ( QDialog, QFormLayout, QVBoxLayout, QHBoxLayout, QPushButton, QDoubleSpinBox, QSpinBox, QMessageBox, QLabel, ) from PyQt5.QtCore import Qt # (레이블, 셀주소, 위젯종류, min, max, 기본값, 소수점자리) _PARAMS = [ ("노출 (밀리초)", "D3", "double", 0.01, 1000.0, 30.0, 3), ("최대 노출 시간", "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), ] # 스캔할 셀 범위 (A1~J20) _SCAN_COLS = list("ABCDEFGHIJ") _SCAN_ROWS = range(1, 21) _STYLE = """ QDialog, QWidget { background: #1a1a1a; color: #ffffff; font-size: 14px; } QLabel { color: #cccccc; } QDoubleSpinBox, QSpinBox { background: #2a2a2a; color: #ffffff; border: 1px solid #555555; border-radius: 4px; padding: 4px 8px; min-height: 38px; } QDoubleSpinBox::up-button, QSpinBox::up-button { subcontrol-origin: border; subcontrol-position: top right; width: 30px; } QDoubleSpinBox::down-button, QSpinBox::down-button { subcontrol-origin: border; subcontrol-position: bottom right; width: 30px; } QPushButton { background: #2e2e2e; color: #ffffff; border: 1px solid #555555; border-radius: 4px; min-height: 56px; padding: 0 24px; font-size: 15px; } QPushButton:hover { background: #3a3a3a; } QPushButton:pressed { background: #1e1e1e; } QPushButton#scan { min-height: 38px; font-size: 13px; color: #aaaaaa; } """ class ImageSettingsDialog(QDialog): def __init__(self, insight_cam, parent=None): super().__init__(parent) self._cam = insight_cam self._widgets = {} # 셀주소 → 위젯 self._originals = {} # 셀주소 → 로드 시 원본값 self.setWindowTitle("이미지 설정 — In-Sight 2000C") self.setMinimumWidth(440) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.setStyleSheet(_STYLE) self._build_ui() self._load_values() # ================================================================== # # UI 구성 # ================================================================== # def _build_ui(self): form = QFormLayout() form.setLabelAlignment(Qt.AlignRight) form.setRowWrapPolicy(QFormLayout.DontWrapRows) form.setHorizontalSpacing(20) form.setVerticalSpacing(12) 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 if decimals > 0 else 1.0) else: w = QSpinBox() w.setRange(int(lo), int(hi)) w.setValue(int(default)) w.setMinimumWidth(140) self._widgets[cell] = w form.addRow(label, w) btn_scan = QPushButton("쓰기 가능한 셀 자동 탐지…") btn_scan.setObjectName("scan") btn_scan.clicked.connect(self._on_scan) 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.addWidget(btn_scan) btn_row.addStretch() btn_row.addWidget(btn_ok) btn_row.addWidget(btn_cancel) root = QVBoxLayout(self) root.setContentsMargins(24, 20, 24, 20) root.setSpacing(16) root.addLayout(form) root.addLayout(btn_row) # ================================================================== # # 값 로드 — GV{cell}: 응답 "1" + 값 # ================================================================== # def _load_values(self): for _, cell, kind, _, _, _, _ in _PARAMS: val = self._gv(cell) print(f"[설정창] GV{cell} → {val!r}") w = self._widgets[cell] if val is not None: w.setValue(val if kind == "double" else int(round(val))) self._originals[cell] = val else: self._originals[cell] = None # 조회 실패 → 기본값 유지 def _gv(self, cell: str): """GV{cell} 전송 → float 반환, 실패 시 None""" try: self._cam._send(f"GV{cell}") status = self._cam._read_line() if status != "1": return None return float(self._cam._read_line()) except Exception: return None # ================================================================== # # 셀 자동 탐지 — GV로 읽히고 SV로 쓸 수 있는 셀 목록 # ================================================================== # def _on_scan(self): """A1~J20 범위를 스캔해 Online(쓰기 가능) 셀을 찾아 표시.""" readable = [] # [(cell, value)] writable = [] # [(cell, value)] for col in _SCAN_COLS: for row in _SCAN_ROWS: cell = f"{col}{row}" val = self._gv(cell) if val is None: continue readable.append((cell, val)) ok, _ = self._sv(cell, val, "double") # 같은 값 재기입 → 실질적 변경 없음 if ok: writable.append((cell, val)) if writable: lines = "\n".join(f" {c} = {v}" for c, v in writable) msg = ( f"쓰기 가능한 셀 {len(writable)}개 발견:\n\n" f"{lines}\n\n" f"image_settings_dialog.py 상단 _PARAMS의\n" f"셀 주소를 위 주소로 변경하세요." ) elif readable: lines = "\n".join(f" {c} = {v}" for c, v in readable[:20]) msg = ( f"읽기 전용 셀 {len(readable)}개 발견 (쓰기 가능 셀 없음):\n\n" f"{lines}\n\n" f"─────────────────────────────\n" f"Cognex In-Sight Explorer에서\n" f"제어하려는 셀을 우클릭 →\n" f"[Cell Properties] → [Online] 체크 후\n" f"job을 카메라에 업로드하세요.\n\n" f"이후 이 버튼을 다시 누르면\n" f"쓰기 가능한 셀 주소를 알 수 있습니다." ) else: msg = ( f"A1~J20 범위에서 읽을 수 있는 셀이 없습니다.\n\n" f"카메라 연결 상태 또는 job 파일을 확인하세요." ) QMessageBox.information(self, "셀 탐지 결과", msg) # ================================================================== # # 확인 버튼 — 변경된 항목만 SV{cell} {value} # ================================================================== # def _on_ok(self): 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 errors = [] for cell, value, kind in changes: ok, resp = self._sv(cell, value, kind) if not ok: errors.append((cell, resp)) if errors: detail = "\n".join(f" • {c} (카메라 응답: {r!r})" for c, r in errors) QMessageBox.critical( self, "설정 실패", f"다음 셀 설정에 실패했습니다:\n{detail}\n\n" f"'쓰기 가능한 셀 자동 탐지' 버튼으로\n" f"현재 Online 셀 주소를 확인하세요.", ) return self.accept() def _sv(self, cell: str, value: float, kind: str): """SV{cell} {value} 전송 → (성공여부, 응답코드 문자열)""" try: fmt = str(int(round(value))) if kind == "int" else f"{value:.6g}" self._cam._send(f"SV{cell} {fmt}") resp = self._cam._read_line() print(f"[설정창] SV{cell} {fmt} → {resp!r}") return resp.strip() == "1", resp.strip() except Exception as e: print(f"[설정창] SV{cell} 오류: {e}") return False, str(e)