import threading
import time
import bpy # type: ignore
from bpy.app.handlers import persistent # type: ignore
from light9.typedgraph import typedValue
from rdfdb.syncedgraph.syncedgraph import SyncedGraph
from rdflib import URIRef
from light9 import networking
from light9.blender.asyncio_thread import startLoopInThread
from light9.namespaces import L9
from light9.run_local import log
def clamp(lo, hi, x):
return max(lo, min(hi, x))
UPDATE_PERIOD = 1 / 20
class Sync:
lock = threading.Lock()
duration: float = 1
wallStartTime: float | None = None
pausedSongTime: float | None = None
playing = False
latestTime: float
def __init__(self):
# main thread
self.lastSetFrame = -1
self.lastGraphFrame = -1
startLoopInThread(self.task())
bpy.app.timers.register(self.update)
bpy.app.handlers.frame_change_post.append(self.on_frame_change_post)
## updates from graph -> self
async def task(self):
# bg thread with asyncio loop
self.graph = SyncedGraph(networking.rdfdb.url, "time_sync")
self.graph.addHandler(self.syncFromGraph)
def syncFromGraph(self):
# bg thread
with self.lock:
asco = L9['ascoltami']
self.wallStartTime = typedValue(float | None, self.graph, asco, L9['wallStartTime'])
self.pausedSongTime = typedValue(float | None, self.graph, asco, L9['pausedSongTime'])
self.duration = typedValue(float | None, self.graph, asco, L9['duration']) or 1.0
self.playing = typedValue(bool|None, self.graph, asco, L9['playing']) or False
def currentTime(self) -> float | None:
if self.wallStartTime is not None:
return time.time() - self.wallStartTime
if self.pausedSongTime is not None:
return self.pausedSongTime
log.warn('no time data')
return None
## graph time -> blender time
@persistent
def update(self):
# main thread? wherever blender runs timers
if self.playing:
with self.lock:
t = self.currentTime()
if t is not None:
self.setBlenderTime(t, self.duration)
return UPDATE_PERIOD
def setBlenderTime(self, t: float, duration: float):
scene = bpy.context.scene
fps = scene.render.fps
scene.frame_start = 0
scene.frame_end = int(duration * fps)
fr = int(clamp(t, 0, duration) * fps)
self.lastSetFrame = fr
scene.frame_set(fr)
## blender time -> graph time
@persistent
def on_frame_change_post(self, scene, deps):
if scene.frame_current != self.lastSetFrame:
# self.setGraphTime(scene.frame_current / scene.render.fps, self.duration)
self.lastSetFrame = scene.frame_current
t = scene.frame_current / scene.render.fps
self.setInGraph(t)
def setInGraph(self, t: float):
log.warning(f'todo: set graph to {t:.2f}')