summaryrefslogtreecommitdiff
path: root/functions/scene.py
diff options
context:
space:
mode:
Diffstat (limited to 'functions/scene.py')
-rw-r--r--functions/scene.py144
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)