--- jupyter: description: 'A stencil from font outlines: the islands inside letters are found and each gets a bridge through its middle so nothing falls out; the cut path as DXF.' --- # Stencil lettering with bridges Lettering cut out of a sheet makes a stencil, but the inside of a p, an a or an e would fall out, which is why a stencil font leaves bridges. The example is a stencil of the word "planex", 40 high: the letters come from a font as outlines, the islands are found, and each gets a bridge through its middle. Lengths are in mm. ![A stencil of the word planex2d with bridges holding the letters' islands](../_static/gallery/stencil-lettering.png){.result} ```python import math import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties from matplotlib.textpath import TextPath import planex as px ``` ```python nbsphinx="hidden" from docs_figures import RED, Row ``` ## The letters Matplotlib turns text into outlines, and their polygons, drawn into one sketch, give the letters as faces, including the holes inside them. The outlines consist of short straight pieces, and `FitArcs` turns them back into arcs and lines, which a laser or a plotter follows more smoothly. ```python outline = TextPath((0, 0), "planex", size=40.0, prop=FontProperties(family="DejaVu Sans", weight="bold")) sketch = px.QuickSketch() for polygon in outline.to_polygons(): points = [tuple(p) for n, p in enumerate(polygon[:-1]) if n == 0 or math.dist(p, polygon[n - 1]) > 1e-9] sketch.Move(points[0]) for p in points[1:]: sketch.Line(p) sketch.Close() letters = sketch.Shape() smooth = px.Unify([face.Copy().FitArcs(tol=0.02, max_edge_length=3) for face in letters.faces]) print(len(letters.faces), "letters with", sum(len(f.inner) for f in letters.faces), "holes") print(len(letters.edges), "edges from the font,", len(smooth.edges), "after fitting arcs") print("area", round(letters.mass, 3), "fitted", round(smooth.mass, 3)) ``` ## Islands The sheet reaches 10 beyond the letters. In the sheet minus the letters, every hole of a letter becomes a loose island: ```python (x0, y0), (x1, y1) = smooth.bounding_box sheet = px.QuickSketch((x0 - 10, y0 - 10)).Rectangle(x1 - x0 + 20, y1 - y0 + 20).Face() plain = sheet - smooth print("the cut sheet falls apart into", len(plain.faces), "pieces") ``` ## Bridges Through the middle of each hole runs a vertical bridge, 2 mm wide, across the whole letter. The letter loses that strip, and the island stays joined to the sheet above and below it (Figure 1). ```python bridges = [] for face in smooth.faces: (lx0, ly0), (lx1, ly1) = face.bounding_box for hole in face.inner: cx = hole.Face().center[0] bridges.append(px.QuickSketch((cx - 1, ly0 - 1)).Rectangle(2, ly1 - ly0 + 2).Face()) stencil = sheet - (smooth - px.Unify(bridges)) print(len(bridges), "bridges, the stencil is", len(stencil.faces), "piece") px.WriteDXF(stencil, "stencil.dxf") ``` ```python nbsphinx="hide-input" islands = [f for f in plain.faces if f.mass < 0.5 * sheet.mass] Row((plain, "Without bridges: the islands fall out", {"fills": [(f, RED) for f in islands]}), (stencil, "With bridges: one piece"), caption="The stencil without and with bridges") ``` The stencil's cut path: [stencil.dxf](stencil.dxf) ```python nbsphinx="hidden" fig, (top, bottom) = plt.subplots(2, 1, figsize=(10, 6)) plain.Plot(ax=top, face_col="steelblue", legend=False) for f in islands: f.Plot(ax=top, face_col="tab:red", legend=False) top.set_title("Without bridges: the islands in red fall out") stencil.Plot(ax=bottom, face_col="steelblue", legend=False) bottom.set_title("With bridges: one piece") for ax in (top, bottom): ax.set_aspect("equal") ax.axis("off") import sys sys.path.insert(0, "..") from gallery import save_scene, save_figure, save_animation, HIDDEN save_figure(fig, "stencil-lettering") plt.close(fig) ```