2432
|
1 import threading
|
|
2 import time
|
|
3
|
|
4 import bpy # type: ignore
|
|
5 from bpy.app.handlers import persistent # type: ignore
|
|
6 from light9.typedgraph import typedValue
|
|
7 from rdfdb.syncedgraph.syncedgraph import SyncedGraph
|
|
8 from rdflib import URIRef
|
|
9
|
|
10 from light9 import networking
|
|
11 from light9.blender.asyncio_thread import startLoopInThread
|
|
12 from light9.namespaces import L9
|
|
13 from light9.run_local import log
|
|
14
|
|
15
|
|
16 def clamp(lo, hi, x):
|
|
17 return max(lo, min(hi, x))
|
|
18
|
|
19
|
|
20 UPDATE_PERIOD = 1 / 20
|
|
21
|
|
22
|
|
23 class Sync:
|
|
24 lock = threading.Lock()
|
|
25 duration: float = 1
|
|
26 wallStartTime: float | None = None
|
|
27 pausedSongTime: float | None = None
|
2433
|
28 playing = False
|
2432
|
29 latestTime: float
|
|
30
|
|
31 def __init__(self):
|
|
32 # main thread
|
|
33 self.lastSetFrame = -1
|
|
34 self.lastGraphFrame = -1
|
|
35 startLoopInThread(self.task())
|
|
36 bpy.app.timers.register(self.update)
|
|
37 bpy.app.handlers.frame_change_post.append(self.on_frame_change_post)
|
|
38
|
2433
|
39 ## updates from graph -> self
|
2432
|
40
|
|
41 async def task(self):
|
|
42 # bg thread with asyncio loop
|
|
43 self.graph = SyncedGraph(networking.rdfdb.url, "time_sync")
|
|
44 self.graph.addHandler(self.syncFromGraph)
|
|
45
|
|
46 def syncFromGraph(self):
|
|
47 # bg thread
|
|
48 with self.lock:
|
|
49 asco = L9['ascoltami']
|
2433
|
50 self.wallStartTime = typedValue(float | None, self.graph, asco, L9['wallStartTime'])
|
|
51 self.pausedSongTime = typedValue(float | None, self.graph, asco, L9['pausedSongTime'])
|
|
52 self.duration = typedValue(float | None, self.graph, asco, L9['duration']) or 1.0
|
2432
|
53 self.playing = typedValue(bool, self.graph, asco, L9['playing'])
|
|
54
|
|
55 def currentTime(self) -> float | None:
|
|
56 if self.wallStartTime is not None:
|
|
57 return time.time() - self.wallStartTime
|
|
58 if self.pausedSongTime is not None:
|
|
59 return self.pausedSongTime
|
|
60 log.warn('no time data')
|
|
61 return None
|
|
62
|
|
63 ## graph time -> blender time
|
|
64
|
|
65 @persistent
|
|
66 def update(self):
|
|
67 # main thread? wherever blender runs timers
|
|
68 if self.playing:
|
|
69 with self.lock:
|
|
70 t = self.currentTime()
|
|
71 if t is not None:
|
|
72 self.setBlenderTime(t, self.duration)
|
|
73 return UPDATE_PERIOD
|
|
74
|
|
75 def setBlenderTime(self, t: float, duration: float):
|
|
76 scene = bpy.context.scene
|
|
77 fps = scene.render.fps
|
|
78 scene.frame_start = 0
|
|
79 scene.frame_end = int(duration * fps)
|
|
80 fr = int(clamp(t, 0, duration) * fps)
|
|
81 self.lastSetFrame = fr
|
|
82 scene.frame_set(fr)
|
|
83
|
|
84 ## blender time -> graph time
|
|
85
|
|
86 @persistent
|
|
87 def on_frame_change_post(self, scene, deps):
|
|
88 if scene.frame_current != self.lastSetFrame:
|
|
89 # self.setGraphTime(scene.frame_current / scene.render.fps, self.duration)
|
|
90 self.lastSetFrame = scene.frame_current
|
|
91 t = scene.frame_current / scene.render.fps
|
|
92 self.setInGraph(t)
|
|
93
|
|
94 def setInGraph(self, t: float):
|
2433
|
95 log.warning(f'todo: set graph to {t}')
|