Files
pyGUI/src/pygui/__main__.py
T

103 lines
4.1 KiB
Python

import logging
import sys
from importlib.metadata import PackageNotFoundError, version
from pathlib import Path
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtQuickControls2 import QQuickStyle
from PySide6.QtWidgets import QApplication
from pygui.backend.controllers.device_controller import DeviceController
from pygui.backend.controllers.session_controller import SessionController
from pygui.backend.data.constants import default_data_dir
from pygui.backend.data.file_browser import FileBrowser
from pygui.backend.data.local_settings import LocalSettings
from pygui.backend.data.local_settings_model import LocalSettingsModel
from pygui.backend.license.license_model import LicenseModel
# Importing wafer_map_item + trend_chart_item registers @QmlElement types (QML: import ISC.Wafer).
from pygui.backend.visualization import (
graph_quick_item as graph_quick_item, # noqa: F401
)
from pygui.backend.visualization import (
trend_chart_item as trend_chart_item, # noqa: F401
)
from pygui.backend.visualization import (
wafer_map_item as wafer_map_item, # noqa: F401
)
# ===== Application Entry Point =====
def main() -> int:
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
# ===== UI Style Setup =====
# Use a non-native controls style so our custom QML button backgrounds are supported.
QQuickStyle.setStyle("Basic")
# ===== Qt Bootstrap =====
# Minimal Qt bootstrap: load the ISC QML module and exit if it fails.
app = QApplication(sys.argv)
engine = QQmlApplicationEngine()
# ===== Shared Settings =====
# One LocalSettings instance, one disk read. LocalSettingsModel and
# DeviceController both hold this same object, so a save from the
# Settings tab is visible to the device layer immediately — no reload,
# no second copy to go stale.
data_dir = str(default_data_dir())
raw_settings = LocalSettings.read_settings(data_dir)
settings_model = LocalSettingsModel(raw_settings, data_dir)
select_file_dialog_model = FileBrowser()
settings_model.loadSettings()
engine.rootContext().setContextProperty("settingsModel", settings_model)
engine.rootContext().setContextProperty("file_browser", select_file_dialog_model)
# App version from package metadata so the About dialog can't drift
# from pyproject.toml.
try:
app_version = version("pygui")
except PackageNotFoundError:
app_version = "dev"
engine.rootContext().setContextProperty("appVersion", app_version)
# ===== Device Controller (serial comm) =====
# TODO P1.1: construct LicenseModel BEFORE DeviceController and pass
# license_lookup=license_model.levelForWafer into the ctor.
# THINKING: controller must answer "is this serial licensed?" at
# read/erase time (P2.1); a bound Callable[[str], str] avoids a
# LicenseModel import in the controller and keeps it testable with a
# lambda. See docs/pending/license-gating-plan.md §1.1.
device_controller = DeviceController(raw_settings, data_dir)
engine.rootContext().setContextProperty("deviceController", device_controller)
# Source panel browses the same folder recordings/reads are saved to.
select_file_dialog_model.setCurrentDirectory(str(device_controller.saveDataDir))
# ===== License Model (About dialog grid, replay trial) =====
license_model = LicenseModel(data_dir)
engine.rootContext().setContextProperty("licenseModel", license_model)
# ===== Session Controller (live/review wafer dashboard) =====
stream_controller = SessionController()
engine.rootContext().setContextProperty("streamController", stream_controller)
# ===== QML Startup =====
# The "ISC" QML module lives alongside this file (src/pygui/ISC), so the
# package directory is the import path the engine searches for qmldir.
engine.addImportPath(Path(__file__).parent)
engine.loadFromModule("ISC", "Main")
# ===== Exit Handling =====
if not engine.rootObjects():
return -1
return app.exec()
if __name__ == "__main__":
sys.exit(main())