141 lines
4.6 KiB
Python
141 lines
4.6 KiB
Python
"""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())
|