170 lines
5.7 KiB
Python
170 lines
5.7 KiB
Python
#!/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 <out stem>_<tab>.<ext>, 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",
|
|
)
|
|
parser.add_argument(
|
|
"--setup-dialog",
|
|
default="",
|
|
choices=("", "connection", "bus"),
|
|
help="photograph the device-setup dialog on this tab instead of the window",
|
|
)
|
|
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)
|
|
|
|
if args.setup_dialog:
|
|
# The dialog is a separate top-level window, so it is grabbed on its own
|
|
# rather than inside the page shot.
|
|
window._show_device_setup(args.setup_dialog) # noqa: SLF001
|
|
app.processEvents()
|
|
output = Path(args.out).resolve()
|
|
target = window.device_setup
|
|
if not target.grab().save(str(output)):
|
|
print(f"could not write {output}", file=sys.stderr)
|
|
return 1
|
|
print(
|
|
f"wrote {output} ({target.width()}x{target.height()}, theme={args.theme}, "
|
|
f"tab={args.setup_dialog})"
|
|
)
|
|
target.close()
|
|
window.close()
|
|
app.processEvents()
|
|
return 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())
|