/ adafruit_led_animation / grid.py
grid.py
1 # SPDX-FileCopyrightText: 2020 Kattni Rembor for Adafruit Industries 2 # 3 # SPDX-License-Identifier: MIT 4 5 """ 6 `adafruit_led_animation.grid` 7 ================================================================================ 8 9 PixelGrid helper for 2D animations. 10 11 * Author(s): Kattni Rembor 12 13 Implementation Notes 14 -------------------- 15 16 **Hardware:** 17 18 * `Adafruit NeoPixels <https://www.adafruit.com/category/168>`_ 19 * `Adafruit DotStars <https://www.adafruit.com/category/885>`_ 20 21 **Software and Dependencies:** 22 23 * Adafruit CircuitPython firmware for the supported boards: 24 https://circuitpython.org/downloads 25 26 """ 27 from micropython import const 28 29 from .helper import PixelMap, horizontal_strip_gridmap, vertical_strip_gridmap 30 31 32 HORIZONTAL = const(1) 33 VERTICAL = const(2) 34 35 36 class PixelGrid: 37 """ 38 PixelGrid lets you address a pixel strip with x and y coordinates. 39 40 :param strip: An object that implements the Neopixel or Dotstar protocol. 41 :param width: Grid width. 42 :param height: Grid height. 43 :param orientation: Orientation of the strip pixels - HORIZONTAL (default) or VERTICAL. 44 :param alternating: Whether the strip alternates direction from row to row (default True). 45 :param reverse_x: Whether the strip X origin is on the right side (default False). 46 :param reverse_y: Whether the strip Y origin is on the bottom (default False). 47 :param tuple top: (x, y) coordinates of grid top left corner (Optional) 48 :param tuple bottom: (x, y) coordinates of grid bottom right corner (Optional) 49 50 To use with individual pixels: 51 52 .. code-block:: python 53 54 import board 55 import neopixel 56 import time 57 from adafruit_led_animation.grid import PixelGrid, VERTICAL 58 59 pixels = neopixel.NeoPixel(board.D11, 256, auto_write=False) 60 61 grid = PixelGrid(pixels, 32, 8, orientation=VERTICAL, alternating=True) 62 63 for x in range(32): 64 for y in range(8): 65 # pg[x, y] = (y*32) + x 66 pg[x][y] = ((y*32) + x) << 8 67 pg.show() 68 69 """ 70 71 def __init__( 72 self, 73 strip, 74 width, 75 height, 76 orientation=HORIZONTAL, 77 alternating=True, 78 reverse_x=False, 79 reverse_y=False, 80 top=0, 81 bottom=0, 82 ): # pylint: disable=too-many-arguments,too-many-locals 83 self._pixels = strip 84 self._x = [] 85 self.height = height 86 self.width = width 87 88 if orientation == HORIZONTAL: 89 mapper = horizontal_strip_gridmap(width, alternating) 90 else: 91 mapper = vertical_strip_gridmap(height, alternating) 92 93 if reverse_x: 94 mapper = reverse_x_mapper(width, mapper) 95 96 if reverse_y: 97 mapper = reverse_y_mapper(height, mapper) 98 99 x_start = 0 100 x_end = width 101 y_start = 0 102 y_end = height 103 if top: 104 x_start, y_start = top 105 if bottom: 106 x_end, y_end = bottom 107 108 self.height = y_end - y_start 109 self.width = x_end - x_start 110 111 for x in range(x_start, x_end): 112 self._x.append( 113 PixelMap( 114 strip, 115 [mapper(x, y) for y in range(y_start, y_end)], 116 individual_pixels=True, 117 ) 118 ) 119 self.n = len(self._x) 120 121 def __repr__(self): 122 return "[" + ", ".join([str(self[x]) for x in range(self.n)]) + "]" 123 124 def __setitem__(self, index, val): 125 if isinstance(index, slice): 126 raise NotImplementedError("PixelGrid does not support slices") 127 128 if isinstance(index, tuple): 129 self._x[index[0]][index[1]] = val 130 else: 131 raise ValueError("PixelGrid assignment needs a sub-index or x,y coordinate") 132 133 if self._pixels.auto_write: 134 self.show() 135 136 def __getitem__(self, index): 137 if isinstance(index, slice): 138 raise NotImplementedError("PixelGrid does not support slices") 139 if index < 0: 140 index += len(self) 141 if index >= self.n or index < 0: 142 raise IndexError("x is out of range") 143 return self._x[index] 144 145 def __len__(self): 146 return self.n 147 148 @property 149 def brightness(self): 150 """ 151 brightness from the underlying strip. 152 """ 153 return self._pixels.brightness 154 155 @brightness.setter 156 def brightness(self, brightness): 157 # pylint: disable=attribute-defined-outside-init 158 self._pixels.brightness = min(max(brightness, 0.0), 1.0) 159 160 def fill(self, color): 161 """ 162 Fill the PixelGrid with the specified color. 163 164 :param color: Color to use. 165 """ 166 for strip in self._x: 167 strip.fill(color) 168 169 def show(self): 170 """ 171 Shows the pixels on the underlying strip. 172 """ 173 self._pixels.show() 174 175 @property 176 def auto_write(self): 177 """ 178 auto_write from the underlying strip. 179 """ 180 return self._pixels.auto_write 181 182 @auto_write.setter 183 def auto_write(self, value): 184 self._pixels.auto_write = value 185 186 187 def reverse_x_mapper(width, mapper): 188 """ 189 Returns a coordinate mapper function for grids with reversed X coordinates. 190 191 :param width: width of strip 192 :param mapper: grid mapper to wrap 193 :return: mapper(x, y) 194 """ 195 max_x = width - 1 196 197 def x_mapper(x, y): 198 return mapper(max_x - x, y) 199 200 return x_mapper 201 202 203 def reverse_y_mapper(height, mapper): 204 """ 205 Returns a coordinate mapper function for grids with reversed Y coordinates. 206 207 :param height: width of strip 208 :param mapper: grid mapper to wrap 209 :return: mapper(x, y) 210 """ 211 max_y = height - 1 212 213 def y_mapper(x, y): 214 return mapper(x, max_y - y) 215 216 return y_mapper