STL: Triangle Meshes
Overview
STL (STereoLithography) is the simplest mesh format: a flat list of triangles, each stored as a facet normal and three corner vertices. It carries no topology — shared vertices are duplicated, and there are no edges or faces as such. It is the lingua franca of 3D printing and quick mesh exchange, and Rasmah reads and writes both binary and ASCII STL natively.
write_stl(mesh, io/path)— binary by default,binary=falsefor ASCII.read_stl(io/path)— auto-detects binary vs ASCII and welds duplicate vertices.
A triangle mesh to exchange
STL works on TriangleMesh. We build one from an SDF with marching cubes:
using Rasmah
mesh = surface_mesh(sphere(1.0); n=48)
println(size(mesh.vertices, 2), " vertices, ", size(mesh.faces, 2), " triangles")
vtk(mesh)2562 vertices, 5120 triangles
Writing binary and ASCII
Binary STL is far more compact; ASCII STL is human-readable (and editable). The writer computes each facet normal from the triangle (right-hand rule):
mktempdir() do dir
bin = joinpath(dir, "sphere.stl")
asc = joinpath(dir, "sphere_ascii.stl")
write_stl(mesh, bin) # binary (default)
write_stl(mesh, asc; binary=false) # ASCII
println("binary: ", filesize(bin), " bytes")
println("ascii: ", filesize(asc), " bytes")
endbinary: 256084 bytes
ascii: 1661881 bytes
Reading back
read_stl detects the encoding automatically and welds coincident vertices back into a shared-vertex TriangleMesh. The round-trip preserves the geometry (area) even though the raw vertex list changed:
mktempdir() do dir
p = joinpath(dir, "sphere.stl")
write_stl(mesh, p)
m2 = read_stl(p)
println("faces: ", size(m2.faces, 2), " (original ", size(mesh.faces, 2), ")")
println("area: ", round(surface_area(m2); digits=4),
" (original ", round(surface_area(mesh); digits=4), ")")
endfaces: 5120 (original 5120)
area: 12.5514 m^2 (original 12.5514 m^2)
Vertex welding
The “no topology” design means a file stores 3 corners per facet. A cube with 12 triangles therefore writes 36 vertex records for only 8 distinct points — and read_stl recovers exactly those 8:
cube = surface_mesh(box(1.0, 1.0, 1.0); n=16)
mktempdir() do dir
p = joinpath(dir, "cube.stl")
write_stl(cube, p)
m2 = read_stl(p)
println("raw facet corners: ", 3 * size(cube.faces, 2))
println("welded vertices: ", size(m2.vertices, 2))
endraw facet corners: 8088
welded vertices: 1350
Next steps
- OBJ: Wavefront Triangle Meshes — the ASCII
v/fcounterpart with polygon support. - PLY: Polygon File Format — binary/ASCII meshes with typed properties.
- Converting Representations — what survives each mesh round-trip and what does not.
- VTK: Legacy and XML — a richer mesh format that keeps topology and fields.
- Boundary Representation — the exact model behind the mesh.
