1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
"""Topology module.
Contains the topology classes for representing the physical and virtual topology of the
lighting setup.
"""
import xml.etree.ElementTree as et
from .constants import BXW
from .interfaces import XMLSerializable
from .exceptions import LoadError
class Fixture(XMLSerializable):
"""Class representing a lighting fixture.
Each lighting fixture has a number of channels, which are mapped to the physical
topology.
Channels must be added by changing the value of ``Fixture.channel_count``, which will
create the necessary channel objects and handle deletion.
"""
def __init__(self, w: "Workspace", id_: int = None, name: str = None, channel_count: int = 0):
self.w = w
self.id = id_ if id_ is not None else w.next_fixture_id
self.name = name if name else "Fixture %d" % self.id
self.channels = ()
self.channel_count = channel_count
self.w.register_fixture(self)
class Channel:
"""Class representing a single channel on a Fixture.
The physical address of the channel is stored in the ``Channel.address`` attribute
and may be changed at will.
This program takes a very lax approach to channel addressing: the address is not
parsed in any way and duplicate addresses are not handled. For convenience with
OLA, Channel is iterable and iteration returns an iterator over its ``address``
attribute.
"""
def __init__(self, f: "Fixture", id_: int, name: str = "Intensity", address = (-1,-1)):
self.f = f
self.id = id_
self.name = name
self.address = address
self._hash = hash((f.id, id_))
def __hash__(self):
return self._hash
def __iter__(self):
return iter(self.address)
def __repr__(self):
return "Channel(fixture={c.f.id}, index={c.id}, name={c.name})".format(c=self)
@property
def channel_count(self):
"""Return the current number of channels on the fixture."""
return len(self.channels)
@channel_count.setter
def channel_count(self, value):
"""Change the number of channels on the fixture.
This function handles deletion of removed channels from functions as well.
"""
if value < 0:
raise ValueError("Number of channels must be nonnegative")
elif value < len(self.channels):
for i in range(value, len(self.channels)):
self.w.delete_channel(self.channels[i])
self.channels = self.channels[:value]
elif value > len(self.channels):
self.channels += tuple((Fixture.Channel(self, i) for i in range(len(self.channels), value)))
def __repr__(self):
return "Fixture({f.name}, id={f.id}, channels={f.channel_count})".format(f=self)
def serialize(self):
e = et.Element(BXW+"fixture")
e.set("name", self.name)
e.set("id", str(self.id))
for c in self.channels:
ce = et.SubElement(e, BXW+"channel")
ce.set("name", c.name)
if c.address is not None:
## TODO: Other addressing modes
try:
univ, addr = c.address
ae = et.SubElement(ce, BXW+"ola")
ae.set("universe", str(univ))
ae.set("address", str(addr))
except ValueError:
pass
return e
@classmethod
def deserialize(cls, w, e):
if e.tag != BXW+"fixture":
raise LoadError("Invalid fixture tag")
id_ = cls.int_or_none(e.get("id"))
if id_ is None:
raise LoadError("Fixture tag has invalid/missing ID")
name = e.get("name")
f = cls(w, id_=id_, name=name, channel_count=len(e))
for n, channel in enumerate(e):
if channel.tag != BXW+"channel":
raise LoadError("Invalid channel tag")
name = channel.get("name")
if name is not None:
f.channels[n].name = name
if len(channel) > 1:
raise LoadError("Channel can have at most one address")
elif len(channel) == 1:
address, = channel
if address.tag == BXW+"ola":
try:
address = (int(address.get("universe")), int(address.get("address")),)
except (ValueError, TypeError):
raise LoadError("Invalid OLA address on channel")
else:
raise LoadError("Unknown address tag \"%s\"" % address.tag)
f.channels[n].address = address
return f
|