Escher's Circle Limit prints show a hyperbolic tiling squeezed into a disc, with tiles shrinking toward the rim. The maths is short: a tile edge is a circle that meets the unit circle at right angles, and the next tile is the mirror image of the current one across that circle. Mirroring across a circle is an inversion. That is all you need to draw the {7,3} tiling (heptagons, three at every corner) and print it as a flat ornament with manifold3d.
1. The first heptagon
For a {p,q} tiling, the vertices of the central polygon sit at a distance from the centre given by a closed formula:
import math, cmath
p, q = 7, 3
r0 = math.sqrt(math.cos(math.pi/p + math.pi/q) / math.cos(math.pi/p - math.pi/q))
V = [r0 * cmath.exp(2j * math.pi * k / p) for k in range(p)]
2. Edge circle and inversion
The geodesic through two points u and v is a circle with centre c and radius R, where |c|^2 = R^2 + 1 (that is the right-angle condition). Solve two linear equations for c:
def circ(u, v):
a1, b1, c1 = u.real, u.imag, (abs(u)**2 + 1) / 2
a2, b2, c2 = v.real, v.imag, (abs(v)**2 + 1) / 2
det = a1*b2 - a2*b1
c = complex((c1*b2 - c2*b1) / det, (a1*c2 - a2*c1) / det)
return c, math.sqrt(abs(c)**2 - 1)
def refl(z, c, R): # inversion in the circle (c, R)
d = z - c
return c + R*R * d / abs(d)**2
3. Grow the tiling
Reflect each polygon across each of its edges. Skip polygons you have already built, which you detect by the rounded sum of their vertices.
polys, seen, frontier = [V], {round(sum(V), 3)}, [V]
for _ in range(3): # 3 rings of neighbours
nxt = []
for P in frontier:
for i in range(p):
c, R = circ(P[i], P[(i+1) % p])
N = [refl(z, c, R) for z in P]
k = complex(round(sum(N).real, 3), round(sum(N).imag, 3))
if k not in seen:
seen.add(k); polys.append(N); nxt.append(N)
frontier = nxt
Three rings give 85 heptagons.
4. Edges become printable lines
Walk each polygon edge as a circular arc, cut it into 12 short straight pieces, and thicken each piece into a thin rectangle with CrossSection. Union everything, add an outer rim, extrude 2.4 mm.
Gotchas
- Stop the tiling at radius 0.93. Past that the tiles are smaller than a nozzle.
- Put the rim at that same radius. If it sits further out, the lattice and the rim are separate islands and
decompose()throws one away. - Make line width depend on distance from the centre, but never below 0.65 mm half-width.
- {5,4}, {4,5} and {3,7} work with the same code: change
p, qand the loop depth.
The finished {7,3} ornament and a {5,4}/{4,5}/{3,7} 3-pack are on Cults3D for $1.27. I post more of these experiments on Patreon.
Top comments (0)