summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--blc2/__init__.py12
-rw-r--r--blc2/exceptions.py2
-rw-r--r--blc2/functions/__init__.py31
-rw-r--r--blc2/functions/join.py29
-rw-r--r--blc2/interfaces.py3
5 files changed, 70 insertions, 7 deletions
diff --git a/blc2/__init__.py b/blc2/__init__.py
index 24a8009..6ca795f 100644
--- a/blc2/__init__.py
+++ b/blc2/__init__.py
@@ -1,4 +1,14 @@
+"""BLC2 library.
+
+This library implements a relatively basic lighting design and rendering system. It is
+inspired by QLC+, but extends functions to make them simpler, more featureful, and more
+oriented to theatrical productions.
+
+Note that this library currently permits creating circular references, but these have no
+physical meaning and cannot be saved or loaded from the disk. Do not make them.
+"""
+
from .workspace import Workspace
from .topology import Fixture
-__version__ = "v0.0.1"
+__version__ = "v0.0.2"
diff --git a/blc2/exceptions.py b/blc2/exceptions.py
index 1b950e8..4434ebd 100644
--- a/blc2/exceptions.py
+++ b/blc2/exceptions.py
@@ -1,4 +1,4 @@
"""Module containing common exceptions."""
class LoadError(Exception):
- pass
+ """Exception for errors in loading a workspace."""
diff --git a/blc2/functions/__init__.py b/blc2/functions/__init__.py
index 921a6b0..8b84835 100644
--- a/blc2/functions/__init__.py
+++ b/blc2/functions/__init__.py
@@ -1,7 +1,34 @@
-from .audio import Audio
+"""Function sub-module.
+
+This module provides an interface for and implementation of the basic lighting functions.
+
+`Function` provides the interface that all functions must implement as well as some
+convenience functions and implementations to simplify writing implementations.
+
+Two "primitive" functions are provided:
+
+`Audio` provides the primitive function for audio cues, each capable of representing a
+single audio file.
+
+`Scene` provides the primitive function for lighting cues, each capable of representing a
+single static lighting cue.
+
+Two "composite" functions are provided:
+
+`Chaser` provides the ability to construct sequences of functions to be run in turn.
+
+`Join` provides the ability to "join" functions together and run them simultaneously, e.g.
+for running cues where the lighting and sound cues must be fired simultaneously.
+
+These composite functions may be nested, i.e. a `Chaser` may have another `Chaser` as one
+of its steps.
+"""
+
from .function import Function
+from .audio import Audio
from .scene import Scene
from .chaser import Chaser
from .chaserstep import ChaserStep
+from .join import Join
-__all__ = ["Audio", "Function", "Scene", "Chaser", "ChaserStep"]
+__all__ = ["Audio", "Function", "Scene", "Chaser", "ChaserStep", "Join"]
diff --git a/blc2/functions/join.py b/blc2/functions/join.py
index 8c911bf..3d23480 100644
--- a/blc2/functions/join.py
+++ b/blc2/functions/join.py
@@ -2,14 +2,19 @@
import xml.etree.ElementTree as et
-from ..constants import JOIN, INFTY, BXW
+from ..constants import JOIN, 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).
+ Joins merge multiple functions into one (e.g. audio and lighting). It takes the
+ duration and actual duration of the longest function (it may be a different function
+ in the two cases).
+
+ For convenience, Join's interface is very similar to that of Chaser, with only some
+ functions missing.
"""
type = JOIN
@@ -60,6 +65,12 @@ class Join(Function):
self._recalculate()
def get_data(self, params=None):
+ """Get the data for the functions.
+
+ :param params: parameters to pass to the composite get_data functions. Must be
+ either `None` or provide a value for each composite function, which will be starred
+ when passed to the functions
+ """
if params is not None:
if len(params) != len(self._steps):
raise ValueError("Must have same number of parameters as steps!")
@@ -71,9 +82,16 @@ class Join(Function):
@property
def steps(self):
+ """Return a tuple of the Join's steps."""
return tuple(self._steps)
def add_step(self, s):
+ """Add the given step to the Join.
+
+ :param s: the step to add, either a Function or an integer.
+ """
+ if isinstance(s, int):
+ s = self.w.functions[s]
if s.id in (i.id for i in self._steps):
raise ValueError("Already added")
self._steps.append(s)
@@ -83,6 +101,11 @@ class Join(Function):
self.w.register_function_delete_callback(s, self._recalculate, self)
def delete_step(self, s, index=False):
+ """Delete a step from the Join.
+
+ :param s: the step to delete, either a Function or an integer.
+ :param index: whether or not `s` is an index of self.steps or a Function/ID.
+ """
if index:
s = self._steps[s]
elif isinstance(s, int):
@@ -100,11 +123,13 @@ class Join(Function):
lc = {c: 0 for c in self._scope}
ac = []
+ ## Render and mix the composite renders
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
+ ## Convolute the audio hash
ac.extend(((hash((self._id, aid)), *others) for aid, *others in acs))
return tuple(lc.items()), tuple(ac), data
diff --git a/blc2/interfaces.py b/blc2/interfaces.py
index e0a904a..a4af83c 100644
--- a/blc2/interfaces.py
+++ b/blc2/interfaces.py
@@ -7,6 +7,7 @@ class XMLSerializable(metaclass=ABCMeta):
"""Interface for XML-serializable Workspace components."""
@staticmethod
def int_or_none(v):
+ """Convert the argument to an int if possible; otherwise return `None`."""
if v is None:
return None
@@ -21,7 +22,7 @@ class XMLSerializable(metaclass=ABCMeta):
def indent(elem, indent=4, level=0):
"""Pretty-indent the XML tree."""
i = "\n" + level*(indent*' ')
- if len(elem) > 0:
+ if elem:
if not elem.text or not elem.text.strip():
elem.text = i + (' '*indent)
if not elem.tail or not elem.tail.strip():