-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdragon_print.py
More file actions
88 lines (70 loc) · 2.9 KB
/
Copy pathdragon_print.py
File metadata and controls
88 lines (70 loc) · 2.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#!/usr/bin/env python3
"""Print a Heighway dragon curve on the S01 thermal printer.
The dragon curve is what you get by repeatedly folding a strip of paper in half
and unfolding each crease to 90 degrees: a single non-crossing line that tiles
the plane into an intricate dragon. Higher orders add detail.
Examples:
python dragon_print.py # order 12
python dragon_print.py --order 13
python dragon_print.py --no-print
"""
from __future__ import annotations
import argparse
from pathlib import Path
from PIL import Image, ImageDraw
from print_common import ROOT, Card
SS = 3 # supersample
def turns(order: int) -> list[int]:
seq = [1]
for _ in range(order - 1):
seq = seq + [1] + [1 - t for t in reversed(seq)] # fold rule
return seq
def points(order: int):
dx, dy = 1, 0
x = y = 0
pts = [(0, 0)]
for t in [None] + turns(order): # first segment has no preceding turn
if t == 1:
dx, dy = -dy, dx # left
elif t == 0:
dx, dy = dy, -dx # right
x += dx
y += dy
pts.append((x, y))
return pts
def render(order: int, target: int) -> Image.Image:
pts = points(order)
xs = [p[0] for p in pts]
ys = [p[1] for p in pts]
minx, maxx, miny, maxy = min(xs), max(xs), min(ys), max(ys)
size = target * SS
pad = size * 0.05
span = max(maxx - minx, maxy - miny) or 1
scale = (size - 2 * pad) / span
ox = (size - (maxx - minx) * scale) / 2
oy = (size - (maxy - miny) * scale) / 2
scaled = [(ox + (x - minx) * scale, oy + (y - miny) * scale) for x, y in pts]
img = Image.new("L", (size, size), 255)
ImageDraw.Draw(img).line(scaled, fill=0, width=SS, joint="curve")
return img.resize((target, target), Image.LANCZOS)
def main() -> int:
parser = argparse.ArgumentParser(description="Print a Heighway dragon curve on the S01 thermal printer.")
parser.add_argument("--order", type=int, default=12, help="Fold order (default: 12).")
parser.add_argument("--out", type=Path, default=None)
parser.add_argument("--darkness", type=int, choices=range(1, 6), default=3)
parser.add_argument("--bottom-feed", type=int, default=24)
parser.add_argument("--no-print", action="store_true")
args = parser.parse_args()
if not 6 <= args.order <= 15:
parser.error("--order must be 6-15")
print(f"Folding an order-{args.order} dragon ({2 ** args.order} segments) ...")
card = Card()
img = render(args.order, card.inner_w)
card.title("DRAGON CURVE")
card.line(f"order {args.order} - {2 ** args.order} folds", size=12, bold=False, center=True)
card.gap(4).image(img, border=True)
card.footer("Heighway dragon", "fold & unfold")
out = args.out or ROOT / f"dragon_{args.order}.png"
return card.finish(out, args.bottom_feed, args.darkness, do_print=not args.no_print)
if __name__ == "__main__":
raise SystemExit(main())