1
0
Fork 0
mirror of https://github.com/imjasonh/terraform-playground synced 2026-07-07 23:35:16 +00:00
terraform-playground/snub-dodecahedron/preview.py
Cursor Agent 6e0225b175
Add parametric magnet pockets (default 1/8" dia x 1/16" deep) on each exterior face
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
2026-06-15 01:36:09 +00:00

107 lines
3.6 KiB
Python

# /// script
# requires-python = ">=3.9"
# dependencies = ["numpy>=1.24", "scipy>=1.10"]
# ///
"""Render an SVG preview of the unfolded net(s) and verify no faces overlap."""
from __future__ import annotations
import numpy as np
from snubgeom import SnubDodecahedron
from unfold import Unfolder, _convex_overlap, _shrink
def layout_pieces(pieces, gap=1.5):
"""Translate each piece so they sit side by side; return offsets."""
offsets = []
x_cursor = 0.0
for p in pieces:
x0, y0, x1, y1 = p.bbox()
offsets.append((x_cursor - x0, -y0))
x_cursor += (x1 - x0) + gap
return offsets
def verify_no_overlap(pieces):
"""Independent global check: no two faces in the same piece overlap."""
bad = 0
for p in pieces:
polys = [poly for _, poly in p.faces]
adj_share = {} # face index pairs that share an edge are allowed to touch
for i in range(len(polys)):
for j in range(i + 1, len(polys)):
a = _shrink(polys[i], 5e-3)
b = _shrink(polys[j], 5e-3)
if _convex_overlap(a, b):
bad += 1
return bad
def write_svg(pieces, path, scale=40.0, gap=1.5, magnet_radius=0.0):
offsets = layout_pieces(pieces, gap=gap)
# global bbox
minx = miny = 1e9
maxx = maxy = -1e9
for p, (ox, oy) in zip(pieces, offsets):
for _, poly in p.faces:
for x, y in poly:
minx = min(minx, x + ox)
miny = min(miny, y + oy)
maxx = max(maxx, x + ox)
maxy = max(maxy, y + oy)
pad = 1.0
W = (maxx - minx + 2 * pad) * scale
H = (maxy - miny + 2 * pad) * scale
def X(x):
return (x - minx + pad) * scale
def Y(y):
return H - (y - miny + pad) * scale # flip for screen coords
out = [
f'<svg xmlns="http://www.w3.org/2000/svg" width="{W:.0f}" height="{H:.0f}" '
f'viewBox="0 0 {W:.0f} {H:.0f}">',
f'<rect width="{W:.0f}" height="{H:.0f}" fill="white"/>',
]
for p, (ox, oy) in zip(pieces, offsets):
for fi, poly in p.faces:
pts = " ".join(f"{X(x + ox):.2f},{Y(y + oy):.2f}" for x, y in poly)
fill = "#ffe9b3" if len(poly) == 5 else "#cfe8ff"
out.append(
f'<polygon points="{pts}" fill="{fill}" stroke="#888" '
f'stroke-width="0.6"/>'
)
if magnet_radius > 0:
cx = sum(x for x, _ in poly) / len(poly)
cy = sum(y for _, y in poly) / len(poly)
out.append(
f'<circle cx="{X(cx + ox):.2f}" cy="{Y(cy + oy):.2f}" '
f'r="{magnet_radius * scale:.2f}" fill="#444" '
f'fill-opacity="0.55" stroke="none"/>'
)
# hinges (fold lines) in green
for h in p.hinges:
x1, y1 = h.p
x2, y2 = h.q
col = "#2a8f2a" if h.kind == "3-3" else "#c000c0"
out.append(
f'<line x1="{X(x1 + ox):.2f}" y1="{Y(y1 + oy):.2f}" '
f'x2="{X(x2 + ox):.2f}" y2="{Y(y2 + oy):.2f}" '
f'stroke="{col}" stroke-width="1.2"/>'
)
out.append("</svg>")
with open(path, "w") as f:
f.write("\n".join(out))
if __name__ == "__main__":
s = SnubDodecahedron()
s.verify()
pieces = Unfolder(s).unfold()
bad = verify_no_overlap(pieces)
print(f"pieces={len(pieces)} faces={sum(len(p.faces) for p in pieces)} "
f"overlapping_pairs={bad}")
write_svg(pieces, "net_preview.svg")
print("wrote net_preview.svg")