comparison 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
comparison
equal deleted inserted replaced
2454:405abed9a45c 2455:2d454737a916
1 from dataclasses import dataclass
2 from typing import Callable
3
4 import bpy
5 from bpy.app.handlers import persistent
6
7 from light9.run_local import log
8
9 UPDATE_PERIOD = 1 / 20
10
11
12 def clamp(lo, hi, x):
13 return max(lo, min(hi, x))
14
15
16 class _TimeEvent():
17 pass
18
19
20 class SceneLoaded(_TimeEvent):
21 pass
22
23
24 @dataclass
25 class PausedGotoTime(_TimeEvent):
26 t: float
27
28
29 @dataclass
30 class PlayingGotoTime(_TimeEvent):
31 t: float
32
33
34 class BlenderTime:
35 """all methods to run in main thread"""
36
37 def __init__(self, onEvent: Callable[[_TimeEvent], None]):
38 self._onEvent = onEvent
39 self.lastSetFrame = -1
40
41 def start(self):
42 bpy.app.handlers.load_post.append(self._on_load_post)
43 bpy.app.handlers.frame_change_post.append(self._on_frame_change_post)
44
45 # need persistent because `blender --addons ...` seems to start addon
46 # before loading scene.
47 bpy.app.timers.register(self.update, persistent=True)
48
49 self.emitEvent(SceneLoaded())
50
51 def setSceneDuration(self, duration: float):
52 self.duration = duration
53 scene = bpy.context.scene
54 fps = scene.render.fps
55 scene.frame_start = 0
56 scene.frame_end = int(duration * fps)
57
58 def frameDuration(self):
59 return
60 # todo: need to be in screen context or something
61 context_override = bpy.context.copy()
62 # context_override["selected_objects"] = list(context.scene.objects)
63 with bpy.context.temp_override(**context_override):
64 bpy.ops.action.view_all()
65
66 def setCurrentTime(self, t: float):
67 scene = bpy.context.scene
68 fps = scene.render.fps
69 fr = int(clamp(t, 0, self.duration) * fps)
70 self.lastSetFrame = fr
71 scene.frame_set(fr)
72
73 def setBlenderTime(self, t: float, duration: float):
74 log.info(f'set blender time to {t:.2f}')
75 self.setSceneDuration(duration)
76 self.frameDuration()
77 self.setCurrentTime(t)
78
79 def emitEvent(self, event: _TimeEvent):
80 log.info(f'🌹 emitEvent {event}')
81 self._onEvent(event)
82
83 @persistent
84 def update(self):
85 if 0:
86 if self.playing:
87 with self.lock:
88 t = self.currentTime()
89 if t is not None:
90 self.blenderTime.setBlenderTime(t, self.duration)
91 return UPDATE_PERIOD
92
93 @persistent
94 def _on_load_post(self, scene, deps):
95 self.emitEvent(SceneLoaded())
96
97 @persistent
98 def _on_frame_change_post(self, scene, deps):
99 # if scene.frame_current == self.lastSetFrame:
100 # return
101 # blender requested this frame change, either playing or paused (scrubbing timeline)
102 self.lastSetFrame = scene.frame_current
103 t = round(scene.frame_current / scene.render.fps, 3)
104 if bpy.context.screen.is_animation_playing and not bpy.context.screen.is_scrubbing:
105 self.emitEvent(PlayingGotoTime(t=t))
106 else:
107 self.emitEvent(PausedGotoTime(t=t))