fix(gui): Ventilator-Icon und Speicheranzeige auf Aufnahme-Vorrat
Das erste Icon las sich als Kleeblatt. Jetzt vier gebogene Pinwheel-Fluegel in einem feststehenden Gehaeusering; nur die Fluegel drehen. Form vorab ohne Browser geprueft (lab/preview_fan.py rendert die Bezier-Kandidaten als PNG). Die Prozentzahl misst nicht mehr die Partition, sondern den Aufnahme-Vorrat: 100 % = freier Platz + alle Audiodateien unter recordings/, belegt = diese Dateien. System, Modelle und node_modules fallen aus Zaehler und Nenner heraus; der df-Wert steht als Fusszeile in der Klappkarte. sysstat.js scannt recordings/ per readdir+stat (kein du-Prozess) und puffert 60 s; status() ist dadurch async, die Route laeuft ueber wrap(). Doku: Kapitel 07 + Changelog.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
# Vorschau der Ventilator-Kandidaten ohne Browser: Bezier-Segmente abtasten,
|
||||
# rotieren, mit Pillow fuellen. Rechts jeweils die 22-px-Miniatur (Originalgroesse).
|
||||
import math
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
BG, FG, CASE = "#1d222a", "#e8804c", "#6f5344"
|
||||
|
||||
|
||||
def bez(p0, p1, p2, p3, n=80):
|
||||
for i in range(n + 1):
|
||||
t = i / n
|
||||
u = 1 - t
|
||||
yield (u**3 * p0[0] + 3 * u * u * t * p1[0] + 3 * u * t * t * p2[0] + t**3 * p3[0],
|
||||
u**3 * p0[1] + 3 * u * u * t * p1[1] + 3 * u * t * t * p2[1] + t**3 * p3[1])
|
||||
|
||||
|
||||
def outline(segs):
|
||||
pts = []
|
||||
for s in segs:
|
||||
pts.extend(bez(*s))
|
||||
return pts
|
||||
|
||||
|
||||
def rot(p, deg, c=12.0):
|
||||
a = math.radians(deg)
|
||||
x, y = p[0] - c, p[1] - c
|
||||
return (c + x * math.cos(a) - y * math.sin(a), c + x * math.sin(a) + y * math.cos(a))
|
||||
|
||||
|
||||
def draw(segs, blades, case_r, hub_r, size):
|
||||
S = size / 24
|
||||
img = Image.new("RGB", (size, size), BG)
|
||||
d = ImageDraw.Draw(img)
|
||||
if case_r:
|
||||
d.ellipse([(12 - case_r) * S, (12 - case_r) * S, (12 + case_r) * S, (12 + case_r) * S],
|
||||
outline=CASE, width=max(1, int(1.4 * S)))
|
||||
pts = outline(segs)
|
||||
step = 360 / blades
|
||||
for i in range(blades):
|
||||
d.polygon([(x * S, y * S) for x, y in (rot(p, i * step) for p in pts)], fill=FG)
|
||||
d.ellipse([(12 - hub_r) * S, (12 - hub_r) * S, (12 + hub_r) * S, (12 + hub_r) * S],
|
||||
fill=BG, outline=FG, width=max(1, int(1.5 * S)))
|
||||
return img
|
||||
|
||||
|
||||
CANDS = {
|
||||
"A_komma4": (4, [((12, 12), (11.4, 8.6), (11.4, 5.4), (12.7, 3.1)),
|
||||
((12.7, 3.1), (15.6, 4.2), (18.2, 6.6), (18.9, 9.4)),
|
||||
((18.9, 9.4), (17.2, 11.2), (14.6, 12), (12, 12))]),
|
||||
"B_schlank4": (4, [((12, 12), (10.6, 9), (10.4, 5.6), (12.2, 3.4)),
|
||||
((12.2, 3.4), (14.8, 4.6), (17.2, 7.2), (17.6, 10)),
|
||||
((17.6, 10), (16, 11.6), (13.8, 12.2), (12, 12))]),
|
||||
"C_breit3": (3, [((12, 12), (10.9, 8.4), (11.2, 4.8), (13.3, 2.9)),
|
||||
((13.3, 2.9), (16.6, 4.4), (18.8, 7.4), (19.2, 10.6)),
|
||||
((19.2, 10.6), (17, 12), (14.2, 12.4), (12, 12))]),
|
||||
"D_sichel5": (5, [((12, 12), (11.2, 9), (11.3, 5.8), (12.9, 3.6)),
|
||||
((12.9, 3.6), (15.1, 5), (16.9, 7.4), (17.3, 10.1)),
|
||||
((17.3, 10.1), (15.8, 11.4), (13.9, 12.1), (12, 12))]),
|
||||
}
|
||||
|
||||
pad = 12
|
||||
big, small = 200, 22
|
||||
W = pad + len(CANDS) * (big + pad)
|
||||
sheet = Image.new("RGB", (W, big + small + 3 * pad), BG)
|
||||
dr = ImageDraw.Draw(sheet)
|
||||
for i, (name, (blades, segs)) in enumerate(CANDS.items()):
|
||||
x = pad + i * (big + pad)
|
||||
sheet.paste(draw(segs, blades, 10.4, 2.1, big), (x, pad))
|
||||
sheet.paste(draw(segs, blades, 10.4, 2.1, small), (x + big // 2 - small // 2, big + 2 * pad))
|
||||
dr.text((x + 4, big + 2 * pad + small + 2), name, fill="#98a2b3")
|
||||
sheet.save("fan_preview.png")
|
||||
print("ok")
|
||||
Reference in New Issue
Block a user