diff options
| author | Ben Connors <benconnors@outlook.com> | 2019-11-30 16:45:18 -0500 | 
|---|---|---|
| committer | Ben Connors <benconnors@outlook.com> | 2019-11-30 16:45:18 -0500 | 
| commit | 61df2609bec5a4493c0839dbc0983dc2356fb1f2 (patch) | |
| tree | 91e3fd96c279b13806fd85f8f4c06dca6f51c3f0 | |
| parent | 4e2e73e1bce3cf51107d7fe1314a92fd519f03f2 (diff) | |
Implement join function
| -rw-r--r-- | blc2/constants.py | 1 | ||||
| -rw-r--r-- | blc2/functions/join.py | 146 | ||||
| -rw-r--r-- | blc2/workspace.py | 11 | 
3 files changed, 157 insertions, 1 deletions
| 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) | 
