#!/usr/bin/env python3 """Render the AutoCal window offscreen to a PNG, for review. python apps/autocal/tools/panel_snapshot.py --theme light --out shot.png Optionally drives a simulated session first, so the shot shows live values: python apps/autocal/tools/panel_snapshot.py --demo --language en_US --out shot.png """ from __future__ import annotations import argparse import os import sys from pathlib import Path APP_ROOT = Path(__file__).resolve().parents[1] for path in (APP_ROOT / "src", APP_ROOT.parents[1] / "shared" / "src"): if str(path) not in sys.path: sys.path.insert(0, str(path)) os.environ.setdefault("ORISYS_APP_ID", "autocal") os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") if sys.platform == "win32" and "QT_QPA_FONTDIR" not in os.environ: _fonts = Path(os.environ.get("WINDIR", r"C:\Windows")) / "Fonts" if _fonts.is_dir(): os.environ["QT_QPA_FONTDIR"] = str(_fonts) def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--theme", default="dark", choices=("dark", "light")) parser.add_argument("--language", default="zh_CN", choices=("zh_CN", "en_US")) parser.add_argument("--size", default="1600x900", help="WIDTHxHEIGHT") parser.add_argument("--out", default="autocal.png") parser.add_argument( "--tab", default="axes", help="workspace tab to show: axes, sequence, sensor, surface, points", ) parser.add_argument( "--compare", action="store_true", help="render every tab, named _., so the layout can be " "reviewed in one pass", ) parser.add_argument( "--demo", action="store_true", help="drive a simulated session so the shot shows live values", ) args = parser.parse_args() width, height = (int(part) for part in args.size.lower().split("x")) from PySide6.QtWidgets import QApplication from orisys_app_shared.i18n import set_locale from orisys_app_shared.theme import build_qss from orisys_autocal.motion import ConnectionSpec, DemoTransport from orisys_autocal.preferences import load_preferences from orisys_autocal.ui.bus_panel import default_bas_dir from orisys_autocal.ui.window import AutoCalMainWindow set_locale(args.language) app = QApplication.instance() or QApplication([]) app.setStyleSheet(build_qss(args.theme)) prefs = load_preferences() prefs["theme"] = args.theme prefs["language"] = args.language prefs["show_diagnostics"] = False window = AutoCalMainWindow(prefs=prefs) window.poller.service._transport = DemoTransport() # noqa: SLF001 window.resize(width, height) window.show() app.processEvents() if args.demo: window.connect_controller(ConnectionSpec(mode="demo"), label="connect") _wait_for(app, lambda: window._last_snapshot.connected, 6.0) window.bus_panel.set_bas_path(str(default_bas_dir() / "ECAT.bas")) window._on_download_bas() _wait_for(app, lambda: window._last_snapshot.bus.init_status == 1, 8.0) window.axis_panel.set_selected_axis(0) window.axis_panel.target_edit.setText("1000") window._on_move_abs(0, 1000.0) _wait_for(app, lambda: window._last_snapshot.axis(0).dpos != 0, 4.0) app.processEvents() if args.compare: return _render_every_tab(window, app, args, width, height) window.workspace.set_current_key(args.tab) app.processEvents() output = Path(args.out).resolve() if not window.grab().save(str(output)): print(f"could not write {output}", file=sys.stderr) return 1 print( f"wrote {output} ({width}x{height}, theme={args.theme}, " f"lang={args.language}, tab={args.tab})" ) window.close() app.processEvents() return 0 def _render_every_tab(window, app, args, width: int, height: int) -> int: """One image per workspace tab, so the whole layout can be reviewed at once.""" base = Path(args.out).resolve() failures = 0 for key in window.workspace.page_keys(): window.workspace.set_current_key(key) app.processEvents() target = base.with_name(f"{base.stem}_{key}{base.suffix or '.png'}") if window.grab().save(str(target)): print(f"wrote {target} ({width}x{height}, tab={key})") else: print(f"could not write {target}", file=sys.stderr) failures += 1 window.close() app.processEvents() return 1 if failures else 0 def _wait_for(app, predicate, timeout: float) -> bool: import time deadline = time.monotonic() + timeout while time.monotonic() < deadline: app.processEvents() if predicate(): return True time.sleep(0.02) return False if __name__ == "__main__": raise SystemExit(main())