Skip to content

Commit 9094402

Browse files
committed
Add PixelGrid
1 parent 968a7be commit 9094402

File tree

2 files changed

+230
-4
lines changed

2 files changed

+230
-4
lines changed

adafruit_led_animation/grid.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# The MIT License (MIT)
2+
#
3+
# Copyright (c) 2019 Roy Hooper
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
# THE SOFTWARE.
22+
"""
23+
`adafruit_led_animation.grid`
24+
================================================================================
25+
26+
PixelGrid helper for 2D animations.
27+
28+
* Author(s): Roy Hooper
29+
30+
Implementation Notes
31+
--------------------
32+
33+
**Hardware:**
34+
35+
* `Adafruit NeoPixels <https://www.adafruit.com/category/168>`_
36+
* `Adafruit DotStars <https://www.adafruit.com/category/885>`_
37+
38+
**Software and Dependencies:**
39+
40+
* Adafruit CircuitPython firmware for the supported boards:
41+
https://circuitpython.org/downloads
42+
43+
"""
44+
from micropython import const
45+
46+
from .helper import PixelMap, horizontal_strip_gridmap, vertical_strip_gridmap
47+
48+
49+
HORIZONTAL = const(1)
50+
VERTICAL = const(2)
51+
52+
53+
class PixelGrid:
54+
"""
55+
PixelGrid lets you address a pixel strip with x and y coordinates.
56+
57+
:param strip: An object that implements the Neopixel or Dotstar protocol.
58+
:param width: Grid width.
59+
:param height: Grid height.
60+
:param orientation: Orientation of the strip pixels - HORIZONTAL (default) or VERTICAL.
61+
:param alternating: Whether the strip alternates direction from row to row (default True).
62+
:param reverse_x: Whether the strip X origin is on the right side (default False).
63+
:param reverse_y: Whether the strip Y origin is on the bottom (default False).
64+
:param tuple top: (x, y) coordinates of grid top left corner (Optional)
65+
:param tuple bottom: (x, y) coordinates of grid bottom right corner (Optional)
66+
67+
To use with individual pixels:
68+
69+
.. code-block:: python
70+
71+
import board
72+
import neopixel
73+
import time
74+
from adafruit_led_animation.grid import PixelGrid, VERTICAL
75+
76+
pixels = neopixel.NeoPixel(board.D11, 256, auto_write=False)
77+
78+
grid = PixelGrid(pixels, 32, 8, orientation=VERTICAL, alternating=True)
79+
80+
for x in range(32):
81+
for y in range(8):
82+
# pg[x, y] = (y*32) + x
83+
pg[x][y] = ((y*32) + x) << 8
84+
pg.show()
85+
86+
"""
87+
88+
def __init__(
89+
self,
90+
strip,
91+
width,
92+
height,
93+
orientation=HORIZONTAL,
94+
alternating=True,
95+
reverse_x=False,
96+
reverse_y=False,
97+
top=0,
98+
bottom=0,
99+
): # pylint: disable=too-many-arguments,too-many-locals
100+
self._pixels = strip
101+
self._x = []
102+
self.height = height
103+
self.width = width
104+
105+
if orientation == HORIZONTAL:
106+
mapper = horizontal_strip_gridmap(width, alternating)
107+
else:
108+
mapper = vertical_strip_gridmap(height, alternating)
109+
110+
if reverse_x:
111+
mapper = reverse_x_mapper(width, mapper)
112+
113+
if reverse_y:
114+
mapper = reverse_y_mapper(height, mapper)
115+
116+
x_start = 0
117+
x_end = width
118+
y_start = 0
119+
y_end = height
120+
if top:
121+
x_start, y_start = top
122+
if bottom:
123+
x_end, y_end = bottom
124+
125+
for x in range(x_start, x_end):
126+
self._x.append(
127+
PixelMap(
128+
strip, [mapper(x, y) for y in range(y_start, y_end)], individual_pixels=True
129+
)
130+
)
131+
self.n = len(self._x)
132+
133+
def __repr__(self):
134+
return "[" + ", ".join([str(self[x]) for x in range(self.n)]) + "]"
135+
136+
def __setitem__(self, index, val):
137+
if isinstance(index, slice):
138+
raise NotImplementedError("PixelGrid does not support slices")
139+
140+
if isinstance(index, tuple):
141+
self._x[index[0]][index[1]] = val
142+
else:
143+
raise ValueError("PixelGrid assignment needs a sub-index or x,y coordinate")
144+
145+
if self._pixels.auto_write:
146+
self.show()
147+
148+
def __getitem__(self, index):
149+
if isinstance(index, slice):
150+
raise NotImplementedError("PixelGrid does not support slices")
151+
if index < 0:
152+
index += len(self)
153+
if index >= self.n or index < 0:
154+
raise IndexError("x is out of range")
155+
return self._x[index]
156+
157+
def __len__(self):
158+
return self.n
159+
160+
@property
161+
def brightness(self):
162+
"""
163+
brightness from the underlying strip.
164+
"""
165+
return self._pixels.brightness
166+
167+
@brightness.setter
168+
def brightness(self, brightness):
169+
# pylint: disable=attribute-defined-outside-init
170+
self._pixels.brightness = min(max(brightness, 0.0), 1.0)
171+
172+
def fill(self, color):
173+
"""
174+
Fill the PixelGrid with the specified color.
175+
176+
:param color: Color to use.
177+
"""
178+
for strip in self._x:
179+
strip.fill(color)
180+
181+
def show(self):
182+
"""
183+
Shows the pixels on the underlying strip.
184+
"""
185+
self._pixels.show()
186+
187+
@property
188+
def auto_write(self):
189+
"""
190+
auto_write from the underlying strip.
191+
"""
192+
return self._pixels.auto_write
193+
194+
@auto_write.setter
195+
def auto_write(self, value):
196+
self._pixels.auto_write = value
197+
198+
199+
def reverse_x_mapper(width, mapper):
200+
"""
201+
Returns a coordinate mapper function for grids with reversed X coordinates.
202+
203+
:param width: width of strip
204+
:param mapper: grid mapper to wrap
205+
:return: mapper(x, y)
206+
"""
207+
max_x = width - 1
208+
209+
def x_mapper(x, y):
210+
return mapper(max_x - x, y)
211+
212+
return x_mapper
213+
214+
215+
def reverse_y_mapper(height, mapper):
216+
"""
217+
Returns a coordinate mapper function for grids with reversed Y coordinates.
218+
219+
:param height: width of strip
220+
:param mapper: grid mapper to wrap
221+
:return: mapper(x, y)
222+
"""
223+
max_y = height - 1
224+
225+
def y_mapper(x, y):
226+
return mapper(x, max_y - y)
227+
228+
return y_mapper

adafruit_led_animation/helper.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,15 +127,13 @@ def __init__(self, strip, pixel_ranges, individual_pixels=False):
127127

128128
self.n = len(self._ranges)
129129
if self.n == 0:
130-
raise (ValueError("A PixelMap must have at least one pixel defined"))
130+
raise ValueError("A PixelMap must have at least one pixel defined")
131131
self._individual_pixels = individual_pixels
132132
self._expand_ranges()
133133

134134
def _expand_ranges(self):
135135
if not self._individual_pixels:
136-
self._ranges = [
137-
[n for n in range(start, end)] for start, end in self._ranges
138-
]
136+
self._ranges = [list(range(start, end)) for start, end in self._ranges]
139137
return
140138
if isinstance(self._ranges[0], int):
141139
self._ranges = [[n] for n in self._ranges]

0 commit comments

Comments
 (0)