Modifying Tilings: Surgery and Coordinate Transforms¶
Conway operators are global; sometimes you want a scalpel. This notebook collects some of the ad-hoc modification primitives that pleat.half exposes:
- finding faces / edges by predicate,
- deleting faces, edges, and (degree-2) vertices,
- subdividing edges and faces,
- transforming all positions in one go (stretch, warp).
We highlight every change by writing a color_key and showing the graph before and after.
import numpy as np
from pleat import (
example_graphs,
example_tilesets,
)
from pleat.rendering import multi_show
Finding by predicate¶
All collections (faces, vertices, halfedges) are plain Python sets. Filter with a list comprehension and mark hits with a colour key.
G = example_graphs.from_tiles(example_tilesets.t_4_6_12(), rings=2)
G.recompute_lengths_and_angles()
hexagons = [f for f in G.faces if f.order() == 6]
for f in hexagons:
f['color_key'] = (1.0, 0.6, 0.0, 0.8)
print(f'{len(hexagons)} hexagons found')
G.show(render_faces=True, face_inset=0.05, render_vertices=False)
6 hexagons found
Deleting faces¶
G.delete_faces(set_of_faces) removes faces and the half-edges that bordered only them. The neighbouring faces become adjacent to a new piece of border.
G_before = G.copy()
to_delete = [f for f in G.faces if f.order() == 6]
G.delete_faces(set(to_delete))
multi_show([G_before, G],
titles=['before delete_faces', f'after (-{len(to_delete)} hex)'],
render_faces=True, face_inset=0.05, render_vertices=False)
Deleting an edge merges two faces¶
G.delete_edge(h) removes the halfedge and its reverse, merging the two adjacent faces into one. We mark the merged face with a single colour to make the result visible.
G = example_graphs.from_tiles(example_tilesets.platonic(6), rings=3)
G.recompute_lengths_and_angles()
central = G.central_face()
h = next(h for h in central.halfedge_iter()
if not h.on_border() and not h.rev.on_border())
G_before = G.copy()
h['color_key'] = h.rev['color_key'] = (0.85, 0.1, 0.1, 1.0)
h['line_width'] = h.rev['line_width'] = 5
G.delete_edge(h)
merged = next(f for f in G.faces if f.order() > 6)
merged['color_key'] = (0.6, 0.85, 0.4, 0.9)
multi_show([G_before, G],
titles=['edge to delete (red)', 'merged face (green)'],
render_faces=True, face_inset=0.05, render_vertices=False)
Subdividing edges and faces¶
G.subdivide_edge(h)inserts a new vertex onh(at the midpoint by default) and returns(h_new, v_new).G.subdivide_face(f, v1, v2)adds a new edge between two off's vertices, splitting it. Returns(h_new, f_new).
Below we subdivide a hexagon's diagonal.
G = example_graphs.from_tiles(example_tilesets.platonic(6), rings=2)
G.recompute_lengths_and_angles()
f = G.central_face()
vs = list(f.vertex_iter())
G_before = G.copy()
h_new, f_new = G.subdivide_face(f, vs[0], vs[3])
f_new['color_key'] = (0.4, 0.7, 1.0, 0.9)
f['color_key'] = (1.0, 0.5, 0.1, 0.9)
multi_show([G_before, G],
titles=['before subdivide_face', 'after — split into halves'],
render_faces=True, face_inset=0.05, render_vertices=False)
Coordinate transformations¶
Vertex positions live at v['pos']. Mutating them in place and then calling G.recompute_lengths_and_angles() is the standard way to bend / stretch / warp a graph.
Below we apply a horizontal shear and a sinusoidal vertical warp.
G = example_graphs.from_tiles(example_tilesets.platonic(4), rings=3)
G.recompute_lengths_and_angles()
G_orig = G.copy()
for v in G.vertices:
x, y = v['pos']
v['pos'] = np.array([x + 0.3 * y, y + 0.4 * np.sin(x)])
G.recompute_lengths_and_angles()
multi_show([G_orig, G],
titles=['original', 'shear + sin warp'],
face_inset=0.05, render_vertices=False)