"""Draw the decal atlas and the pendant HUD texture (Pillow). Run: python make_decals.py """ import sys, os, math, random from PIL import Image, ImageDraw, ImageFont, ImageFilter out = sys.argv[1] os.makedirs(out, exist_ok=True) F = "C:/Windows/Fonts/" def font(name, size): for n in (name, "arialbd.ttf"): try: return ImageFont.truetype(F + n, size) except Exception: pass return ImageFont.load_default() W = H = 2048 atlas = Image.new("RGBA", (W, H), (0, 0, 0, 0)) def paste_fit(img, rect, rot=None): x, y, w, h = rect if rot: img = img.rotate(rot, expand=True) img = img.resize((w - 8, h - 8), Image.LANCZOS) atlas.alpha_composite(img, (x + 4, y + 4)) def text_img(txt, fnt, color, pad=10, shear=0.0): tmp = Image.new("RGBA", (4000, 1400), (0, 0, 0, 0)) d = ImageDraw.Draw(tmp) d.text((pad, pad), txt, font=fnt, fill=color) bb = tmp.getbbox() tmp = tmp.crop(bb) if shear: w, h = tmp.size tmp = tmp.transform((w + int(abs(shear) * h), h), Image.AFFINE, (1, shear, -shear * h if shear > 0 else 0, 0, 1, 0), Image.BICUBIC) return tmp WHITE = (238, 241, 245, 255) # --- A: vertical chevron stack (generic sci-fi panel marking, points downwards) a_ = Image.new("RGBA", (512, 2048), (0, 0, 0, 0)) d = ImageDraw.Draw(a_) for i in range(7): y0 = 40 + i * 270 wd = 440 - i * 22 x0 = (512 - wd) // 2 d.polygon([(x0, y0), (x0 + wd // 2, y0 + 130), (x0 + wd, y0), (x0 + wd, y0 + 56), (x0 + wd // 2, y0 + 186), (x0, y0 + 56)], fill=WHITE) d.rectangle((226, 1950, 286, 2030), fill=WHITE) atlas.alpha_composite(a_, (0, 0)) # --- B: column of hexagon outlines + dots b_ = Image.new("RGBA", (320, 1024), (0, 0, 0, 0)) d = ImageDraw.Draw(b_) for i in range(4): cx, cy, r = 160, 90 + i * 220, 78 pts = [(cx + r * math.cos(math.radians(60 * k)), cy + r * math.sin(math.radians(60 * k))) for k in range(6)] d.polygon(pts, outline=WHITE, width=14) d.ellipse((cx - 14, cy - 14, cx + 14, cy + 14), fill=WHITE) for i in range(5): d.ellipse((150, 940 + 0 * i, 170, 960), fill=WHITE) atlas.alpha_composite(b_, (512, 0)) # --- C: caution stripes (yellow / black diagonals) in a rounded band c_ = Image.new("RGBA", (1024, 384), (0, 0, 0, 0)) d = ImageDraw.Draw(c_) d.rounded_rectangle((8, 8, 1016, 376), radius=40, fill=(245, 205, 20, 255)) mask = Image.new("L", (1024, 384), 0) md = ImageDraw.Draw(mask) for x in range(-400, 1100, 120): md.polygon([(x, 384), (x + 60, 384), (x + 60 + 384, 0), (x + 384, 0)], fill=255) dark = Image.new("RGBA", (1024, 384), (22, 22, 22, 255)) rr = Image.new("L", (1024, 384), 0) ImageDraw.Draw(rr).rounded_rectangle((8, 8, 1016, 376), radius=40, fill=255) from PIL import ImageChops mask = ImageChops.multiply(mask, rr) c_.paste(dark, (0, 0), mask) atlas.alpha_composite(c_, (832, 0)) # --- D: generic emblem (ring + hexagon, no lettering) c = Image.new("RGBA", (384, 384), (0, 0, 0, 0)) d = ImageDraw.Draw(c) d.ellipse((12, 12, 372, 372), outline=WHITE, width=24) pts = [(192 + 120 * math.cos(math.radians(60 * k + 30)), 192 + 120 * math.sin(math.radians(60 * k + 30))) for k in range(6)] d.polygon(pts, outline=WHITE, width=20) d.ellipse((160, 160, 224, 224), fill=WHITE) atlas.alpha_composite(c, (832, 400)) # --- hazard label hz = Image.new("RGBA", (256, 256), (0, 0, 0, 0)) d = ImageDraw.Draw(hz) d.rounded_rectangle((8, 8, 248, 248), radius=22, fill=(255, 140, 20, 255)) d.polygon([(128, 40), (222, 208), (34, 208)], fill=(20, 20, 20, 255)) d.polygon([(128, 62), (202, 196), (54, 196)], fill=(255, 140, 20, 255)) d.rectangle((120, 100, 136, 156), fill=(20, 20, 20, 255)) d.ellipse((119, 166, 137, 184), fill=(20, 20, 20, 255)) atlas.alpha_composite(hz, (1232, 400)) # --- electrical label el = Image.new("RGBA", (256, 256), (0, 0, 0, 0)) d = ImageDraw.Draw(el) d.rounded_rectangle((8, 8, 248, 248), radius=22, fill=(245, 205, 20, 255)) d.polygon([(140, 30), (82, 138), (122, 138), (104, 226), (176, 108), (134, 108)], fill=(20, 20, 20, 255)) atlas.alpha_composite(el, (1504, 400)) # --- sticky note nt = Image.new("RGBA", (256, 256), (0, 0, 0, 0)) d = ImageDraw.Draw(nt) d.rectangle((14, 14, 242, 242), fill=(244, 222, 70, 255)) for i, yy in enumerate((70, 110, 150, 190)): d.line((40, yy, 216 - (i % 2) * 50, yy), fill=(60, 55, 20, 255), width=6) atlas.alpha_composite(nt, (1776, 400)) atlas.save(os.path.join(out, "T_VMC_Decals_BaseColor.png")) # ---------------------------------------------------------------- pendant HUD (cyan UI on dark glass) HW, HH = 1024, 640 hud = Image.new("RGBA", (HW, HH), (4, 10, 16, 255)) d = ImageDraw.Draw(hud) CY = (40, 220, 255, 255); CYD = (20, 110, 140, 255); AM = (255, 170, 40, 255); RD = (255, 60, 40, 255) mono = font("consola.ttf", 30); monos = font("consola.ttf", 22); mb = font("consolab.ttf", 36) # grid for gx in range(0, HW, 64): d.line((gx, 0, gx, HH), fill=(10, 30, 40, 255), width=1) for gy in range(0, HH, 64): d.line((0, gy, HW, gy), fill=(10, 30, 40, 255), width=1) d.rectangle((0, 0, HW - 1, 52), fill=(8, 40, 54, 255)) d.text((18, 8), "VMC-01 // SPINDLE 12000 RPM", font=mb, fill=CY) d.text((HW - 220, 12), "AUTO RUN", font=mono, fill=AM) # axis readouts for i, (ax, val) in enumerate((("X", "+0412.350"), ("Y", "-0128.075"), ("Z", "+0250.000"))): y = 80 + i * 70 d.rectangle((18, y, 330, y + 56), outline=CYD, width=2) d.text((30, y + 10), ax, font=mb, fill=CY) d.text((90, y + 12), val, font=mb, fill=(220, 250, 255, 255)) # tool-path plot px0, py0, px1, py1 = 360, 80, 1000, 380 d.rectangle((px0, py0, px1, py1), outline=CYD, width=2) pts = [] for k in range(0, 360, 6): r = math.radians(k) pts.append((px0 + 320 + 200 * math.cos(r) * (1 + 0.15 * math.cos(5 * r)), py0 + 150 + 105 * math.sin(r) * (1 + 0.15 * math.cos(5 * r)))) d.line(pts + [pts[0]], fill=CY, width=3) d.line((px0 + 20, py0 + 150, px1 - 20, py0 + 150), fill=CYD, width=1) d.line((px0 + 320, py0 + 12, px0 + 320, py1 - 12), fill=CYD, width=1) d.ellipse((px0 + 320 + 200 - 8, py0 + 150 - 8, px0 + 320 + 200 + 8, py0 + 150 + 8), fill=AM) # bars labels = (("FEED", 0.78, CY), ("SPDL LOAD", 0.46, AM), ("COOLANT", 0.92, CY), ("TEMP", 0.34, RD)) for i, (lb, v, col) in enumerate(labels): y = 410 + i * 54 d.text((18, y), lb, font=monos, fill=(170, 220, 235, 255)) d.rectangle((200, y + 4, 700, y + 28), outline=CYD, width=2) d.rectangle((202, y + 6, 202 + int(496 * v), y + 26), fill=col) # program lines lines = ("N120 G01 X412.35 Y-128.07", "N130 G03 X380.0 Y-90.0 R32", "N140 M08 (COOLANT ON)", "N150 G01 Z-4.2 F900") for i, ln in enumerate(lines): d.text((730, 410 + i * 34), ln, font=monos, fill=(150, 235, 255, 255) if i != 1 else AM) d.rectangle((0, HH - 40, HW, HH), fill=(8, 40, 54, 255)) d.text((18, HH - 34), "F1 MAIN F2 TOOLS F3 PROG F4 DIAG", font=monos, fill=CY) hud = hud.filter(ImageFilter.GaussianBlur(0.4)) hud.convert("RGB").save(os.path.join(out, "T_VMC_PendantHUD_Emissive.png")) print("ok", os.listdir(out))