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.
73 lines
2.8 KiB
Python
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")
|