├── .gitignore ├── GUI.png ├── eight.png ├── GUI-phat.png ├── 8x8grid.desktop ├── 8x8grid.py ├── led.py ├── licence.md ├── buttons.py ├── README.md ├── 8x8grid-sense.py ├── 8x8grid-unicorn.py └── 8x8grid-unicornphat.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .AppleDouble -------------------------------------------------------------------------------- /GUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/topshed/RPi_8x8GridDraw/HEAD/GUI.png -------------------------------------------------------------------------------- /eight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/topshed/RPi_8x8GridDraw/HEAD/eight.png -------------------------------------------------------------------------------- /GUI-phat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/topshed/RPi_8x8GridDraw/HEAD/GUI-phat.png -------------------------------------------------------------------------------- /8x8grid.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Encoding=UTF-8 4 | Name=8x8grid 5 | Exec=/usr/bin/python /home/pi/RPi_8x8GridDraw/8x8grid.py 6 | Terminal=false 7 | Categories=Application 8 | StartupNotify=true 9 | Icon=/home/pi/RPi_8x8GridDraw/eight.png 10 | -------------------------------------------------------------------------------- /8x8grid.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | 3 | if os.path.isfile("/proc/device-tree/hat/product"): 4 | file = open("/proc/device-tree/hat/product","r") 5 | hat = file.readline() 6 | if hat == "Sense HAT\x00": 7 | print('Sense HAT detected') 8 | mypath = os.path.dirname(os.path.abspath(__file__)) 9 | file.close() 10 | os.system("/usr/bin/env python3 " + mypath+"/8x8grid-sense.py") 11 | elif hat == "Unicorn HAT\x00": 12 | print('Unicorn HAT detected') 13 | mypath = os.path.dirname(os.path.abspath(__file__)) 14 | file.close() 15 | os.system("/usr/bin/env python3 " + mypath+"/8x8grid-unicorn.py") 16 | else: 17 | print("Unknown HAT : " + str(hat)) 18 | file.close() 19 | sys.exit() 20 | else: 21 | print('No HAT detected') 22 | answer = input('Do you have a Unicorn Phat (y/n)?') 23 | if answer == 'y': 24 | print('Configuring for Unicorn Phat') 25 | mypath = os.path.dirname(os.path.abspath(__file__)) 26 | os.system("/usr/bin/env python3 " + mypath+"/8x8grid-unicornphat.py") 27 | else: 28 | sys.exit() 29 | -------------------------------------------------------------------------------- /led.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | 3 | 4 | class LED(): 5 | def __init__(self, pos=(0, 0), radius=25, lit=False): 6 | 7 | # Initializes the LED 8 | 9 | self.pos = pos 10 | self.lit = lit 11 | self.radius = radius 12 | self.screen = pygame.display.get_surface() 13 | self.color = (255, 255, 255) 14 | self.pos_x = int(self.pos[0] * (self.radius * 2 + 5)) + (self.radius) 15 | self.pos_y = int(self.pos[1] * (self.radius * 2 + 5)) + (self.radius) + 40 16 | 17 | def draw(self): 18 | 19 | #Draws a circle, 20 | w = [] 21 | if self.lit: # has it been clicked? 22 | thickness = 0 23 | else: 24 | self.color = [255,255,255] 25 | thickness = 1 26 | 27 | pygame.draw.circle(self.screen, self.color, (self.pos_x, self.pos_y), self.radius, thickness) 28 | 29 | # Draws a square 30 | pygame.draw.rect(self.screen,self.color,(self.pos_x-self.radius, self.pos_y-self.radius, (2*self.radius),(2*self.radius)),thickness) 31 | 32 | def clicked(self, colour): 33 | 34 | # what to do when clicked 35 | self.color = colour 36 | if self.lit: 37 | self.lit = False 38 | else: 39 | self.lit = True 40 | -------------------------------------------------------------------------------- /licence.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Richard Hayler 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /buttons.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | 3 | 4 | class Button(): 5 | def __init__(self, text, pos, action=None, size=(100, 30), color=(0, 0, 153), hilight=(0, 200, 200)): 6 | # Button class 7 | 8 | self.normal = color 9 | self.hilight = hilight 10 | self.rect = pygame.Rect(pos, size) 11 | self.mouseover = False 12 | self.text = text 13 | self.font = pygame.font.Font(None, 18) 14 | self.text_image = self.font.render(text, 1, (0,51,25)) 15 | w, h = self.font.size(text) # size of font image 16 | self.text_pos = (pos[0] + size[0] / 2 - w / 2, pos[1] + size[1] / 2 - h / 2) # center text 17 | self.buttondown = False 18 | self.action = action 19 | 20 | 21 | def draw(self, surface): 22 | 23 | #draws the button 24 | 25 | rectout = self.rect.inflate(2, 2) 26 | rectin = self.rect.inflate(1, 1) 27 | if self.buttondown: 28 | pygame.draw.rect(surface, (0, 0, 0), rectout) 29 | pygame.draw.rect(surface, (255, 255, 255), rectin) 30 | else: 31 | pygame.draw.rect(surface, (255, 255, 255), rectout) 32 | pygame.draw.rect(surface, (0, 0, 0), rectin) 33 | if self.mouseover: 34 | pygame.draw.rect(surface, self.hilight, self.rect) 35 | else: 36 | pygame.draw.rect(surface, self.normal, self.rect) 37 | surface.blit(self.text_image, self.text_pos) 38 | 39 | def click(self): 40 | 41 | # runs the appropriate action when button is clicked 42 | 43 | if self.action: 44 | self.action() 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 8x8GridDraw 2 | 3 | These Python Pygame applications are for creating 8x8 pixel images and animations for loading onto either a Raspberry Pi [Sense HAT] (https://www.raspberrypi.org/blog/buy-the-sense-hat-as-seen-in-space/) (as used on the Astro Pi), [UnicornHat] (http://shop.pimoroni.com/products/unicorn-hat) or [UnicornPHAT] (https://shop.pimoroni.com/products/unicorn-phat) LED matrix. 4 | 5 | ![alt tag](https://raw.githubusercontent.com/topshed/RPi_8x8GridDraw/master/GUI.png) 6 | 7 | It offers a choice of several colours for the LEDs: 8 | 9 | Red, Blue, Green, Yellow, White, Orange, Pink, Purple, Cyan 10 | 11 | 12 | See my [blog] (http://richardhayler.blogspot.com/2015/06/creating-images-for-astro-pi-hat.html) for more details, screenshots, updates and other Raspberry Pi projects. 13 | 14 | ###Installation 15 | 16 | This software is written for Python3.x 17 | 18 | 1. Install the pypng Python3 module 19 | 20 | sudo pip3 install pypng 21 | 22 | 2. Install the appropriate library for your HAT 23 | 3. Clone this repo 24 | 25 | ### Getting started 26 | 27 | 1. Run startx then open a terminal window. 28 | 2. Run 8x8grid.py. This will detect which kind of HAT you have and run the appropriate sub-program ( 8x8grid-unicorn for UnicornHat) or 8x8grid-sense.py (for Sense HAT). If you are using a Unicorn PHAT, type 'y' when asked and 8x8grid-unicornphat will be run. You can, of course, just run either program directly (which will also work if you want to use Python2.x). 29 | 30 | This Python program uses pygame and the Sense/UnicornHAT APIs to create and edit single frames or multi-frame animations. The animations can be exported as Python code or a single frame can be saved as an 8x8 png. 31 | 32 | Animations and single frames can be directly loaded on the the LED matrix. 33 | 34 | ##Buttons 35 | 36 | ###Export 37 | 38 | There are two ways to load a pattern on to the LED matrix using Python: 39 | 40 | 1) As an 8x8 pixel png file (load_image() 41 | 2) As a 64 element list, with each element being the RGB value of the led (xxxxx()) 42 | 43 | So to build and display an animation, we either need a series of pngs or a bunch of consecutive lists. 44 | 45 | There are three export methods from the animator program, two for simgle frames and one for the entire sequence. 46 | 47 | ####Single Frame 48 | 49 | **Export to console**: simply prints the current frame as a list to the console. (terminal window). 50 | 51 | **Export to png**: creates an 8x8 pixel png of the current frame, image8x8.png 52 | 53 | ####Animation 54 | 55 | **Export to py**: generates a .py file which when run, will display the animation on the LED matrix. The file contains a few lines of Python and the raw list containing each frame (itself a list) as elements. If you want to use the list in another Python program, simply copy-n-paste the FRAMES list from the file. The output file is always called animation8x8.py and will be overwritten without warning you if it already exists. 56 | 57 | When you've saved an animation, I recommend you copy anaimation8x8.py to another filename to avoid accidental overwriting. 58 | 59 | If your animation is just a single frame, this method will work fine too and is the recommended way of saving your work. 60 | 61 | ###Import 62 | 63 | The '**Import from file**' loads the contents of animation8x8.py into the editor. 64 | 65 | ###Basic features 66 | 67 | On the right hand side are a set of buttons to select the desired LED colour. If you want a different colour then you can edit (or add) a *setColourXXX* function in 8x8grid-xxxxhat.py and change the RGB value. 68 | 69 | The currently selected colour is shown in the circle below the buttons. 70 | 71 | To set an LED's colour, click on its position in the editing window. If the LED is already set to a colour then clicking again will turn it to off, even if you have selected a new colour as the current one in use. Clicking a second time will set the LED to the new colour. 72 | 73 | To add a new frame, click on the **>>** button. This will create a duplicate on the current frame and increment the frame number displayed above. If you want a blank frame rather than a copy of the preceding one, just click the 'clear frame' button. 74 | 75 | The **>>** button is also used to move to the next frame if one exists. There is currently no way to add a new frame in between existing frames. 76 | 77 | Similarly, the **<<** button moves backwards through the frames of the animation. 78 | 79 | The '**Play on LEDs**' button will load the image you have created onto the LED matrix and – if there is more than one frame - run through the frames of the00 animation. 80 | 81 | To increase or decrease the speed at which the animation is played on the LED matrix, use the **+** and **–** buttons. You should the Frames per Second (FPS) value change accordingly. Note that this will also alter the frame-rate set in the exported .py file (default is 1 fps). 82 | 83 | The '**Rotate LEDs**' button will rotate the image displayed on the LEDs by 90 degrees clockwise for SenseHAT and UnicornHAT and 180 degrees for UnicornPHAT. It will not alter the orientation of the image within the editor. 84 | 85 | ![alt tag](https://raw.githubusercontent.com/topshed/RPi_8x8GridDraw/master/GUI-phat.png) 86 | 87 | As mentioned earlier, the '**Clear Grid**' button will set all the LEDs on the screen to off (this will not affect any image loaded on to the LED matrix). 88 | 89 | The '**Delete**' button will remove the current frame and set the displayed frame to be the previous on in the series (e.g. if you're editing frame 6 and press the '**Delete**' button, you will set the current frame to number 5). 90 | 91 | The '**Quit**' button will exit the program. 92 | 93 | ###Colour Definitions: 94 | 95 | RED: [255,0,0] 96 | 97 | BLUE: [0,0,255] 98 | 99 | GREEN: [0,255,0] 100 | 101 | PURPLE: [102,0,204] 102 | 103 | PINK: [255,0,255] 104 | 105 | ORANGE:[255,128,0] 106 | 107 | YELLOW:[255,255,0] 108 | 109 | WHITE:[255,255,255] 110 | 111 | CYAN: [0,255,255] 112 | -------------------------------------------------------------------------------- /8x8grid-sense.py: -------------------------------------------------------------------------------- 1 | ''' 8x8grid-sense.py 2 | Animation and single frame creation append 3 | for SenseHAT 8x8 LED matrix''' 4 | import pygame 5 | import sys 6 | import math 7 | from pygame.locals import * 8 | from led import LED 9 | from buttons import Button 10 | import png # pypng 11 | from sense_hat import SenseHat 12 | import copy, time 13 | 14 | saved = True 15 | warning = False 16 | pygame.init() 17 | pygame.font.init() 18 | 19 | sh=SenseHat() 20 | screen = pygame.display.set_mode((530, 395), 0, 32) 21 | pygame.display.set_caption('Sense HAT Grid Editor') 22 | pygame.mouse.set_visible(1) 23 | 24 | background = pygame.Surface(screen.get_size()) 25 | background = background.convert() 26 | background.fill((0, 51, 25)) 27 | colour = (255,0,0) # Set default colour to red 28 | rotation = 0 29 | frame_number = 1 30 | fps = 4 31 | 32 | 33 | 34 | def setColourRed(): 35 | global colour 36 | colour = (255,0,0) 37 | 38 | def setColourBlue(): 39 | global colour 40 | colour = (0,0,255) 41 | 42 | def setColourGreen(): 43 | global colour 44 | colour = (0,255,0) 45 | 46 | def setColourPurple(): 47 | global colour 48 | colour = (102,0,204) 49 | 50 | def setColourPink(): 51 | global colour 52 | colour = (255,0,255) 53 | 54 | def setColourYellow(): 55 | global colour 56 | colour = (255,255,0) 57 | 58 | def setColourOrange(): 59 | global colour 60 | colour = (255,128,0) 61 | 62 | def setColourWhite(): 63 | global colour 64 | colour = (255,255,255) 65 | 66 | def setColourCyan(): 67 | global colour 68 | colour = (0,255,255) 69 | 70 | def clearGrid(): # Clears the pygame LED grid and sets all the leds.lit back to False 71 | 72 | for led in leds: 73 | led.lit = False 74 | 75 | def buildGrid(): # Takes a grid and builds versions for exporting (png and text) 76 | 77 | e = [0,0,0] 78 | e_png = (0,0,0) 79 | 80 | grid = [ 81 | e,e,e,e,e,e,e,e, 82 | e,e,e,e,e,e,e,e, 83 | e,e,e,e,e,e,e,e, 84 | e,e,e,e,e,e,e,e, 85 | e,e,e,e,e,e,e,e, 86 | e,e,e,e,e,e,e,e, 87 | e,e,e,e,e,e,e,e, 88 | e,e,e,e,e,e,e,e 89 | ] 90 | #png_grid =[] 91 | 92 | png_grid = ['blank','blank','blank','blank','blank','blank','blank','blank'] 93 | for led in leds: 94 | if led.lit: 95 | val = led.pos[0] + (8 * led.pos[1]) 96 | #print val 97 | grid[val] = [led.color[0], led.color[1], led.color[2]] 98 | if png_grid[led.pos[0]] == 'blank': 99 | png_grid[led.pos[0]] = (led.color[0], led.color[1], led.color[2]) 100 | else: 101 | png_grid[led.pos[0]] = png_grid[led.pos[0]] + (led.color[0], led.color[1], led.color[2]) 102 | else: 103 | if png_grid[led.pos[0]] == 'blank': 104 | png_grid[led.pos[0]] = (0,0,0) 105 | else: 106 | png_grid[led.pos[0]] = png_grid[led.pos[0]] + (0,0,0) 107 | return (grid, png_grid) 108 | 109 | def piLoad(): # Loads image onto SenseHAT matrix 110 | grid, grid_png = buildGrid() 111 | sh.set_pixels(grid) 112 | 113 | def exportGrid(): # Writes png to file 114 | 115 | global saved 116 | grid, png_grid = buildGrid() 117 | FILE=open('image8x8.png','wb') 118 | w = png.Writer(8,8) 119 | w.write(FILE,png_grid) 120 | FILE.close() 121 | saved = True 122 | 123 | def exportCons(): # Writes raw list to console 124 | 125 | grid, png_grid = buildGrid() 126 | print(grid) 127 | 128 | 129 | def rotate(): #Rotates image on SenseHAT LED matrix 130 | global rotation 131 | if rotation == 270: 132 | rotation = 0 133 | else: 134 | rotation = rotation + 90 135 | sh.set_rotation(rotation) 136 | 137 | 138 | 139 | def handleClick(): 140 | 141 | global saved 142 | global warning 143 | pos = pygame.mouse.get_pos() 144 | led = findLED(pos, leds) 145 | if led: 146 | #print 'led ' + str(led) + ' clicked' 147 | led.clicked(colour) 148 | saved = False 149 | for butt in buttons: 150 | if butt.rect.collidepoint(pos): 151 | butt.click() 152 | #print 'button clicked' 153 | if warning: 154 | for butt in buttons_warn: 155 | if butt.rect.collidepoint(pos): 156 | butt.click() 157 | 158 | 159 | def findLED(clicked_pos, leds): # reads leds and checks if clicked position is in one of them 160 | 161 | x = clicked_pos[0] 162 | y = clicked_pos[1] 163 | for led in leds: 164 | if math.hypot(led.pos_x - x, led.pos_y - y) <= led.radius: 165 | return led 166 | #print 'hit led' 167 | return None 168 | 169 | 170 | def drawEverything(): 171 | 172 | global warning 173 | screen.blit(background, (0, 0)) 174 | #draw the leds 175 | for led in leds: 176 | led.draw() 177 | for button in buttons: 178 | button.draw(screen) 179 | font = pygame.font.Font(None,16) 180 | 181 | frame_text = 'Frame ' 182 | text = font.render(frame_text,1,(255,255,255)) 183 | screen.blit(text, (5,5)) 184 | frame_num_text = str(frame_number) 185 | text = font.render(frame_num_text,1,(255,255,255)) 186 | screen.blit(text, (18,18)) 187 | fps_text = 'Frame rate= ' + str(fps) +' fps' 188 | text = font.render(fps_text,1,(255,255,255)) 189 | screen.blit(text, (175,10)) 190 | font = pygame.font.Font(None,18) 191 | export_text = 'Animation' 192 | text = font.render(export_text,1,(255,255,255)) 193 | screen.blit(text, (445,15)) 194 | export_text = 'Single Frame' 195 | text = font.render(export_text,1,(255,255,255)) 196 | screen.blit(text, (435,120)) 197 | pygame.draw.circle(screen,colour,(390,345),20,0) 198 | #flip the screen 199 | if warning: 200 | for button in buttons_warn: 201 | button.draw(screen) 202 | pygame.display.flip() 203 | 204 | def load_leds_to_animation(): 205 | 206 | global frame_number 207 | global leds 208 | for saved_led in animation[frame_number]: 209 | if saved_led.lit: 210 | for led in leds: 211 | if led.pos == saved_led.pos: 212 | led.color = saved_led.color 213 | led.lit = True 214 | 215 | def nextFrame(): 216 | 217 | global frame_number 218 | global leds 219 | #print(frame_number) 220 | animation[frame_number] = copy.deepcopy(leds) 221 | #clearGrid() 222 | frame_number+=1 223 | if frame_number in animation: 224 | leds =[] 225 | for x in range(0, 8): 226 | for y in range(0, 8): 227 | led = LED(radius=20,pos=(x, y)) 228 | leds.append(led) 229 | load_leds_to_animation() 230 | 231 | 232 | 233 | def prevFrame(): 234 | 235 | global frame_number 236 | global leds 237 | #print(frame_number) 238 | animation[frame_number] = copy.deepcopy(leds) 239 | clearGrid() 240 | if frame_number != 1: 241 | frame_number-=1 242 | if frame_number in animation: 243 | leds =[] 244 | for x in range(0, 8): 245 | for y in range(0, 8): 246 | led = LED(radius=20,pos=(x, y)) 247 | leds.append(led) 248 | load_leds_to_animation() 249 | 250 | def delFrame(): 251 | global frame_number 252 | #print('ani length is ' + str(len(animation)) + ' frame is ' + str(frame_number)) 253 | if len(animation) > 1: 254 | print('length =' + str(len(animation))) 255 | animation[frame_number] = copy.deepcopy(leds) 256 | print('deleting ' + str(frame_number)) 257 | del animation[frame_number] 258 | print('length now =' + str(len(animation))) 259 | prevFrame() 260 | for shuffle_frame in range(frame_number+1,len(animation)): 261 | print('shifting ' + str(shuffle_frame+1) + ' to be ' + str(shuffle_frame)) 262 | animation[shuffle_frame] = animation[shuffle_frame+1] 263 | print('deleting ' + str(len(animation))) 264 | del animation[len(animation)] 265 | 266 | 267 | 268 | def getLitLEDs(): 269 | 270 | points = [] 271 | for led in leds: 272 | if led.lit: 273 | points.append(led.pos) 274 | return points 275 | 276 | # Main program body - set up leds and buttons 277 | 278 | leds = [] 279 | for x in range(0, 8): 280 | for y in range(0, 8): 281 | led = LED(radius=20,pos=(x, y)) 282 | leds.append(led) 283 | buttons = [] 284 | buttons_warn = [] 285 | animation={} 286 | #global frame_number 287 | 288 | def play(): 289 | 290 | global leds 291 | global frame_number 292 | animation[frame_number] = copy.deepcopy(leds) 293 | #print 'length of ani is ' + str(len(animation)) 294 | for playframe in range(1,(len(animation)+1)): 295 | #print(playframe) 296 | leds =[] 297 | for x in range(0, 8): 298 | for y in range(0, 8): 299 | led = LED(radius=20,pos=(x, y)) 300 | leds.append(led) 301 | for saved_led in animation[playframe]: 302 | if saved_led.lit: 303 | for led in leds: 304 | if led.pos == saved_led.pos: 305 | led.color = saved_led.color 306 | led.lit = True 307 | piLoad() 308 | time.sleep(1.0/fps) 309 | frame_number = len(animation) 310 | 311 | def faster(): 312 | global fps 313 | fps+=1 314 | 315 | def slower(): 316 | global fps 317 | if fps != 1: 318 | fps-=1 319 | 320 | def exportAni(): 321 | 322 | global saved 323 | FILE=open('animation8x8.py','w') 324 | FILE.write('from sense_hat import SenseHat\n') 325 | FILE.write('import time\n') 326 | FILE.write('sh=SenseHat()\n') 327 | FILE.write('FRAMES = [\n') 328 | global leds 329 | global frame_number 330 | animation[frame_number] = copy.deepcopy(leds) 331 | #print 'length of ani is ' + str(len(animation)) 332 | for playframe in range(1,(len(animation)+1)): 333 | #print(playframe) 334 | leds =[] 335 | for x in range(0, 8): 336 | for y in range(0, 8): 337 | led = LED(radius=20,pos=(x, y)) 338 | leds.append(led) 339 | for saved_led in animation[playframe]: 340 | if saved_led.lit: 341 | for led in leds: 342 | if led.pos == saved_led.pos: 343 | led.color = saved_led.color 344 | led.lit = True 345 | grid, png_grid = buildGrid() 346 | 347 | FILE.write(str(grid)) 348 | FILE.write(',\n') 349 | FILE.write(']\n') 350 | FILE.write('for x in FRAMES:\n') 351 | FILE.write('\t sh.set_pixels(x)\n') 352 | FILE.write('\t time.sleep('+ str(1.0/fps) + ')\n') 353 | FILE.close() 354 | saved = True 355 | 356 | def prog_exit(): 357 | print('exit clicked') 358 | global warning 359 | warning = False 360 | clearGrid() 361 | pygame.quit() 362 | sys.exit() 363 | 364 | def save_it(): 365 | print('save clicked') 366 | global warning 367 | exportAni() 368 | warning = False 369 | 370 | def quit(): 371 | global saved 372 | if saved == False: 373 | nosave_warn() 374 | else: 375 | prog_exit() 376 | 377 | def importAni(): 378 | global leds 379 | global frame_number 380 | with open('animation8x8.py') as ll: 381 | line_count = sum(1 for _ in ll) 382 | ll.close() 383 | 384 | #animation = {} 385 | frame_number = 1 386 | file = open('animation8x8.py') 387 | for r in range(4): 388 | file.readline() 389 | 390 | for frame in range(line_count-8): 391 | buff = file.readline() 392 | 393 | load_frame = buff.split('], [') 394 | counter = 1 395 | leds =[] 396 | for f in load_frame: 397 | 398 | if counter == 1: 399 | f = f[2:] 400 | elif counter == 64: 401 | 402 | f = f[:-4] 403 | 404 | y = int((counter-1)/8) 405 | x = int((counter-1)%8) 406 | 407 | #print(str(counter) + ' ' + f + ' x= ' + str(x) + ' y= ' + str(y)) 408 | led = LED(radius=20,pos=(x, y)) 409 | if f == '0, 0, 0': 410 | led.lit = False 411 | 412 | else: 413 | led.lit = True 414 | f_colours = f.split(',') 415 | #print(f_colours) 416 | led.color = [int(f_colours[0]),int(f_colours[1]),int(f_colours[2])] 417 | leds.append(led) 418 | counter+=1 419 | animation[frame_number] = copy.deepcopy(leds) 420 | frame_number+=1 421 | counter+=1 422 | 423 | file.close() 424 | #drawEverything() 425 | 426 | exportAniButton = Button('Export to py', action=exportAni, pos=(425, 45), color=(153,0,0)) 427 | buttons.append(exportAniButton) 428 | importAniButton = Button('Import from file', action=importAni, pos=(425, 80), color=(153,0,0)) 429 | buttons.append(importAniButton) 430 | 431 | exportConsButton = Button('Export to console', action=exportCons, pos=(425, 150), color=(160,160,160)) 432 | buttons.append(exportConsButton) 433 | exportPngButton = Button('Export to PNG', action=exportGrid, pos=(425, 185), color=(160,160,160)) 434 | buttons.append(exportPngButton) 435 | 436 | RotateButton = Button('Rotate LEDs', action=rotate, pos=(425, 255), color=(205,255,255)) 437 | buttons.append(RotateButton) 438 | clearButton = Button('Clear Grid', action=clearGrid, pos=(425, 220), color=(204,255,255)) 439 | buttons.append(clearButton) 440 | 441 | quitButton = Button('Quit', action=quit, pos=(425, 290), color=(96,96,96)) 442 | buttons.append(quitButton) 443 | 444 | FasterButton = Button('+', action=faster, size=(40,30), pos=(270, 5), color=(184,138,0)) 445 | buttons.append(FasterButton) 446 | SlowerButton = Button('-', action=slower, size=(40,30), pos=(315, 5), color=(184,138,0)) 447 | buttons.append(SlowerButton) 448 | 449 | PlayButton = Button('Play on LEDs', action=play, pos=(425, 340), color=(184,138,0)) 450 | buttons.append(PlayButton) 451 | 452 | RedButton = Button('', action=setColourRed, size=(50,30), pos=(365, 10),hilight=(0, 200, 200),color=(255,0,0)) 453 | buttons.append(RedButton) 454 | OrangeButton = Button('', action=setColourOrange, size=(50,30), pos=(365, 45),hilight=(0, 200, 200),color=(255,128,0)) 455 | buttons.append(OrangeButton) 456 | YellowButton = Button('', action=setColourYellow, size=(50,30), pos=(365, 80),hilight=(0, 200, 200),color=(255,255,0)) 457 | buttons.append(YellowButton) 458 | GreenButton = Button('', action=setColourGreen, size=(50,30), pos=(365, 115),hilight=(0, 200, 200),color=(0,255,0)) 459 | buttons.append(GreenButton) 460 | CyanButton = Button('', action=setColourCyan, size=(50,30), pos=(365, 150),hilight=(0, 200, 200),color=(0,255,255)) 461 | buttons.append(CyanButton) 462 | BlueButton = Button('', action=setColourBlue, size=(50,30), pos=(365, 185),hilight=(0, 200, 200),color=(0,0,255)) 463 | buttons.append(BlueButton) 464 | PurpleButton = Button('', action=setColourPurple, size=(50,30), pos=(365, 220),hilight=(0, 200, 200),color=(102,0,204)) 465 | buttons.append(PurpleButton) 466 | PinkButton = Button('', action=setColourPink, size=(50,30), pos=(365, 255),hilight=(0, 200, 200),color=(255,0,255)) 467 | buttons.append(PinkButton) 468 | WhiteButton = Button('', action=setColourWhite, size=(50,30), pos=(365, 290),hilight=(0, 200, 200),color=(255,255,255)) 469 | buttons.append(WhiteButton) 470 | 471 | PrevFrameButton = Button('<-', action=prevFrame, size=(25,30), pos=(50, 5), color=(184,138,0)) 472 | buttons.append(PrevFrameButton) 473 | NextFrameButton = Button('->', action=nextFrame, size=(25,30), pos=(80, 5), color=(184,138,0)) 474 | buttons.append(NextFrameButton) 475 | 476 | DelFrame = Button('Delete', action=delFrame, size=(45,25), pos=(115, 7), color=(184,138,0)) 477 | buttons.append(DelFrame) 478 | 479 | saveButton = Button('Save', action=save_it, size=(60,50), pos=(150, 250),hilight=(200, 0, 0),color=(255,255,0)) 480 | buttons_warn.append(saveButton) 481 | QuitButton = Button('Quit', action=prog_exit, size=(60,50), pos=(260, 250),hilight=(200, 0, 0),color=(255,255,0)) 482 | buttons_warn.append(QuitButton) 483 | 484 | 485 | 486 | def nosave_warn(): 487 | global warning 488 | warning = True 489 | font = pygame.font.Font(None,48) 490 | frame_text = 'Unsaved Frames ' 491 | 492 | for d in range(5): 493 | text = font.render(frame_text,1,(255,0,0)) 494 | screen.blit(text, (100,100)) 495 | pygame.display.flip() 496 | time.sleep(0.1) 497 | text = font.render(frame_text,1,(0,255,0)) 498 | screen.blit(text, (100,100)) 499 | pygame.display.flip() 500 | time.sleep(0.1) 501 | drawEverything() 502 | # Main prog loop 503 | 504 | 505 | while True: 506 | 507 | for event in pygame.event.get(): 508 | if event.type == QUIT: 509 | if saved == False: 510 | nosave_warn() 511 | else: 512 | prog_exit() 513 | 514 | if event.type == MOUSEBUTTONDOWN: 515 | handleClick() 516 | 517 | #update the display 518 | drawEverything() 519 | #print(frame_number) 520 | -------------------------------------------------------------------------------- /8x8grid-unicorn.py: -------------------------------------------------------------------------------- 1 | ''' 8x8grid-unicorn.py 2 | Animation and single frame creation append 3 | for Pimoroni UnicornHat 8x8 LED matrix''' 4 | import pygame 5 | import sys 6 | import math 7 | from pygame.locals import * 8 | from led import LED 9 | from buttons import Button 10 | import png # pypng 11 | #from astro_pi import AstroPi 12 | import unicornhat as uh 13 | import copy, time 14 | 15 | saved = True 16 | warning = False 17 | pygame.display.init() 18 | pygame.font.init() 19 | 20 | #ap=AstroPi() 21 | screen = pygame.display.set_mode((530, 395), 0, 32) 22 | pygame.display.set_caption('UnicornHAT Grid editor') 23 | pygame.mouse.set_visible(1) 24 | 25 | background = pygame.Surface(screen.get_size()) 26 | background = background.convert() 27 | background.fill((0, 51, 25)) 28 | colour = (255,0,0) # Set default colour to red 29 | rotation = 0 30 | #uh.rotation(rotation) 31 | frame_number = 1 32 | fps = 4 33 | 34 | 35 | 36 | def setColourRed(): 37 | global colour 38 | colour = (255,0,0) 39 | 40 | def setColourBlue(): 41 | global colour 42 | colour = (0,0,255) 43 | 44 | def setColourGreen(): 45 | global colour 46 | colour = (0,255,0) 47 | 48 | def setColourPurple(): 49 | global colour 50 | colour = (102,0,204) 51 | 52 | def setColourPink(): 53 | global colour 54 | colour = (255,0,255) 55 | 56 | def setColourYellow(): 57 | global colour 58 | colour = (255,255,0) 59 | 60 | def setColourOrange(): 61 | global colour 62 | colour = (255,128,0) 63 | 64 | def setColourWhite(): 65 | global colour 66 | colour = (255,255,255) 67 | 68 | def setColourCyan(): 69 | global colour 70 | colour = (0,255,255) 71 | 72 | def clearGrid(): # Clears the pygame LED grid and sets all the leds.lit back to False 73 | 74 | for led in leds: 75 | led.lit = False 76 | 77 | def buildGrid(): # Takes a grid and builds versions for exporting (png and text) 78 | 79 | e = [0,0,0] 80 | e_png = (0,0,0) 81 | 82 | grid = [ 83 | [e,e,e,e,e,e,e,e], 84 | [e,e,e,e,e,e,e,e], 85 | [e,e,e,e,e,e,e,e], 86 | [e,e,e,e,e,e,e,e], 87 | [e,e,e,e,e,e,e,e], 88 | [e,e,e,e,e,e,e,e], 89 | [e,e,e,e,e,e,e,e], 90 | [e,e,e,e,e,e,e,e], 91 | ] 92 | #png_grid =[] 93 | 94 | png_grid = ['blank','blank','blank','blank','blank','blank','blank','blank'] 95 | for led in leds: 96 | if led.lit: 97 | #val = led.pos[0] + (8 * led.pos[1]) 98 | val = (8* led.pos[0]) + led.pos[1] 99 | #print val 100 | grid[led.pos[1]][led.pos[0]] = [led.color[0], led.color[1], led.color[2]] 101 | if png_grid[led.pos[0]] == 'blank': 102 | png_grid[led.pos[0]] = (led.color[0], led.color[1], led.color[2]) 103 | else: 104 | png_grid[led.pos[0]] = png_grid[led.pos[0]] + (led.color[0], led.color[1], led.color[2]) 105 | else: 106 | if png_grid[led.pos[0]] == 'blank': 107 | png_grid[led.pos[0]] = (0,0,0) 108 | else: 109 | png_grid[led.pos[0]] = png_grid[led.pos[0]] + (0,0,0) 110 | return (grid, png_grid) 111 | 112 | def piLoad(): # Loads image onto AstroPi matrix 113 | #grid, grid_png = buildGrid() 114 | #ap.set_pixels(grid) 115 | uh.off() 116 | for led in leds: 117 | if led.lit: 118 | uh.set_pixel(led.pos[0], led.pos[1], led.color[0], led.color[1], led.color[2]) 119 | 120 | #print str(led.pos[0])+ ' ' +str(led.pos[1]) + ' ' + str(led.color[1]) 121 | uh.show() 122 | 123 | 124 | def exportGrid(): # Writes png to file 125 | 126 | global saved 127 | grid, png_grid = buildGrid() 128 | FILE=open('image8x8.png','wb') 129 | w = png.Writer(8,8) 130 | w.write(FILE,png_grid) 131 | FILE.close() 132 | saved = True 133 | 134 | def exportCons(): # Writes raw list to console 135 | 136 | grid, png_grid = buildGrid() 137 | print(grid) 138 | 139 | 140 | def rotate(): #Rotates image on AstroPi LED matrix 141 | global rotation 142 | if rotation == 270: 143 | rotation = 0 144 | else: 145 | rotation = rotation + 90 146 | #ap.set_rotation(rotation) 147 | uh.rotation(rotation) 148 | play() 149 | 150 | def handleClick(): 151 | 152 | global saved 153 | global warning 154 | pos = pygame.mouse.get_pos() 155 | led = findLED(pos, leds) 156 | if led: 157 | #print 'led ' + str(led) + ' clicked' 158 | led.clicked(colour) 159 | saved = False 160 | for butt in buttons: 161 | if butt.rect.collidepoint(pos): 162 | butt.click() 163 | #print 'button clicked' 164 | if warning: 165 | for butt in buttons_warn: 166 | if butt.rect.collidepoint(pos): 167 | butt.click() 168 | 169 | 170 | def findLED(clicked_pos, leds): # reads leds and checks if clicked position is in one of them 171 | 172 | x = clicked_pos[0] 173 | y = clicked_pos[1] 174 | for led in leds: 175 | if math.hypot(led.pos_x - x, led.pos_y - y) <= led.radius: 176 | return led 177 | #print 'hit led' 178 | return None 179 | 180 | 181 | def drawEverything(): 182 | 183 | global warning 184 | screen.blit(background, (0, 0)) 185 | #draw the leds 186 | for led in leds: 187 | led.draw() 188 | for button in buttons: 189 | button.draw(screen) 190 | font = pygame.font.Font(None,16) 191 | 192 | frame_text = 'Frame ' 193 | text = font.render(frame_text,1,(255,255,255)) 194 | screen.blit(text, (5,5)) 195 | frame_num_text = str(frame_number) 196 | text = font.render(frame_num_text,1,(255,255,255)) 197 | screen.blit(text, (18,18)) 198 | fps_text = 'Frame rate= ' + str(fps) +' fps' 199 | text = font.render(fps_text,1,(255,255,255)) 200 | screen.blit(text, (175,10)) # done 201 | font = pygame.font.Font(None,18) 202 | export_text = 'Animation' # done 203 | text = font.render(export_text,1,(255,255,255)) 204 | screen.blit(text, (445,15)) # done 205 | export_text = 'Single Frame' 206 | text = font.render(export_text,1,(255,255,255)) 207 | screen.blit(text, (435,120)) # done 208 | pygame.draw.circle(screen,colour,(390,345),20,0) 209 | #flip the screen 210 | if warning: 211 | for button in buttons_warn: 212 | button.draw(screen) 213 | pygame.display.flip() 214 | 215 | def load_leds_to_animation(): 216 | 217 | global frame_number 218 | global leds 219 | for saved_led in animation[frame_number]: 220 | if saved_led.lit: 221 | for led in leds: 222 | if led.pos == saved_led.pos: 223 | led.color = saved_led.color 224 | led.lit = True 225 | 226 | def nextFrame(): 227 | 228 | global frame_number 229 | global leds 230 | #print(frame_number) 231 | animation[frame_number] = copy.deepcopy(leds) 232 | #clearGrid() 233 | frame_number+=1 234 | if frame_number in animation: 235 | leds =[] 236 | for x in range(0, 8): 237 | for y in range(0, 8): 238 | led = LED(radius=20,pos=(x, y)) 239 | leds.append(led) 240 | load_leds_to_animation() 241 | 242 | 243 | 244 | def prevFrame(): 245 | 246 | global frame_number 247 | global leds 248 | #print(frame_number) 249 | animation[frame_number] = copy.deepcopy(leds) 250 | clearGrid() 251 | if frame_number != 1: 252 | frame_number-=1 253 | if frame_number in animation: 254 | leds =[] 255 | for x in range(0, 8): 256 | for y in range(0, 8): 257 | led = LED(radius=20,pos=(x, y)) 258 | leds.append(led) 259 | load_leds_to_animation() 260 | 261 | def delFrame(): 262 | global frame_number 263 | #print('ani length is ' + str(len(animation)) + ' frame is ' + str(frame_number)) 264 | if len(animation) > 1: 265 | animation[frame_number] = copy.deepcopy(leds) 266 | del animation[frame_number] 267 | prevFrame() 268 | for shuffle_frame in range(frame_number+1,len(animation)): 269 | animation[shuffle_frame] = animation[shuffle_frame+1] 270 | del animation[len(animation)] 271 | 272 | 273 | 274 | def getLitLEDs(): 275 | 276 | points = [] 277 | for led in leds: 278 | if led.lit: 279 | points.append(led.pos) 280 | return points 281 | 282 | # Main program body - set up leds and buttons 283 | 284 | leds = [] 285 | for x in range(0, 8): 286 | for y in range(0, 8): 287 | led = LED(radius=20,pos=(x, y)) 288 | leds.append(led) 289 | buttons = [] 290 | buttons_warn = [] 291 | animation={} 292 | #global frame_number 293 | 294 | def play(): 295 | 296 | global leds 297 | global frame_number 298 | animation[frame_number] = copy.deepcopy(leds) 299 | #print 'length of ani is ' + str(len(animation)) 300 | for playframe in range(1,(len(animation)+1)): 301 | #print(playframe) 302 | leds =[] 303 | for x in range(0, 8): 304 | for y in range(0, 8): 305 | led = LED(radius=20,pos=(x, y)) 306 | leds.append(led) 307 | for saved_led in animation[playframe]: 308 | if saved_led.lit: 309 | for led in leds: 310 | if led.pos == saved_led.pos: 311 | led.color = saved_led.color 312 | led.lit = True 313 | piLoad() 314 | time.sleep(1.0/fps) 315 | frame_number = len(animation) 316 | 317 | def faster(): 318 | global fps 319 | fps+=1 320 | 321 | def slower(): 322 | global fps 323 | if fps != 1: 324 | fps-=1 325 | 326 | def exportAni(): 327 | 328 | global saved 329 | FILE=open('animation8x8.py','w') 330 | FILE.write('import unicornhat as uh\n') 331 | FILE.write('import time\n') 332 | FILE.write('FRAMES = [\n') 333 | global leds 334 | global frame_number 335 | animation[frame_number] = copy.deepcopy(leds) 336 | #print 'length of ani is ' + str(len(animation)) 337 | for playframe in range(1,(len(animation)+1)): 338 | #print(playframe) 339 | leds =[] 340 | for x in range(0,8): 341 | for y in range(0,8): 342 | led = LED(radius=20,pos=(x, y)) 343 | leds.append(led) 344 | for saved_led in animation[playframe]: 345 | if saved_led.lit: 346 | for led in leds: 347 | if led.pos == saved_led.pos: 348 | led.color = saved_led.color 349 | led.lit = True 350 | grid, png_grid = buildGrid() 351 | #grid = uh.get_pixels() 352 | 353 | FILE.write(str(grid)) 354 | FILE.write(',\n') 355 | FILE.write(']\n') 356 | FILE.write('for x in FRAMES:\n') 357 | FILE.write('\t uh.set_pixels(x)\n') 358 | FILE.write('\t uh.show()\n') 359 | FILE.write('\t time.sleep('+ str(1.0/fps) + ')\n') 360 | FILE.close() 361 | saved = True 362 | 363 | def prog_exit(): 364 | print('exit clicked') 365 | global warning 366 | warning = False 367 | #clearGrid() 368 | pygame.quit() 369 | sys.exit(-1) 370 | 371 | def save_it(): 372 | print('save clicked') 373 | global warning 374 | exportAni() 375 | warning = False 376 | 377 | def quit(): 378 | global saved 379 | if saved == False: 380 | nosave_warn() 381 | else: 382 | prog_exit() 383 | 384 | def importAni(): 385 | global leds 386 | global frame_number 387 | with open('animation8x8.py') as ll: 388 | line_count = sum(1 for _ in ll) 389 | ll.close() 390 | 391 | #animation = {} 392 | frame_number = 1 393 | file = open('animation8x8.py') 394 | for r in range(3): 395 | file.readline() 396 | 397 | for frame in range(line_count-8): 398 | buff = file.readline() 399 | 400 | load_frame = buff.split('], [') 401 | #print load_frame 402 | counter = 1 403 | leds =[] 404 | for f in load_frame: 405 | 406 | if counter == 1: 407 | f = f[3:] 408 | elif counter == 64: 409 | f = f[:-5] 410 | elif counter%8 == 0 and counter != 64: 411 | f = f[:-1] 412 | elif (counter-1)%8 == 0: 413 | f = f[1:] 414 | 415 | y = int((counter-1)/8) 416 | x = int((counter-1)%8) 417 | #print(counter,x,y) 418 | 419 | #print(str(counter) + ' ' + f + ' x= ' + str(x) + ' y= ' + str(y)) 420 | led = LED(radius=20,pos=(x, y)) 421 | if f == '0, 0, 0': 422 | led.lit = False 423 | 424 | else: 425 | led.lit = True 426 | f_colours = f.split(',') 427 | #print(f_colours) 428 | led.color = [int(f_colours[0]),int(f_colours[1]),int(f_colours[2])] 429 | leds.append(led) 430 | counter+=1 431 | animation[frame_number] = copy.deepcopy(leds) 432 | frame_number+=1 433 | counter+=1 434 | 435 | file.close() 436 | #drawEverything() 437 | 438 | exportAniButton = Button('Export to py', action=exportAni, pos=(425, 45), color=(153,0,0)) # done 439 | buttons.append(exportAniButton) 440 | importAniButton = Button('Import from file', action=importAni, pos=(425, 80 ), color=(153,0,0)) # done 441 | buttons.append(importAniButton) 442 | 443 | exportConsButton = Button('Export to console', action=exportCons, pos=(425, 150), color=(160,160,160)) # done 444 | buttons.append(exportConsButton) 445 | exportPngButton = Button('Export to PNG', action=exportGrid, pos=(425, 185), color=(160,160,160)) # done 446 | buttons.append(exportPngButton) 447 | 448 | RotateButton = Button('Rotate LEDs', action=rotate, pos=(425, 255), color=(205,255,255)) # done 449 | buttons.append(RotateButton) 450 | clearButton = Button('Clear Grid', action=clearGrid, pos=(425, 220), color=(204,255,255))# done 451 | buttons.append(clearButton) 452 | 453 | quitButton = Button('Quit', action=quit, pos=(425, 290), color=(96,96,96)) 454 | buttons.append(quitButton) 455 | 456 | FasterButton = Button('+', action=faster, size=(40,30), pos=(270, 5), color=(184,138,0)) # done 457 | buttons.append(FasterButton) 458 | SlowerButton = Button('-', action=slower, size=(40,30), pos=(315, 5), color=(184,138,0))# done 459 | buttons.append(SlowerButton) 460 | 461 | PlayButton = Button('Play on LEDs', action=play, pos=(425, 340), color=(184,138,0)) # done 462 | buttons.append(PlayButton) 463 | 464 | RedButton = Button('', action=setColourRed, size=(50,30), pos=(365, 10),hilight=(0, 200, 200),color=(255,0,0)) # done 465 | buttons.append(RedButton) 466 | OrangeButton = Button('', action=setColourOrange, size=(50,30), pos=(365, 45),hilight=(0, 200, 200),color=(255,128,0)) # done 467 | buttons.append(OrangeButton) 468 | YellowButton = Button('', action=setColourYellow, size=(50,30), pos=(365, 80),hilight=(0, 200, 200),color=(255,255,0)) # done 469 | buttons.append(YellowButton) 470 | GreenButton = Button('', action=setColourGreen, size=(50,30), pos=(365, 115),hilight=(0, 200, 200),color=(0,255,0)) # done 471 | buttons.append(GreenButton) 472 | CyanButton = Button('', action=setColourCyan, size=(50,30), pos=(365, 150),hilight=(0, 200, 200),color=(0,255,255)) # done 473 | buttons.append(CyanButton) 474 | BlueButton = Button('', action=setColourBlue, size=(50,30), pos=(365, 185),hilight=(0, 200, 200),color=(0,0,255)) # done 475 | buttons.append(BlueButton) 476 | PurpleButton = Button('', action=setColourPurple, size=(50,30), pos=(365, 220),hilight=(0, 200, 200),color=(102,0,204)) # done 477 | buttons.append(PurpleButton) 478 | PinkButton = Button('', action=setColourPink, size=(50,30), pos=(365, 255),hilight=(0, 200, 200),color=(255,0,255)) # done 479 | buttons.append(PinkButton) 480 | WhiteButton = Button('', action=setColourWhite, size=(50,30), pos=(365, 290),hilight=(0, 200, 200),color=(255,255,255)) # done 481 | buttons.append(WhiteButton) 482 | 483 | PrevFrameButton = Button('<-', action=prevFrame, size=(25,30), pos=(50, 5), color=(184,138,0)) # done 484 | buttons.append(PrevFrameButton) 485 | NextFrameButton = Button('->', action=nextFrame, size=(25,30), pos=(80, 5), color=(184,138,0)) # done 486 | buttons.append(NextFrameButton) 487 | 488 | DelFrame = Button('Delete', action=delFrame, size=(45,25), pos=(115, 7), color=(184,138,0)) # done 489 | buttons.append(DelFrame) 490 | 491 | saveButton = Button('Save', action=save_it, size=(60,50), pos=(150, 250),hilight=(200, 0, 0),color=(255,255,0)) # done 492 | buttons_warn.append(saveButton) 493 | QuitButton = Button('Quit', action=prog_exit, size=(60,50), pos=(260, 250),hilight=(200, 0, 0),color=(255,255,0)) # done 494 | buttons_warn.append(QuitButton) 495 | 496 | 497 | def nosave_warn(): 498 | global warning 499 | warning = True 500 | font = pygame.font.Font(None,48) 501 | frame_text = 'Unsaved Frames ' 502 | 503 | for d in range(5): 504 | text = font.render(frame_text,1,(255,0,0)) 505 | screen.blit(text, (100,100)) 506 | pygame.display.flip() 507 | time.sleep(0.1) 508 | text = font.render(frame_text,1,(0,255,0)) 509 | screen.blit(text, (100,100)) 510 | pygame.display.flip() 511 | time.sleep(0.1) 512 | drawEverything() 513 | # Main prog loop 514 | 515 | 516 | while True: 517 | 518 | for event in pygame.event.get(): 519 | if event.type == QUIT: 520 | if saved == False: 521 | nosave_warn() 522 | else: 523 | prog_exit() 524 | 525 | if event.type == MOUSEBUTTONDOWN: 526 | handleClick() 527 | 528 | #update the display 529 | drawEverything() 530 | -------------------------------------------------------------------------------- /8x8grid-unicornphat.py: -------------------------------------------------------------------------------- 1 | ''' 8x4grid-unicornphat.py 2 | Animation and single frame creation append 3 | for Pimoroni UnicornPHat 8x8 LED matrix''' 4 | import pygame 5 | import sys 6 | import math 7 | from pygame.locals import * 8 | from led import LED 9 | from buttons import Button 10 | import png # pypng 11 | import unicornhat as uh 12 | import copy, time 13 | uh.set_layout(uh.PHAT) 14 | saved = True 15 | warning = False 16 | pygame.display.init() 17 | pygame.font.init() 18 | 19 | screen = pygame.display.set_mode((530, 395), 0, 32) 20 | pygame.display.set_caption('UnicornPHAT Grid editor') 21 | pygame.mouse.set_visible(1) 22 | 23 | background = pygame.Surface(screen.get_size()) 24 | background = background.convert() 25 | background.fill((0, 51, 25)) 26 | colour = (255,0,0) # Set default colour to red 27 | rotation = 0 28 | #uh.rotation(rotation) 29 | frame_number = 1 30 | fps = 4 31 | 32 | 33 | 34 | def setColourRed(): 35 | global colour 36 | colour = (255,0,0) 37 | 38 | def setColourBlue(): 39 | global colour 40 | colour = (0,0,255) 41 | 42 | def setColourGreen(): 43 | global colour 44 | colour = (0,255,0) 45 | 46 | def setColourPurple(): 47 | global colour 48 | colour = (102,0,204) 49 | 50 | def setColourPink(): 51 | global colour 52 | colour = (255,0,255) 53 | 54 | def setColourYellow(): 55 | global colour 56 | colour = (255,255,0) 57 | 58 | def setColourOrange(): 59 | global colour 60 | colour = (255,128,0) 61 | 62 | def setColourWhite(): 63 | global colour 64 | colour = (255,255,255) 65 | 66 | def setColourCyan(): 67 | global colour 68 | colour = (0,255,255) 69 | 70 | def clearGrid(): # Clears the pygame LED grid and sets all the leds.lit back to False 71 | 72 | for led in leds: 73 | led.lit = False 74 | 75 | def buildGrid(): # Takes a grid and builds versions for exporting (png and text) 76 | 77 | e = [0,0,0] 78 | e_png = (0,0,0) 79 | 80 | grid = [ 81 | [e,e,e,e,e,e,e,e], 82 | [e,e,e,e,e,e,e,e], 83 | [e,e,e,e,e,e,e,e], 84 | [e,e,e,e,e,e,e,e], 85 | [e,e,e,e,e,e,e,e], 86 | [e,e,e,e,e,e,e,e], 87 | [e,e,e,e,e,e,e,e], 88 | [e,e,e,e,e,e,e,e], 89 | ] 90 | #png_grid =[] 91 | #print(grid) 92 | 93 | png_grid = ['blank','blank','blank','blank','blank','blank','blank','blank'] 94 | # png_grid = ['blank','blank','blank','blank'] 95 | for led in leds: 96 | if led.lit: 97 | grid[led.pos[1]][led.pos[0]] = [led.color[0], led.color[1], led.color[2]] 98 | grid[led.pos[1]+4][led.pos[0]] = [led.color[0], led.color[1], led.color[2]] 99 | if png_grid[led.pos[0]] == 'blank': 100 | png_grid[led.pos[0]] = (led.color[0], led.color[1], led.color[2]) 101 | else: 102 | png_grid[led.pos[0]] = png_grid[led.pos[0]] + (led.color[0], led.color[1], led.color[2]) 103 | else: 104 | if png_grid[led.pos[0]] == 'blank': 105 | png_grid[led.pos[0]] = (0,0,0) 106 | else: 107 | png_grid[led.pos[0]] = png_grid[led.pos[0]] + (0,0,0) 108 | return (grid, png_grid) 109 | 110 | def piLoad(): # Loads image onto matrix 111 | uh.off() 112 | for led in leds: 113 | if led.lit: 114 | uh.set_pixel(led.pos[0], led.pos[1], led.color[0], led.color[1], led.color[2]) 115 | 116 | #print str(led.pos[0])+ ' ' +str(led.pos[1]) + ' ' + str(led.color[1]) 117 | uh.show() 118 | 119 | 120 | def exportGrid(): # Writes png to file 121 | 122 | global saved 123 | grid, png_grid = buildGrid() 124 | FILE=open('image8x4.png','wb') 125 | w = png.Writer(4,8) 126 | w.write(FILE,png_grid) 127 | FILE.close() 128 | saved = True 129 | 130 | def exportCons(): # Writes raw list to console 131 | 132 | grid, png_grid = buildGrid() 133 | print(grid) 134 | 135 | 136 | def rotate(): #Rotates image on AstroPi LED matrix 137 | global rotation 138 | if rotation == 180: 139 | rotation = 0 140 | else: 141 | rotation = rotation + 180 142 | #ap.set_rotation(rotation) 143 | uh.rotation(rotation) 144 | play() 145 | 146 | def handleClick(): 147 | 148 | global saved 149 | global warning 150 | pos = pygame.mouse.get_pos() 151 | led = findLED(pos, leds) 152 | if led: 153 | #print('led ' + str(led.pos_y) + ' clicked') 154 | led.clicked(colour) 155 | saved = False 156 | for butt in buttons: 157 | if butt.rect.collidepoint(pos): 158 | butt.click() 159 | #print 'button clicked' 160 | if warning: 161 | for butt in buttons_warn: 162 | if butt.rect.collidepoint(pos): 163 | butt.click() 164 | 165 | 166 | def findLED(clicked_pos, leds): # reads leds and checks if clicked position is in one of them 167 | 168 | x = clicked_pos[0] 169 | y = clicked_pos[1] 170 | for led in leds: 171 | if math.hypot(led.pos_x - x, led.pos_y - y) <= led.radius: 172 | return led 173 | #print 'hit led' 174 | return None 175 | 176 | 177 | def drawEverything(): 178 | 179 | global warning 180 | screen.blit(background, (0, 0)) 181 | #draw the leds 182 | for led in leds: 183 | led.draw() 184 | #print(led.pos_x,led.pos_y) 185 | for button in buttons: 186 | button.draw(screen) 187 | font = pygame.font.Font(None,16) 188 | 189 | frame_text = 'Frame ' 190 | text = font.render(frame_text,1,(255,255,255)) 191 | screen.blit(text, (5,5)) 192 | frame_num_text = str(frame_number) 193 | text = font.render(frame_num_text,1,(255,255,255)) 194 | screen.blit(text, (18,18)) 195 | fps_text = 'Frame rate= ' + str(fps) +' fps' 196 | text = font.render(fps_text,1,(255,255,255)) 197 | screen.blit(text, (175,10)) # done 198 | font = pygame.font.Font(None,18) 199 | export_text = 'Animation' # done 200 | text = font.render(export_text,1,(255,255,255)) 201 | screen.blit(text, (445,15)) # done 202 | export_text = 'Single Frame' 203 | text = font.render(export_text,1,(255,255,255)) 204 | screen.blit(text, (435,120)) # done 205 | pygame.draw.circle(screen,colour,(390,345),20,0) 206 | #flip the screen 207 | if warning: 208 | for button in buttons_warn: 209 | button.draw(screen) 210 | pygame.display.flip() 211 | 212 | def load_leds_to_animation(): 213 | 214 | global frame_number 215 | global leds 216 | for saved_led in animation[frame_number]: 217 | if saved_led.lit: 218 | for led in leds: 219 | if led.pos == saved_led.pos: 220 | led.color = saved_led.color 221 | led.lit = True 222 | 223 | def nextFrame(): 224 | 225 | global frame_number 226 | global leds 227 | #print(frame_number) 228 | animation[frame_number] = copy.deepcopy(leds) 229 | #clearGrid() 230 | frame_number+=1 231 | if frame_number in animation: 232 | leds =[] 233 | for x in range(0, 8): 234 | for y in range(0, 4): 235 | led = LED(radius=20,pos=(x, y)) 236 | #print(' x= ' + str(led.pos_x) + ' y= ' + str(led.pos_y)) 237 | leds.append(led) 238 | load_leds_to_animation() 239 | 240 | 241 | 242 | def prevFrame(): 243 | 244 | global frame_number 245 | global leds 246 | #print(frame_number) 247 | animation[frame_number] = copy.deepcopy(leds) 248 | clearGrid() 249 | if frame_number != 1: 250 | frame_number-=1 251 | if frame_number in animation: 252 | leds =[] 253 | for x in range(0, 8): 254 | for y in range(0, 4): 255 | led = LED(radius=20,pos=(x, y)) 256 | leds.append(led) 257 | load_leds_to_animation() 258 | 259 | def delFrame(): 260 | global frame_number 261 | #print('ani length is ' + str(len(animation)) + ' frame is ' + str(frame_number)) 262 | if len(animation) > 1: 263 | animation[frame_number] = copy.deepcopy(leds) 264 | del animation[frame_number] 265 | prevFrame() 266 | for shuffle_frame in range(frame_number+1,len(animation)): 267 | animation[shuffle_frame] = animation[shuffle_frame+1] 268 | del animation[len(animation)] 269 | 270 | 271 | 272 | def getLitLEDs(): 273 | 274 | points = [] 275 | for led in leds: 276 | if led.lit: 277 | points.append(led.pos) 278 | return points 279 | 280 | # Main program body - set up leds and buttons 281 | 282 | leds = [] 283 | for x in range(0, 8): 284 | for y in range(0, 4): 285 | led = LED(radius=20,pos=(x, y)) 286 | leds.append(led) 287 | buttons = [] 288 | buttons_warn = [] 289 | animation={} 290 | #global frame_number 291 | 292 | def play(): 293 | 294 | global leds 295 | global frame_number 296 | animation[frame_number] = copy.deepcopy(leds) 297 | #print 'length of ani is ' + str(len(animation)) 298 | for playframe in range(1,(len(animation)+1)): 299 | #print(playframe) 300 | leds =[] 301 | for x in range(0, 8): 302 | for y in range(0, 4): 303 | led = LED(radius=20,pos=(x, y)) 304 | leds.append(led) 305 | for saved_led in animation[playframe]: 306 | if saved_led.lit: 307 | for led in leds: 308 | if led.pos == saved_led.pos: 309 | led.color = saved_led.color 310 | led.lit = True 311 | piLoad() 312 | time.sleep(1.0/fps) 313 | frame_number = len(animation) 314 | 315 | def faster(): 316 | global fps 317 | fps+=1 318 | 319 | def slower(): 320 | global fps 321 | if fps != 1: 322 | fps-=1 323 | 324 | def exportAni(): 325 | 326 | global saved 327 | FILE=open('animation8x4.py','w') 328 | FILE.write('import unicornhat as uh\n') 329 | FILE.write('uh.set_layout(uh.PHAT)\n') 330 | FILE.write('import time\n') 331 | FILE.write('FRAMES = [\n') 332 | global leds 333 | global frame_number 334 | animation[frame_number] = copy.deepcopy(leds) 335 | #print 'length of ani is ' + str(len(animation)) 336 | for playframe in range(1,(len(animation)+1)): 337 | #print(playframe) 338 | leds =[] 339 | for x in range(0,8): 340 | for y in range(0,4): 341 | led = LED(radius=20,pos=(x, y)) 342 | leds.append(led) 343 | for saved_led in animation[playframe]: 344 | if saved_led.lit: 345 | for led in leds: 346 | if led.pos == saved_led.pos: 347 | led.color = saved_led.color 348 | led.lit = True 349 | 350 | grid, png_grid = buildGrid() 351 | #grid = uh.get_pixels() 352 | 353 | FILE.write(str(grid)) 354 | FILE.write(',\n') 355 | FILE.write(']\n') 356 | FILE.write('for x in FRAMES:\n') 357 | FILE.write('\t uh.set_pixels(x)\n') 358 | FILE.write('\t uh.show()\n') 359 | FILE.write('\t time.sleep('+ str(1.0/fps) + ')\n') 360 | FILE.close() 361 | saved = True 362 | 363 | def prog_exit(): 364 | print('exit clicked') 365 | global warning 366 | warning = False 367 | #clearGrid() 368 | pygame.quit() 369 | sys.exit(-1) 370 | 371 | def save_it(): 372 | print('save clicked') 373 | global warning 374 | exportAni() 375 | warning = False 376 | 377 | def quit(): 378 | global saved 379 | if saved == False: 380 | nosave_warn() 381 | else: 382 | prog_exit() 383 | 384 | def importAni(): 385 | global leds 386 | global frame_number 387 | with open('animation8x4.py') as ll: 388 | line_count = sum(1 for _ in ll) 389 | ll.close() 390 | 391 | #animation = {} 392 | frame_number = 1 393 | file = open('animation8x4.py') 394 | for r in range(4): 395 | file.readline() 396 | 397 | for frame in range(line_count-9): 398 | buff = file.readline() 399 | 400 | load_frame = buff.split('], [') 401 | #print(load_frame) 402 | counter = 1 403 | uh_leds =[] 404 | for f in load_frame: 405 | 406 | if counter == 1: 407 | f = f[3:] 408 | elif counter == 64: 409 | f = f[:-5] 410 | elif counter%8 == 0 and counter != 64: 411 | f = f[:-1] 412 | elif (counter-1)%8 == 0: 413 | f = f[1:] 414 | 415 | y = int((counter-1)/8) 416 | x = int((counter-1)%8) 417 | #print(counter,x,y) 418 | 419 | 420 | led = LED(radius=20,pos=(x, y)) 421 | #print(' x= ' + str(led.pos_x) + ' y= ' + str(led.pos_y)) 422 | 423 | #print(f) 424 | if f == '0, 0, 0': 425 | led.lit = False 426 | 427 | else: 428 | led.lit = True 429 | f_colours = f.split(',') 430 | #print(f_colours) 431 | led.color = [int(f_colours[0]),int(f_colours[1]),int(f_colours[2])] 432 | uh_leds.append(led) 433 | 434 | counter+=1 435 | leds = [] 436 | for pp in range(32): 437 | leds.append(uh_leds[pp]) 438 | 439 | animation[frame_number] = copy.deepcopy(leds) 440 | frame_number+=1 441 | counter+=1 442 | 443 | file.close() 444 | 445 | #drawEverything() 446 | 447 | exportAniButton = Button('Export to py', action=exportAni, pos=(425, 45), color=(153,0,0)) # done 448 | buttons.append(exportAniButton) 449 | importAniButton = Button('Import from file', action=importAni, pos=(425, 80 ), color=(153,0,0)) # done 450 | buttons.append(importAniButton) 451 | 452 | exportConsButton = Button('Export to console', action=exportCons, pos=(425, 150), color=(160,160,160)) # done 453 | buttons.append(exportConsButton) 454 | exportPngButton = Button('Export to PNG', action=exportGrid, pos=(425, 185), color=(160,160,160)) # done 455 | buttons.append(exportPngButton) 456 | 457 | RotateButton = Button('Rotate LEDs', action=rotate, pos=(425, 255), color=(205,255,255)) # done 458 | buttons.append(RotateButton) 459 | clearButton = Button('Clear Grid', action=clearGrid, pos=(425, 220), color=(204,255,255))# done 460 | buttons.append(clearButton) 461 | 462 | quitButton = Button('Quit', action=quit, pos=(425, 290), color=(96,96,96)) 463 | buttons.append(quitButton) 464 | 465 | FasterButton = Button('+', action=faster, size=(40,30), pos=(270, 5), color=(184,138,0)) # done 466 | buttons.append(FasterButton) 467 | SlowerButton = Button('-', action=slower, size=(40,30), pos=(315, 5), color=(184,138,0))# done 468 | buttons.append(SlowerButton) 469 | 470 | PlayButton = Button('Play on LEDs', action=play, pos=(425, 340), color=(184,138,0)) # done 471 | buttons.append(PlayButton) 472 | 473 | RedButton = Button('', action=setColourRed, size=(50,30), pos=(365, 10),hilight=(0, 200, 200),color=(255,0,0)) # done 474 | buttons.append(RedButton) 475 | OrangeButton = Button('', action=setColourOrange, size=(50,30), pos=(365, 45),hilight=(0, 200, 200),color=(255,128,0)) # done 476 | buttons.append(OrangeButton) 477 | YellowButton = Button('', action=setColourYellow, size=(50,30), pos=(365, 80),hilight=(0, 200, 200),color=(255,255,0)) # done 478 | buttons.append(YellowButton) 479 | GreenButton = Button('', action=setColourGreen, size=(50,30), pos=(365, 115),hilight=(0, 200, 200),color=(0,255,0)) # done 480 | buttons.append(GreenButton) 481 | CyanButton = Button('', action=setColourCyan, size=(50,30), pos=(365, 150),hilight=(0, 200, 200),color=(0,255,255)) # done 482 | buttons.append(CyanButton) 483 | BlueButton = Button('', action=setColourBlue, size=(50,30), pos=(365, 185),hilight=(0, 200, 200),color=(0,0,255)) # done 484 | buttons.append(BlueButton) 485 | PurpleButton = Button('', action=setColourPurple, size=(50,30), pos=(365, 220),hilight=(0, 200, 200),color=(102,0,204)) # done 486 | buttons.append(PurpleButton) 487 | PinkButton = Button('', action=setColourPink, size=(50,30), pos=(365, 255),hilight=(0, 200, 200),color=(255,0,255)) # done 488 | buttons.append(PinkButton) 489 | WhiteButton = Button('', action=setColourWhite, size=(50,30), pos=(365, 290),hilight=(0, 200, 200),color=(255,255,255)) # done 490 | buttons.append(WhiteButton) 491 | 492 | PrevFrameButton = Button('<-', action=prevFrame, size=(25,30), pos=(50, 5), color=(184,138,0)) # done 493 | buttons.append(PrevFrameButton) 494 | NextFrameButton = Button('->', action=nextFrame, size=(25,30), pos=(80, 5), color=(184,138,0)) # done 495 | buttons.append(NextFrameButton) 496 | 497 | DelFrame = Button('Delete', action=delFrame, size=(45,25), pos=(115, 7), color=(184,138,0)) # done 498 | buttons.append(DelFrame) 499 | 500 | saveButton = Button('Save', action=save_it, size=(60,50), pos=(150, 250),hilight=(200, 0, 0),color=(255,255,0)) # done 501 | buttons_warn.append(saveButton) 502 | QuitButton = Button('Quit', action=prog_exit, size=(60,50), pos=(260, 250),hilight=(200, 0, 0),color=(255,255,0)) # done 503 | buttons_warn.append(QuitButton) 504 | 505 | 506 | def nosave_warn(): 507 | global warning 508 | warning = True 509 | font = pygame.font.Font(None,48) 510 | frame_text = 'Unsaved Frames ' 511 | 512 | for d in range(5): 513 | text = font.render(frame_text,1,(255,0,0)) 514 | screen.blit(text, (100,100)) 515 | pygame.display.flip() 516 | time.sleep(0.1) 517 | text = font.render(frame_text,1,(0,255,0)) 518 | screen.blit(text, (100,100)) 519 | pygame.display.flip() 520 | time.sleep(0.1) 521 | drawEverything() 522 | # Main prog loop 523 | 524 | 525 | while True: 526 | 527 | for event in pygame.event.get(): 528 | if event.type == QUIT: 529 | if saved == False: 530 | nosave_warn() 531 | else: 532 | prog_exit() 533 | 534 | if event.type == MOUSEBUTTONDOWN: 535 | handleClick() 536 | 537 | #update the display 538 | drawEverything() 539 | --------------------------------------------------------------------------------