Some example templates as SVG for laser cutting
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

125 lines
3.7 KiB

#!/usr/bin/env python3
"""
Visual Cryptograpy Experiment.
Create two paper cards (credit card size) which reveal the plaintext message when put over each other.
"""
import argparse
import secrets
import string
import svgwrite
import math
from HersheyFonts import HersheyFonts
STROKE_WIDTH = 0.2
RADIUS = 30
parser = argparse.ArgumentParser()
parser.add_argument("output")
parser.add_argument("--text")
parser.add_argument("--stroke-width", type=float, default=STROKE_WIDTH)
parser.add_argument("--radius", type=float, default=RADIUS)
args = parser.parse_args()
STROKE_WIDTH = args.stroke_width
RADIUS = args.radius
dwg = svgwrite.Drawing(
args.output, ("210mm", "297mm"), profile="tiny", viewBox="0 0 210 297"
)
# add stroke width to match outer radius
c = dwg.circle(
r=RADIUS + STROKE_WIDTH, stroke_width=STROKE_WIDTH, fill="none", stroke="black"
)
dwg.add(c)
# do not add stroke width to get 4.1mm hole
c = dwg.circle(r=2, stroke_width=STROKE_WIDTH, fill="none", stroke="black")
dwg.add(c)
WIDTH = 7
HEIGHT = 7.5
inner_reserved = 0.8
slice_share = 0.7
for j in range(3):
r1 = (j + inner_reserved + 0.8) * HEIGHT
r2 = (j + inner_reserved) * HEIGHT
slices = int((math.pi * 2 * (r1 + r2) / 2) // WIDTH)
slices_to_skip = set()
for i in range(slices // 2):
slices_to_skip.add(secrets.choice(list(set(range(slices)) - slices_to_skip)))
print(j, slices, len(slices_to_skip))
slice_deg = (2 * math.pi) / slices
for i in range(slices):
offset = 0 if j % 2 == 0 else (1 - slice_share / 2)
deg = (i + offset) * slice_deg
ndeg = (i + offset + slice_share) * slice_deg
skip = i in slices_to_skip
if skip:
thefont = HersheyFonts()
thefont.load_default_font("futural")
thefont.normalize_rendering(5)
g2 = dwg.g()
g = dwg.g()
text = secrets.choice(string.ascii_uppercase + string.digits)
strokes = list(thefont.strokes_for_text(text))
minp = [math.inf, math.inf]
maxp = [-math.inf, -math.inf]
for stroke in strokes:
for d in range(2):
for p in stroke:
minp[d] = min(minp[d], p[d])
maxp[d] = max(maxp[d], p[d])
centerx = (minp[0] + maxp[0]) / 2
centery = (minp[1] + maxp[1]) / 2
for stroke in strokes:
for idx, point in enumerate(stroke):
stroke[idx] = (point[0] - centerx, -point[1] + centery)
pl = dwg.polyline(
stroke, stroke="green", stroke_width=STROKE_WIDTH, fill="none"
)
g.add(pl)
g.rotate(180 - ((180 / math.pi) * (deg + ndeg) / 2))
g2.add(g)
g2.translate(
(r1 + r2) / 2.0 * math.sin((deg + ndeg) / 2),
(r1 + r2) / 2.0 * math.cos((deg + ndeg) / 2),
)
dwg.add(g2)
p = dwg.path(
stroke_width=STROKE_WIDTH, fill="none", stroke="green" if skip else "black"
)
p.push("M")
p.push(r1 * math.sin(deg))
p.push(r1 * math.cos(deg))
p.push_arc(
(r1 * math.sin(ndeg), r1 * math.cos(ndeg)),
0,
r1,
large_arc=False,
angle_dir="-",
absolute=True,
)
p.push("L")
p.push(r2 * math.sin(ndeg))
p.push(r2 * math.cos(ndeg))
p.push_arc(
(r2 * math.sin(deg), r2 * math.cos(deg)),
0,
r2,
large_arc=False,
angle_dir="+",
absolute=True,
)
p.push("Z")
dwg.add(p)
dwg.save()