ASCII STL I/O

August 29, 2026 ยท View on GitHub

speedups.stl exposes the low-level parser and writer used by numpy-stl. Both functions work with the structured NumPy mesh layout from that project.

Most applications should use numpy-stl for complete STL file handling. I would call speedups.stl directly only when the surrounding application already owns the file handling and mesh API.

Mesh dtype

The parser returns, and the writer expects, one structured NumPy row per facet. Define the dtype like this:

import numpy as np

mesh_dtype = np.dtype(
    [
        ('normals', np.float32, 3),
        ('vectors', np.float32, (3, 3)),
        ('attr', np.uint16, (1,)),
    ]
)

The three fields map directly to one STL facet:

  • normals: the facet normal vector.
  • vectors: the three vertex vectors.
  • attr: the STL attribute field used by the numpy-stl layout.

Reading ASCII STL

ascii_read() accepts an initial buffer so callers can inspect a file before choosing the ASCII parser. Read at most 8192 bytes, then pass the same handle and buffer to the parser:

from speedups.stl import ascii_read

with open('model.stl', 'rb') as fh:
    buffer = fh.read(8192)
    name, mesh = ascii_read(fh, buffer)

print(name.strip())
print(mesh.dtype)
print(mesh['vectors'].shape)

The first printed value is the STL solid name as bytes. ASCII uppercase characters are normalized to lowercase while parsing, including characters in the solid name. The dtype is the structured layout above, and mesh['vectors'] has shape (facets, 3, 3). The parser expects the file handle to remain positioned immediately after the initial read.

Note

The initial buffer cannot exceed 8192 bytes. Malformed input and lines longer than the internal limit raise an exception instead of being silently truncated.

Writing ASCII STL

The writer needs a binary file handle, a bytes solid name, and a contiguous mesh array using the dtype above:

import numpy as np

from speedups.stl import ascii_write

mesh_dtype = np.dtype(
    [
        ('normals', np.float32, 3),
        ('vectors', np.float32, (3, 3)),
        ('attr', np.uint16, (1,)),
    ]
)

mesh = np.zeros(1, dtype=mesh_dtype)
mesh['normals'][0] = [0.0, 0.0, 1.0]
mesh['vectors'][0] = [
    [0.0, 0.0, 0.0],
    [1.0, 0.0, 0.0],
    [0.0, 1.0, 0.0],
]

with open('triangle.stl', 'wb') as fh:
    ascii_write(fh, b'triangle', mesh)

The resulting file starts with solid triangle, contains one facet block, and ends with endsolid triangle. The handle remains positioned after the written data.

API Reference

ascii_read(fh, buf: bytes) -> tuple[bytes, np.ndarray]

Reads ASCII STL data from fh, using buf as the already-read initial data. The handle must be a seekable binary file backed by an OS file descriptor. The implementation calls fileno(), tell(), and seek(), so io.BytesIO is not accepted.

ascii_write(fh, name: bytes, arr: np.ndarray) -> None

Writes arr to fh as an ASCII STL solid named name. The handle has the same file descriptor and seek requirements as ascii_read().