Add initial project structure and configuration files

This commit is contained in:
Jack.Le
2026-05-18 14:14:16 -07:00
commit b9adc7c0b8
41 changed files with 94365 additions and 0 deletions
+198
View File
@@ -0,0 +1,198 @@
"""Reusable Qt widgets for the wafer monitor demo."""
from __future__ import annotations
import math
from typing import Any
from .qt import require_qt
QtCore, QtGui, QtWidgets = require_qt()
class MonitorPlot(QtWidgets.QWidget):
"""Operator monitor plot driven by a UI snapshot."""
def __init__(self, parent: QtWidgets.QWidget | None = None) -> None:
super().__init__(parent)
self.setMinimumSize(430, 390)
self._snapshot: Any | None = None
def set_snapshot(self, snapshot: Any | None) -> None:
self._snapshot = snapshot
self.update()
def paintEvent(self, event: QtGui.QPaintEvent) -> None:
del event
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.RenderHint.Antialiasing)
rect = self.rect().adjusted(0, 0, -1, -1)
painter.fillRect(rect, QtGui.QColor("#ffffff"))
legend_height = 32
plot_rect = rect.adjusted(30, 28, -30, -(legend_height + 16))
center = QtCore.QPointF(plot_rect.center())
radius = min(plot_rect.width(), plot_rect.height()) * 0.34
painter.setPen(QtGui.QPen(QtGui.QColor("#93c5fd"), 2, QtCore.Qt.PenStyle.DashLine))
painter.drawEllipse(center, radius, radius)
painter.setPen(QtGui.QPen(QtGui.QColor("#64748b"), 1))
for label, angle in [("CAM 1", -90), ("CAM 2", 0), ("CAM 3", 90), ("CAM 4", 180)]:
point = _polar(center, radius * 1.18, angle)
painter.drawText(
_camera_label_rect(point, angle),
QtCore.Qt.AlignmentFlag.AlignCenter,
label,
)
if self._snapshot:
reference_radius = max(float(self._snapshot.reference_radius_mm), 1.0)
detected_radius = float(self._snapshot.detected_radius_mm)
detected_center_mm = self._snapshot.detected_center_mm
scale = radius / reference_radius
detected_center = QtCore.QPointF(
center.x() + float(detected_center_mm[0]) * scale,
center.y() + float(detected_center_mm[1]) * scale,
)
painter.setPen(QtGui.QPen(QtGui.QColor("#f97316"), 2))
painter.drawEllipse(detected_center, detected_radius * scale, detected_radius * scale)
painter.setPen(QtGui.QPen(QtGui.QColor("#2563eb"), 7))
painter.drawPoint(center)
painter.setPen(QtGui.QPen(QtGui.QColor("#f97316"), 7))
painter.drawLine(center, detected_center)
painter.drawPoint(detected_center)
else:
painter.setPen(QtGui.QColor("#64748b"))
painter.drawText(plot_rect, QtCore.Qt.AlignmentFlag.AlignCenter, "No wafer read")
_draw_plot_legend(painter, rect, rect.bottom() - legend_height // 2)
painter.end()
class OffsetCard(QtWidgets.QFrame):
"""Metric card with a separate unit label."""
def __init__(
self,
label: str,
unit: str,
value: str = "--",
parent: QtWidgets.QWidget | None = None,
) -> None:
super().__init__(parent)
self.setObjectName("MetricCard")
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(20, 17, 20, 17)
layout.setSpacing(8)
label_widget = QtWidgets.QLabel(label)
label_widget.setObjectName("Metric")
row = QtWidgets.QHBoxLayout()
row.setSpacing(8)
self.value_widget = QtWidgets.QLabel(value)
self.value_widget.setObjectName("Value")
unit_widget = QtWidgets.QLabel(unit)
unit_widget.setObjectName("Unit")
row.addWidget(self.value_widget)
row.addWidget(unit_widget)
row.addStretch(1)
layout.addWidget(label_widget)
layout.addLayout(row)
def set_value(self, value: str) -> None:
self.value_widget.setText(value)
class CameraStatusCard(QtWidgets.QFrame):
"""Camera health row with a colored status badge."""
def __init__(self, label: str, parent: QtWidgets.QWidget | None = None) -> None:
super().__init__(parent)
self.setObjectName("MetricCard")
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(20, 16, 20, 16)
layout.setSpacing(12)
self.name_label = QtWidgets.QLabel(label)
self.name_label.setObjectName("Metric")
self.badge = QtWidgets.QLabel("Wait")
self.badge.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.badge.setMinimumWidth(66)
layout.addWidget(self.name_label)
layout.addStretch(1)
layout.addWidget(self.badge)
self.set_status("wait")
def set_label(self, label: str) -> None:
self.name_label.setText(label)
def set_status(self, status: str, reading_text: str = "") -> None:
normalized = status.lower()
text = "OK"
color = "#e8fff6"
fg = "#006a55"
if normalized in {"warn", "noisy"}:
text = "Noisy"
color = "#fff3dc"
fg = "#835008"
elif normalized == "fail":
text = "Fail"
color = "#ffe8e8"
fg = "#a53535"
elif normalized == "wait":
text = "Wait"
color = "#353632"
fg = "#b8b8b2"
self.badge.setText(reading_text or text)
self.badge.setStyleSheet(
f"background: {color}; color: {fg}; border-radius: 15px; "
"padding: 6px 12px; font-weight: 750;"
)
def _polar(center: QtCore.QPointF, radius: float, angle_deg: float) -> QtCore.QPointF:
radians = math.radians(angle_deg)
return QtCore.QPointF(
center.x() + math.cos(radians) * radius,
center.y() + math.sin(radians) * radius,
)
def _draw_plot_legend(
painter: QtGui.QPainter, rect: QtCore.QRect, legend_y: int
) -> None:
"""Draw Reference / Detected legend centered with measured spacing."""
line_width = 36
text_gap = 8
entry_gap = 28
entries = [
(QtGui.QPen(QtGui.QColor("#93c5fd"), 2, QtCore.Qt.PenStyle.DashLine), "Reference"),
(QtGui.QPen(QtGui.QColor("#f97316"), 2), "Detected"),
]
metrics = QtGui.QFontMetrics(painter.font())
text_y = legend_y + 5
entry_widths = [
line_width + text_gap + metrics.horizontalAdvance(label)
for _, label in entries
]
total_width = sum(entry_widths) + entry_gap * (len(entries) - 1)
x = rect.center().x() - total_width / 2
painter.setPen(QtGui.QColor("#64748b"))
for index, (line_pen, label) in enumerate(entries):
painter.setPen(line_pen)
painter.drawLine(int(x), legend_y, int(x + line_width), legend_y)
painter.setPen(QtGui.QColor("#64748b"))
painter.drawText(int(x + line_width + text_gap), text_y, label)
x += entry_widths[index] + entry_gap
def _camera_label_rect(point: QtCore.QPointF, angle_deg: float) -> QtCore.QRectF:
width, height = 56.0, 18.0
if angle_deg == -90:
return QtCore.QRectF(point.x() - width / 2, point.y() - height - 6, width, height)
if angle_deg == 90:
return QtCore.QRectF(point.x() - width / 2, point.y() - height - 4, width, height)
if angle_deg == 0:
return QtCore.QRectF(point.x() + 8, point.y() - height / 2, width, height)
return QtCore.QRectF(point.x() - width - 8, point.y() - height / 2, width, height)