feat(map): add batch export and adjust layout alignment
- Implement synchronous offscreen wafer map PNG rendering and CSV summary. - Add "Batch Export" button to file browser sidebar. - Adjust trend pane bottom spacer to align with About button when visible. - Fix type check errors and add pytest coverage for batch export.
This commit is contained in:
@@ -14,7 +14,7 @@ import logging
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
from PySide6.QtCore import Property, QPoint, Qt, Signal, Slot
|
||||
from PySide6.QtCore import Property, QPoint, QRectF, Qt, Signal, Slot
|
||||
from PySide6.QtGui import (
|
||||
QBrush,
|
||||
QColor,
|
||||
@@ -310,17 +310,42 @@ class WaferMapItem(QQuickPaintedItem):
|
||||
Returns:
|
||||
True on success, False on failure
|
||||
"""
|
||||
if not file_path:
|
||||
if not file_path or not self._values:
|
||||
# No file loaded / no stream played yet — only the empty template
|
||||
# would render, which isn't a meaningful export.
|
||||
return False
|
||||
try:
|
||||
result = self.grabToImage()
|
||||
img = result.image() # type: ignore[attr-defined]
|
||||
img.save(file_path, "PNG")
|
||||
return True
|
||||
# Render synchronously via paint() into our own image —
|
||||
# grabToImage() resolves on a later render frame, so its result
|
||||
# is null when read immediately (the old silent failure).
|
||||
w, h = int(self.width()), int(self.height())
|
||||
if w <= 0 or h <= 0:
|
||||
return False
|
||||
metrics = self._metrics_text()
|
||||
footer_h = 28 if metrics else 0
|
||||
img = QImage(w, h + footer_h, QImage.Format.Format_ARGB32)
|
||||
img.fill(0) # transparent background
|
||||
painter = QPainter(img)
|
||||
self.paint(painter)
|
||||
if metrics:
|
||||
footer = QRectF(0, h, w, footer_h)
|
||||
painter.fillRect(footer, QColor("#101014"))
|
||||
painter.setPen(QColor("#CBD5E1"))
|
||||
painter.drawText(footer, Qt.AlignmentFlag.AlignCenter, metrics)
|
||||
painter.end()
|
||||
return bool(img.save(file_path))
|
||||
except Exception as e:
|
||||
log.error("Export failed: %s", e)
|
||||
return False
|
||||
|
||||
def _metrics_text(self) -> str:
|
||||
"""One-line metric readout for the export footer; '' when no data."""
|
||||
if not self._values:
|
||||
return ""
|
||||
vs = self._values
|
||||
return (f"Sensors: {len(vs)} Min: {min(vs):.2f}°C "
|
||||
f"Max: {max(vs):.2f}°C Avg: {sum(vs) / len(vs):.2f}°C")
|
||||
|
||||
# ── internal ─────────────────────────────────────────────────────────
|
||||
|
||||
def _rebuild_thickness(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user