feat: 초기 프로젝트 구조 추가
This commit is contained in:
297
gui/pages/register_page.py
Normal file
297
gui/pages/register_page.py
Normal file
@@ -0,0 +1,297 @@
|
||||
# 제품 등록 페이지 — 기준 이미지 캡처
|
||||
import cv2
|
||||
import numpy as np
|
||||
from PyQt5.QtCore import Qt, QSize
|
||||
from PyQt5.QtGui import QImage, QPixmap
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QHBoxLayout, QVBoxLayout, QGroupBox,
|
||||
QPushButton, QListWidget, QListWidgetItem, QLabel,
|
||||
QMessageBox, QScrollArea, QFrame,
|
||||
)
|
||||
|
||||
|
||||
_GRP_STYLE = (
|
||||
"QGroupBox {"
|
||||
" background:#222222; border:1px solid #333333; border-radius:6px;"
|
||||
" margin-top:14px; padding:14px 12px 12px 12px;"
|
||||
"}"
|
||||
"QGroupBox::title { color:#aaaaaa; subcontrol-origin:margin; left:10px; padding:0 4px; }"
|
||||
)
|
||||
|
||||
|
||||
class RegisterPage(QWidget):
|
||||
def __init__(self, insight_cam, matcher=None, db_client=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self._insight = insight_cam
|
||||
self._db_client = db_client
|
||||
self._db_items = []
|
||||
self._selected = None
|
||||
self._captured_img = None
|
||||
|
||||
self._build_ui()
|
||||
|
||||
# ================================================================== #
|
||||
# UI 구성
|
||||
# ================================================================== #
|
||||
|
||||
def _build_ui(self):
|
||||
root = QHBoxLayout(self)
|
||||
root.setContentsMargins(0, 0, 0, 0)
|
||||
root.setSpacing(0)
|
||||
root.addWidget(self._build_left_panel(), stretch=2)
|
||||
root.addWidget(self._build_right_panel(), stretch=3)
|
||||
|
||||
def _build_left_panel(self) -> QWidget:
|
||||
w = QWidget()
|
||||
w.setStyleSheet("background:#1a1a1a;")
|
||||
layout = QVBoxLayout(w)
|
||||
layout.setContentsMargins(16, 16, 8, 16)
|
||||
layout.setSpacing(10)
|
||||
|
||||
self._btn_mes = QPushButton("MES 불러오기")
|
||||
self._btn_mes.setEnabled(False)
|
||||
self._btn_mes.setFixedHeight(56)
|
||||
self._btn_mes.setToolTip("DB 연결 후 사용 가능")
|
||||
self._btn_mes.setStyleSheet(
|
||||
"background:#444444; color:#777777; border:none; border-radius:4px; font-size:15px;"
|
||||
)
|
||||
self._btn_mes.clicked.connect(self._on_load_from_db)
|
||||
layout.addWidget(self._btn_mes)
|
||||
|
||||
lbl = QLabel("제품 목록")
|
||||
lbl.setStyleSheet("font-size:13px; color:#777777;")
|
||||
layout.addWidget(lbl)
|
||||
|
||||
self._list = QListWidget()
|
||||
self._list.setStyleSheet("""
|
||||
QListWidget {
|
||||
background:#222222; border:1px solid #333333;
|
||||
border-radius:4px; outline:none; font-size:15px;
|
||||
}
|
||||
QListWidget::item {
|
||||
padding:0px 14px; border-bottom:1px solid #2a2a2a; color:#dddddd;
|
||||
}
|
||||
QListWidget::item:selected { background:#185FA5; color:#ffffff; }
|
||||
QListWidget::item:hover:!selected { background:#2d2d2d; }
|
||||
""")
|
||||
self._list.currentRowChanged.connect(self._on_select)
|
||||
layout.addWidget(self._list, stretch=1)
|
||||
return w
|
||||
|
||||
def _build_right_panel(self) -> QScrollArea:
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setFrameShape(QScrollArea.NoFrame)
|
||||
scroll.setStyleSheet("background:#1a1a1a;")
|
||||
|
||||
inner = QWidget()
|
||||
inner.setStyleSheet("background:#1a1a1a;")
|
||||
layout = QVBoxLayout(inner)
|
||||
layout.setContentsMargins(8, 16, 16, 16)
|
||||
layout.setSpacing(0)
|
||||
|
||||
layout.addWidget(self._build_detail_section())
|
||||
layout.addWidget(_divider())
|
||||
layout.addWidget(self._build_capture_section())
|
||||
layout.addStretch()
|
||||
|
||||
scroll.setWidget(inner)
|
||||
return scroll
|
||||
|
||||
# ── 섹션 1: 제품 상세 ──────────────────────────────────────────────
|
||||
|
||||
def _build_detail_section(self) -> QGroupBox:
|
||||
g = QGroupBox("제품 상세 정보")
|
||||
g.setStyleSheet(_GRP_STYLE)
|
||||
layout = QVBoxLayout(g)
|
||||
layout.setSpacing(8)
|
||||
|
||||
self._lbl_name = _info_value("—")
|
||||
self._lbl_model = _info_value("—")
|
||||
self._lbl_type = _info_value("—")
|
||||
layout.addLayout(_info_row("카테고리", self._lbl_name))
|
||||
layout.addLayout(_info_row("모델명", self._lbl_model))
|
||||
layout.addLayout(_info_row("Type", self._lbl_type))
|
||||
|
||||
self._arrow_lbl = QLabel("")
|
||||
self._arrow_lbl.setAlignment(Qt.AlignCenter)
|
||||
self._arrow_lbl.setFixedHeight(80)
|
||||
self._arrow_lbl.setStyleSheet("font-size:60px; background:transparent;")
|
||||
layout.addWidget(self._arrow_lbl)
|
||||
return g
|
||||
|
||||
# ── 섹션 2: 캡처 ──────────────────────────────────────────────────
|
||||
|
||||
def _build_capture_section(self) -> QGroupBox:
|
||||
g = QGroupBox("기준 이미지 캡처")
|
||||
g.setStyleSheet(_GRP_STYLE)
|
||||
layout = QVBoxLayout(g)
|
||||
layout.setSpacing(12)
|
||||
|
||||
btn = QPushButton("캡처 (In-Sight 트리거)")
|
||||
btn.setFixedHeight(56)
|
||||
btn.setStyleSheet(
|
||||
"background:#1a3a5c; color:#ffffff; border:none; border-radius:4px;"
|
||||
"font-size:15px; font-weight:bold;"
|
||||
)
|
||||
btn.clicked.connect(self._on_capture)
|
||||
|
||||
self._preview = QLabel()
|
||||
self._preview.setAlignment(Qt.AlignCenter)
|
||||
self._preview.setFixedSize(400, 300)
|
||||
self._preview.setStyleSheet(
|
||||
"background:#111111; color:#555555; border:1px solid #333333;"
|
||||
"border-radius:4px; font-size:14px;"
|
||||
)
|
||||
self._preview.setText("캡처 이미지 없음")
|
||||
|
||||
layout.addWidget(btn)
|
||||
layout.addWidget(self._preview, alignment=Qt.AlignHCenter)
|
||||
return g
|
||||
|
||||
# ================================================================== #
|
||||
# 슬롯
|
||||
# ================================================================== #
|
||||
|
||||
def _on_select(self, row: int):
|
||||
if row < 0:
|
||||
return
|
||||
item = self._list.item(row)
|
||||
if item is None:
|
||||
return
|
||||
|
||||
article_id = item.data(Qt.UserRole)
|
||||
if article_id is None:
|
||||
return
|
||||
|
||||
db_item = next(
|
||||
(x for x in self._db_items if x["article_id"] == article_id), None
|
||||
)
|
||||
r = {
|
||||
"id": article_id,
|
||||
"name": item.text(),
|
||||
"model": db_item.get("buyer_article_no", "") if db_item else "",
|
||||
"type": "",
|
||||
}
|
||||
|
||||
self._selected = r
|
||||
self._captured_img = None
|
||||
|
||||
self._lbl_name.setText(r["name"])
|
||||
self._lbl_model.setText(r["model"])
|
||||
t = r.get("type", "")
|
||||
self._lbl_type.setText(t if t else "—")
|
||||
|
||||
if t == "RH":
|
||||
self._arrow_lbl.setText("→")
|
||||
self._arrow_lbl.setStyleSheet("font-size:60px; color:#4488ff; background:transparent;")
|
||||
elif t == "LH":
|
||||
self._arrow_lbl.setText("←")
|
||||
self._arrow_lbl.setStyleSheet("font-size:60px; color:#ff8844; background:transparent;")
|
||||
else:
|
||||
self._arrow_lbl.setText("")
|
||||
|
||||
self._reset_preview()
|
||||
|
||||
def _on_capture(self):
|
||||
if self._selected is None:
|
||||
QMessageBox.warning(self, "경고", "먼저 제품을 선택하세요.")
|
||||
return
|
||||
if not self._insight.is_connected():
|
||||
QMessageBox.critical(self, "오류", "Cognex 카메라가 연결되어 있지 않습니다.")
|
||||
return
|
||||
|
||||
raw = self._insight.trigger_and_get_image()
|
||||
if not raw:
|
||||
QMessageBox.critical(self, "캡처 실패",
|
||||
"이미지를 수신하지 못했습니다.\n카메라 연결 상태를 확인하세요.")
|
||||
return
|
||||
|
||||
arr = np.frombuffer(raw, dtype=np.uint8)
|
||||
img = cv2.imdecode(arr, cv2.IMREAD_UNCHANGED)
|
||||
if img is None:
|
||||
QMessageBox.critical(self, "캡처 실패", "이미지 디코딩에 실패했습니다.")
|
||||
return
|
||||
|
||||
self._captured_img = img
|
||||
self._show_ndarray(img)
|
||||
|
||||
def update_db(self, db_client):
|
||||
"""MainWindow에서 DB 연결/해제 시 호출."""
|
||||
self._db_client = db_client
|
||||
enabled = db_client is not None and db_client.is_connected()
|
||||
self._btn_mes.setEnabled(enabled)
|
||||
self._btn_mes.setStyleSheet(
|
||||
"background:#1a3a5c; color:#ffffff; border:none; border-radius:4px; font-size:15px;"
|
||||
if enabled else
|
||||
"background:#444444; color:#777777; border:none; border-radius:4px; font-size:15px;"
|
||||
)
|
||||
|
||||
def _on_load_from_db(self):
|
||||
if not self._db_client or not self._db_client.is_connected():
|
||||
QMessageBox.warning(self, "경고", "DB를 먼저 연결해주세요.")
|
||||
return
|
||||
|
||||
items = self._db_client.get_reflector_list()
|
||||
if not items:
|
||||
QMessageBox.warning(self, "경고", "조회된 제품이 없습니다.")
|
||||
return
|
||||
|
||||
self._list.clear()
|
||||
self._db_items = items
|
||||
for item in items:
|
||||
li = QListWidgetItem(item['article'])
|
||||
li.setSizeHint(QSize(0, 52))
|
||||
li.setData(Qt.UserRole, item["article_id"])
|
||||
self._list.addItem(li)
|
||||
|
||||
QMessageBox.information(
|
||||
self, "완료", f"{len(items)}개 제품을 불러왔습니다."
|
||||
)
|
||||
|
||||
# ================================================================== #
|
||||
# 헬퍼
|
||||
# ================================================================== #
|
||||
|
||||
def _show_ndarray(self, img: np.ndarray):
|
||||
h_img, w_img = img.shape[:2]
|
||||
if img.ndim == 2:
|
||||
qimg = QImage(img.data, w_img, h_img, w_img, QImage.Format_Grayscale8)
|
||||
else:
|
||||
rgb = np.ascontiguousarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
|
||||
qimg = QImage(rgb.data, w_img, h_img, w_img * 3, QImage.Format_RGB888)
|
||||
pix = QPixmap.fromImage(qimg).scaled(400, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
||||
self._preview.setPixmap(pix)
|
||||
self._preview.setText("")
|
||||
|
||||
def _reset_preview(self):
|
||||
self._preview.clear()
|
||||
self._preview.setText("캡처 이미지 없음")
|
||||
|
||||
|
||||
# ================================================================== #
|
||||
# 모듈 수준 유틸리티
|
||||
# ================================================================== #
|
||||
|
||||
def _divider() -> QFrame:
|
||||
f = QFrame()
|
||||
f.setFrameShape(QFrame.HLine)
|
||||
f.setFixedHeight(1)
|
||||
f.setStyleSheet("background:#333333; border:none;")
|
||||
return f
|
||||
|
||||
|
||||
def _info_value(text: str) -> QLabel:
|
||||
lbl = QLabel(text)
|
||||
lbl.setStyleSheet("color:#ffffff; font-size:16px; font-weight:bold;")
|
||||
return lbl
|
||||
|
||||
|
||||
def _info_row(label: str, value_widget: QLabel) -> QHBoxLayout:
|
||||
row = QHBoxLayout()
|
||||
key = QLabel(label + ":")
|
||||
key.setFixedWidth(80)
|
||||
key.setStyleSheet("color:#888888; font-size:14px;")
|
||||
row.addWidget(key)
|
||||
row.addWidget(value_widget, stretch=1)
|
||||
return row
|
||||
Reference in New Issue
Block a user