showcase.py
3.6 kB · python · 121 lines
1import sys23import lib # noqa: F40145from PIL import Image, ImageDraw, ImageFont6from mrlypy.six import designs7from mrlypy.six.geometry import radial8from mrlypy.six.renderer import draw9from mrlypy.two import designs as designs_2d10from mrlypy.two.renderer import to_image1112from lib.canvas import H3, INK, PAPER, quantize13from lib.gif import write_gif14from lib.paths import GIFS, ensure, show15from lib.terminal import menu1617NUMBERS = [3, 5, 7]18LEVEL = 219RADIUS = 220SCALE = 1021CANVAS = 81022COLORS = 6423STRIP = 11024FAMILIES = ["carpet", "net", "tree", "void"]25VIEWS = ["flat", "iso", "pro", "cut"]2627# FRAME2829def label(canvas, family):30 try:31 font = ImageFont.load_default(48)32 except TypeError:33 font = ImageFont.load_default()34 ImageDraw.Draw(canvas).text((30, CANVAS - 78), family, fill=INK.to_rgb(), font=font)3536def flat_frame(family, number):37 cell = getattr(designs_2d, f"{family}_2d")(number, LEVEL).paint()38 image = to_image(cell, max(1, (CANVAS - STRIP) // cell.height))39 canvas = Image.new("RGB", (CANVAS, CANVAS), PAPER.to_rgb())40 canvas.paste(image, ((CANVAS - image.width) // 2, (CANVAS - STRIP - image.height) // 2), image)41 label(canvas, family)42 return quantize(canvas, COLORS)4344def frame(family, projection, number):45 if projection == "flat":46 return flat_frame(family, number)47 cell = getattr(designs, f"{family}_{projection}")(number, LEVEL).paint()48 sheet = radial(cell, RADIUS)49 image = draw(sheet, scale=SCALE, start=1 - cell.start)50 w, h = image.size51 if cell.orientation == "horizontal":52 image = image.resize((w, round(h * H3)), Image.LANCZOS)53 else:54 image = image.resize((round(w * H3), h), Image.LANCZOS)55 image.thumbnail((CANVAS, CANVAS), Image.LANCZOS)56 canvas = Image.new("RGB", (CANVAS, CANVAS), PAPER.to_rgb())57 canvas.paste(image, ((CANVAS - image.width) // 2, (CANVAS - image.height) // 2), image)58 label(canvas, family)59 return quantize(canvas, COLORS)6061# SHEETS6263def sheets(projection, numbers):64 for number in numbers:65 frames = [frame(family, projection, number) for family in FAMILIES]66 path = GIFS / f"showcase-{projection}-{number}.gif"67 write_gif(path, frames)68 print(f"{show(path)} ({' '.join(FAMILIES)})")69 return 07071def flat(numbers):72 return sheets("flat", numbers)7374def iso(numbers):75 return sheets("iso", numbers)7677def pro(numbers):78 return sheets("pro", numbers)7980def cut(numbers):81 return sheets("cut", numbers)8283def every(numbers):84 for projection in VIEWS:85 sheets(projection, numbers)86 return 08788def pick(token):89 if not token.isdigit() or int(token) not in NUMBERS:90 sys.exit(f"'{token}' is not in the showcase: " + ", ".join(str(n) for n in NUMBERS))91 return [int(token)]9293# TERMINAL9495COMMANDS = {96 "all": (every, "every view at every number"),97 "flat": (flat, "the family rule in 2D, before the third axis"),98 "iso": (iso, "the whole sponge, seen isometrically"),99 "pro": (pro, "the three faces the sponge shows you"),100 "cut": (cut, "the diagonal slice through the middle"),101}102103def help():104 menu(f"showcase.py <command> <number> {' '.join(FAMILIES)} at level {LEVEL}",105 COMMANDS,106 [str(number) for number in NUMBERS],107 "a command alone sweeps every number; add a number to draw just that one.")108109def terminal():110 match sys.argv[1:]:111 case [cmd] if cmd in COMMANDS:112 ensure()113 sys.exit(COMMANDS[cmd][0](NUMBERS) or 0)114 case [cmd, token] if cmd in COMMANDS:115 ensure()116 sys.exit(COMMANDS[cmd][0](pick(token)) or 0)117 case _:118 help()119120if __name__ == "__main__":121 terminal()