From 7369f3e554c22a36cd1f9f1fcc226180c83f09cb Mon Sep 17 00:00:00 2001 From: programer Date: Sun, 26 Dec 2021 03:48:12 +0300 Subject: [PATCH 01/12] some packages and OOP improving --- .../AnimationFileReader.py | 13 +++ .../CsvAnimationFileReader.py | 31 +++++++ 03_execution/animationFileReaders/__init__.py | 0 03_execution/ledsAdapters/LedsAdapter.py | 9 ++ .../ledsAdapters/PhysicalLedsAdapter.py | 14 +++ 03_execution/ledsAdapters/__init__.py | 0 03_execution/run.py | 85 +++++-------------- 7 files changed, 90 insertions(+), 62 deletions(-) create mode 100644 03_execution/animationFileReaders/AnimationFileReader.py create mode 100644 03_execution/animationFileReaders/CsvAnimationFileReader.py create mode 100644 03_execution/animationFileReaders/__init__.py create mode 100644 03_execution/ledsAdapters/LedsAdapter.py create mode 100644 03_execution/ledsAdapters/PhysicalLedsAdapter.py create mode 100644 03_execution/ledsAdapters/__init__.py diff --git a/03_execution/animationFileReaders/AnimationFileReader.py b/03_execution/animationFileReaders/AnimationFileReader.py new file mode 100644 index 0000000..f597947 --- /dev/null +++ b/03_execution/animationFileReaders/AnimationFileReader.py @@ -0,0 +1,13 @@ +class AnimationFileReader(): + def __init__(self): + pass + + def nextFrame(self): + return false + + def getFrame(self): + return None + + def resetAnimation(self): + pass + diff --git a/03_execution/animationFileReaders/CsvAnimationFileReader.py b/03_execution/animationFileReaders/CsvAnimationFileReader.py new file mode 100644 index 0000000..311b9ea --- /dev/null +++ b/03_execution/animationFileReaders/CsvAnimationFileReader.py @@ -0,0 +1,31 @@ +from csv import reader +from animationFileReaders.AnimationFileReader import AnimationFileReader + + +class CsvAnimationFileReader(AnimationFileReader): + def __init__(self, filePath): + AnimationFileReader.__init__(self) + self._filePath = filePath + self._file = None + self._fileReader = None + self._frame = None + self.resetAnimation() + + def resetAnimation(self): + if (self._file != None): + self._file.close() + self._file = open(self._filePath, 'r') + self._fileReader = reader(self._file) + self.nextFrame() + self.nextFrame() + + def nextFrame(self): + row = next(self._fileReader, None) + if (row == None): return False + row.pop(0) + self._frame = [(row[pixelNumber], row[pixelNumber+1], row[pixelNumber+2]) for pixelNumber in range(0, len(row), 3)] + return True + + def getFrame(self): + return self._frame + diff --git a/03_execution/animationFileReaders/__init__.py b/03_execution/animationFileReaders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/03_execution/ledsAdapters/LedsAdapter.py b/03_execution/ledsAdapters/LedsAdapter.py new file mode 100644 index 0000000..3515026 --- /dev/null +++ b/03_execution/ledsAdapters/LedsAdapter.py @@ -0,0 +1,9 @@ +class LedsAdapter(): + def __init__(self, ledsCount): + self._ledsCount = ledsCount + + def showFrame(self, frame): + pass + + def flush(self): + self.showFrame([(0, 0, 0) for x in range(self._ledsCount)]) diff --git a/03_execution/ledsAdapters/PhysicalLedsAdapter.py b/03_execution/ledsAdapters/PhysicalLedsAdapter.py new file mode 100644 index 0000000..cd96f60 --- /dev/null +++ b/03_execution/ledsAdapters/PhysicalLedsAdapter.py @@ -0,0 +1,14 @@ +import board +import neopixel +from ledsAdapters.LedsAdapter import LedsAdapter + +class PhysicalLedsAdapter(LedsAdapter): + def __init__(self, ledsCount): + LedsAdapter.__init__(self, ledsCount) + self._pixels = neopixel.NeoPixel(board.D18, ledsCount, auto_write=False) + + def showFrame(self, frame): + ledsCounter = 0 + for color in frame: + self.pixels[ledsCounter] = color + ledsCounter += 1 diff --git a/03_execution/ledsAdapters/__init__.py b/03_execution/ledsAdapters/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/03_execution/run.py b/03_execution/run.py index e0fad50..c0d5cc2 100644 --- a/03_execution/run.py +++ b/03_execution/run.py @@ -1,73 +1,34 @@ # Based on code from https://github.com/standupmaths/xmastree2020 -import board -import neopixel -import time -from csv import reader +import time import sys -# helper function for chunking -def chunks(lst, n): - for i in range(0, len(lst), n): - yield lst[i:i+n] +from ledsAdapters.PhysicalLedsAdapter import PhysicalLedsAdapter +from animationFileReaders.CsvAnimationFileReader import CsvAnimationFileReader -# sleep_time = 0.033 # approx 30fps -sleep_time = 0.017 # approx 60fps -NUMBEROFLEDS = 500 -pixels = neopixel.NeoPixel(board.D18, NUMBEROFLEDS, auto_write=False) +class Tree(): + def __init__(self, ledsAdapter): + self._ledsAdapter = ledsAdapter -csvFile = sys.argv[1] + def runRepeatedAnimation(self, animationFileReader, repeats = 0, frameRate = 60): + repeatCounter = 0; + while (repeatCounter < repeats) or (repeats == 0): + animationFileReader.resetAnimation() + self.runAnimation(animationFileReader, frameRate) + repeatCounter += 1 + self.flushLeds() + def runAnimation(self, animationFileReader, frameRate = 60): + while True: + frame = animationFileReader.getFrame() + self._ledsAdapter.showFrame(frame) + time.sleep(1/frameRate) + if (not animationFileReader.nextFrame()): break -# read the file -# iterate through the entire thing and make all the points the same colour -lightArray = [] + def flushLeds(self): + self._ledsAdapter.flush() -with open(csvFile, 'r') as read_obj: - # pass the file object to reader() to get the reader object - csv_reader = reader(read_obj) - - # Iterate over each row in the csv using reader object - lineNumber = 0 - for row in csv_reader: - # row variable is a list that represents a row in csv - # break up the list of rgb values - # remove the first item - if lineNumber > 0: - parsed_row = [] - row.pop(0) - chunked_list = list(chunks(row, 3)) - for element_num in range(len(chunked_list)): - # this is a single light - r = float(chunked_list[element_num][0]) - g = float(chunked_list[element_num][1]) - b = float(chunked_list[element_num][2]) - light_val = (g, r, b) - # turn that led on - parsed_row.append(light_val) - - # append that line to lightArray - lightArray.append(parsed_row) - # time.sleep(0.03) - - lineNumber += 1 - -print("Finished Parsing") - - - -# run the code on the tree -while True: - f = 0 - for frame in lightArray: - print("running frame " + str(f)) - LED = 0 - while LED < NUMBEROFLEDS: - pixels[LED] = frame[LED] - LED += 1 - pixels.show() - - f += 1 -# time.sleep(sleep_time) +tree = Tree(PhysicalLedsAdapter(500)) +tree.runRepeatedAnimation(CsvAnimationFileReader(sys.argv[1]), 0, 60) From 780226a5148372c3b8c4cf70c4c9f5a81f93dcf4 Mon Sep 17 00:00:00 2001 From: programer Date: Sun, 26 Dec 2021 06:52:01 +0300 Subject: [PATCH 02/12] added OpenGL tree visualization --- .../CsvAnimationFileReader.py | 7 ++- .../ledsAdapters/VisualLedsAdapter.py | 63 +++++++++++++++++++ .../ledsMapReaders/CsvLedsMapReader.py | 10 +++ 03_execution/ledsMapReaders/LedsMapReader.py | 15 +++++ .../ledsMapReaders/TxtLedsMapReader.py | 11 ++++ 03_execution/ledsMapReaders/__init__.py | 0 03_execution/run.py | 33 +++++++++- 7 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 03_execution/ledsAdapters/VisualLedsAdapter.py create mode 100644 03_execution/ledsMapReaders/CsvLedsMapReader.py create mode 100644 03_execution/ledsMapReaders/LedsMapReader.py create mode 100644 03_execution/ledsMapReaders/TxtLedsMapReader.py create mode 100644 03_execution/ledsMapReaders/__init__.py diff --git a/03_execution/animationFileReaders/CsvAnimationFileReader.py b/03_execution/animationFileReaders/CsvAnimationFileReader.py index 311b9ea..1860f14 100644 --- a/03_execution/animationFileReaders/CsvAnimationFileReader.py +++ b/03_execution/animationFileReaders/CsvAnimationFileReader.py @@ -16,14 +16,17 @@ def resetAnimation(self): self._file.close() self._file = open(self._filePath, 'r') self._fileReader = reader(self._file) + self.skipFrame() self.nextFrame() - self.nextFrame() + + def skipFrame(self): + row = next(self._fileReader, None) def nextFrame(self): row = next(self._fileReader, None) if (row == None): return False row.pop(0) - self._frame = [(row[pixelNumber], row[pixelNumber+1], row[pixelNumber+2]) for pixelNumber in range(0, len(row), 3)] + self._frame = [(float(row[pixelNumber]), float(row[pixelNumber+1]), float(row[pixelNumber+2])) for pixelNumber in range(0, len(row), 3)] return True def getFrame(self): diff --git a/03_execution/ledsAdapters/VisualLedsAdapter.py b/03_execution/ledsAdapters/VisualLedsAdapter.py new file mode 100644 index 0000000..6da04c6 --- /dev/null +++ b/03_execution/ledsAdapters/VisualLedsAdapter.py @@ -0,0 +1,63 @@ +import threading +import OpenGL +from OpenGL.GL import * +from OpenGL.GLUT import * +from OpenGL.GLU import * + +from ledsMapReaders.LedsMapReader import LedsMapReader +from ledsAdapters.LedsAdapter import LedsAdapter + + +class VisualLedsAdapter(LedsAdapter): + def __init__(self, ledsCount, ledsMapReader, w, h): + LedsAdapter.__init__(self, ledsCount) + self._leds = [(0, 0, 0) for i in range(ledsCount)] + self._ledsMapReader = ledsMapReader + self._thread = threading.Thread(target=self.startOpenGlThread, args=()) + self._thread.start() + self._cameraRotation = 0 + self._cameraZoom = 1 + self._bounds = (w, h) + + def startOpenGlThread(self): + glutInit() + glutInitDisplayMode(GLUT_RGBA) + glutInitWindowSize(self._bounds[0], self._bounds[1]) + glutInitWindowPosition(0, 0) + wind = glutCreateWindow("OpenGL tree visualization") + glutDisplayFunc(self.paint) + glutIdleFunc(self.paint) + + glutMotionFunc(self.rotateAndZoomTree) + glutMainLoop() # Keeps the window created above displaying/running in a loop + + def rotateAndZoomTree(self, x, y): + self._cameraRotation = 360*(x - self._bounds[0]/2)/self._bounds[0] + self._cameraZoom = (self._bounds[1]/2 - y)/(self._bounds[1]/2) + 1 + + def paint(self): + glEnable(GL_DEPTH_TEST) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Remove everything from screen (i.e. displays all white) + glMatrixMode(GL_PROJECTION); + glLoadIdentity() # Reset all graphic/shape's position + glFrustum(-2.0, 2.0, -0.5, 3.5, 6.0, 10.0) + glTranslate(0.0, 0.0, -8.0) + glRotate(10, 1.0, 0.0, 0.0) + glRotate(-self._cameraRotation, 0.0, 1.0, 0.0) + self.drawTree() + glRotate(self._cameraRotation, 0.0, 1.0, 0.0) + glRotate(-10, 1.0, 0.0, 0.0) + glTranslate(0.0, 0.0, 8.0) + glMatrixMode(GL_MODELVIEW) + glutSwapBuffers() + + def drawTree(self): + for ledIndex in range(0, self._ledsCount): + glColor3f(float(self._leds[ledIndex][0])/255, float(self._leds[ledIndex][1])/255, float(self._leds[ledIndex][2])/255) + ledCoords = self._ledsMapReader.getLed(ledIndex) + glTranslate(ledCoords[0]*self._cameraZoom, ledCoords[1]*self._cameraZoom, ledCoords[2]*self._cameraZoom) + gluSphere(gluNewQuadric(), 0.03, 10, 10) + glTranslate(-ledCoords[0]*self._cameraZoom, -ledCoords[1]*self._cameraZoom, -ledCoords[2]*self._cameraZoom) + + def showFrame(self, frame): + self._leds = frame diff --git a/03_execution/ledsMapReaders/CsvLedsMapReader.py b/03_execution/ledsMapReaders/CsvLedsMapReader.py new file mode 100644 index 0000000..42b78fc --- /dev/null +++ b/03_execution/ledsMapReaders/CsvLedsMapReader.py @@ -0,0 +1,10 @@ +from csv import reader +from ledsMapReaders.LedsMapReader import LedsMapReader + + +class CsvLedsMapReader(LedsMapReader): + def __init__(self, filePath): + LedsMapReader.__init__(self) + csvReader = reader(open(filePath, mode='r', encoding='utf-8-sig')) + for ledCoords in csvReader: + self._leds.append([float(ledCoords[0]), float(ledCoords[2]), float(ledCoords[1])]) diff --git a/03_execution/ledsMapReaders/LedsMapReader.py b/03_execution/ledsMapReaders/LedsMapReader.py new file mode 100644 index 0000000..df9fc77 --- /dev/null +++ b/03_execution/ledsMapReaders/LedsMapReader.py @@ -0,0 +1,15 @@ +class LedsMapReader(): + def __init__(self): + self._leds = [] + + def normalize(self): + koef = 0 + for led in self._leds: + if koef < abs(led[0]): koef = abs(led[0]) + if koef < abs(led[2]): koef = abs(led[2]) + + for i in range(len(self._leds)): + self._leds[i] = [self._leds[i][0]/koef, self._leds[i][1]/koef, self._leds[i][2]/koef] + + def getLed(self, index): + return self._leds[index] diff --git a/03_execution/ledsMapReaders/TxtLedsMapReader.py b/03_execution/ledsMapReaders/TxtLedsMapReader.py new file mode 100644 index 0000000..65af21f --- /dev/null +++ b/03_execution/ledsMapReaders/TxtLedsMapReader.py @@ -0,0 +1,11 @@ +import json +from ledsMapReaders.LedsMapReader import LedsMapReader + + +class TxtLedsMapReader(LedsMapReader): + def __init__(self, filePath): + LedsMapReader.__init__(self) + ledsData = open(filePath, mode='r', encoding='utf-8-sig') + for ledRow in ledsData: + ledCoords = json.loads(ledRow) + self._leds.append([float(ledCoords[0]), float(ledCoords[2]), float(ledCoords[1])]) diff --git a/03_execution/ledsMapReaders/__init__.py b/03_execution/ledsMapReaders/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/03_execution/run.py b/03_execution/run.py index c0d5cc2..8f96721 100644 --- a/03_execution/run.py +++ b/03_execution/run.py @@ -4,6 +4,10 @@ import sys from ledsAdapters.PhysicalLedsAdapter import PhysicalLedsAdapter + +from ledsMapReaders.CsvLedsMapReader import CsvLedsMapReader +from ledsMapReaders.TxtLedsMapReader import TxtLedsMapReader + from animationFileReaders.CsvAnimationFileReader import CsvAnimationFileReader @@ -30,5 +34,32 @@ def flushLeds(self): self._ledsAdapter.flush() -tree = Tree(PhysicalLedsAdapter(500)) +# if LEDs map file is set then select map reader by extension and configure Visual LEDs adapter +if len(sys.argv) > 2: + mapFileName = sys.argv[2] + mapFileNameExtension = mapFileName.split(".")[-1] + if (mapFileNameExtension == 'txt'): + TxtLedsMapReaderModule = __import__('ledsMapReaders.TxtLedsMapReader') + TxtLedsMapReader = getattr(TxtLedsMapReaderModule, 'TxtLedsMapReader') + mapReader = TxtLedsMapReader.TxtLedsMapReader(mapFileName) + elif (mapFileNameExtension == 'csv'): + CsvLedsMapReaderModule = __import__('ledsMapReaders.CsvLedsMapReader') + CsvLedsMapReader = getattr(CsvLedsMapReaderModule, 'CsvLedsMapReader') + mapReader = CsvLedsMapReader.CsvLedsMapReader(mapFileName) + else: + print('Unknown LED map type') + quit() + + VisualLedsAdapterModule = __import__('ledsAdapters.VisualLedsAdapter') + VisualLedsAdapter = getattr(VisualLedsAdapterModule, 'VisualLedsAdapter') + ledsAdapter = VisualLedsAdapter.VisualLedsAdapter(500, mapReader, 800, 800) +# if LEDs file isn't set then configure Physical LEDs adapter +else: + PhysicalLedsAdapterModule = __import__('ledsAdapters.PhysicalLedsAdapter') + PhysicalLedsAdapter = getattr(PhysicalLedsAdapterModule, 'PhysicalLedsAdapter') + ledsAdapter = PhysicalLedsAdapter.PhysicalLedsAdapter(500) + +mapReader.normalize() + +tree = Tree(ledsAdapter) tree.runRepeatedAnimation(CsvAnimationFileReader(sys.argv[1]), 0, 60) From 128021a504e75b9b159e5f59c9caa71b2db24a46 Mon Sep 17 00:00:00 2001 From: programer Date: Sun, 26 Dec 2021 07:03:33 +0300 Subject: [PATCH 03/12] adding the new features to the readme file --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5fbdb0b..247d313 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ A few scripts, based on Matt's original code, are provided to load the CSV seque 1. Copy the content of the `execution` folder to the RPi, as well as a few of the CSV sequences. 2. To run a sequence: `$ sudo python3 run.py light-sequence.csv` + * To run a sequence with OpenGL simulation: `$ sudo python3 run.py light-sequence.csv tree-map-file.csv` 3. To run all the sequences in a folder: `$ sudo python3 run-folder.py seq-folder/ 3 30`. The optional number `3` indicates that each sequence will be looped `3` times before moving on to the next, and the optional `30` indicates that sequences will be blended over `30` frames. 4. If you stop a sequence and want to turn off the tree, run `$ sudo python3 flush.py` 5. If you need to fine calibrate the tree, you can turn on specific lights by ID running `$ sudo python3 turnon.py 0 99 199 299 399 499`. From ff61a877ee7c82c591c82edd97c3f9c41c659f72 Mon Sep 17 00:00:00 2001 From: programer Date: Sun, 26 Dec 2021 08:03:34 +0300 Subject: [PATCH 04/12] fix: deleted deprecated imports --- 03_execution/run.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/03_execution/run.py b/03_execution/run.py index 8f96721..7c28e85 100644 --- a/03_execution/run.py +++ b/03_execution/run.py @@ -5,9 +5,6 @@ from ledsAdapters.PhysicalLedsAdapter import PhysicalLedsAdapter -from ledsMapReaders.CsvLedsMapReader import CsvLedsMapReader -from ledsMapReaders.TxtLedsMapReader import TxtLedsMapReader - from animationFileReaders.CsvAnimationFileReader import CsvAnimationFileReader From b8ebed8dfc331405406da67a5b4de725191ee86b Mon Sep 17 00:00:00 2001 From: programer Date: Sun, 26 Dec 2021 08:08:17 +0300 Subject: [PATCH 05/12] fix: deleted deprecated imports 2 --- 03_execution/run.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/03_execution/run.py b/03_execution/run.py index 7c28e85..22e3132 100644 --- a/03_execution/run.py +++ b/03_execution/run.py @@ -3,8 +3,6 @@ import time import sys -from ledsAdapters.PhysicalLedsAdapter import PhysicalLedsAdapter - from animationFileReaders.CsvAnimationFileReader import CsvAnimationFileReader From 9d135e09ef30825534c61c02149b9d74ecac6212 Mon Sep 17 00:00:00 2001 From: programer Date: Mon, 27 Dec 2021 03:54:30 +0300 Subject: [PATCH 06/12] separated opengl visualization and real execution --- 03_execution/run.py | 26 ++--------------- 03_execution/visualization.py | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 24 deletions(-) create mode 100644 03_execution/visualization.py diff --git a/03_execution/run.py b/03_execution/run.py index 22e3132..d0f8d45 100644 --- a/03_execution/run.py +++ b/03_execution/run.py @@ -4,6 +4,7 @@ import sys from animationFileReaders.CsvAnimationFileReader import CsvAnimationFileReader +from ledsAdapters.PhysicalLedsAdapter import PhysicalLedsAdapter class Tree(): @@ -29,30 +30,7 @@ def flushLeds(self): self._ledsAdapter.flush() -# if LEDs map file is set then select map reader by extension and configure Visual LEDs adapter -if len(sys.argv) > 2: - mapFileName = sys.argv[2] - mapFileNameExtension = mapFileName.split(".")[-1] - if (mapFileNameExtension == 'txt'): - TxtLedsMapReaderModule = __import__('ledsMapReaders.TxtLedsMapReader') - TxtLedsMapReader = getattr(TxtLedsMapReaderModule, 'TxtLedsMapReader') - mapReader = TxtLedsMapReader.TxtLedsMapReader(mapFileName) - elif (mapFileNameExtension == 'csv'): - CsvLedsMapReaderModule = __import__('ledsMapReaders.CsvLedsMapReader') - CsvLedsMapReader = getattr(CsvLedsMapReaderModule, 'CsvLedsMapReader') - mapReader = CsvLedsMapReader.CsvLedsMapReader(mapFileName) - else: - print('Unknown LED map type') - quit() - - VisualLedsAdapterModule = __import__('ledsAdapters.VisualLedsAdapter') - VisualLedsAdapter = getattr(VisualLedsAdapterModule, 'VisualLedsAdapter') - ledsAdapter = VisualLedsAdapter.VisualLedsAdapter(500, mapReader, 800, 800) -# if LEDs file isn't set then configure Physical LEDs adapter -else: - PhysicalLedsAdapterModule = __import__('ledsAdapters.PhysicalLedsAdapter') - PhysicalLedsAdapter = getattr(PhysicalLedsAdapterModule, 'PhysicalLedsAdapter') - ledsAdapter = PhysicalLedsAdapter.PhysicalLedsAdapter(500) +ledsAdapter = PhysicalLedsAdapter(500) mapReader.normalize() diff --git a/03_execution/visualization.py b/03_execution/visualization.py new file mode 100644 index 0000000..1a30973 --- /dev/null +++ b/03_execution/visualization.py @@ -0,0 +1,53 @@ +# Based on code from https://github.com/standupmaths/xmastree2020 + +import time +import sys + +from animationFileReaders.CsvAnimationFileReader import CsvAnimationFileReader +from ledsAdapters.VisualLedsAdapter import VisualLedsAdapter + + +class Tree(): + def __init__(self, ledsAdapter): + self._ledsAdapter = ledsAdapter + + def runRepeatedAnimation(self, animationFileReader, repeats = 0, frameRate = 60): + repeatCounter = 0; + while (repeatCounter < repeats) or (repeats == 0): + animationFileReader.resetAnimation() + self.runAnimation(animationFileReader, frameRate) + repeatCounter += 1 + self.flushLeds() + + def runAnimation(self, animationFileReader, frameRate = 60): + while True: + frame = animationFileReader.getFrame() + self._ledsAdapter.showFrame(frame) + time.sleep(1/frameRate) + if (not animationFileReader.nextFrame()): break + + def flushLeds(self): + self._ledsAdapter.flush() + + +if len(sys.argv) > 2: + mapFileName = sys.argv[2] + mapFileNameExtension = mapFileName.split(".")[-1] + if (mapFileNameExtension == 'txt'): + TxtLedsMapReaderModule = __import__('ledsMapReaders.TxtLedsMapReader') + TxtLedsMapReader = getattr(TxtLedsMapReaderModule, 'TxtLedsMapReader') + mapReader = TxtLedsMapReader.TxtLedsMapReader(mapFileName) + elif (mapFileNameExtension == 'csv'): + CsvLedsMapReaderModule = __import__('ledsMapReaders.CsvLedsMapReader') + CsvLedsMapReader = getattr(CsvLedsMapReaderModule, 'CsvLedsMapReader') + mapReader = CsvLedsMapReader.CsvLedsMapReader(mapFileName) + else: + print('Unknown LED map type') + quit() + + ledsAdapter = VisualLedsAdapter(500, mapReader, 800, 800) + +mapReader.normalize() + +tree = Tree(ledsAdapter) +tree.runRepeatedAnimation(CsvAnimationFileReader(sys.argv[1]), 0, 60) From 22f53df3ef18c58c5a69f145e40bb5d6a0acce7d Mon Sep 17 00:00:00 2001 From: programer Date: Mon, 27 Dec 2021 06:57:14 +0300 Subject: [PATCH 07/12] deleted unnecessary condition --- 03_execution/run.py | 4 +--- 03_execution/visualization.py | 32 +++++++++++++++----------------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/03_execution/run.py b/03_execution/run.py index d0f8d45..5b9ce24 100644 --- a/03_execution/run.py +++ b/03_execution/run.py @@ -29,10 +29,8 @@ def runAnimation(self, animationFileReader, frameRate = 60): def flushLeds(self): self._ledsAdapter.flush() - -ledsAdapter = PhysicalLedsAdapter(500) - mapReader.normalize() +ledsAdapter = PhysicalLedsAdapter(500) tree = Tree(ledsAdapter) tree.runRepeatedAnimation(CsvAnimationFileReader(sys.argv[1]), 0, 60) diff --git a/03_execution/visualization.py b/03_execution/visualization.py index 1a30973..2103829 100644 --- a/03_execution/visualization.py +++ b/03_execution/visualization.py @@ -30,24 +30,22 @@ def flushLeds(self): self._ledsAdapter.flush() -if len(sys.argv) > 2: - mapFileName = sys.argv[2] - mapFileNameExtension = mapFileName.split(".")[-1] - if (mapFileNameExtension == 'txt'): - TxtLedsMapReaderModule = __import__('ledsMapReaders.TxtLedsMapReader') - TxtLedsMapReader = getattr(TxtLedsMapReaderModule, 'TxtLedsMapReader') - mapReader = TxtLedsMapReader.TxtLedsMapReader(mapFileName) - elif (mapFileNameExtension == 'csv'): - CsvLedsMapReaderModule = __import__('ledsMapReaders.CsvLedsMapReader') - CsvLedsMapReader = getattr(CsvLedsMapReaderModule, 'CsvLedsMapReader') - mapReader = CsvLedsMapReader.CsvLedsMapReader(mapFileName) - else: - print('Unknown LED map type') - quit() - - ledsAdapter = VisualLedsAdapter(500, mapReader, 800, 800) - +mapFileName = sys.argv[2] +mapFileNameExtension = mapFileName.split(".")[-1] +if (mapFileNameExtension == 'txt'): + TxtLedsMapReaderModule = __import__('ledsMapReaders.TxtLedsMapReader') + TxtLedsMapReader = getattr(TxtLedsMapReaderModule, 'TxtLedsMapReader') + mapReader = TxtLedsMapReader.TxtLedsMapReader(mapFileName) +elif (mapFileNameExtension == 'csv'): + CsvLedsMapReaderModule = __import__('ledsMapReaders.CsvLedsMapReader') + CsvLedsMapReader = getattr(CsvLedsMapReaderModule, 'CsvLedsMapReader') + mapReader = CsvLedsMapReader.CsvLedsMapReader(mapFileName) +else: + print('Unknown LED map type') + quit() + mapReader.normalize() +ledsAdapter = VisualLedsAdapter(500, mapReader, 800, 800) tree = Tree(ledsAdapter) tree.runRepeatedAnimation(CsvAnimationFileReader(sys.argv[1]), 0, 60) From cb6851f3d9bcce560909abe9ce6c200e4dbb998f Mon Sep 17 00:00:00 2001 From: programer Date: Mon, 27 Dec 2021 08:22:52 +0300 Subject: [PATCH 08/12] zooming changed to 2-dimensional rotation. fixed openGL depth issues --- .../ledsAdapters/VisualLedsAdapter.py | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/03_execution/ledsAdapters/VisualLedsAdapter.py b/03_execution/ledsAdapters/VisualLedsAdapter.py index 6da04c6..fe3d8f3 100644 --- a/03_execution/ledsAdapters/VisualLedsAdapter.py +++ b/03_execution/ledsAdapters/VisualLedsAdapter.py @@ -16,38 +16,43 @@ def __init__(self, ledsCount, ledsMapReader, w, h): self._thread = threading.Thread(target=self.startOpenGlThread, args=()) self._thread.start() self._cameraRotation = 0 + self._cameraRotationX = 0 self._cameraZoom = 1 self._bounds = (w, h) def startOpenGlThread(self): glutInit() - glutInitDisplayMode(GLUT_RGBA) + glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE) glutInitWindowSize(self._bounds[0], self._bounds[1]) glutInitWindowPosition(0, 0) wind = glutCreateWindow("OpenGL tree visualization") glutDisplayFunc(self.paint) glutIdleFunc(self.paint) + glEnable(GL_DEPTH_TEST) + glDepthFunc(GL_LESS); - glutMotionFunc(self.rotateAndZoomTree) + glutMotionFunc(self.rotateTree) glutMainLoop() # Keeps the window created above displaying/running in a loop - def rotateAndZoomTree(self, x, y): + def rotateTree(self, x, y): self._cameraRotation = 360*(x - self._bounds[0]/2)/self._bounds[0] - self._cameraZoom = (self._bounds[1]/2 - y)/(self._bounds[1]/2) + 1 + self._cameraRotationX = -360*(y - self._bounds[1]/2)/self._bounds[1] def paint(self): - glEnable(GL_DEPTH_TEST) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Remove everything from screen (i.e. displays all white) + glMatrixMode(GL_PROJECTION); glLoadIdentity() # Reset all graphic/shape's position glFrustum(-2.0, 2.0, -0.5, 3.5, 6.0, 10.0) - glTranslate(0.0, 0.0, -8.0) - glRotate(10, 1.0, 0.0, 0.0) + glTranslate(0.0, 1.0, -8.0) + glRotate(-self._cameraRotationX, 1.0, 0.0, 0.0) glRotate(-self._cameraRotation, 0.0, 1.0, 0.0) + glTranslate(0.0, -1.0, 0.0) self.drawTree() + glTranslate(0.0, 1.0, 0.0) glRotate(self._cameraRotation, 0.0, 1.0, 0.0) - glRotate(-10, 1.0, 0.0, 0.0) - glTranslate(0.0, 0.0, 8.0) + glRotate(self._cameraRotationX, 1.0, 0.0, 0.0) + glTranslate(0.0, -1.0, 8.0) glMatrixMode(GL_MODELVIEW) glutSwapBuffers() From e7d14180cd92a1636f8ff48a46361520fe7574f7 Mon Sep 17 00:00:00 2001 From: programer Date: Mon, 27 Dec 2021 22:56:42 +0300 Subject: [PATCH 09/12] changing documentation for visualization script --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 247d313..c090608 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,14 @@ A few scripts, based on Matt's original code, are provided to load the CSV seque 1. Copy the content of the `execution` folder to the RPi, as well as a few of the CSV sequences. 2. To run a sequence: `$ sudo python3 run.py light-sequence.csv` - * To run a sequence with OpenGL simulation: `$ sudo python3 run.py light-sequence.csv tree-map-file.csv` 3. To run all the sequences in a folder: `$ sudo python3 run-folder.py seq-folder/ 3 30`. The optional number `3` indicates that each sequence will be looped `3` times before moving on to the next, and the optional `30` indicates that sequences will be blended over `30` frames. 4. If you stop a sequence and want to turn off the tree, run `$ sudo python3 flush.py` 5. If you need to fine calibrate the tree, you can turn on specific lights by ID running `$ sudo python3 turnon.py 0 99 199 299 399 499`. +## Visualization +If you haven't got real Cristmas tree with light, you still can visualize a tree from lights coordinates given in a csv file where each line represents a light position as "x,y,z", or in a txt file where each line also represents a light but the format of each line is "\[x, y, z\]" +To run a sequence with OpenGL simulator execute this command: `$ sudo python3 visualization.py light-sequence.csv tree-map-file.csv` +### Known Issues +There are some issues with running the visualization script on windows (problems with GLUT) From 96bfb4a105ff6deda62a3e752ae54301315dd5a2 Mon Sep 17 00:00:00 2001 From: programer Date: Mon, 27 Dec 2021 23:05:52 +0300 Subject: [PATCH 10/12] dynamic imports became unnecessary and have been changed to static --- 03_execution/visualization.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/03_execution/visualization.py b/03_execution/visualization.py index 2103829..2c661eb 100644 --- a/03_execution/visualization.py +++ b/03_execution/visualization.py @@ -5,6 +5,8 @@ from animationFileReaders.CsvAnimationFileReader import CsvAnimationFileReader from ledsAdapters.VisualLedsAdapter import VisualLedsAdapter +from ledsMapReaders.CsvLedsMapReader import CsvLedsMapReader +from ledsMapReaders.TxtLedsMapReader import TxtLedsMapReader class Tree(): @@ -33,13 +35,9 @@ def flushLeds(self): mapFileName = sys.argv[2] mapFileNameExtension = mapFileName.split(".")[-1] if (mapFileNameExtension == 'txt'): - TxtLedsMapReaderModule = __import__('ledsMapReaders.TxtLedsMapReader') - TxtLedsMapReader = getattr(TxtLedsMapReaderModule, 'TxtLedsMapReader') - mapReader = TxtLedsMapReader.TxtLedsMapReader(mapFileName) + mapReader = TxtLedsMapReader(mapFileName) elif (mapFileNameExtension == 'csv'): - CsvLedsMapReaderModule = __import__('ledsMapReaders.CsvLedsMapReader') - CsvLedsMapReader = getattr(CsvLedsMapReaderModule, 'CsvLedsMapReader') - mapReader = CsvLedsMapReader.CsvLedsMapReader(mapFileName) + mapReader = CsvLedsMapReader(mapFileName) else: print('Unknown LED map type') quit() From c3f865ca6a8b79bb8991dc0e717681db3af79258 Mon Sep 17 00:00:00 2001 From: programer Date: Mon, 27 Dec 2021 23:24:44 +0300 Subject: [PATCH 11/12] added support of csv files in format id,x,y,z --- 03_execution/ledsMapReaders/CsvLedsMapReader.py | 5 +++++ README.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/03_execution/ledsMapReaders/CsvLedsMapReader.py b/03_execution/ledsMapReaders/CsvLedsMapReader.py index 42b78fc..9d7a478 100644 --- a/03_execution/ledsMapReaders/CsvLedsMapReader.py +++ b/03_execution/ledsMapReaders/CsvLedsMapReader.py @@ -7,4 +7,9 @@ def __init__(self, filePath): LedsMapReader.__init__(self) csvReader = reader(open(filePath, mode='r', encoding='utf-8-sig')) for ledCoords in csvReader: + try: + float(ledCoords[0]) + except ValueError: + continue + if (len(ledCoords) == 4): ledCoords.pop(0) self._leds.append([float(ledCoords[0]), float(ledCoords[2]), float(ledCoords[1])]) diff --git a/README.md b/README.md index dcf1cdf..8782853 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ A few scripts, based on Matt's original code, are provided to load the CSV seque 5. If you need to fine calibrate the tree, you can turn on specific lights by ID running `$ sudo python3 turnon.py 0 99 199 299 399 499`. ## Visualization -If you haven't got real Cristmas tree with light, you still can visualize a tree from lights coordinates given in a csv file where each line represents a light position as "x,y,z", or in a txt file where each line also represents a light but the format of each line is "\[x, y, z\]" +If you haven't got real Cristmas tree with light, you still can visualize a tree from lights coordinates given in a csv file where each line represents a light position as "x,y,z" or "id,x,y,z", or in a txt file where each line also represents a light but the format of each line is "\[x, y, z\]" To run a sequence with OpenGL simulator execute this command: `$ sudo python3 visualization.py light-sequence.csv tree-map-file.csv` From 2e5b5e4d6be1df90487c3b9e4c9ae08d6642309f Mon Sep 17 00:00:00 2001 From: programer Date: Wed, 29 Dec 2021 01:52:44 +0300 Subject: [PATCH 12/12] changed appearance of lights and added simple tree imitation --- .../ledsAdapters/VisualLedsAdapter.py | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/03_execution/ledsAdapters/VisualLedsAdapter.py b/03_execution/ledsAdapters/VisualLedsAdapter.py index fe3d8f3..a87c9f9 100644 --- a/03_execution/ledsAdapters/VisualLedsAdapter.py +++ b/03_execution/ledsAdapters/VisualLedsAdapter.py @@ -7,6 +7,8 @@ from ledsMapReaders.LedsMapReader import LedsMapReader from ledsAdapters.LedsAdapter import LedsAdapter +import math + class VisualLedsAdapter(LedsAdapter): def __init__(self, ledsCount, ledsMapReader, w, h): @@ -30,6 +32,8 @@ def startOpenGlThread(self): glutIdleFunc(self.paint) glEnable(GL_DEPTH_TEST) glDepthFunc(GL_LESS); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glutMotionFunc(self.rotateTree) glutMainLoop() # Keeps the window created above displaying/running in a loop @@ -49,18 +53,50 @@ def paint(self): glRotate(-self._cameraRotation, 0.0, 1.0, 0.0) glTranslate(0.0, -1.0, 0.0) self.drawTree() + self.drawLights() glTranslate(0.0, 1.0, 0.0) glRotate(self._cameraRotation, 0.0, 1.0, 0.0) glRotate(self._cameraRotationX, 1.0, 0.0, 0.0) glTranslate(0.0, -1.0, 8.0) glMatrixMode(GL_MODELVIEW) glutSwapBuffers() - + def drawTree(self): + topLedCoords = [0.0, 0.0, 0.0] + for ledIndex in range(0, self._ledsCount): + led = self._ledsMapReader.getLed(ledIndex) + if led[1] >= topLedCoords[1]: + topLedCoords = led + + glTranslate(topLedCoords[0], 0, topLedCoords[2]) + + glColor4f(0.3, 0.2, 0.0, 1.0) + glRotate(-90, 1.0, 0.0, 0.0) + glutSolidCone(0.1, 3.2, 10, 10) + glRotate(90, 1.0, 0.0, 0.0) + + glColor4f(0.1, 0.8, 0.1, 0.15) + for hInt in range(17): + h = float(hInt)/5 + glTranslate(0, h, 0) + glRotate((hInt/16)*70, 0.0, 1.0, 0.0) + glRotate(90, 1.0, 0.0, 0.0) + glutWireCone(1.2 - 0.5/(3.6 - h), h/6, 50, 1) + glRotate(-90, 1.0, 0.0, 0.0) + glRotate(-(hInt/16)*70, 0.0, 1.0, 0.0) + glTranslate(0, -h, 0) + + glTranslate(-topLedCoords[0], 0, -topLedCoords[2]) + + + def drawLights(self): for ledIndex in range(0, self._ledsCount): - glColor3f(float(self._leds[ledIndex][0])/255, float(self._leds[ledIndex][1])/255, float(self._leds[ledIndex][2])/255) + color = [float(self._leds[ledIndex][0])/255, float(self._leds[ledIndex][1])/255, float(self._leds[ledIndex][2])/255] ledCoords = self._ledsMapReader.getLed(ledIndex) glTranslate(ledCoords[0]*self._cameraZoom, ledCoords[1]*self._cameraZoom, ledCoords[2]*self._cameraZoom) + glColor3f(color[0], color[1], color[2]) + gluSphere(gluNewQuadric(), 0.02, 10, 10) + glColor4f(color[0], color[1], color[2], 0.8) gluSphere(gluNewQuadric(), 0.03, 10, 10) glTranslate(-ledCoords[0]*self._cameraZoom, -ledCoords[1]*self._cameraZoom, -ledCoords[2]*self._cameraZoom)