Files
Aime/app/lab/preview_fan.py
Claude db50c9616d 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.
2026-08-17 22:03:17 +02:00

73 lines
2.8 KiB
Python

# 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")