Mercurial > code > home > repos > light9
diff blender/time_sync/blender_time.py @ 2455:2d454737a916
split blender code to new file
author | drewp@bigasterisk.com |
---|---|
date | Tue, 20 May 2025 09:24:35 -0700 |
parents | blender/time_sync/time_from_graph.py@405abed9a45c |
children | d94480bfb179 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/blender/time_sync/blender_time.py Tue May 20 09:24:35 2025 -0700 @@ -0,0 +1,107 @@ +from dataclasses import dataclass +from typing import Callable + +import bpy +from bpy.app.handlers import persistent + +from light9.run_local import log + +UPDATE_PERIOD = 1 / 20 + + +def clamp(lo, hi, x): + return max(lo, min(hi, x)) + + +class _TimeEvent(): + pass + + +class SceneLoaded(_TimeEvent): + pass + + +@dataclass +class PausedGotoTime(_TimeEvent): + t: float + + +@dataclass +class PlayingGotoTime(_TimeEvent): + t: float + + +class BlenderTime: + """all methods to run in main thread""" + + def __init__(self, onEvent: Callable[[_TimeEvent], None]): + self._onEvent = onEvent + self.lastSetFrame = -1 + + def start(self): + bpy.app.handlers.load_post.append(self._on_load_post) + bpy.app.handlers.frame_change_post.append(self._on_frame_change_post) + + # need persistent because `blender --addons ...` seems to start addon + # before loading scene. + bpy.app.timers.register(self.update, persistent=True) + + self.emitEvent(SceneLoaded()) + + def setSceneDuration(self, duration: float): + self.duration = duration + scene = bpy.context.scene + fps = scene.render.fps + scene.frame_start = 0 + scene.frame_end = int(duration * fps) + + def frameDuration(self): + return + # todo: need to be in screen context or something + context_override = bpy.context.copy() + # context_override["selected_objects"] = list(context.scene.objects) + with bpy.context.temp_override(**context_override): + bpy.ops.action.view_all() + + def setCurrentTime(self, t: float): + scene = bpy.context.scene + fps = scene.render.fps + fr = int(clamp(t, 0, self.duration) * fps) + self.lastSetFrame = fr + scene.frame_set(fr) + + def setBlenderTime(self, t: float, duration: float): + log.info(f'set blender time to {t:.2f}') + self.setSceneDuration(duration) + self.frameDuration() + self.setCurrentTime(t) + + def emitEvent(self, event: _TimeEvent): + log.info(f'🌹 emitEvent {event}') + self._onEvent(event) + + @persistent + def update(self): + if 0: + if self.playing: + with self.lock: + t = self.currentTime() + if t is not None: + self.blenderTime.setBlenderTime(t, self.duration) + return UPDATE_PERIOD + + @persistent + def _on_load_post(self, scene, deps): + self.emitEvent(SceneLoaded()) + + @persistent + def _on_frame_change_post(self, scene, deps): + # if scene.frame_current == self.lastSetFrame: + # return + # blender requested this frame change, either playing or paused (scrubbing timeline) + self.lastSetFrame = scene.frame_current + t = round(scene.frame_current / scene.render.fps, 3) + if bpy.context.screen.is_animation_playing and not bpy.context.screen.is_scrubbing: + self.emitEvent(PlayingGotoTime(t=t)) + else: + self.emitEvent(PausedGotoTime(t=t))