"""AutoCal entrypoint: logging, locale, theme, then the window.""" from __future__ import annotations import argparse import logging import os import sys # The app id must be set before anything imports orisys_app_shared.paths, because # the config/output roots are resolved on first use and cached by callers. os.environ.setdefault("ORISYS_APP_ID", "autocal") def _force_utf8_streams() -> None: """Make stdout/stderr UTF-8 where the platform lets us. The controller's error strings and every panel label are Chinese, and a Windows console defaults to a legacy code page that cannot encode them. A failed reconfigure is not fatal — the log file is UTF-8 regardless. """ for stream in (sys.stdout, sys.stderr): reconfigure = getattr(stream, "reconfigure", None) if reconfigure is None: continue try: reconfigure(encoding="utf-8", errors="replace") except (ValueError, OSError): # pragma: no cover - stream already detached pass def _pyside_version() -> str: import PySide6 return getattr(PySide6, "__version__", "unknown") def _parse_args(argv: list[str]) -> argparse.Namespace: parser = argparse.ArgumentParser(prog="orisys-autocal", description=__doc__) parser.add_argument( "--demo", action="store_true", help="start on the simulated platform (no hardware, no controller DLL)", ) parser.add_argument( "--log-level", default="INFO", choices=("DEBUG", "INFO", "WARNING", "ERROR"), help="console and file log level", ) return parser.parse_args(argv) def _request_compatible_gl() -> bool: """Ask for a GL profile the 3D preview can draw with; see the preview module. Thin wrapper so the entry point does not have to know the details — the reasoning and the version choice live next to the drawing code that needs them. """ from .ui.pose_preview import configure_compatible_gl return configure_compatible_gl() def main(argv: list[str] | None = None) -> int: args = _parse_args(list(argv if argv is not None else sys.argv[1:])) _force_utf8_streams() from orisys_app_shared import API_VERSION from orisys_app_shared import __version__ as shared_version from orisys_app_shared.compat import check_shared_compat from orisys_app_shared.i18n import set_locale from orisys_app_shared.logging import init_logging, install_exception_hooks from orisys_app_shared.theme import build_qss from PySide6.QtWidgets import QApplication from . import REQUIRES_SHARED_API from . import __version__ as app_version from .motion import ConnectionSpec, DemoTransport from .preferences import load_preferences from .ui.window import AutoCalMainWindow init_logging("autocal", level=getattr(logging, args.log_level)) install_exception_hooks() check_shared_compat(REQUIRES_SHARED_API) logger = logging.getLogger(__name__) prefs = load_preferences() language = str(prefs.get("language") or "zh_CN") theme = str(prefs.get("theme") or "dark") set_locale(language) app = QApplication(sys.argv[:1]) app.setApplicationName("Orisys AutoCal") app.setApplicationVersion(app_version) _request_compatible_gl() app.setStyleSheet(build_qss(theme)) transport = None if args.demo: transport = DemoTransport() connection = dict(prefs.get("connection") or {}) connection["mode"] = "demo" prefs["connection"] = connection window = AutoCalMainWindow(prefs=prefs) if transport is not None: window.poller.service._transport = transport # noqa: SLF001 - deliberate swap window.show() logger.info( "AutoCal started (version=%s shared=%s api=%s binding=PySide6 %s " "theme=%s language=%s demo=%s)", app_version, shared_version, API_VERSION, _pyside_version(), theme, language, args.demo, ) if args.demo: window.connect_controller(ConnectionSpec(mode="demo"), label="connect") code = int(app.exec()) # Leave the process immediately. The ZMotion runtime keeps native threads of # its own, so a normal interpreter shutdown can hang after the window closes; # everything durable was already flushed by closeEvent and the log handlers. logging.shutdown() for stream in (sys.stdout, sys.stderr): try: stream.flush() except Exception: # noqa: BLE001 - a closed stream must not block exit pass os._exit(code) if __name__ == "__main__": raise SystemExit(main())