Changeset - b8a408caf115
[Not reviewed]
default
0 0 6
drewp@bigasterisk.com - 8 months ago 2024-05-28 22:35:12
drewp@bigasterisk.com
start blender sync
6 files changed with 204 insertions and 0 deletions:
0 comments (0 inline, 0 general)
src/light9/blender/__init__.py
Show inline comments
 
new file 100644
 
# ln -s ./ ~/.config/blender/4.2/scripts/addons/light9_sync/
 

	
 
#~/own/tool/blender $LIGHT9_SHOW/song1.blend --addons light9_sync
 
import logging
 
import sys
 

	
 
from . import light_control, time_sync
 

	
 
bl_info = {
 
    "name": "light9_sync",
 
    "description": "light9 sync",
 
    "version": (0, 0, 1),
 
    "blender": (4, 2, 0),
 
    "category": "Object",
 
}
 

	
 
modules = (time_sync, light_control)
 

	
 

	
 
def register():
 
    logging.getLogger('autodepgraphapi').setLevel(logging.INFO)
 
    logging.getLogger('syncedgraph').setLevel(logging.INFO)
 
    for p in [
 
            '/home/drewp/.local/share/pdm/venvs/light9-zxQNOBNq-3.11/lib/python3.11/site-packages',
 
            '/home/drewp/projects/light9/src/',
 
            '/my/proj/rdfdb',
 
    ]:
 
        if p not in sys.path:
 
            sys.path.append(p)
 

	
 
    for module in modules:
 
        module.register()
 

	
 

	
 
def unregister():
 
    for module in reversed(modules):
 
        module.unregister()
src/light9/blender/asyncio_thread.py
Show inline comments
 
new file 100644
 
import asyncio
 
from threading import Thread
 
from typing import Coroutine
 

	
 

	
 
def startLoopInThread(task: Coroutine) -> asyncio.AbstractEventLoop:
 

	
 
    def start_background_loop(loop: asyncio.AbstractEventLoop) -> None:
 
        asyncio.set_event_loop(loop)
 
        loop.run_forever()
 

	
 
    loop = asyncio.new_event_loop()
 
    t = Thread(target=start_background_loop, args=(loop,), daemon=True)
 
    t.start()
 
    asyncio.run_coroutine_threadsafe(task, loop)
 
    return loop
src/light9/blender/light_control/__init__.py
Show inline comments
 
new file 100644
 
"""
 
watch blender lights, output to real lights
 
"""
 

	
 
import time
 
from typing import cast
 

	
 
import bpy  # type: ignore
 
from bpy.app.handlers import persistent
 

	
 
sender: object = None
 

	
 

	
 
def register():
 
    global sender
 
    from .send_to_collector import Sender
 

	
 
    sender = Sender()
 

	
 
    # @persistent
 
    # def fcp(scene):
 
    #     cast(Sender, sender).on_frame_change_post(scene)
 

	
 
    # bpy.app.handlers.frame_change_post.append(fcp)
 

	
 

	
 
def unregister():
 
    raise NotImplementedError
src/light9/blender/light_control/send_to_collector.py
Show inline comments
 
new file 100644
 
class Sender:
 

	
 
    def blenderTime(self) -> float:
 
        return -55
 

	
 
    def on_frame_change_post(self, scene, *args):
 
        print('ofcp',scene,args)
src/light9/blender/time_sync/__init__.py
Show inline comments
 
new file 100644
 
"""
 
sets blender time to track ascoltami time;
 
blender-side time changes are sent back to ascoltami
 
"""
 

	
 
sync: object = None
 

	
 

	
 
def register():
 
    global sync
 
    from .time_from_graph import Sync
 
    sync = Sync()
 

	
 

	
 
def unregister():
 
    raise NotImplementedError
src/light9/blender/time_sync/time_from_graph.py
Show inline comments
 
new file 100644
 
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))
 

	
 

	
 
def floatValue(graph: SyncedGraph, s: URIRef, p: URIRef) -> float | None:
 
    return typedValue(float | None, graph, s, p)
 

	
 

	
 
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
 
        self.frameSetBy = 'init'
 
        startLoopInThread(self.task())
 
        bpy.app.timers.register(self.update)
 
        bpy.app.handlers.frame_change_post.append(self.on_frame_change_post)
 

	
 
    ## read from graph
 

	
 
    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 = floatValue(self.graph, asco, L9['wallStartTime'])
 
            self.pausedSongTime = floatValue(self.graph, asco, L9['pausedSongTime'])
 
            self.duration = floatValue(self.graph, asco, L9['duration']) or 1.0
 
            self.playing = typedValue(bool, self.graph, asco, L9['playing'])
 

	
 
    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}')
 
\ No newline at end of file
0 comments (0 inline, 0 general)