"""Textures drawn for the nutrition vat (system Python with PIL): python nv_textures.py -> ../textures/T_NV_Decals.png, T_NV_CanLabel.png, T_NV_Bubbles.png, T_NV_Perforation.png T_NV_Decals (2048 x 1024, RGBA): the side label (lowercase, after the picture's lettering, with this vat's own word instead of the picture's), a generic warning sticker, the keypad's digits. Their places on the pod are DECALS in nv_lib.py. No maker or brand names anywhere. T_NV_CanLabel (1024 x 512): the canister's wrap: smoked glass with a white label facing out (local +x). T_NV_Bubbles (1024 x 1024, grey): bubbles for the liquid, tiling round the tank (u) and up it (v). T_NV_Perforation (128 x 128, grey): one hole of the grille, tiled every 4 mm. """ import os, math, random from PIL import Image, ImageDraw, ImageFont, ImageFilter HERE = os.path.dirname(os.path.abspath(__file__)) OUT = os.path.join(os.path.dirname(HERE), "textures") os.makedirs(OUT, exist_ok=True) FONT = r"C:\Windows\Fonts\bahnschrift.ttf" FONT_B = r"C:\Windows\Fonts\arialbd.ttf" LABEL_WORD = "nutrivat" def font(path, size): return ImageFont.truetype(path, size) def decals(): im = Image.new("RGBA", (2048, 1024), (0, 0, 0, 0)) d = ImageDraw.Draw(im) # label: dark sage letters, lowercase, like the picture's side lettering f = font(FONT, 210) try: f.set_variation_by_name("SemiLight") except Exception: pass tb = d.textbbox((0, 0), LABEL_WORD, font=f) w, h = tb[2] - tb[0], tb[3] - tb[1] d.text(((1024 - w) / 2 - tb[0], (297 - h) / 2 - tb[1]), LABEL_WORD, font=f, fill=(58, 74, 68, 235)) # warning sticker (1024..1543 x 0..300): white card, pictogram, an orange band, small grey text lines x0, y0, x1, y1 = 1030, 8, 1537, 292 d.rounded_rectangle((x0, y0, x1, y1), 14, fill=(236, 236, 230, 255), outline=(40, 40, 40, 255), width=4) d.rectangle((x0 + 18, y0 + 26, x0 + 168, y1 - 26), fill=(250, 250, 248, 255), outline=(20, 20, 20, 255), width=6) cx, cy = x0 + 93, (y0 + y1) / 2 for a in (45, -45): # a generic crossed-tools pictogram t = math.radians(a) dx, dy = math.cos(t) * 52, math.sin(t) * 52 d.line((cx - dx, cy - dy, cx + dx, cy + dy), fill=(15, 15, 15, 255), width=16) d.ellipse((cx + dx - 14, cy + dy - 14, cx + dx + 14, cy + dy + 14), fill=(15, 15, 15, 255)) d.rectangle((x0 + 190, y0 + 26, x1 - 18, y0 + 92), fill=(232, 120, 24, 255)) fb = font(FONT_B, 46) d.text((x0 + 204, y0 + 34), "CAUTION", font=fb, fill=(20, 20, 20, 255)) rnd = random.Random(3) for k in range(5): yy = y0 + 118 + k * 30 d.rectangle((x0 + 196, yy, x0 + 196 + rnd.randint(170, 300), yy + 12), fill=(120, 120, 118, 255)) # keypad digits (1536..2048 x 0..512): red figures at the nine keys fd = font(FONT_B, 92) for j in range(3): for i in range(3): n = str(1 + i + 3 * j) cx, cy = 1536 + 512 * (1 + 2 * i) / 6, 512 * (1 + 2 * j) / 6 tb = d.textbbox((0, 0), n, font=fd) d.text((cx - (tb[2] + tb[0]) / 2, cy - (tb[3] + tb[1]) / 2), n, font=fd, fill=(205, 30, 26, 255)) im.save(os.path.join(OUT, "T_NV_Decals.png")) def can_label(): W, H = 1024, 512 im = Image.new("RGB", (W, H), (150, 160, 155)) d = ImageDraw.Draw(im) rnd = random.Random(11) for y in range(H): # smoked glass: a faint vertical sheen pass # the label faces local +x: u 0 at +x, so it spans u -0.2..0.2 (wrapping) lw = int(W * 0.32) lab = Image.new("RGB", (lw, int(H * 0.80)), (232, 232, 226)) ld = ImageDraw.Draw(lab) ld.rectangle((0, 0, lw - 1, 10), fill=(120, 120, 118)) fb = font(FONT_B, 26) ld.text((14, 16), "NUTRIENT CONC.", font=fb, fill=(40, 40, 40)) # hazard diamond cx, cy, r = lw * 0.74, 150, 46 ld.polygon([(cx, cy - r), (cx + r, cy), (cx, cy + r), (cx - r, cy)], fill=(240, 150, 30), outline=(30, 30, 30)) ld.text((cx - 8, cy - 22), "!", font=font(FONT_B, 40), fill=(25, 25, 25)) for k in range(9): yy = 70 + k * 26 ld.rectangle((16, yy, 16 + rnd.randint(90, 150), yy + 10), fill=(110, 110, 108)) for k in range(40): # a bar code x = 16 + k * 5 if rnd.random() < 0.6 and x < lw - 20: ld.rectangle((x, 320, x + rnd.choice((1, 2, 3)), 370), fill=(25, 25, 25)) ld.rectangle((0, lab.height - 10, lw - 1, lab.height - 1), fill=(120, 120, 118)) y0 = int(H * 0.10) im.paste(lab.crop((lw // 2, 0, lw, lab.height)), (0, y0)) im.paste(lab.crop((0, 0, lw // 2, lab.height)), (W - lw // 2, y0)) im.save(os.path.join(OUT, "T_NV_CanLabel.png")) def bubbles(): N = 1024 im = Image.new("L", (N, N), 0) d = ImageDraw.Draw(im) rnd = random.Random(5) streams = [0.12, 0.45, 0.78] # rising columns round the tank for _ in range(420): s = rnd.choice(streams) u = (s + rnd.gauss(0, 0.035)) % 1.0 v = rnd.random() r = max(1.6, rnd.gauss(3.6, 1.6)) * (0.6 + 0.7 * v) # bigger toward the top x, y = u * N, (1 - v) * N for ox in (-N, 0, N): # wrap round for oy in (-N, 0, N): X, Yy = x + ox, y + oy if -20 < X < N + 20 and -20 < Yy < N + 20: d.ellipse((X - r, Yy - r, X + r, Yy + r), outline=230, width=max(1, int(r * 0.35))) d.ellipse((X - r * 0.5, Yy - r * 0.75, X - r * 0.05, Yy - r * 0.3), fill=255) im = im.filter(ImageFilter.GaussianBlur(0.7)) im.save(os.path.join(OUT, "T_NV_Bubbles.png")) def perforation(): N = 128 im = Image.new("L", (N, N), 210) d = ImageDraw.Draw(im) d.ellipse((N * 0.22, N * 0.22, N * 0.78, N * 0.78), fill=10) im = im.filter(ImageFilter.GaussianBlur(2.0)) im.save(os.path.join(OUT, "T_NV_Perforation.png")) decals() can_label() bubbles() perforation() print("TEXTURES OK", sorted(os.listdir(OUT)))