diff options
Diffstat (limited to 'functions/scene.py')
-rw-r--r-- | functions/scene.py | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/functions/scene.py b/functions/scene.py new file mode 100644 index 0000000..958138f --- /dev/null +++ b/functions/scene.py @@ -0,0 +1,144 @@ +"""Scene function module. + +Contains the definition of the Scene, the lighting primitive. +""" + +import xml.etree.ElementTree as et +from typing import Mapping + +from .function import Function +from ..topology import Fixture +from ..constants import INFTY, SCENE, BXW +from ..exceptions import LoadError + +class Scene(Function): + """Class representing a lighting scene. + + This is the primitive for lighting, and all light cues must be based in some manner off + of Scene. Scenes are simple, having no fading and infinite duration. + + Modifying the scene can be done in a few ways: the easiest is using the dictionary- + style accesses, e.g. setting the values using: + + scene[channel] = value + + Values may be removed from the scene using the ``del`` operator and retrieved in the + same fashion. Alternatively, values can be updated in bulk using ``Scene.update`` or + set in bulk with ``Scene.set``. + """ + audio_scope = () + scope = frozenset() + actual_duration = INFTY + values = frozenset() + + def __init__(self, w, id_ = None, name = None, values: Mapping[Fixture.Channel, int] = None): + super().__init__(w=w, type_=SCENE, id_=id_, name=name, duration=INFTY, fade_in=0, + fade_out=0) + self._values = {} + self._render = () + + self._update(values, changed=False) + + def _update_render(self, changed=True): + self._render = tuple(self._values.items()) + self.scope = frozenset(self._values.keys()) + self.values = self._render + + if changed: + self.w.function_changed(self) + + def set(self, v): + """Set the scene's values to the given ones. + + Equivalent to update, except that all non-given values are deleted from the scene. + """ + self._values = {} + self.update(v) + + def _update(self, v, changed=True): + if isinstance(v, dict): + v = v.items() + vn = [] + for c, val in v: + val = int(val) + if val < 0 or val > 255: + raise ValueError("Values must be integers on [0,256)") + vn.append((c, val)) + for c, val in vn: + self._values[c] = val + self._update_render(changed=changed) + + def update(self, v): + """Update the scene's values.""" + self._update(v=v, changed=True) + + def __getitem__(self, c: Fixture.Channel): + return self._values[c] if c in self._values else None + + def __setitem__(self, c: Fixture.Channel, v: int): + self.update(((c, v),)) + + def __delitem__(self, c: Fixture.Channel): + if c in self._values: + del self._values[c] + self._update_render() + + def get_data(self): + return None + + def copy_data(self, data): + return None + + def render(self, t, data = None): + return (self._render, (), None) + + 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 c, v in self.values: + ce = et.SubElement(e, BXW+"value") + ce.set("fixture", str(c.f.id)) + ce.set("channel", str(c.id)) + ce.text = str(v) + + return e + + @classmethod + def deserialize(cls, w, e): + if e.tag != BXW+"function": + raise LoadError("Invalid function tag") + elif e.get("type") != SCENE: + 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") + + values = {} + for ve in e: + if ve.tag != BXW+"value": + raise LoadError("Invalid value tag") + + fixture = cls.int_or_none(ve.get("fixture")) + channel = cls.int_or_none(ve.get("channel")) + + if None in (fixture, channel): + raise LoadError("Missing/invalid fixture/channel value") + elif fixture not in w.fixtures: + raise LoadError("Missing fixture ID %d" % fixture) + elif channel >= len(w.fixtures[fixture].channels): + raise LoadError("Fixture %d missing channel ID %d" % (fixture, channel)) + + channel = w.fixtures[fixture].channels[channel] + + value = cls.int_or_none(ve.text) + if value is None: + raise LoadError("Missing/invalid value for channel") + + values[channel] = value + + return cls(w=w, id_=id_, name=name, values=values) |