From 61df2609bec5a4493c0839dbc0983dc2356fb1f2 Mon Sep 17 00:00:00 2001 From: Ben Connors Date: Sat, 30 Nov 2019 16:45:18 -0500 Subject: Implement join function --- blc2/constants.py | 1 + blc2/functions/join.py | 146 +++++++++++++++++++++++++++++++++++++++++++++++++ blc2/workspace.py | 11 +++- 3 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 blc2/functions/join.py diff --git a/blc2/constants.py b/blc2/constants.py index bb207f9..ca43367 100644 --- a/blc2/constants.py +++ b/blc2/constants.py @@ -66,6 +66,7 @@ SCENE = "Scene" AUDIO = "Audio" CHASER = "Chaser" CHASERSTEP = "ChaserStep" +JOIN = "Join" FUNCTION = "Function" INTERNAL = "Internal" diff --git a/blc2/functions/join.py b/blc2/functions/join.py new file mode 100644 index 0000000..9ab2f73 --- /dev/null +++ b/blc2/functions/join.py @@ -0,0 +1,146 @@ +"""Module for join functions.""" + +import xml.etree.ElementTree as et + +from ..constants import JOIN, INFTY, BXW +from .function import Function +from ..exceptions import LoadError + +class Join(Function): + """Class for joins. + + Joins merge multiple functions into one (e.g. audio and lighting). + """ + type = JOIN + + def __init__(self, w, id_ = None, name = None): + super().__init__(w=w, id_=id_, name=name) + + self._steps = [] + self._duration = 0 + self._actual_duration = 0 + + self._scope = frozenset() + self._audio_scope = frozenset() + + def _recalculate(self, update=True): + self._duration = max(0, *(s.duration for s in self._steps)) + self._actual_duration = max(0, *(s.actual_duration for s in self._steps)) + self._scope = frozenset().union(*(s.scope for s in self._steps)) + self._audio_scope = frozenset().union(*(s.audio_scope for s in self._steps)) + + if update: + self.w.function_changed(self) + + @property + def actual_duration(self): + return self._actual_duration + + @property + def duration(self): + return self._duration + + @property + def scope(self): + return self._scope + + @property + def audio_scope(self): + return self._audio_scope + + @property + def fade_in(self): + return 0 + + @property + def fade_out(self): + return 0 + + def _function_changed(self, _): + self._recalculate() + + def get_data(self, params=None): + if params is not None: + if len(params) != len(self._steps): + raise ValueError("Must have same number of parameters as steps!") + return [s.get_data(*p) for s, p in zip(self._steps, params)] + return [s.get_data() for s in self._steps] + + def copy_data(self, d): + return [s.copy_data(d) for s, d in zip(self.steps, d)] + + @property + def steps(self): + return tuple(self._steps) + + def add_step(self, s): + if s.id in (i.id for i in self._steps): + raise ValueError("Already added") + self._steps.insert(-1, s) + self._recalculate() + + self.w.register_function_change_callback(s, self._recalculate, self) + self.w.register_function_delete_ballback(s, self._recalculate, self) + + def delete_step(self, s, index=False): + if index: + s = self._steps[s] + elif isinstance(s, int): + s = self.w.functions[s] + + self._steps.remove(s) + + self.w.delete_callbacks(self, s) + self._recalculate() + + def render(self, t, data=None): + if data is None: + data = self.get_data() + + lc = {c: 0 for c in self._scope} + ac = [] + + for i, s in enumerate(self.steps): + lcs, acs, data[i] = s.render(t, data=data[i]) + for c, v in lcs: + if v > lc[c]: + lc[c] = v + ac.extend(((hash((self._id, aid)), *others) for aid, *others in acs)) + + return tuple(lc.items()), tuple(ac), data + + def serialize(self): + e = et.Element(BXW+"function") + e.set("type", self.type) + e.set("id", str(self.id)) + e.set("name", self.name) + for s in self.steps: + se = et.SubElement(e, BXW+"member") + se.set("id", s.id) + + return e + + @classmethod + def deserialize(cls, w, e): + if e.tag != BXW+"function": + raise LoadError("Invalid function tag") + elif e.get("type") != JOIN: + raise LoadError("Load delegated to wrong class (this is a bug)") + + id_ = cls.int_or_none(e.get("id")) + if id_ is None: + raise LoadError("Function tag has invalid/missing ID") + + name = e.get("name") + + c = cls(w=w, id_=id_, name=name) + + for se in e: + if se.tag != BXW+"member": + raise LoadError("Invalid member tag") + + fid = cls.int_or_none(se.get("id")) + if fid is None: + raise LoadError("Missing/invalid member id") + + c.add_step(w.functions[fid]) diff --git a/blc2/workspace.py b/blc2/workspace.py index 8abf8a1..1983ac7 100644 --- a/blc2/workspace.py +++ b/blc2/workspace.py @@ -8,7 +8,7 @@ import json import subprocess as subp import xml.etree.ElementTree as et -from .constants import AUDIO, SCENE, BXW, CHASER, CHASERSTEP +from .constants import AUDIO, SCENE, BXW, CHASER, CHASERSTEP, JOIN from .functions.function import Function from .exceptions import LoadError from .interfaces import XMLSerializable @@ -292,6 +292,15 @@ class Workspace(XMLSerializable): done.add(f.id) continue all_f.append(f) + elif f.type in (JOIN,): + for step in f.steps: + if step.id not in done: + break + else: + f_order.append(f) + done.add(f.id) + continue + all_f.append(f) else: raise ValueError("Unknown function type "+f.type) -- cgit v1.2.3