Skip to content
This repository was archived by the owner on Dec 1, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions 03_execution/animationFileReaders/AnimationFileReader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class AnimationFileReader():
def __init__(self):
pass

def nextFrame(self):
return false

def getFrame(self):
return None

def resetAnimation(self):
pass

34 changes: 34 additions & 0 deletions 03_execution/animationFileReaders/CsvAnimationFileReader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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.skipFrame()
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 = [(float(row[pixelNumber]), float(row[pixelNumber+1]), float(row[pixelNumber+2])) for pixelNumber in range(0, len(row), 3)]
return True

def getFrame(self):
return self._frame

Empty file.
9 changes: 9 additions & 0 deletions 03_execution/ledsAdapters/LedsAdapter.py
Original file line number Diff line number Diff line change
@@ -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)])
14 changes: 14 additions & 0 deletions 03_execution/ledsAdapters/PhysicalLedsAdapter.py
Original file line number Diff line number Diff line change
@@ -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, pixel_order=neopixel.RGB)

def showFrame(self, frame):
ledsCounter = 0
for color in frame:
self.pixels[ledsCounter] = color
ledsCounter += 1
104 changes: 104 additions & 0 deletions 03_execution/ledsAdapters/VisualLedsAdapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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

import math


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._cameraRotationX = 0
self._cameraZoom = 1
self._bounds = (w, h)

def startOpenGlThread(self):
glutInit()
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);
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

def rotateTree(self, x, y):
self._cameraRotation = 360*(x - self._bounds[0]/2)/self._bounds[0]
self._cameraRotationX = -360*(y - self._bounds[1]/2)/self._bounds[1]

def paint(self):
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, 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()
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):
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)

def showFrame(self, frame):
self._leds = frame
Empty file.
15 changes: 15 additions & 0 deletions 03_execution/ledsMapReaders/CsvLedsMapReader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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:
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])])
15 changes: 15 additions & 0 deletions 03_execution/ledsMapReaders/LedsMapReader.py
Original file line number Diff line number Diff line change
@@ -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]
11 changes: 11 additions & 0 deletions 03_execution/ledsMapReaders/TxtLedsMapReader.py
Original file line number Diff line number Diff line change
@@ -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])])
Empty file.
87 changes: 25 additions & 62 deletions 03_execution/run.py
Original file line number Diff line number Diff line change
@@ -1,73 +1,36 @@
# 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 animationFileReaders.CsvAnimationFileReader import CsvAnimationFileReader
from ledsAdapters.PhysicalLedsAdapter import PhysicalLedsAdapter

# sleep_time = 0.033 # approx 30fps
sleep_time = 0.017 # approx 60fps

NUMBEROFLEDS = 500
pixels = neopixel.NeoPixel(board.D18, NUMBEROFLEDS, auto_write=False, pixel_order=neopixel.RGB)
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()

mapReader.normalize()

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 = (r, g, 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)
ledsAdapter = PhysicalLedsAdapter(500)
tree = Tree(ledsAdapter)
tree.runRepeatedAnimation(CsvAnimationFileReader(sys.argv[1]), 0, 60)
49 changes: 49 additions & 0 deletions 03_execution/visualization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Based on code from https://github.com/standupmaths/xmastree2020

import time
import sys

from animationFileReaders.CsvAnimationFileReader import CsvAnimationFileReader
from ledsAdapters.VisualLedsAdapter import VisualLedsAdapter
from ledsMapReaders.CsvLedsMapReader import CsvLedsMapReader
from ledsMapReaders.TxtLedsMapReader import TxtLedsMapReader


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()


mapFileName = sys.argv[2]
mapFileNameExtension = mapFileName.split(".")[-1]
if (mapFileNameExtension == 'txt'):
mapReader = TxtLedsMapReader(mapFileName)
elif (mapFileNameExtension == 'csv'):
mapReader = 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)
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ A few scripts, based on Matt's original code, are provided to load the CSV seque
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 "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`

### Known Issues
There are some issues with running the visualization script on windows (problems with GLUT)

## Contributions

This repo is just a snapshot of the work we did in the Fall 2021 edition of the GSD-6338 course. Unfortunately, we do not have the resources to maintain, manage or extend it beyond what is available. If you want to ask questions, discuss standards, contribute new code, features and add new sexy goodness, please refer to [Matt's repo](https://github.com/standupmaths/xmastree2021) or start your own fork. Crediting is always welcome, thank you!
Expand Down