|
1 | | -"""Pattern generator connection setup (madVR / Prisma) — Qt port. |
| 1 | +"""Pattern generator connection setup (madVR / Prisma / live) — Qt port. |
2 | 2 |
|
3 | 3 | Qt port of the madVR and Prisma branches of |
4 | 4 | ``MainFrame.setup_patterngenerator`` (``display_cal.py:10738-11065``): connects |
|
9 | 9 | ``install_via_api`` branch, previously a "not available in this Qt build yet" |
10 | 10 | notice). |
11 | 11 |
|
12 | | -Only the two branches :meth:`~DisplayCAL.ui.main_window.MainWindow |
13 | | -._install_3dlut` actually reaches are ported here. wx's Resolve / "Web @ |
14 | | -localhost" / ``Chromecast *`` branch (and the final "no LUT access" error |
15 | | -branch) belong to the still-unported *live* pattern-generator measurement |
16 | | -flow (``visual_whitepoint_editor_handler``, ``luminance_measure_handler``, |
17 | | -``MainFrame.setup_measurement``'s pattern-generator path), not the 3D LUT |
18 | | -install offer, and are left for a future session if that flow is ever |
19 | | -ported. |
| 12 | +:func:`connect_patterngenerator` covers the two branches |
| 13 | +:meth:`~DisplayCAL.ui.main_window.MainWindow._install_3dlut` reaches (madVR, |
| 14 | +Prisma). :func:`connect_live_patterngenerator` separately ports the Resolve / |
| 15 | +"Web @ localhost" / ``Chromecast *`` branch (``display_cal.py:11042-11089``) |
| 16 | +for the visual whitepoint editor's live patch-streaming flow |
| 17 | +(:mod:`DisplayCAL.ui.tools.visual_whitepoint_editor`) -- unlike the |
| 18 | +madVR/Prisma flow this waits for an *incoming* connection from the |
| 19 | +destination rather than dialing out, so it drives ``patterngenerator.wait()`` |
| 20 | +on a background thread instead of ``connect()``. |
20 | 21 |
|
21 | 22 | Prisma discovery/connectivity reuses |
22 | 23 | :class:`DisplayCAL.patterngenerators.PrismaPatternGeneratorClient` directly |
|
43 | 44 | from time import localtime, strftime |
44 | 45 | from typing import Callable |
45 | 46 |
|
46 | | -from qtpy.QtCore import QObject, Qt, QThread, Signal |
| 47 | +from qtpy.QtCore import QObject, Qt, QThread, QTimer, Signal |
47 | 48 | from qtpy.QtWidgets import ( |
48 | 49 | QComboBox, |
49 | 50 | QDialog, |
|
62 | 63 | from DisplayCAL.debughelpers import Error, Info |
63 | 64 | from DisplayCAL.meta import NAME as APPNAME |
64 | 65 | from DisplayCAL.ui import message_box |
| 66 | +from DisplayCAL.util_io import LineCache |
65 | 67 | from DisplayCAL.worker import Worker |
66 | 68 |
|
67 | 69 |
|
@@ -360,6 +362,86 @@ def connect_patterngenerator( |
360 | 362 | return False |
361 | 363 |
|
362 | 364 |
|
| 365 | +def connect_live_patterngenerator( |
| 366 | + worker: Worker, parent: QWidget | None, title: str = APPNAME |
| 367 | +) -> bool: |
| 368 | + """Connect to a pattern generator for live measurement patch output. |
| 369 | +
|
| 370 | + Qt port of the Resolve / "Web @ localhost" / ``Chromecast *`` branch of |
| 371 | + ``MainFrame.setup_patterngenerator`` (``display_cal.py:11042-11089``), |
| 372 | + used by the visual whitepoint editor's live patch streaming |
| 373 | + (:mod:`DisplayCAL.ui.tools.visual_whitepoint_editor`). Unlike |
| 374 | + :func:`connect_patterngenerator` (madVR/Prisma, which dials out), these |
| 375 | + destinations accept an *incoming* connection, so this instantiates the |
| 376 | + client via :meth:`Worker.setup_patterngenerator` and then waits on a |
| 377 | + background thread, showing the client's own log output (e.g. "waiting |
| 378 | + for connection host:port") in a cancellable progress dialog. |
| 379 | +
|
| 380 | + Returns: |
| 381 | + bool: True once ``worker.patterngenerator`` is connected, False if |
| 382 | + setup failed or the user cancelled. |
| 383 | + """ |
| 384 | + logfile = LineCache(3) |
| 385 | + try: |
| 386 | + worker.setup_patterngenerator(logfile) |
| 387 | + except Exception as exception: # noqa: BLE001 (reported to the user below) |
| 388 | + message_box.critical(parent, title, str(exception)) |
| 389 | + return False |
| 390 | + patterngenerator = worker.patterngenerator |
| 391 | + if hasattr(patterngenerator, "conn"): |
| 392 | + return True |
| 393 | + |
| 394 | + progress = QProgressDialog("", lang.getstr("cancel"), 0, 0, parent) |
| 395 | + progress.setWindowTitle(title) |
| 396 | + progress.setWindowModality(Qt.WindowModal) |
| 397 | + progress.setMinimumDuration(0) |
| 398 | + progress.setAutoClose(False) |
| 399 | + progress.setAutoReset(False) |
| 400 | + |
| 401 | + thread = _CallThread(patterngenerator.wait) |
| 402 | + cancelled = False |
| 403 | + finished_naturally = False |
| 404 | + |
| 405 | + def _on_cancel() -> None: |
| 406 | + # Same "who closed the dialog" race as connect_madvr's _on_cancel. |
| 407 | + nonlocal cancelled |
| 408 | + if finished_naturally: |
| 409 | + return |
| 410 | + cancelled = True |
| 411 | + patterngenerator.listening = False |
| 412 | + |
| 413 | + def _on_thread_finished() -> None: |
| 414 | + nonlocal finished_naturally |
| 415 | + finished_naturally = True |
| 416 | + progress.close() |
| 417 | + |
| 418 | + def _poll_log() -> None: |
| 419 | + line = logfile.read() |
| 420 | + if line: |
| 421 | + progress.setLabelText(line) |
| 422 | + |
| 423 | + timer = QTimer(progress) |
| 424 | + timer.timeout.connect(_poll_log) |
| 425 | + timer.start(100) |
| 426 | + |
| 427 | + progress.canceled.connect(_on_cancel) |
| 428 | + thread.finished.connect(_on_thread_finished) |
| 429 | + thread.start() |
| 430 | + if thread.isFinished(): |
| 431 | + _on_thread_finished() |
| 432 | + else: |
| 433 | + progress.exec_() |
| 434 | + timer.stop() |
| 435 | + thread.wait() |
| 436 | + |
| 437 | + if cancelled: |
| 438 | + return False |
| 439 | + if isinstance(thread.result, Exception): |
| 440 | + message_box.critical(parent, title, str(thread.result)) |
| 441 | + return False |
| 442 | + return hasattr(patterngenerator, "conn") |
| 443 | + |
| 444 | + |
363 | 445 | class Lut3DAPIInstallController(QObject): |
364 | 446 | """Install a just-created 3D LUT directly to madVR or Prisma. |
365 | 447 |
|
|
0 commit comments