버전 업그레이드
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# 검사 페이지 — 코그넥스/Basler 영상, 그룹 A/B 설정, Pass/Fail 표시
|
||||
# 검사 페이지 — 코그넥스/Basler 영상, Pass/Fail 표시
|
||||
import time
|
||||
import threading
|
||||
import itertools
|
||||
@@ -8,12 +8,14 @@ from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSize
|
||||
from PyQt5.QtGui import QImage, QPixmap
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QHBoxLayout, QVBoxLayout, QGroupBox,
|
||||
QPushButton, QLabel, QCheckBox, QFrame,
|
||||
QGridLayout, QSizePolicy,
|
||||
QPushButton, QLabel, QFrame,
|
||||
QGridLayout, QSizePolicy, QScrollArea, QScroller, QMessageBox,
|
||||
)
|
||||
|
||||
from logic.inspector import Inspector
|
||||
from logic.group_manager import GroupManager
|
||||
from logic.products import build_patmax_cells, article_label, MAX_PATMAX_SLOTS
|
||||
from db.sql_client import SQLClient
|
||||
from logger import log_inspect_result, log_camera_timing, log_action, log_defect_image
|
||||
|
||||
_DEFECT_COLORS = {
|
||||
@@ -40,33 +42,6 @@ def _draw_detections(frame: np.ndarray, detections: list) -> np.ndarray:
|
||||
)
|
||||
return img
|
||||
|
||||
# 검사 그룹 A/B 선택용 모델 목록
|
||||
_MODELS = [
|
||||
"LOW REF / LX3 / RH",
|
||||
"LOW REF / LX3 / LH",
|
||||
"LOW REF NAS / LX3 / RH",
|
||||
"LOW REF NAS / LX3 / LH",
|
||||
"LOW REF NAS / MX5a 2.0TH / RH",
|
||||
"LOW REF NAS / MX5a 2.0TH / LH",
|
||||
"HIGH REF / LX3 / RH",
|
||||
"HIGH REF / LX3 / LH",
|
||||
"LOW REF NAS 1.5 GEN / CN7 PE / RH",
|
||||
"LOW REF DOM 1.5 GEN / CN7 PE / LH",
|
||||
]
|
||||
|
||||
_MODEL_ID_MAP = {
|
||||
"LOW REF / LX3 / RH": 1,
|
||||
"LOW REF / LX3 / LH": 2,
|
||||
"LOW REF NAS / LX3 / RH": 3,
|
||||
"LOW REF NAS / LX3 / LH": 4,
|
||||
"LOW REF NAS / MX5a 2.0TH / RH": 5,
|
||||
"LOW REF NAS / MX5a 2.0TH / LH": 6,
|
||||
"HIGH REF / LX3 / RH": 7,
|
||||
"HIGH REF / LX3 / LH": 8,
|
||||
"LOW REF NAS 1.5 GEN / CN7 PE / RH": 9,
|
||||
"LOW REF DOM 1.5 GEN / CN7 PE / LH": 10,
|
||||
}
|
||||
|
||||
|
||||
# ================================================================== #
|
||||
# 백그라운드 워커 — 파이프라인 방식
|
||||
@@ -97,6 +72,18 @@ class InspectWorker(QThread):
|
||||
self._stop_flag = False
|
||||
self._pause_flag = False
|
||||
self._seq = itertools.count(1)
|
||||
self._wk_result_ids: set = set()
|
||||
self._wk_check_enabled = False
|
||||
self._allowed_article_ids: set = set()
|
||||
|
||||
def set_work_targets(self, allowed_article_ids: "set | None"):
|
||||
"""WK_Result 작업 대상 ArticleID (정규화된 set). None이면 WK 검사 비활성."""
|
||||
if allowed_article_ids is None:
|
||||
self._wk_check_enabled = False
|
||||
self._allowed_article_ids = set()
|
||||
return
|
||||
self._wk_check_enabled = True
|
||||
self._allowed_article_ids = allowed_article_ids
|
||||
|
||||
# ── 외부 제어 ──────────────────────────────────────────────────── #
|
||||
|
||||
@@ -207,18 +194,25 @@ class InspectWorker(QThread):
|
||||
ct.join(timeout=10.0)
|
||||
log_camera_timing(seq, "cognex_join_done", _ms())
|
||||
|
||||
# ── 모델 판별 ──
|
||||
results = cognex_out.get("results", {})
|
||||
active_names = self._groups.get_active_group()
|
||||
allowed_ids = [_MODEL_ID_MAP[n] for n in active_names if n in _MODEL_ID_MAP]
|
||||
result_info = {
|
||||
# ── 모델 판별 (WK_Result 작업 대상만 허용) ──
|
||||
results = cognex_out.get("results", {})
|
||||
result_info = {
|
||||
"matched": False, "in_allowed": False,
|
||||
"model": None, "score": 0.0,
|
||||
"cognex_pass": False, "status": "인식 불가",
|
||||
}
|
||||
try:
|
||||
if results:
|
||||
result_info = self._inspector.identify_model(results, allowed_ids)
|
||||
if self._wk_check_enabled:
|
||||
result_info = self._inspector.identify_model(
|
||||
results, allowed_article_ids=self._allowed_article_ids,
|
||||
)
|
||||
else:
|
||||
result_info = self._inspector.identify_model(
|
||||
results, allowed_model_ids=self._groups.get_allowed_ids(),
|
||||
)
|
||||
if result_info["matched"] and self._wk_check_enabled:
|
||||
result_info["in_wk_result"] = result_info["in_allowed"]
|
||||
except Exception as e:
|
||||
print(f"[워커 오류] 모델 판별: {e}")
|
||||
|
||||
@@ -258,18 +252,17 @@ class InspectWorker(QThread):
|
||||
|
||||
class InspectPage(QWidget):
|
||||
def __init__(self, insight_cam, basler_cam, detector=None,
|
||||
belt_delay: float = 3.33, parent=None):
|
||||
belt_delay: float = 3.33, db_client=None, config=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self._insight = insight_cam
|
||||
self._basler = basler_cam
|
||||
self._db_client = db_client
|
||||
self._config = config or {}
|
||||
self.detector = detector
|
||||
self._inspector = Inspector()
|
||||
self._groups = GroupManager()
|
||||
|
||||
self._counts = {
|
||||
"A": {"total": 0, "pass": 0, "fail": 0, "unknown": 0},
|
||||
"B": {"total": 0, "pass": 0, "fail": 0, "unknown": 0},
|
||||
}
|
||||
self._counts = {"total": 0, "pass": 0, "fail": 0, "unknown": 0}
|
||||
|
||||
self._matcher = None
|
||||
|
||||
@@ -284,6 +277,7 @@ class InspectPage(QWidget):
|
||||
self._worker.result_ready.connect(self._on_result)
|
||||
|
||||
self._build_ui()
|
||||
self.refresh_wk_results()
|
||||
|
||||
# ================================================================== #
|
||||
# 최상위 레이아웃
|
||||
@@ -347,68 +341,48 @@ class InspectPage(QWidget):
|
||||
layout.setContentsMargins(8, 6, 8, 6)
|
||||
layout.setSpacing(0)
|
||||
|
||||
layout.addWidget(self._build_col_groups(), stretch=5)
|
||||
layout.addWidget(self._build_col_products(), stretch=5)
|
||||
layout.addWidget(_vline())
|
||||
layout.addWidget(self._build_col_controls(), stretch=3)
|
||||
layout.addWidget(_vline())
|
||||
layout.addWidget(self._build_col_counters(), stretch=4)
|
||||
return w
|
||||
|
||||
def _build_col_groups(self) -> QWidget:
|
||||
def _build_col_products(self) -> QWidget:
|
||||
w = QWidget()
|
||||
layout = QVBoxLayout(w)
|
||||
layout.setContentsMargins(4, 4, 8, 4)
|
||||
layout.setSpacing(6)
|
||||
|
||||
checks_row = QHBoxLayout()
|
||||
checks_row.setSpacing(8)
|
||||
checks_row.addWidget(self._build_group_section("A"), stretch=1)
|
||||
checks_row.addWidget(self._build_group_section("B"), stretch=1)
|
||||
layout.addLayout(checks_row, stretch=1)
|
||||
|
||||
self._switch_btn = QPushButton("현재: 그룹 A 활성 → B로 전환")
|
||||
self._switch_btn.setFixedHeight(56)
|
||||
self._switch_btn.setStyleSheet(
|
||||
"background:#1a3a5c; color:#ffffff; border:none; border-radius:4px;"
|
||||
"font-size:14px; font-weight:bold;"
|
||||
)
|
||||
self._switch_btn.clicked.connect(self._on_switch)
|
||||
layout.addWidget(self._switch_btn)
|
||||
return w
|
||||
|
||||
def _build_group_section(self, name: str) -> QGroupBox:
|
||||
active_color = "#4488ff" if name == "A" else "#cc8844"
|
||||
g = QGroupBox(f"그룹 {name} (최대 4종)")
|
||||
g = QGroupBox("검사 대상")
|
||||
self._products_group = g
|
||||
g.setStyleSheet(
|
||||
f"QGroupBox {{ background:#222222; border:1px solid #333333; border-radius:6px;"
|
||||
f" margin-top:12px; padding:6px 4px 4px 4px; }}"
|
||||
f"QGroupBox::title {{ color:{active_color}; subcontrol-origin:margin;"
|
||||
f" left:8px; font-size:13px; font-weight:bold; }}"
|
||||
"QGroupBox { background:#222222; border:1px solid #333333; border-radius:6px;"
|
||||
" margin-top:12px; padding:6px 4px 4px 4px; }"
|
||||
"QGroupBox::title { color:#4488ff; subcontrol-origin:margin;"
|
||||
" left:8px; font-size:13px; font-weight:bold; }"
|
||||
)
|
||||
layout = QVBoxLayout(g)
|
||||
layout.setSpacing(1)
|
||||
layout.setContentsMargins(4, 2, 4, 2)
|
||||
outer = QVBoxLayout(g)
|
||||
outer.setSpacing(0)
|
||||
outer.setContentsMargins(4, 2, 4, 2)
|
||||
|
||||
checks = []
|
||||
for model in _MODELS:
|
||||
cb = QCheckBox(model)
|
||||
cb.setStyleSheet(
|
||||
"QCheckBox { font-size:12px; min-height:24px; color:#cccccc; }"
|
||||
"QCheckBox::indicator { width:16px; height:16px; }"
|
||||
)
|
||||
checks.append(cb)
|
||||
layout.addWidget(cb)
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setFrameShape(QFrame.NoFrame)
|
||||
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
scroll.setStyleSheet("background:transparent;")
|
||||
QScroller.grabGesture(scroll.viewport(), QScroller.LeftMouseButtonGesture)
|
||||
|
||||
if name == "A":
|
||||
self._group_a_checks = checks
|
||||
for cb in checks:
|
||||
cb.clicked.connect(lambda checked, c=cb: self._on_group_changed("A", c, checked))
|
||||
else:
|
||||
self._group_b_checks = checks
|
||||
for cb in checks:
|
||||
cb.clicked.connect(lambda checked, c=cb: self._on_group_changed("B", c, checked))
|
||||
inner = QWidget()
|
||||
inner.setStyleSheet("background:transparent;")
|
||||
self._products_inner_layout = QVBoxLayout(inner)
|
||||
self._products_inner_layout.setSpacing(2)
|
||||
self._products_inner_layout.setContentsMargins(2, 2, 2, 2)
|
||||
|
||||
return g
|
||||
scroll.setWidget(inner)
|
||||
outer.addWidget(scroll)
|
||||
layout.addWidget(g, stretch=1)
|
||||
return w
|
||||
|
||||
def _build_col_controls(self) -> QWidget:
|
||||
w = QWidget()
|
||||
@@ -433,9 +407,9 @@ class InspectPage(QWidget):
|
||||
)
|
||||
self._pause_btn.clicked.connect(self._on_pause)
|
||||
|
||||
self._active_lbl = QLabel("활성 그룹: A")
|
||||
self._active_lbl.setAlignment(Qt.AlignCenter)
|
||||
self._active_lbl.setStyleSheet("font-size:14px; color:#aaaaaa;")
|
||||
self._scope_lbl = QLabel("검사 범위: —")
|
||||
self._scope_lbl.setAlignment(Qt.AlignCenter)
|
||||
self._scope_lbl.setStyleSheet("font-size:14px; color:#aaaaaa;")
|
||||
|
||||
self._model_lbl = QLabel("인식 모델: —")
|
||||
self._model_lbl.setAlignment(Qt.AlignCenter)
|
||||
@@ -446,19 +420,19 @@ class InspectPage(QWidget):
|
||||
f"벨트 딜레이: {self._worker._belt_delay:.2f}s"
|
||||
)
|
||||
self._belt_lbl.setAlignment(Qt.AlignCenter)
|
||||
self._belt_lbl.setStyleSheet("font-size:12px; color:#666666;")
|
||||
self._belt_lbl.setStyleSheet("font-size:13px; color:#999999;")
|
||||
|
||||
self._result_lbl = QLabel("대기 중")
|
||||
self._result_lbl.setAlignment(Qt.AlignCenter)
|
||||
self._result_lbl.setStyleSheet(
|
||||
"font-size:48px; font-weight:bold; background:#2a2a2a;"
|
||||
"color:#666666; border-radius:8px;"
|
||||
"font-size:64px; font-weight:bold; background:#2a2a2a;"
|
||||
"color:#888888; border-radius:8px;"
|
||||
)
|
||||
self._result_lbl.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
layout.addWidget(self._start_btn)
|
||||
layout.addWidget(self._pause_btn)
|
||||
layout.addWidget(self._active_lbl)
|
||||
layout.addWidget(self._scope_lbl)
|
||||
layout.addWidget(self._model_lbl)
|
||||
layout.addWidget(self._belt_lbl)
|
||||
layout.addWidget(self._result_lbl, stretch=1)
|
||||
@@ -473,7 +447,7 @@ class InspectPage(QWidget):
|
||||
grid = QGridLayout()
|
||||
grid.setSpacing(4)
|
||||
|
||||
for col, text in enumerate(["", "그룹 A", "그룹 B"]):
|
||||
for col, text in enumerate(["", "집계"]):
|
||||
lbl = QLabel(text)
|
||||
lbl.setAlignment(Qt.AlignCenter)
|
||||
lbl.setStyleSheet("font-size:13px; color:#888888; font-weight:bold;")
|
||||
@@ -491,16 +465,14 @@ class InspectPage(QWidget):
|
||||
row_lbl.setStyleSheet(f"font-size:14px; color:{color}; font-weight:bold;")
|
||||
grid.addWidget(row_lbl, r, 0)
|
||||
|
||||
self._cnt_lbls[key] = {}
|
||||
for g_col, group in enumerate(["A", "B"], start=1):
|
||||
lbl = QLabel("0")
|
||||
lbl.setAlignment(Qt.AlignCenter)
|
||||
lbl.setStyleSheet(
|
||||
f"font-size:36px; font-weight:bold; color:{color};"
|
||||
"background:#222222; border-radius:4px; padding:2px 6px;"
|
||||
)
|
||||
grid.addWidget(lbl, r, g_col)
|
||||
self._cnt_lbls[key][group] = lbl
|
||||
lbl = QLabel("0")
|
||||
lbl.setAlignment(Qt.AlignCenter)
|
||||
lbl.setStyleSheet(
|
||||
f"font-size:36px; font-weight:bold; color:{color};"
|
||||
"background:#222222; border-radius:4px; padding:2px 6px;"
|
||||
)
|
||||
grid.addWidget(lbl, r, 1)
|
||||
self._cnt_lbls[key] = lbl
|
||||
|
||||
layout.addLayout(grid)
|
||||
|
||||
@@ -518,24 +490,6 @@ class InspectPage(QWidget):
|
||||
# 슬롯 — UI 이벤트
|
||||
# ================================================================== #
|
||||
|
||||
def _on_group_changed(self, group: str, changed_cb: QCheckBox, is_checked: bool):
|
||||
checks = self._group_a_checks if group == "A" else self._group_b_checks
|
||||
if is_checked and sum(1 for c in checks if c.isChecked()) > GroupManager.MAX_PER_GROUP:
|
||||
changed_cb.setChecked(False)
|
||||
return
|
||||
models = [c.text() for c in checks if c.isChecked()]
|
||||
if group == "A":
|
||||
self._groups.set_group_a(models)
|
||||
else:
|
||||
self._groups.set_group_b(models)
|
||||
|
||||
def _on_switch(self):
|
||||
active = self._groups.switch_group()
|
||||
other = "B" if active == "A" else "A"
|
||||
self._switch_btn.setText(f"현재: 그룹 {active} 활성 → {other}로 전환")
|
||||
self._active_lbl.setText(f"활성 그룹: {active}")
|
||||
print(f"[검사] 그룹 전환 → 활성 그룹 {active}")
|
||||
|
||||
def _on_start(self):
|
||||
if self._worker.isRunning():
|
||||
return
|
||||
@@ -558,11 +512,18 @@ class InspectPage(QWidget):
|
||||
log_action("[검사] 검사 재개")
|
||||
|
||||
def _on_reset(self):
|
||||
reply = QMessageBox.question(
|
||||
self, "카운터 초기화",
|
||||
"검사 카운트를 0으로 초기화할까요?\n"
|
||||
"초기화한 집계는 되돌릴 수 없습니다.",
|
||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No,
|
||||
)
|
||||
if reply != QMessageBox.Yes:
|
||||
return
|
||||
log_action("[검사] 카운트 리셋")
|
||||
for key in ("total", "pass", "fail", "unknown"):
|
||||
for g in ("A", "B"):
|
||||
self._counts[g][key] = 0
|
||||
self._cnt_lbls[key][g].setText("0")
|
||||
self._counts[key] = 0
|
||||
self._cnt_lbls[key].setText("0")
|
||||
|
||||
def closeEvent(self, event):
|
||||
self._worker.stop()
|
||||
@@ -598,6 +559,106 @@ class InspectPage(QWidget):
|
||||
self._worker.set_belt_delay(delay)
|
||||
self._belt_lbl.setText(f"벨트 딜레이: {delay:.2f}s")
|
||||
|
||||
def update_db(self, db_client):
|
||||
"""MainWindow에서 DB 연결/해제 시 호출."""
|
||||
self._db_client = db_client
|
||||
self.refresh_wk_results()
|
||||
|
||||
def refresh_wk_results(self):
|
||||
"""vi_AI_WK_Result 기준 작업 대상을 UI·PatMax 매핑·워커에 반영."""
|
||||
if not self._db_client or not self._db_client.is_connected():
|
||||
self._inspector.set_pattern_cells({})
|
||||
self._worker.set_work_targets(None)
|
||||
self._update_product_list([], [])
|
||||
self._scope_lbl.setText("검사 범위: DB 미연결")
|
||||
self._products_group.setTitle("검사 대상")
|
||||
print("[검사] WK_Result 비활성 — DB 미연결")
|
||||
return
|
||||
|
||||
mes_selected = self._config.get("mes", {}).get("selected_article_ids")
|
||||
if mes_selected is not None and len(mes_selected) == 0:
|
||||
self._inspector.set_pattern_cells({})
|
||||
self._worker.set_work_targets(set())
|
||||
self._update_product_list([], [])
|
||||
self._scope_lbl.setText("검사 범위: MES 제품 미선택")
|
||||
self._products_group.setTitle("검사 대상 (0종)")
|
||||
return
|
||||
|
||||
if mes_selected is not None:
|
||||
all_items = self._db_client.get_reflector_list_ordered(mes_selected)
|
||||
else:
|
||||
all_items = self._db_client.get_reflector_list()
|
||||
|
||||
self._inspector.set_pattern_cells(build_patmax_cells(all_items))
|
||||
|
||||
active, inactive = self._db_client.split_articles_by_wk(mes_selected)
|
||||
allowed = {SQLClient._norm_id(a["article_id"]) for a in active}
|
||||
|
||||
self._worker.set_work_targets(allowed)
|
||||
self._update_product_list(active, inactive)
|
||||
|
||||
total = len(active) + len(inactive)
|
||||
if active:
|
||||
self._scope_lbl.setText(f"검사 범위: 작업 대상 {len(active)}종")
|
||||
else:
|
||||
self._scope_lbl.setText("검사 범위: 작업 대상 없음")
|
||||
self._products_group.setTitle(
|
||||
f"검사 대상 (작업 {len(active)}종 / 전체 {total}종)"
|
||||
)
|
||||
print(
|
||||
f"[검사] WK_Result 작업 대상: {len(active)}종 (전체 {total}종, "
|
||||
f"PatMax 슬롯 {min(len(all_items), MAX_PATMAX_SLOTS)}개)"
|
||||
)
|
||||
|
||||
def _update_product_list(self, active: list, inactive: list):
|
||||
if not hasattr(self, "_products_inner_layout"):
|
||||
return
|
||||
|
||||
layout = self._products_inner_layout
|
||||
while layout.count():
|
||||
item = layout.takeAt(0)
|
||||
if item.widget():
|
||||
item.widget().deleteLater()
|
||||
|
||||
if active:
|
||||
layout.addWidget(self._make_section_label(
|
||||
f"작업 대상 — WK_Result ({len(active)})", active=True
|
||||
))
|
||||
for i, article in enumerate(active, 1):
|
||||
layout.addWidget(self._make_article_label(article, i, active=True))
|
||||
|
||||
if inactive:
|
||||
layout.addWidget(self._make_section_label(
|
||||
f"기타 ({len(inactive)})", active=False
|
||||
))
|
||||
for i, article in enumerate(inactive, 1):
|
||||
layout.addWidget(self._make_article_label(article, i, active=False))
|
||||
|
||||
if not active and not inactive:
|
||||
layout.addWidget(self._make_section_label("작업 대상 없음", active=False))
|
||||
|
||||
layout.addStretch()
|
||||
|
||||
@staticmethod
|
||||
def _make_section_label(text: str, active: bool) -> QLabel:
|
||||
lbl = QLabel(text)
|
||||
color = "#aaaaaa" if active else "#666666"
|
||||
lbl.setStyleSheet(
|
||||
f"font-size:12px; color:{color}; font-weight:bold;"
|
||||
"padding:6px 4px 2px 4px; background:#2a2a2a;"
|
||||
)
|
||||
return lbl
|
||||
|
||||
@staticmethod
|
||||
def _make_article_label(article: dict, index: int, active: bool) -> QLabel:
|
||||
lbl = QLabel(f"{index:2d}. {article_label(article)}")
|
||||
if active:
|
||||
style = "font-size:13px; min-height:32px; color:#eeeeee; padding:2px 4px;"
|
||||
else:
|
||||
style = "font-size:13px; min-height:32px; color:#555555; padding:2px 4px;"
|
||||
lbl.setStyleSheet(style)
|
||||
return lbl
|
||||
|
||||
# ================================================================== #
|
||||
# 워커 signal 슬롯 (메인 스레드)
|
||||
# ================================================================== #
|
||||
@@ -606,24 +667,21 @@ class InspectPage(QWidget):
|
||||
self._display_basler_image(frame, detections=detections)
|
||||
|
||||
def _on_result(self, data: dict):
|
||||
group = data["group"]
|
||||
matched = data["matched"]
|
||||
result = data["result"]
|
||||
cognex_pass = data["cognex_pass"]
|
||||
basler_pass = data["basler_pass"]
|
||||
result_info = data["result_info"]
|
||||
|
||||
self._model_lbl.setText(result_info["status"])
|
||||
|
||||
self._counts[group]["total"] += 1
|
||||
self._counts["total"] += 1
|
||||
if not matched:
|
||||
self._counts[group]["unknown"] += 1
|
||||
self._counts["unknown"] += 1
|
||||
elif result == "PASS":
|
||||
self._counts[group]["pass"] += 1
|
||||
self._counts["pass"] += 1
|
||||
else:
|
||||
self._counts[group]["fail"] += 1
|
||||
self._counts["fail"] += 1
|
||||
for key in ("total", "pass", "fail", "unknown"):
|
||||
self._cnt_lbls[key][group].setText(str(self._counts[group][key]))
|
||||
self._cnt_lbls[key].setText(str(self._counts[key]))
|
||||
|
||||
if not matched:
|
||||
self._set_result("미인식", "#332200", "#ff9900")
|
||||
@@ -639,7 +697,7 @@ class InspectPage(QWidget):
|
||||
def _set_result(self, text: str, bg: str, fg: str):
|
||||
self._result_lbl.setText(text)
|
||||
self._result_lbl.setStyleSheet(
|
||||
f"font-size:48px; font-weight:bold; background:{bg};"
|
||||
f"font-size:64px; font-weight:bold; background:{bg};"
|
||||
f"color:{fg}; border-radius:8px;"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user