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))