103 lines
4.1 KiB
Python
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())
|