"""Exercise real Sequencer UI ticks, rebuild, render, then render again in one editor.""" import json import os import traceback import unreal from ue_workshop import render from ue_workshop.paths import PROJECT unreal.EditorPythonScripting.set_keep_python_script_alive(True) # The test owns shutdown; allow multiple renders in the same editor process. os.environ["WORKSHOP_BATCH"] = "0" code = (PROJECT / "exercises/my_shot.py").read_text(encoding="utf-8") code = code.replace("RENDER = True", "RENDER = False") state = {"stage": 0, "ticks": 0, "outputs": []} def build(): namespace = {"__name__": "__main__"} exec(compile(code, "my_shot.py", "exec"), namespace) return namespace namespace = build() def tick(delta): global namespace state["ticks"] += 1 try: if state["stage"] in {0, 2} and state["ticks"] >= 30: # The previous sequence editor has ticked before its actors and # bindings are replaced, matching repeated Execute Python Script. next_stage = state["stage"] + 1 # Asset saves can pump Slate recursively. Do not re-enter the test. state["stage"] = -1 namespace = build() output = render.start(namespace["shot"], mp4=True) state["outputs"].append(output) state["stage"] = next_stage elif state["stage"] in {1, 3} and not render.is_rendering(): result = json.loads((state["outputs"][-1] / "result.json").read_text()) assert result["success"], result if state["stage"] == 3: finish({"success": True, "renders": [str(path) for path in state["outputs"]]}) else: unreal.LevelSequenceEditorBlueprintLibrary.open_level_sequence(namespace["shot"].asset) state["stage"] = 2 state["ticks"] = 0 except Exception: finish({"success": False, "error": traceback.format_exc()}) def finish(result): unreal.unregister_slate_post_tick_callback(handle) render._write_status(result) unreal.log("[Workshop UI test] " + str(result)) unreal.EditorPythonScripting.set_keep_python_script_alive(False) handle = unreal.register_slate_post_tick_callback(tick)