comparison blender/light_control/send_to_collector.py @ 2453:b23afde50bc2

blender addons get thier own pdm setup for now. fix time_from_graph startup race
author drewp@bigasterisk.com
date Sun, 18 May 2025 20:08:35 -0700
parents src/light9/blender/light_control/send_to_collector.py@e683b449506b
children 2d454737a916
comparison
equal deleted inserted replaced
2452:f21b61884b0f 2453:b23afde50bc2
1 import asyncio
2 import logging
3 from dataclasses import dataclass, field
4 from os import sync
5 from typing import cast
6
7 import bpy # type: ignore
8 from light9_sync.time_sync.time_from_graph import clamp
9 from light9.effect.effecteval import literalColor
10 from rdfdb.patch import Patch
11 from rdfdb.syncedgraph.syncedgraph import SyncedGraph
12 from rdflib import RDF, ConjunctiveGraph, Literal, URIRef
13 from rdflib.graph import _ContextType
14
15 from light9 import networking
16 from light9_sync.asyncio_thread import startLoopInThread
17 from light9.namespaces import FUNC, L9
18 from light9.newtypes import DeviceUri
19 from light9.showconfig import showUri
20
21 log = logging.getLogger('sendcoll')
22
23 UPDATE_PERIOD = 1 / 20
24
25
26 async def waitForConnection(graph: SyncedGraph):
27 # workaround for patch() quietly failing before connection. See SyncedGraph.
28 while not graph._isConnected():
29 await asyncio.sleep(.5)
30
31 @dataclass
32 class BlenderDev:
33 ctx: URIRef
34 obj: bpy.types.Object
35 uri: DeviceUri
36 setting: URIRef
37 devSets: URIRef
38
39 color: tuple[float, float, float] = (0, 0, 0)
40
41 def updateColor(self):
42 self.color = self.obj.data.color
43
44 def effectGraphStmts(self, bset0) -> list:
45 return [
46 (self.devSets, L9['setting'], self.setting, self.ctx),
47 (self.setting, L9['device'], self.uri, self.ctx),
48 (self.setting, L9['deviceAttr'], L9['color'], self.ctx),
49 ]
50
51 def makePatch(self, graph: SyncedGraph) -> Patch:
52 c = literalColor(*[clamp(x, 0, 1) for x in self.color])
53 return graph.getObjectPatch(self.ctx, self.setting, L9['value'], c)
54
55
56 @dataclass
57 class Sender:
58 syncedObjects: dict[str, BlenderDev] = field(default_factory=dict)
59
60 ctx = URIRef(showUri() + '/blender')
61 devSets = L9['blenderControlDevSets']
62
63 def __post_init__(self):
64 bpy.app.timers.register(self.findLightsInScene, first_interval=0.1) # todo: what event to use?
65 startLoopInThread(self.task())
66
67 async def task(self):
68 log.info('start task')
69 try:
70 self.graph = SyncedGraph(networking.rdfdb.url, "blender_light_control")
71 await waitForConnection(self.graph)
72 self.patchInitialGraph()
73
74 while True:
75 try:
76 await self.patchCurrentLightColors()
77 except Exception:
78 log.error('skip', exc_info=True)
79 await asyncio.sleep(1)
80 # todo: this could be triggered by onColorChanges instead of running constantly
81 await asyncio.sleep(UPDATE_PERIOD)
82 except Exception:
83 log.error('task', exc_info=True)
84
85 def patchInitialGraph(self):
86 g = self.effectGraph()
87 for d in self.syncedObjects.values():
88 for stmt in d.effectGraphStmts(self.devSets):
89 g.add(stmt)
90 for stmt in g:
91 log.info("adding %s", stmt)
92 self.graph.patchSubgraph(self.ctx, g)
93
94 async def patchCurrentLightColors(self):
95 p = Patch(addQuads=[], delQuads=[])
96 for d in self.syncedObjects.values():
97 p = p.update(d.makePatch(self.graph))
98 if p:
99 await self.graph.patch(p)
100
101 def effectGraph(self) -> ConjunctiveGraph:
102 g = ConjunctiveGraph()
103 ctx = cast(_ContextType, self.ctx)
104 g.add((L9['blenderControl'], RDF.type, L9['Effect'], ctx))
105 g.add((L9['blenderControl'], L9['effectFunction'], FUNC['scale'], ctx))
106 g.add((L9['blenderControl'], L9['publishAttr'], L9['strength'], ctx))
107 setting = L9['blenderControl/set0']
108 g.add((L9['blenderControl'], L9['setting'], setting, ctx))
109 g.add((setting, L9['value'], self.devSets, ctx))
110 g.add((setting, L9['effectAttr'], L9['deviceSettings'], ctx))
111 return g
112
113 def findLightsInScene(self):
114 self.syncedObjects.clear()
115 for obj in sorted(bpy.data.objects, key=lambda x: x.name):
116 if obj.get('l9dev'):
117 uri = DeviceUri(URIRef(obj['l9dev']))
118 self.syncedObjects[obj.name] = BlenderDev(
119 self.ctx,
120 obj,
121 uri,
122 setting=L9['blenderControl/set1'],
123 devSets=self.devSets,
124 )
125 log.info(f'found {self.syncedObjects[obj.name]}')
126
127 self.watchForUpdates()
128
129 def watchForUpdates(self):
130 bpy.app.handlers.depsgraph_update_post.append(self.onColorChanges)
131 bpy.app.handlers.frame_change_post.append(self.onColorChanges)
132
133 def onColorChanges(self, scene, deps):
134 for obj in deps.objects:
135 dev = self.syncedObjects.get(obj.name)
136 if dev is None:
137 continue
138 dev.updateColor()