Mercurial > code > home > repos > light9
diff blender/time_sync/time_from_graph.py @ 2455:2d454737a916
split blender code to new file
author | drewp@bigasterisk.com |
---|---|
date | Tue, 20 May 2025 09:24:35 -0700 |
parents | 405abed9a45c |
children | d94480bfb179 |
line wrap: on
line diff
--- a/blender/time_sync/time_from_graph.py Mon May 19 21:25:32 2025 -0700 +++ b/blender/time_sync/time_from_graph.py Tue May 20 09:24:35 2025 -0700 @@ -1,60 +1,70 @@ import asyncio import threading import time -from typing import Coroutine, cast +from typing import Coroutine -import bpy # type: ignore -from bpy.app.handlers import persistent # type: ignore -from ..asyncio_thread import startLoopInThread +from rdfdb.patch import Patch from rdfdb.syncedgraph.syncedgraph import SyncedGraph -from rdflib import URIRef -from rdflib.graph import _ContextType +from rdflib import Literal from light9 import networking, showconfig +from light9.ascoltami.graph_context import ascoltamiContext from light9.namespaces import L9 from light9.newtypes import decimalLiteral from light9.run_local import log from light9.typedgraph import typedValue - -def clamp(lo, hi, x): - return max(lo, min(hi, x)) +from ..asyncio_thread import startLoopInThread +from .blender_time import ( + BlenderTime, + PausedGotoTime, + PlayingGotoTime, + SceneLoaded, + _TimeEvent, +) -UPDATE_PERIOD = 1 / 20 + class Sync: + """asco is the authority on playback status. Sync maintains a copy of the state""" lock = threading.Lock() + + # these are written ONLY by bg thread duration: float = 1 wallStartTime: float | None = None pausedSongTime: float | None = None + latestSongTime: float playing = False - latestTime: float def __init__(self): # main thread - self.lastSetFrame = -1 - self.lastGraphFrame = -1 - show = showconfig.showUri() - self.ctx = cast(_ContextType, (URIRef(show + '/ascoltami'))) + self.blenderTime = BlenderTime(self.onEvent) + self.blenderTime.start() + + self.ctx = ascoltamiContext(showconfig.showUri()) log.debug('🚋3 startLoopInThread') - self._loop = startLoopInThread(self.task()) - - # need persistent because `blender --addons ...` seems to start addon - # before loading scene. - bpy.app.timers.register(self.update, persistent=True) - bpy.app.handlers.frame_change_post.append(self.on_frame_change_post) + self._loop = startLoopInThread(self.connectGraph()) log.info('🚋10 Sync initd') - ## updates from graph -> self + def onEvent(self, event: _TimeEvent): + # main thread + match event: + case SceneLoaded(): + if hasattr(self, "latestSongTime"): + self.blenderTime.setBlenderTime(self.latestSongTime, self.duration) + case PausedGotoTime(t): + self.runInBackgroundLoop(self.setInGraph(t, False)) + case PlayingGotoTime(t): + self.runInBackgroundLoop(self.setInGraph(t, True)) def runInBackgroundLoop(self, f: Coroutine): + # main thread asyncio.run_coroutine_threadsafe(f, self._loop) - - async def task(self): - # bg thread with asyncio loop + async def connectGraph(self): + # bg thread log.info('🚋11 start SyncedGraph') self.graph = SyncedGraph(networking.rdfdb.url, "time_sync") self.graph.addHandler(self.syncFromGraph) @@ -76,44 +86,25 @@ 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.runInBackgroundLoop(self.setInGraph(t, bpy.context.screen.is_animation_playing)) + async def setGraphPlaying(self, isBlenderPlaying: bool): + # bg thread + log.info(f'set graph playing to {isBlenderPlaying}') + self.graph.patchObject(self.ctx, L9['ascoltami'], L9['playing'], Literal(isBlenderPlaying)) async def setInGraph(self, t: float, isBlenderPlaying: bool): - log.info(f'set graph time to {t:.2f}') + # bg thread + log.info(f'set graph time to {t:.2f} {isBlenderPlaying=}') + p = Patch() if isBlenderPlaying: - log.info(' playing mode') - p = self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['wallStartTime'], decimalLiteral(t)) + p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['playing'], Literal(True))) + p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['wallStartTime'], decimalLiteral(round(time.time() - t, 1)))) + p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['pausedSongTime'], None)) else: - log.info(' paused mode') - p = self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['pausedSongTime'], decimalLiteral(t)) + p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['playing'], Literal(False))) + p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['wallStartTime'], None)) + p = p.update(self.graph.getObjectPatch(self.ctx, L9['ascoltami'], L9['pausedSongTime'], decimalLiteral(round(t, 1)))) + if p.isEmpty(): + return + log.info(f'setInGraph {p.shortSummary()}') await self.graph.patch(p)