RasmahRasmah

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=false for 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")
end
binary: 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), ")")
end
faces: 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))
end
raw facet corners: 8088
welded vertices:   1350

Next steps