更新
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user