Skip to content

Commit 3f2a09b

Browse files
authored
Merge pull request #910 from eoyilmaz/879-qt-whitepoint-editor-patterngenerator
[#879] Port pattern-generator patch output to Qt visual whitepoint editor
2 parents bbf7b51 + bc2f7eb commit 3f2a09b

6 files changed

Lines changed: 522 additions & 19 deletions

File tree

DisplayCAL/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.10.0.dev64
1+
3.10.0.dev65

DisplayCAL/ui/main_window.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,11 @@
253253
)
254254
from DisplayCAL.ui.measurement_report import ReportPanel
255255
from DisplayCAL.ui.measurement_sanity_dialog import MeasurementSanityDialog
256-
from DisplayCAL.ui.patterngenerator_setup import Lut3DAPIInstallController
256+
from DisplayCAL.ui.patterngenerator_setup import (
257+
Lut3DAPIInstallController,
258+
connect_live_patterngenerator,
259+
connect_patterngenerator,
260+
)
257261
from DisplayCAL.ui.profile_finish_dialog import ProfileFinishDialog
258262
from DisplayCAL.ui.profile_install_window import (
259263
InstallProfileWindow,
@@ -6628,15 +6632,53 @@ def _visual_whitepoint_editor_btn_handler(self) -> None:
66286632
Qt port of ``visual_whitepoint_editor_handler``: reuses a single
66296633
window instance, matching the ``_gamap_window`` /
66306634
``_testchart_editor_window`` singleton precedent elsewhere on this
6631-
window.
6632-
"""
6635+
window. Unlike wx (which constructs a fresh frame per open with the
6636+
pattern generator baked in), the connection is (re-)established here
6637+
on every open and pushed into the existing window via
6638+
:meth:`~DisplayCAL.ui.tools.visual_whitepoint_editor
6639+
.VisualWhitepointEditorWindow.set_patterngenerator`, since the
6640+
configured display (and thus destination) can change between opens.
6641+
"""
6642+
title = lang.getstr("whitepoint.visual_editor")
6643+
display_name = config.get_display_name(None, True)
6644+
patterngenerator = None
6645+
if display_name in ("Prisma", "madVR"):
6646+
connected = connect_patterngenerator(self.worker, self, title)
6647+
if connected is None or connected is False:
6648+
return
6649+
elif display_name in (
6650+
"Resolve",
6651+
"Web @ localhost",
6652+
) or display_name.startswith("Chromecast "):
6653+
if not connect_live_patterngenerator(self.worker, self, title):
6654+
return
6655+
if display_name == "madVR":
6656+
self.worker.madtpg.set_device_gamma_ramp(None)
6657+
self.worker.madtpg.disable_3dlut()
6658+
if self.worker.madtpg.is_fullscreen():
6659+
self.worker.madtpg.leave_fullscreen()
6660+
elif display_name == "Prisma":
6661+
try:
6662+
self.worker.patterngenerator.disable_processing()
6663+
except OSError as exception:
6664+
message_box.critical(self, APPNAME, str(exception))
6665+
return
6666+
if display_name in (
6667+
"madVR",
6668+
"Prisma",
6669+
"Resolve",
6670+
"Web @ localhost",
6671+
) or display_name.startswith("Chromecast "):
6672+
patterngenerator = self.worker.patterngenerator
6673+
66336674
window = self._visual_whitepoint_editor_window
66346675
if window is None:
66356676
window = VisualWhitepointEditorWindow()
66366677
window.measure_requested.connect(
66376678
self._visual_whitepoint_editor_measure_handler
66386679
)
66396680
self._visual_whitepoint_editor_window = window
6681+
window.set_patterngenerator(patterngenerator)
66406682
window.show()
66416683
window.raise_()
66426684
window.activateWindow()

DisplayCAL/ui/patterngenerator_setup.py

Lines changed: 92 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Pattern generator connection setup (madVR / Prisma) — Qt port.
1+
"""Pattern generator connection setup (madVR / Prisma / live) — Qt port.
22
33
Qt port of the madVR and Prisma branches of
44
``MainFrame.setup_patterngenerator`` (``display_cal.py:10738-11065``): connects
@@ -9,14 +9,15 @@
99
``install_via_api`` branch, previously a "not available in this Qt build yet"
1010
notice).
1111
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()``.
2021
2122
Prisma discovery/connectivity reuses
2223
:class:`DisplayCAL.patterngenerators.PrismaPatternGeneratorClient` directly
@@ -43,7 +44,7 @@
4344
from time import localtime, strftime
4445
from typing import Callable
4546

46-
from qtpy.QtCore import QObject, Qt, QThread, Signal
47+
from qtpy.QtCore import QObject, Qt, QThread, QTimer, Signal
4748
from qtpy.QtWidgets import (
4849
QComboBox,
4950
QDialog,
@@ -62,6 +63,7 @@
6263
from DisplayCAL.debughelpers import Error, Info
6364
from DisplayCAL.meta import NAME as APPNAME
6465
from DisplayCAL.ui import message_box
66+
from DisplayCAL.util_io import LineCache
6567
from DisplayCAL.worker import Worker
6668

6769

@@ -360,6 +362,86 @@ def connect_patterngenerator(
360362
return False
361363

362364

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+
363445
class Lut3DAPIInstallController(QObject):
364446
"""Install a just-created 3D LUT directly to madVR or Prisma.
365447

0 commit comments

Comments
 (0)