import copy
from . update_threading import UpdateThreading
from .. project import attributes, data_maker, fields
from .. util import deprecated, exception, log
from .. colors import COLORS, conversions, make
[docs]class Layout(object):
"""
Base Layer class. Use Strip, Matrix, Cube, or Circle instead!
:param drivers: A list of drivers
:param threadedUpdate: If True, updates to this layout are done in a
separate thread
:param brightness: An initial brightness value from 0 to 255
:param maker: A data maker to make color_lists. TODO: Link to what a maker
is.
:param color_list: If non-Null, the layout uses this color_list instead
of creating its own
:ivar int numLEDs: Total number of pixels held by this layout instance
"""
CLONE_ATTRS = 'maker', 'brightness'
pre_recursion = fields.default_converter
[docs] @classmethod
def construct(cls, project, **desc):
"""Construct a layout.
SHOULD BE PRIVATE
"""
return cls(project.drivers, maker=project.maker, **desc)
def __init__(self, drivers, threadedUpdate=False, brightness=255,
maker=data_maker.MAKER, color_list=None, **kwds):
attributes.set_reserved(self, 'layout', **kwds)
self.drivers = drivers if isinstance(drivers, list) else [drivers]
self.maker = maker
# self._colors will always be the same list - i.e. is guaranteed only
# to be changed by list surgery, never assignment.
if color_list is None:
if not hasattr(self, 'numLEDs'):
self.numLEDs = sum(d.numLEDs for d in self.drivers)
self._colors = maker.color_list(self.numLEDs)
else:
self.numLEDs = len(color_list)
self._colors = color_list
pos = 0
for d in self.drivers:
d.set_colors(self._colors, pos)
pos += d.numLEDs
self.frame_render_time = 0
self.threading = UpdateThreading(threadedUpdate, self)
self.brightness = brightness
self.set_brightness(brightness)
self.needs_cleanup = True
[docs] def set_pixel_positions(self, pixel_positions):
"""SHOULD BE PRIVATE"""
for d in self.drivers:
d.set_pixel_positions(pixel_positions)
[docs] def start(self):
for d in self.drivers:
d.start()
[docs] def stop(self):
self.threading.stop()
for d in self.drivers:
d.stop()
[docs] def join(self, timeout=None):
self.threading.wait()
for d in self.drivers:
d.join(timeout)
[docs] def cleanup_drivers(self):
for d in self.drivers:
d.cleanup()
[docs] def cleanup(self):
if self.needs_cleanup:
self.needs_cleanup = False
self.all_off()
exception.report(self.push_to_driver)
self.threading.wait_for_update()
[docs] def clone(self):
"""
Return an independent copy of this layout with a completely separate
color_list and no drivers.
"""
args = {k: getattr(self, k) for k in self.CLONE_ATTRS}
args['color_list'] = copy.copy(self.color_list)
return self.__class__([], **args)
@property
def shape(self):
"""
Return a tuple indicating the dimensions of the layout - (x,) for a
strip, (x, y) for an array, (x, y, z) for a cube, and
(ring_count, ring_steps) for a circle.
"""
raise NotImplementedError
@property
def dimensions(self):
deprecated.deprecated('Layout.dimensions')
return self.shape
@property
def color_list(self):
return self._colors
@color_list.setter
def color_list(self, color_list):
self.set_color_list(color_list)
[docs] def set_color_list(self, color_list, offset=0):
"""
Set the internal colors starting at an optional offset.
If `color_list` is a list or other 1-dimensional array, it is reshaped
into an N x 3 list.
If `color_list` too long it is truncated; if it is too short then only
the initial colors are set.
"""
if not len(color_list):
return
color_list = make.colors(color_list)
size = len(self._colors) - offset
if len(color_list) > size:
color_list = color_list[:size]
self._colors[offset:offset + len(color_list)] = color_list
def _get_base(self, pixel):
if pixel >= 0 and pixel < self.numLEDs:
return self._colors[pixel]
return COLORS.Black # don't go out of bounds
def _set_base(self, pixel, color):
if pixel >= 0 and pixel < self.numLEDs:
if isinstance(color, str):
color = COLORS[color]
else:
color = tuple(color)
self.color_list[pixel] = color
[docs] def get_pixel_positions(self):
result = []
for x in range(len(self.numLEDs)):
result.append([x, 0, 0])
return result
[docs] def push_to_driver(self):
"""
Push the current pixel state to the driver
Do not call this method from user code!
"""
# This is overridden elsewhere.
self.threading.push_to_driver()
if deprecated.allowed():
[docs] def update(self):
"""
DEPRECATED: Use :py:func:`push_to_driver` instead
Do not call this method from user code!
"""
deprecated.deprecated('Layout.update')
return self.push_to_driver()
[docs] def set_brightness(self, brightness):
self.brightness = brightness
for d in self.drivers:
d.set_brightness(brightness)
# Set single pixel to RGB value
[docs] def setRGB(self, pixel, r, g, b):
"""Set single pixel using individual RGB values instead of tuple"""
self._set_base(pixel, (r, g, b))
[docs] def setHSV(self, pixel, hsv):
"""Set single pixel to HSV tuple"""
color = conversions.hsv2rgb(hsv)
self._set_base(pixel, color)
# turns off the desired pixel
[docs] def setOff(self, pixel):
"""Set single pixel off"""
self._set_base(pixel, (0, 0, 0))
[docs] def all_off(self):
"""Set all pixels off"""
self._colors[:] = [(0, 0, 0)] * self.numLEDs
# Fill the strand (or a subset) with a single color using a Color object
[docs] def fill(self, color, start=0, end=-1):
"""Fill the entire strip with RGB color tuple"""
start = max(start, 0)
if end < 0 or end >= self.numLEDs:
end = self.numLEDs - 1
for led in range(start, end + 1): # since 0-index include end in range
self._set_base(led, color)
# Fill the strand (or a subset) with a single color using RGB values
[docs] def fillRGB(self, r, g, b, start=0, end=-1):
"""Fill entire strip by giving individual RGB values instead of tuple"""
self.fill((r, g, b), start, end)
# Fill the strand (or a subset) with a single color using HSV values
[docs] def fillHSV(self, hsv, start=0, end=-1):
"""Fill the entire strip with HSV color tuple"""
self.fill(conversions.hsv2rgb(hsv), start, end)
[docs]class MultiLayout(Layout):
CLONE_ATTRS = Layout.CLONE_ATTRS + ('gen_coord_map', 'coord_map')
def __init__(self, *args, gen_coord_map=None, coord_map=None, **kwds):
super().__init__(*args, **kwds)
self.gen_coord_map = gen_coord_map
if gen_coord_map:
if coord_map:
log.warning('Cannot set both coord_map and gen_coord_map')
elif isinstance(gen_coord_map, dict):
coord_map = self.gen_multi(**gen_coord_map)
else:
coord_map = self.gen_multi(gen_coord_map)
self.coord_map = coord_map
[docs] def gen_multi(self, *args, **kwds):
raise NotImplementedError
[docs] def set_colors(self, buf):
"""
DEPRECATED: use self.color_list
Use with extreme caution!
Directly sets the internal buffer and bypasses all brightness and
rotation control buf must also be in the exact format required by the
display type.
"""
deprecated.deprecated('layout.set_colors')
if len(self._colors) != len(buf):
raise IOError("Data buffer size incorrect! "
"Expected: {} bytes / Received: {} bytes"
.format(len(self._colors), len(buf)))
self._colors[:] = buf
[docs] def setBuffer(self, buf):
deprecated.deprecated('layout.setBuffer')
self.color_list = buf