Changeset - 9b101d8bd7ea
[Not reviewed]
default
0 3 0
drewp@bigasterisk.com - 8 months ago 2024-05-29 00:56:16
drewp@bigasterisk.com
discover annotated lights in blender; send their color to the graph (temporary stmt)
3 files changed with 80 insertions and 18 deletions:
0 comments (0 inline, 0 general)
src/light9/blender/light_control/__init__.py
Show inline comments
 
"""
 
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
 
import asyncio
 
import logging
 
from dataclasses import dataclass, field
 

	
 
import bpy  # type: ignore
 
from light9.blender.time_sync.time_from_graph import clamp
 
from light9.effect.effecteval import literalColor
 
from rdfdb.patch import Patch
 
from rdfdb.syncedgraph.syncedgraph import SyncedGraph
 
from rdflib import Literal, URIRef
 

	
 
from light9 import networking
 
from light9.blender.asyncio_thread import startLoopInThread
 
from light9.namespaces import L9
 
from light9.newtypes import DeviceUri
 
from light9.showconfig import showUri
 

	
 
log = logging.getLogger('sendcoll')
 

	
 
UPDATE_PERIOD = 1 / 20
 

	
 

	
 
@dataclass
 
class BlenderDev:
 
    ctx: URIRef
 
    obj: bpy.types.Object
 
    uri: DeviceUri
 
    color: tuple[float, float, float] = (0, 0, 0)
 

	
 
    def updateColor(self):
 
        self.color = self.obj.data.color
 

	
 
    def makePatch(self, graph: SyncedGraph) -> Patch:
 
        c = literalColor(*[clamp(x, 0, 1) for x in self.color])
 
        return graph.getObjectPatch(self.ctx, self.uri, L9['blenderColor'], c)
 

	
 

	
 
@dataclass
 
class Sender:
 
    syncedObjects: dict[str, BlenderDev] = field(default_factory=dict)
 

	
 
    def blenderTime(self) -> float:
 
        return -55
 
    ctx = URIRef(showUri() + '/blender')
 

	
 
    def __post_init__(self):
 
        bpy.app.timers.register(self.findLightsInScene, first_interval=0.1)  # todo: what event to use?
 
        startLoopInThread(self.task())
 

	
 
    async def task(self):
 
        graph = SyncedGraph(networking.rdfdb.url, "blender_light_control")
 
        while True:
 
            try:
 
                p = Patch(addQuads=[], delQuads=[])
 
                for d in self.syncedObjects.values():
 
                    p = p.update(d.makePatch(graph))
 
                if p:
 
                    await graph.patch(p)
 
            except Exception:
 
                log.error('skip', exc_info=True)
 
                await asyncio.sleep(1)
 
            await asyncio.sleep(UPDATE_PERIOD)
 

	
 
    def on_frame_change_post(self, scene, *args):
 
        print('ofcp',scene,args)
 
    def findLightsInScene(self):
 
        self.syncedObjects.clear()
 
        for obj in bpy.data.objects:
 
            if obj.get('l9dev'):
 
                uri = DeviceUri(URIRef(obj['l9dev']))
 
                self.syncedObjects[obj.name] = BlenderDev(self.ctx, obj, uri)
 
                log.info(f'found {self.syncedObjects[obj.name]}')
 

	
 
        self.watchForUpdates()
 

	
 
    def watchForUpdates(self):
 
        bpy.app.handlers.depsgraph_update_post.append(self.onColorChanges)
 
        bpy.app.handlers.frame_change_post.append(self.onColorChanges)
 

	
 
    def onColorChanges(self, scene, deps):
 
        for obj in deps.objects:
 
            dev = self.syncedObjects.get(obj.name)
 
            if dev is None:
 
                continue
 
            dev.updateColor()
src/light9/blender/time_sync/time_from_graph.py
Show inline comments
 
@@ -29,67 +29,67 @@ class Sync:
 
    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, self.graph, asco, L9['playing'])
 
            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}')
 
        log.warning(f'todo: set graph to {t:.2f}')
0 comments (0 inline, 0 general)