Intersecting Cylinders¶
These curved-folding tessellations can be generated from tilings whose faces have incircles which are mutually tangential. Each vertex becomes a curved 'spike' that meets its neighbours along the edges of the original tiling; with the canonical quarter-circle profile on the platonic square tiling the result is a tessellation of intersecting half-cylinders.
To the best of my knowledge, Daniel Kwan initially came up with the concept, which Philip Chapman-Bell (Oschene) later refined. Here, I generalize this type of tessellation to arbitrary tilings whose faces have mutually tangential incircles, with configurable cross-section profiles.
The crease pattern inserts a small twist at every original face and uses straight
and curved creases between the twists and the face incenters.
The straight side of the curved triangles connects incenters of adjacent faces.
With r=1 curved triangles meet at the original vertices creating spiky points (as in all examples above).
With 0 < r < 1 the triangles become curved
quadrilaterals, and a flat polygon dual to the original vertex will appear:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import pleat
from pleat.intersecting_cylinders import (
Profile,
circular_profile,
make_intersecting_cylinders,
show_3d,
top_view,
)
Cross-section profiles¶
A Profile describes the shape of the curved triangles perpendicular to their flat
edge. The default circular_profile(scale=1.3) is a tall quarter-ellipse;
circular_profile(scale=1.0) is the canonical quarter-circle that yields exact
intersecting half-cylinders on the platonic 4 and 3 tilings.
fig, axes = plt.subplots(1, 2, figsize=(8, 4))
circular_profile(scale=1.0).plot(ax=axes[0])
axes[0].set_title('circular_profile(scale=1.0)')
circular_profile(scale=1.3).plot(ax=axes[1])
axes[1].set_title('circular_profile(scale=1.3)')
plt.tight_layout()
plt.show()
Building the crease pattern¶
Provide any tiling whose faces have well-defined incenters, such that the corresponding incircles are mutually tangential at the shared edges. Every tiling made solely of regular polygons satisfies this automatically; more general examples come from circle packings.
Here we use an irregular triangle tiling derived from a circle packing computed with CirclePack, Ken Stephenson's software for the creation, manipulation, and display of circle packings. His book Introduction to Circle Packing: The Theory of Discrete Analytic Functions is the standard reference on the underlying theory. Such packings can also be computed natively in pleat, via the Collins-Stephenson algorithm — see the Circle_Packing notebook.
G = pleat.io.load_graph('../../graphs/irregular2.heg')
G.show()
# visualize the two dual circle packings
from pleat.intersecting_cylinders import show_dual_circle_packings
show_dual_circle_packings(G)
CP = make_intersecting_cylinders(G, circular_profile(), r=1.0)
CP.show(line_width='50%', render_vertices=False, render_faces=False)
Top-view projection¶
The flat top-view of the folded model is obtained by joining each edge to the
corresponding face incenter (the Conway join operator):
tv = top_view(G, r=1.0)
tv.show(line_width=0.02, render_vertices=False, render_faces=True)
Preserving the original faces with r < 1¶
With r < 1, a downscaled copy of every original face is kept and the curved
triangles become curved quadrilaterals reaching down to it. The corresponding
top view is the Conway chamfer operator with parameter t = r.
# G = pleat.example_graphs.from_tiles(pleat.example_tilesets.platonic(4), rings=2)
CP = make_intersecting_cylinders(G, circular_profile(), r=0.7)
CP.show(line_width="50%", render_vertices=False, render_faces=False)
show_3d(G, circular_profile(scale=1.0), r=1.0)
3D preview with r < 1¶
For r < 1 the curved patches end on the lifted, shrunken inner faces:
show_3d(G, circular_profile(1), r=0.6)
Custom profiles¶
Any function f: [0, 1] -> R with f(0) = 0 works via Profile.from_function.
Below we use a profile derived from the sin function.
sin_profile = Profile.from_function(lambda x: 0.5 * (1 + np.sin(-np.pi/2 + np.pi * x)))
CP = make_intersecting_cylinders(G, sin_profile, r=1.0)
CP.show(line_width="50%", render_vertices=False, render_faces=False)
show_3d(G, sin_profile, r=1.0)