"""Draw the decal atlas (Pillow). Generic markings only, no brand or maker names. Run: python make_decals.py """ import sys, os, math from PIL import Image, ImageDraw, ImageFont out = sys.argv[1] os.makedirs(out, exist_ok=True) F = "C:/Windows/Fonts/" def font(size, name="bahnschrift.ttf"): for n in (name, "arialbd.ttf"): try: return ImageFont.truetype(F + n, size) except Exception: pass return ImageFont.load_default() W = H = 1024 atlas = Image.new("RGBA", (W, H), (0, 0, 0, 0)) INK = (34, 38, 44, 255) # dark graphite lettering on the grey-white paint (arm numbers) LIGHT = (226, 230, 235, 255) # light lettering for the marks that sit on graphite pocket floors CYAN = (40, 200, 235, 255) YEL = (245, 190, 20, 255) BLK = (22, 22, 24, 255) # rect table shared with build_dockarm.py (x, y, w, h), origin top-left RECT = {"n01": (0, 0, 320, 224), "n02": (320, 0, 320, 224), "n03": (640, 0, 320, 224), "chev": (0, 256, 256, 128), "warn": (288, 256, 160, 160), "turn": (480, 256, 160, 160), "label": (0, 448, 640, 160)} def number(key, txt): x, y, w, h = RECT[key] d = ImageDraw.Draw(atlas) f = font(170) bb = d.textbbox((0, 0), txt, font=f) tx = x + 96 + (w - 110 - (bb[2] - bb[0])) // 2 - bb[0] ty = y + (h - (bb[3] - bb[1])) // 2 - bb[1] - 6 d.text((tx, ty), txt, font=f, fill=INK) # corner brackets and a cyan index bar for (cx, cy, sx, sy) in ((x + 10, y + 10, 1, 1), (x + w - 10, y + h - 10, -1, -1)): d.line([(cx, cy), (cx + 46 * sx, cy)], fill=INK, width=7) d.line([(cx, cy), (cx, cy + 46 * sy)], fill=INK, width=7) d.rectangle([x + 34, y + 54, x + 50, y + h - 54], fill=CYAN) d.text((x + 64, y + h - 44), "CRADLE", font=font(26), fill=INK) number("n01", "01"); number("n02", "02"); number("n03", "03") # hazard chevrons: 256 px strip, the build maps the middle 128 px (one period) per quad so it tiles x, y, w, h = RECT["chev"] strip = Image.new("RGBA", (w, h), YEL) d = ImageDraw.Draw(strip) for i in range(-4, 8): x0 = i * 64 d.polygon([(x0, h), (x0 + 32, h), (x0 + 32 + h, 0), (x0 + h, 0)], fill=BLK) atlas.alpha_composite(strip, (x, y)) # warning triangle (suspended load) x, y, w, h = RECT["warn"] d = ImageDraw.Draw(atlas) d.polygon([(x + w // 2, y + 8), (x + w - 8, y + h - 14), (x + 8, y + h - 14)], fill=YEL, outline=BLK) d.line([(x + w // 2, y + 8), (x + w - 8, y + h - 14), (x + 8, y + h - 14), (x + w // 2, y + 8)], fill=BLK, width=9) d.rectangle([x + w // 2 - 22, y + 84, x + w // 2 + 22, y + 112], fill=BLK) # the load d.line([(x + w // 2, y + 52), (x + w // 2, y + 84)], fill=BLK, width=5) # its line d.polygon([(x + w // 2 - 14, y + 122), (x + w // 2 + 14, y + 122), (x + w // 2, y + 138)], fill=BLK) # rotation arrows x, y, w, h = RECT["turn"] cx, cy, r = x + w // 2, y + h // 2, 56 d.arc([cx - r, cy - r, cx + r, cy + r], 200, 520 - 40, fill=LIGHT, width=12) a = math.radians(200) px, py = cx + r * math.cos(a), cy + r * math.sin(a) d.polygon([(px - 22, py + 6), (px + 20, py + 2), (px + 2, py - 30)], fill=LIGHT) d.ellipse([cx - 10, cy - 10, cx + 10, cy + 10], fill=CYAN) # label block x, y, w, h = RECT["label"] d.rectangle([x + 8, y + 8, x + w - 8, y + h - 8], outline=LIGHT, width=6) d.rectangle([x + 8, y + 8, x + 40, y + h - 8], fill=CYAN) d.text((x + 62, y + 22), "TENDER CRADLE", font=font(58), fill=LIGHT) d.text((x + 64, y + 96), "KEEP CLEAR BELOW // SWL 14 t", font=font(30), fill=LIGHT) atlas.save(os.path.join(out, "T_DockArm_Decals_BaseColor.png")) print("wrote", os.path.join(out, "T_DockArm_Decals_BaseColor.png"))