diff blender/time_sync/blender_time.py @ 2457:d94480bfb179

more work on blender time sync. Might be working, aside from blender play-button
author drewp@bigasterisk.com
date Tue, 20 May 2025 13:48:07 -0700
parents 2d454737a916
children
line wrap: on
line diff
--- a/blender/time_sync/blender_time.py	Tue May 20 09:25:18 2025 -0700
+++ b/blender/time_sync/blender_time.py	Tue May 20 13:48:07 2025 -0700
@@ -5,6 +5,7 @@
 from bpy.app.handlers import persistent
 
 from light9.run_local import log
+from light9.ascoltami.play_state import AscoPlayState
 
 UPDATE_PERIOD = 1 / 20
 
@@ -31,12 +32,23 @@
     t: float
 
 
+@dataclass
+class BlenderPlayState:
+    isAnimationPlaying: bool
+    isScrubbing: bool
+
+
 class BlenderTime:
     """all methods to run in main thread"""
 
-    def __init__(self, onEvent: Callable[[_TimeEvent], None]):
+    def __init__(self, onEvent: Callable[[_TimeEvent], None], ascoltamiPlayStateRO: AscoPlayState):
+        self.ascoltamiPlayStateRO = ascoltamiPlayStateRO
         self._onEvent = onEvent
         self.lastSetFrame = -1
+        self.lastPlayState = BlenderPlayState(False, False)
+        self.loaded = False
+        self.curFrameDirty = True
+        self.durationDirty = True
 
     def start(self):
         bpy.app.handlers.load_post.append(self._on_load_post)
@@ -44,11 +56,13 @@
 
         # need persistent because `blender --addons ...` seems to start addon
         # before loading scene.
-        bpy.app.timers.register(self.update, persistent=True)
+        bpy.app.timers.register(self._update, persistent=True)
 
-        self.emitEvent(SceneLoaded())
+        self._emitEvent(SceneLoaded())
 
     def setSceneDuration(self, duration: float):
+        if not self.loaded:
+            return
         self.duration = duration
         scene = bpy.context.scene
         fps = scene.render.fps
@@ -56,6 +70,7 @@
         scene.frame_end = int(duration * fps)
 
     def frameDuration(self):
+        """press Home on the timeline to frame the whole range"""
         return
         # todo: need to be in screen context or something
         context_override = bpy.context.copy()
@@ -64,44 +79,55 @@
             bpy.ops.action.view_all()
 
     def setCurrentTime(self, t: float):
+        if not self.loaded:
+            return
         scene = bpy.context.scene
         fps = scene.render.fps
-        fr = int(clamp(t, 0, self.duration) * fps)
+        fr = int(clamp(t * fps, 0, scene.frame_end))
         self.lastSetFrame = fr
         scene.frame_set(fr)
 
-    def setBlenderTime(self, t: float, duration: float):
-        log.info(f'set blender time to {t:.2f}')
+    def setRange(self, duration: float):
         self.setSceneDuration(duration)
         self.frameDuration()
-        self.setCurrentTime(t)
 
-    def emitEvent(self, event: _TimeEvent):
-        log.info(f'🌹 emitEvent {event}')
+    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)
+    def _update(self):
+        if self.curFrameDirty or self.ascoltamiPlayStateRO.playing:
+            self._followAscoTime()
+            self.curFrameDirty=False
+        if self.durationDirty:
+            if (d := self.ascoltamiPlayStateRO.duration) is not None:
+                self.setRange(d)
+                self.durationDirty = False
+            # # todo: playing in blender should start ascoltami playback
+            # cur =  BlenderPlayState(bpy.context.screen.is_animation_playing, bpy.context.screen.is_scrubbing)
+
         return UPDATE_PERIOD
 
+    def _followAscoTime(self):
+        if (t := self.ascoltamiPlayStateRO.getCurrentSongTime()) is not None:
+            self.setCurrentTime(t)
+
     @persistent
     def _on_load_post(self, scene, deps):
-        self.emitEvent(SceneLoaded())
+        self._emitEvent(SceneLoaded())
+        self.loaded = True
+        self.curFrameDirty = True
 
     @persistent
     def _on_frame_change_post(self, scene, deps):
-        # if scene.frame_current == self.lastSetFrame:
-        #     return
+        # log.info(f' _on_frame_change_post  {self.lastPlayState}')
+        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))
+        if self.lastPlayState.isAnimationPlaying and not self.lastPlayState.isScrubbing:
+            self._emitEvent(PlayingGotoTime(t=t))
         else:
-            self.emitEvent(PausedGotoTime(t=t))
+            self._emitEvent(PausedGotoTime(t=t))