Repository: ManiacalLabs/BiblioPixel
Pull request #701 (2018-03-26 22:12:54)

Make layout.color_list setter more flexible
Ah, I feel bad writing new code when the documentation waits, _but_ this is all part of getting Adam2's thesis project done - which will allow us to accept ArtNet messages as a general control, so that will be super-deluxe!

This makes the setter for Layout.color_list more [procrustean](https://en.wikipedia.org/wiki/Procrustes):  you can either use a list of 3-tuples, or a 1-dimensional list that gets resized.  It also handles lists that are too long or short, and you can call it as a method with an optional offset.

Why is this useful?  Well, the ArtNet data comes in as RGBRGBRGB bytes and this means that I can literally have this routing address: `layout.color_list` _and nothing else_ and it will all work.



adammhaile: New features is a always why we don't have docs :P But this is for a good reason :)
Looks great. +1
rec: And this sorta needs to be documented "less" because it will "just work" more often.

It's so much easier if you see "it's cut off" or "it's not long enough" and it works and doesn't fail.

Patch:
diff --git a/bibliopixel/layout/layout.py b/bibliopixel/layout/layout.py
index da7d1a6..a1bf4e7 100644
--- a/bibliopixel/layout/layout.py
+++ b/bibliopixel/layout/layout.py
@@ -99,8 +99,32 @@ class Layout(object):
         return self._colors
 
     @color_list.setter
-    def color_list(self, cl):
-        self._colors[:] = cl
+    def color_list(self, color_list):
+        self.set_color_list(color_list)
+
+    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
+        try:
+            color_list[0][0]
+        except TypeError:
+            # It's a 1-dimensional list
+            assert not (len(color_list) % 3)
+            color_list = list(zip(*[iter(color_list)] * 3))
+
+        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:
@@ -126,24 +150,6 @@ class Layout(object):
         # This is overridden elsewhere.
         self.threading.push_to_driver()
 
-    # use with caution!
-    def set_colors(self, buf):
-        """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.
-        """
-        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
-
-    def setBuffer(self, buf):
-        util.deprecated.deprecated('layout.setBuffer')
-
-        # https://stackoverflow.com/questions/1624883
-        self.set_colors(buf=list(zip(*(iter(buf),) * 3)))
-
     def set_brightness(self, brightness):
         self.brightness = brightness
         for d in self.drivers:
@@ -202,3 +208,22 @@ class MultiLayout(Layout):
 
     def gen_multi(self, *args, **kwds):
         raise NotImplementedError
+
+    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.
+        """
+        util.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
+
+    def setBuffer(self, buf):
+        util.deprecated.deprecated('layout.setBuffer')
+        self.color_list = buf


Test patch:
diff --git a/test/bibliopixel/layout/layout_test.py b/test/bibliopixel/layout/layout_test.py
index 4136d81..485e8e2 100644
--- a/test/bibliopixel/layout/layout_test.py
+++ b/test/bibliopixel/layout/layout_test.py
@@ -1,6 +1,11 @@
 import unittest
 from bibliopixel.layout import Strip
-from bibliopixel.util import colors
+from bibliopixel.util.colors import COLORS
+
+RED = COLORS.Red
+GREEN = COLORS.Green
+BLACK = COLORS.Black
+HOT_PINK = COLORS['hot pink']
 
 
 class MockDriver:
@@ -13,10 +18,34 @@ class MockDriver:
 
 
 class LayoutTest(unittest.TestCase):
-    def test_set_colors(self):
+    def test_set_individual_colors(self):
         strip = Strip(MockDriver(4))
         strip.set(0, 'red'),
-        strip.set(1, colors.Green)
+        strip.set(1, GREEN)
         strip.set(2, 'hot pink')
-        expected = [(255, 0, 0), (0, 255, 0), (255, 105, 180), (0, 0, 0)]
+        expected = [RED, GREEN, HOT_PINK, BLACK]
+        self.assertEquals(expected, strip.color_list)
+
+    def test_set_color(self):
+        strip = Strip(MockDriver(4))
+        strip.color_list = RED, GREEN, HOT_PINK
+        expected = [RED, GREEN, HOT_PINK, BLACK]
+        self.assertEquals(expected, strip.color_list)
+
+    def test_set_linear_color(self):
+        strip = Strip(MockDriver(4))
+        strip.color_list = RED + GREEN + HOT_PINK
+        expected = [RED, GREEN, HOT_PINK, BLACK]
+        self.assertEquals(expected, strip.color_list)
+
+    def test_set_too_much_color(self):
+        strip = Strip(MockDriver(4))
+        strip.color_list = RED + GREEN + HOT_PINK + GREEN + HOT_PINK
+        expected = [RED, GREEN, HOT_PINK, GREEN]
+        self.assertEquals(expected, strip.color_list)
+
+    def test_set_offset(self):
+        strip = Strip(MockDriver(4))
+        strip.set_color_list(RED + GREEN, 2)
+        expected = [BLACK, BLACK, RED, GREEN]
         self.assertEquals(expected, strip.color_list)