Files
zyt/app/scripts/render_diagnosis_media_thumbnail_visual.py
T
2026-08-11 09:12:51 +08:00

159 lines
5.8 KiB
Python

"""Render the Notes, Chat, and Daily-lower parity states without external network."""
from __future__ import annotations
import os
from pathlib import Path
from typing import Any
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from PySide6.QtCore import QBuffer, QIODevice, QPointF, Qt, QTimer
from PySide6.QtGui import QColor, QFont, QFontDatabase, QImage, QPainter, QPen
from PySide6.QtWidgets import QApplication
from render_diagnosis_detail_visual import ScreenshotRepository, _run_immediately
from doctor_workstation.core import PermissionSet
from doctor_workstation.ui import diagnosis_drawer
from doctor_workstation.ui.dialogs import diagnosis as diagnosis_module
from doctor_workstation.ui.dialogs.diagnosis import DiagnosisDialog
class MediaScreenshotRepository(ScreenshotRepository):
"""Use the established artifact fixture with production-shaped media URLs."""
def get_doctor_notes(self, diagnosis_id: int) -> list[dict[str, Any]]:
notes = super().get_doctor_notes(diagnosis_id)
notes[0]["tongue_images"] = [
{"url": "https://media.example.invalid/diagnosis/501/tongue-7001.jpg"}
]
return notes
def _image_bytes(kind: str) -> bytes:
if kind == "tongue":
image = QImage(240, 180, QImage.Format.Format_ARGB32)
image.fill(QColor("#F4E3D8"))
painter = QPainter(image)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
painter.setPen(QPen(QColor("#D1A08F"), 3))
painter.setBrush(QColor("#D77F83"))
painter.drawEllipse(52, 18, 136, 162)
painter.setPen(QPen(QColor("#B75F65"), 2))
painter.drawLine(120, 42, 120, 148)
painter.setPen(QPen(QColor("#F7D8D2"), 10, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap))
painter.drawLine(91, 55, 103, 132)
painter.drawLine(149, 55, 137, 132)
painter.end()
else:
image = QImage(640, 360, QImage.Format.Format_ARGB32)
image.fill(QColor("#F8FAFC"))
painter = QPainter(image)
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
painter.setPen(QPen(QColor("#DCE3EC"), 1))
for x in range(48, 610, 70):
painter.drawLine(x, 36, x, 316)
for y in range(56, 310, 52):
painter.drawLine(42, y, 606, y)
points = [
QPointF(48, 250),
QPointF(125, 226),
QPointF(205, 238),
QPointF(285, 168),
QPointF(365, 190),
QPointF(445, 112),
QPointF(525, 142),
QPointF(602, 82),
]
painter.setPen(QPen(QColor("#0F766E"), 7, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap))
for start, end in zip(points, points[1:], strict=False):
painter.drawLine(start, end)
painter.setPen(QPen(QColor("#FFFFFF"), 3))
painter.setBrush(QColor("#0F766E"))
for point in points:
painter.drawEllipse(point, 8, 8)
painter.end()
buffer = QBuffer()
if not buffer.open(QIODevice.OpenModeFlag.WriteOnly) or not image.save(buffer, "PNG"):
raise RuntimeError("failed to encode deterministic visual image")
return bytes(buffer.data())
TONGUE_IMAGE = _image_bytes("tongue")
CHAT_IMAGE = _image_bytes("chat")
def _offline_remote_load(self: diagnosis_drawer._RemoteImageButton, source: str) -> None:
"""Preserve asynchronous completion while replacing only external transport."""
self._source = str(source).strip()
generation = self._invalidate_request()
self.setToolTip(self._source)
self._show_loading()
payload = TONGUE_IMAGE if self.objectName() == "DiagnosisTongueThumb" else CHAT_IMAGE
QTimer.singleShot(0, lambda: self._apply_payload(payload, generation))
def _prepare_app() -> QApplication:
app = QApplication.instance() or QApplication([])
app.setFont(QFont("Microsoft YaHei", 9))
font_path = Path("C:/Windows/Fonts/msyh.ttc")
if font_path.is_file():
font_id = QFontDatabase.addApplicationFont(str(font_path))
families = QFontDatabase.applicationFontFamilies(font_id)
if families:
app.setFont(QFont(families[0], 9))
return app
def _open_tab(app: QApplication, key: str) -> DiagnosisDialog:
dialog = DiagnosisDialog(
MediaScreenshotRepository(locked=False),
permissions=PermissionSet(["*"]),
)
dialog.resize(1024, 640)
dialog.open_for(501, editable=True)
index = next(
item for item in range(dialog.tabs.count()) if dialog.tabs.tabBar().tabData(item) == key
)
dialog.tabs.setCurrentIndex(index)
for _ in range(10):
app.processEvents()
return dialog
def render() -> list[Path]:
app = _prepare_app()
diagnosis_module.run_async = _run_immediately
diagnosis_drawer._RemoteImageButton.load_url = _offline_remote_load
output = Path(__file__).resolve().parents[1] / "artifacts" / "diagnosis_visual"
output.mkdir(parents=True, exist_ok=True)
rendered: list[Path] = []
for tab_key, state_name in (("notes", "notes_actions"), ("chat", "chat_archive")):
dialog = _open_tab(app, tab_key)
path = output / f"diagnosis_state_{state_name}_1024x640.png"
if not dialog.grab().save(str(path), "PNG"):
raise RuntimeError(f"failed to save {path}")
rendered.append(path)
dialog.close()
app.processEvents()
daily = _open_tab(app, "daily")
page = daily._tab_pages["daily"]
page.verticalScrollBar().setValue(page.verticalScrollBar().maximum())
for _ in range(4):
app.processEvents()
daily_path = output / "diagnosis_state_daily_lower_1024x640.png"
if not daily.grab().save(str(daily_path), "PNG"):
raise RuntimeError(f"failed to save {daily_path}")
rendered.append(daily_path)
daily.close()
app.processEvents()
return rendered
if __name__ == "__main__":
for rendered_path in render():
print(rendered_path)