Initial snapshot
This commit is contained in:
commit
659d9dccbe
3 changed files with 370 additions and 0 deletions
326
LeitLightLightLeitLeidLite/__init__.py
Normal file
326
LeitLightLightLeitLeidLite/__init__.py
Normal file
|
@ -0,0 +1,326 @@
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
|
||||||
|
# This is the source information:
|
||||||
|
# - x/y is in LED distances
|
||||||
|
# - x is distance from right wall
|
||||||
|
# - y is distance from ceiling near door
|
||||||
|
# - Indices increase along rows
|
||||||
|
# - Cells are [right, ..., left]
|
||||||
|
# - Index increases from right-to-left if rev else left-to-right
|
||||||
|
# - Negative numbers in cells (also fractions) denote a gap
|
||||||
|
rows = [
|
||||||
|
|
||||||
|
# Controller: 0-218
|
||||||
|
|
||||||
|
# 139, # unter decke (rechts)
|
||||||
|
{ 'y': 0/2, 'x': 0, 'cells': [58, 60, 21], 'rev': False, 'name': 'unter_decke_rechts' },
|
||||||
|
|
||||||
|
# 80, # unter decke (links)
|
||||||
|
{ 'y': -5/2, 'x': 139, 'cells': [19, 39, 22], 'rev': False, 'name': 'unter_decke_links' },
|
||||||
|
|
||||||
|
# Controller: 219-702
|
||||||
|
|
||||||
|
#236, # ueber ampel
|
||||||
|
{ 'y': 40/2, 'x': 0, 'cells': [57, 59, 39, 37, 44], 'rev': False, 'name': 'ueber_ampel' },
|
||||||
|
|
||||||
|
#-248, # unter ampel
|
||||||
|
{ 'y': 70/2, 'x': 0, 'cells': [57, 59, 40, 37, 55], 'rev': True, 'name': 'unter_ampel' },
|
||||||
|
|
||||||
|
# Controller: 703-1295
|
||||||
|
|
||||||
|
#153, # ueber garderobe
|
||||||
|
{ 'y': 100/2, 'x': 0, 'cells': [55, 58, 40], 'rev': False, 'name': 'ueber_garderobe' },
|
||||||
|
|
||||||
|
#35, # gewuerze (oben)
|
||||||
|
{ 'y': 115/2, 'x': 153, 'cells': [35], 'rev': False, 'name': 'gewuerze_oben' },
|
||||||
|
|
||||||
|
#47, # unter schrank
|
||||||
|
{ 'y': 110/2, 'x': 153+35, 'cells': [47], 'rev': False, 'name': 'unter_schrank' },
|
||||||
|
|
||||||
|
#-36, # gewuerze (unten)
|
||||||
|
{ 'y': 130/2, 'x': 153, 'cells': [36], 'rev': True, 'name': 'gewuerze_unten' },
|
||||||
|
|
||||||
|
#-89, # zwei von unten #FIXME: Bad source?
|
||||||
|
{ 'y': 130/2, 'x': 55, 'cells': [49+5, 40], 'rev': True, 'name': 'zwei_von_unten' },
|
||||||
|
|
||||||
|
# 100, # eins von unten #FIXME: Bad source?
|
||||||
|
{ 'y': 160/2, 'x': 55, 'cells': [61-5, 39], 'rev': False, 'name': 'eins_von_unten' },
|
||||||
|
|
||||||
|
#FIXME: Missing one LED?
|
||||||
|
# -132 # unten
|
||||||
|
{ 'y': 190/2, 'x': 55, 'cells': [57, 39, 36], 'rev': True, 'name': 'unten' },
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 3D vector math
|
||||||
|
|
||||||
|
def scaleV(v, s):
|
||||||
|
return (v[0]*s,v[1]*s,v[2]*s)
|
||||||
|
|
||||||
|
def addV(a, b):
|
||||||
|
return (a[0]+b[0], a[1]+b[1], a[2]+b[2])
|
||||||
|
|
||||||
|
def subV(a, b):
|
||||||
|
return addV(a, scaleV(b, -1.0))
|
||||||
|
|
||||||
|
def mulV(a, b):
|
||||||
|
return (a[0]*b[0], a[1]*b[1], a[2]*b[2])
|
||||||
|
|
||||||
|
def dotV(a, b):
|
||||||
|
t = mulV(a, b)
|
||||||
|
return t[0]+t[1]+t[2]
|
||||||
|
|
||||||
|
def normalizeV(v):
|
||||||
|
l = math.sqrt(dotV(v, v))
|
||||||
|
return scaleV(v, 1.0 / l)
|
||||||
|
|
||||||
|
def lerp(a, b, p):
|
||||||
|
return round(a + (b - a) * p)
|
||||||
|
|
||||||
|
def lerpV(a, b, p):
|
||||||
|
return [lerp(a[0],b[0],p), lerp(a[1],b[1],p), lerp(a[2],b[2],p)]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Strip functions
|
||||||
|
|
||||||
|
# Turn strips into 3D line segments
|
||||||
|
def getSourceStrips(rows):
|
||||||
|
sourceStrips = []
|
||||||
|
index = 0
|
||||||
|
for row in rows:
|
||||||
|
|
||||||
|
rowSourceStrips = []
|
||||||
|
|
||||||
|
# Extract cells (right to left)
|
||||||
|
step = (1, 0, 0)
|
||||||
|
start = (row['x'], row['y'], 0)
|
||||||
|
for length in row['cells']:
|
||||||
|
end = addV(start, scaleV(step, abs(length)))
|
||||||
|
if length > 0:
|
||||||
|
rowSourceStrip = {
|
||||||
|
'start': start,
|
||||||
|
'end': end,
|
||||||
|
'count': length,
|
||||||
|
'index': len(rowSourceStrips),
|
||||||
|
'name': row['name']
|
||||||
|
}
|
||||||
|
rowSourceStrips += [rowSourceStrip]
|
||||||
|
start = end
|
||||||
|
|
||||||
|
if row['rev']:
|
||||||
|
rowSourceStrips = rowSourceStrips[::-1]
|
||||||
|
for rowSourceStrip in rowSourceStrips:
|
||||||
|
rowSourceStrip['start'], rowSourceStrip['end'] = rowSourceStrip['end'], rowSourceStrip['start']
|
||||||
|
|
||||||
|
sourceStrips += rowSourceStrips
|
||||||
|
|
||||||
|
print(sourceStrips)
|
||||||
|
return sourceStrips
|
||||||
|
|
||||||
|
# Adds index information to each strip
|
||||||
|
def bakeStrips(sourceStrips):
|
||||||
|
strips = []
|
||||||
|
first = 0
|
||||||
|
for sourceStrip in sourceStrips:
|
||||||
|
strip = dict(sourceStrip)
|
||||||
|
|
||||||
|
# Fix stupidity in sourceStrip
|
||||||
|
strip['direction'] = scaleV(subV(sourceStrip['end'], sourceStrip['start']), 1.0 / sourceStrip['count'])
|
||||||
|
del strip['end']
|
||||||
|
|
||||||
|
# Bake the index
|
||||||
|
strip['first'] = first
|
||||||
|
strips += [strip]
|
||||||
|
|
||||||
|
# Prepare for next strip
|
||||||
|
first += sourceStrip['count']
|
||||||
|
|
||||||
|
return strips
|
||||||
|
|
||||||
|
# Only keeps strip in range start to end (in range 0.0 - 1.0)
|
||||||
|
def extractStrip(strip, start, startInclusive, end, endInclusive):
|
||||||
|
strip = dict(strip)
|
||||||
|
count = strip['count']
|
||||||
|
halfStep = 1.0 / count
|
||||||
|
startRounder = math.floor if startInclusive else math.ceil
|
||||||
|
endRounder = math.ceil if endInclusive else math.floor
|
||||||
|
startIndex = max(0, min(startRounder(start * (count - 1) + halfStep), count - 1))
|
||||||
|
endIndex = max(0, min(endRounder(end * (count - 1) - halfStep), count - 1))
|
||||||
|
strip['first'] += startIndex
|
||||||
|
strip['start'] = addV(strip['start'], scaleV(strip['direction'], startIndex))
|
||||||
|
strip['count'] = (endIndex - startIndex) + 1
|
||||||
|
assert(strip['count'] >= 0)
|
||||||
|
return strip
|
||||||
|
|
||||||
|
# Only keep portions of the clip above the plane surface
|
||||||
|
def clipStripAgainstPlane(strip, origin, direction):
|
||||||
|
strip = dict(strip)
|
||||||
|
start = strip['start']
|
||||||
|
stripDirection = strip['direction']
|
||||||
|
count = strip['count']
|
||||||
|
|
||||||
|
# If we don't have any LEDs left, then there's nothing to clip against
|
||||||
|
if count == 0:
|
||||||
|
return strip
|
||||||
|
|
||||||
|
assert(count > 0)
|
||||||
|
|
||||||
|
startToEnd = scaleV(stripDirection, count - 1)
|
||||||
|
startToOrigin = subV(origin, start)
|
||||||
|
towardsPlane = dotV(startToEnd, direction) # 0 if in plane, positive if along plane direction
|
||||||
|
abovePlane = dotV(startToOrigin, direction) # negative if start above
|
||||||
|
|
||||||
|
isStartAbove = abovePlane < 0.0
|
||||||
|
isTowardsPlane = (towardsPlane < 0.0) == isStartAbove
|
||||||
|
|
||||||
|
# Strip parallel to plane
|
||||||
|
if (abs(towardsPlane) <= 1.0e-6):
|
||||||
|
|
||||||
|
# Force no intersection
|
||||||
|
t = -1.0
|
||||||
|
|
||||||
|
# Any other case
|
||||||
|
else:
|
||||||
|
t = abovePlane / towardsPlane
|
||||||
|
|
||||||
|
print("above" if isStartAbove else "behind", "towards" if isTowardsPlane else "away", t)
|
||||||
|
|
||||||
|
# No intersection
|
||||||
|
if t <= 0 or t >= 1:
|
||||||
|
# Remove everything if we are below the plane
|
||||||
|
if not isStartAbove:
|
||||||
|
strip['count'] = 0
|
||||||
|
return strip
|
||||||
|
|
||||||
|
# Handle intersection
|
||||||
|
if isStartAbove:
|
||||||
|
if isTowardsPlane:
|
||||||
|
# Remove end of strip
|
||||||
|
strip = extractStrip(strip, 0.0, True, t, True)
|
||||||
|
else:
|
||||||
|
# Keep entire strip
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if isTowardsPlane:
|
||||||
|
# Remove start of strip
|
||||||
|
strip = extractStrip(strip, t, True, 1.0, True)
|
||||||
|
else:
|
||||||
|
# Remove entire strip
|
||||||
|
strip['count'] = 0
|
||||||
|
return strip
|
||||||
|
|
||||||
|
def clipStripsAgainstPlane(strips, origin, direction):
|
||||||
|
clippedStrips = []
|
||||||
|
for strip in strips:
|
||||||
|
clippedStrip = clipStripAgainstPlane(strip, origin, direction)
|
||||||
|
clippedStrips += [clippedStrip]
|
||||||
|
return clippedStrips
|
||||||
|
|
||||||
|
def clipStripsAgainstAabb(strips, xyz0, xyz1):
|
||||||
|
strips = clipStripsAgainstPlane(strips, xyz0, ( 1, 0, 0))
|
||||||
|
strips = clipStripsAgainstPlane(strips, xyz1, (-1, 0, 0))
|
||||||
|
strips = clipStripsAgainstPlane(strips, xyz0, ( 0, 1, 0))
|
||||||
|
strips = clipStripsAgainstPlane(strips, xyz1, ( 0, -1, 0))
|
||||||
|
strips = clipStripsAgainstPlane(strips, xyz0, ( 0, 0, 1))
|
||||||
|
strips = clipStripsAgainstPlane(strips, xyz1, ( 0, 0, -1))
|
||||||
|
return strips
|
||||||
|
|
||||||
|
# Debug helpers
|
||||||
|
|
||||||
|
def generateObj(strips, onlyBounds=False):
|
||||||
|
s = []
|
||||||
|
vs = []
|
||||||
|
ls = []
|
||||||
|
for strip in strips:
|
||||||
|
count = strip['count']
|
||||||
|
assert(count >= 0)
|
||||||
|
l = []
|
||||||
|
if onlyBounds:
|
||||||
|
for i in range(2):
|
||||||
|
v = addV(strip['start'], scaleV(strip['direction'], i * (count - 1)))
|
||||||
|
vs += [v]
|
||||||
|
l += [len(vs)]
|
||||||
|
else:
|
||||||
|
for i in range(count):
|
||||||
|
v = addV(strip['start'], scaleV(strip['direction'], i))
|
||||||
|
vs += [v]
|
||||||
|
l += [len(vs)]
|
||||||
|
ls += [(l, strip['name'], strip['index'])]
|
||||||
|
for v in vs:
|
||||||
|
s += ["v %f %f %f" % (v[0], v[1], v[2])]
|
||||||
|
for l in ls:
|
||||||
|
s += ["# %s [%d]" % (l[1], l[2])]
|
||||||
|
if len(l[0]) >= 2:
|
||||||
|
s += ["l " + " ".join(["%d" % i for i in l[0]])]
|
||||||
|
return "\n".join(s)
|
||||||
|
|
||||||
|
def testStrip(a, b, count=10):
|
||||||
|
return {
|
||||||
|
'name': 'test',
|
||||||
|
'index': 0,
|
||||||
|
'start': a,
|
||||||
|
'direction': scaleV(subV(b, a), 1.0 / (count - 1)),
|
||||||
|
'first': 0,
|
||||||
|
'count': count
|
||||||
|
}
|
||||||
|
|
||||||
|
def debugStrip(strip):
|
||||||
|
strip = dict(strip)
|
||||||
|
strip['end'] = addV(strip['start'], scaleV(strip['direction'], strip['count'] - 1))
|
||||||
|
#del strip['direction']
|
||||||
|
return strip
|
||||||
|
|
||||||
|
|
||||||
|
def shadeStrips(strips, shader):
|
||||||
|
stripDatas = []
|
||||||
|
|
||||||
|
for strip in strips:
|
||||||
|
data = []
|
||||||
|
first = strip['first']
|
||||||
|
|
||||||
|
def emitData():
|
||||||
|
stripData = {
|
||||||
|
'first': strip['first'],
|
||||||
|
'data': bytes(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in range(strip['count']):
|
||||||
|
color = shader(position)
|
||||||
|
data += [color]
|
||||||
|
|
||||||
|
emitData()
|
||||||
|
|
||||||
|
stripDatas += [stripData]
|
||||||
|
return stripData
|
||||||
|
|
||||||
|
|
||||||
|
def sendSequence(sock, start, data, d = 1):
|
||||||
|
|
||||||
|
# https://kno.wled.ge/interfaces/udp-realtime/
|
||||||
|
#FIXME: Somehow 489 does not work
|
||||||
|
maxChunk = 489-10
|
||||||
|
while len(data) > 0:
|
||||||
|
|
||||||
|
buf = bytearray()
|
||||||
|
|
||||||
|
buf += bytes([4, d]) # DNRGB, Number of seconds before normal mode
|
||||||
|
|
||||||
|
buf += bytes([(start >> 8) & 0xFF])
|
||||||
|
buf += bytes([(start >> 0) & 0xFF])
|
||||||
|
|
||||||
|
buf += data[0:maxChunk*3]
|
||||||
|
data = data[maxChunk*3:]
|
||||||
|
start += maxChunk
|
||||||
|
|
||||||
|
sock.send(buf)
|
||||||
|
|
||||||
|
def fade(a, b, count):
|
||||||
|
buf = []
|
||||||
|
for i in range(count):
|
||||||
|
p = i / (count - 1)
|
||||||
|
buf += lerp(a, b, p)
|
||||||
|
return buf
|
||||||
|
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## LeitLightLightLeitLeidLite
|
||||||
|
|
||||||
|
LeitLightLightLeitLeidLite macht dein Leid mit den Leitstellen Lights Lite'r indem es Light Leitet.
|
41
test.py
Executable file
41
test.py
Executable file
|
@ -0,0 +1,41 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import LeitLightLightLeitLeidLite as l6
|
||||||
|
|
||||||
|
rows = l6.rows
|
||||||
|
|
||||||
|
# Export 3D model of physical arrangement
|
||||||
|
sourceStrips = l6.getSourceStrips(rows)
|
||||||
|
print(sourceStrips)
|
||||||
|
strips = l6.bakeStrips(sourceStrips)
|
||||||
|
obj = l6.generateObj(strips)
|
||||||
|
open("/tmp/strips.obj", "w").write(obj)
|
||||||
|
obj = l6.generateObj(strips, True)
|
||||||
|
open("/tmp/strips-bounds.obj", "w").write(obj)
|
||||||
|
|
||||||
|
# From behind of plane to above plane
|
||||||
|
print(l6.debugStrip(l6.clipStripAgainstPlane(l6.testStrip((-10, 0, 0), (10, 0, 0), 21), (0, 0, 0), (1, 0, 0))))
|
||||||
|
|
||||||
|
# From above of plane to behind plane
|
||||||
|
print(l6.debugStrip(l6.clipStripAgainstPlane(l6.testStrip((10, 0, 0), (-10, 0, 0), 21), (0, 0, 0), (1, 0, 0))))
|
||||||
|
|
||||||
|
# Parallel above of plane
|
||||||
|
print(l6.debugStrip(l6.clipStripAgainstPlane(l6.testStrip((10, -10, 0), (10, 10, 0), 21), (0, 0, 0), (1, 0, 0))))
|
||||||
|
|
||||||
|
# Parallel behind of plane
|
||||||
|
print(l6.debugStrip(l6.clipStripAgainstPlane(l6.testStrip((-10, -10, 0), (-10, 10, 0), 21), (0, 0, 0), (1, 0, 0))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
stripsXp = l6.clipStripsAgainstPlane(strips, (100, 0, 0), (1, 0, 0))
|
||||||
|
obj = l6.generateObj(stripsXp)
|
||||||
|
open("/tmp/strips-xp.obj", "w").write(obj)
|
||||||
|
|
||||||
|
stripsYp = l6.clipStripsAgainstPlane(strips, (0, 50, 0), (0, 1, 0))
|
||||||
|
obj = l6.generateObj(stripsYp)
|
||||||
|
open("/tmp/strips-yp.obj", "w").write(obj)
|
||||||
|
|
||||||
|
stripsAabb = l6.clipStripsAgainstAabb(strips, (80, 60/2, -100), (120, 140/2, +100))
|
||||||
|
obj = l6.generateObj(stripsAabb)
|
||||||
|
open("/tmp/strips-aabb.obj", "w").write(obj)
|
||||||
|
|
Loading…
Add table
Reference in a new issue