stl.Mesh

class stl.Mesh(*args, **kwargs)[source]

Bases: BaseStl

Primary user-facing class for STL mesh operations.

Inherits all functionality from BaseStl and BaseMesh. Use class methods like from_file() and from_multi_file() to load STL files, or pass a structured NumPy array to the constructor.

Example

>>> import numpy as np
>>> from stl import mesh
>>> data = np.zeros(1, dtype=mesh.Mesh.dtype)
>>> m = mesh.Mesh(data, remove_empty_areas=False)
>>> len(m)
1
Parameters:
  • data (ndarray[tuple[int], dtype[void]])

  • calculate_normals (bool)

  • remove_empty_areas (bool)

  • remove_duplicate_polygons (_Dedupe)

  • name (bytes | str)

  • speedups (Final[bool])

  • kwargs (object)

dtype = numpy.dtype([('normals', '<f4', (3,)), ('vectors', '<f4', (3, 3)), ('attr', '<u2', (1,))])

numpy.float32(), (3, ) - vectors: numpy.float32(), (3, 3) - attr: numpy.uint16(), (1, )

Type:
  • normals

The structured NumPy dtype used for mesh data storage. Each record contains: normals (3x float32), vectors (3x3 float32), and attr (1x uint16).

property areas: ndarray[tuple[int, int], dtype[float32]]

Per-triangle surface areas, shape (N, 1).

Lazily computed and cached. Call update_areas() after modifying vertices to refresh.

Example

>>> import numpy as np
>>> from stl.base import BaseMesh
>>> data = np.zeros(2, dtype=BaseMesh.dtype)
>>> data['vectors'][0] = [[0, 0, 0], [1, 0, 0], [0, 1, 0]]
>>> m = BaseMesh(data, remove_empty_areas=False)
>>> float(m.areas[0][0])
0.5

Warning

This value is cached on first access. If you modify vertices, call update_areas() to get correct results.

property attr: ndarray[tuple[int, int], dtype[uint16]]

Per-triangle attribute field (uint16), shape (N, 1).

property centroids: ndarray[tuple[int, int], dtype[float32]]

Per-triangle centroids, shape (N, 3).

Lazily computed and cached. Call update_centroids() after modifying vertices.

Example

>>> import numpy as np
>>> from stl.base import BaseMesh
>>> data = np.zeros(1, dtype=BaseMesh.dtype)
>>> data['vectors'][0] = [[0, 0, 0], [3, 0, 0], [0, 3, 0]]
>>> m = BaseMesh(data, remove_empty_areas=False)
>>> m.centroids[0].tolist()
[1.0, 1.0, 0.0]
check(exact=False)

Check whether the mesh is valid (closed).

Parameters:

exact (bool) – If True, perform an exact edge-matching check. If False, use a faster normal-sum heuristic.

Returns:

True if the mesh is closed, False otherwise.

Return type:

bool

Warning

The non-exact check (exact=False) can produce false positives and false negatives. For reliable results, use exact=True. See #198 and #213.

classmethod from_3mf_file(filename, calculate_normals=True, **kwargs)

Load meshes from a 3MF file (read-only).

Parses the 3MF ZIP archive and yields one Mesh per <mesh> element found.

Parameters:
  • filename (str) – Path to the .3mf file.

  • calculate_normals (bool) – Whether to recalculate normals. Defaults to True.

  • **kwargs (object) – Additional arguments.

Yields:

Mesh instances, one per 3MF mesh element.

Return type:

Generator[Self, None, None]

Example

>>> from stl import mesh
>>> meshes = list(mesh.Mesh.from_3mf_file('tests/3mf/Moon.3mf'))
>>> len(meshes) > 0
True

Note

3MF support is experimental and read-only. Not all 3MF features are supported.

classmethod from_file(filename, calculate_normals=True, fh=None, mode=Mode.AUTOMATIC, speedups=True, **kwargs)

Load a mesh from an STL file.

Reads binary or ASCII STL files. Format is auto-detected unless mode is explicitly set.

Parameters:
  • filename (str) – Path to the STL file.

  • calculate_normals (bool) – Whether to recalculate normals after loading. Defaults to True.

  • fh (IO[bytes] | None) – Optional pre-opened binary file handle. If provided, filename is used only for the mesh name.

  • mode (Mode) – Force ASCII or BINARY loading, or AUTOMATIC detection (default).

  • speedups (bool) – Use Cython speedups for ASCII parsing when available. Defaults to True.

  • **kwargs (Any) – Additional arguments passed to the Mesh constructor.

Returns:

A new Mesh instance containing the loaded data.

Raises:

ValueError – If the file is empty.

Return type:

Self

Example

>>> from stl import mesh
>>> m = mesh.Mesh.from_file('tests/stl_binary/HalfDonut.stl')
>>> len(m.data) > 0
True

Note

When speedups is True and the speedups package is installed, ASCII parsing uses a fast C implementation. Speedups are automatically disabled for non-seekable streams (e.g., stdin).

classmethod from_files(filenames, calculate_normals=True, mode=Mode.AUTOMATIC, speedups=True, **kwargs)

Load and merge multiple STL files into one mesh.

Parameters:
  • filenames (list[str]) – List of STL file paths.

  • calculate_normals (bool) – Whether to recalculate normals. Defaults to True.

  • mode (Mode) – Format mode for each file.

  • speedups (bool) – Use Cython speedups when available.

  • **kwargs (Any) – Additional arguments passed to the Mesh constructor.

Returns:

A single Mesh with data from all files.

Return type:

Self

Example

>>> from stl import mesh
>>> m = mesh.Mesh.from_files(['tests/stl_binary/HalfDonut.stl'])
>>> len(m.data) > 0
True
classmethod from_multi_file(filename, calculate_normals=True, fh=None, mode=Mode.AUTOMATIC, speedups=True, **kwargs)

Load multiple solids from a single STL file.

Yields one Mesh per solid block found.

Parameters:
  • filename (str) – Path to the STL file.

  • calculate_normals (bool) – Whether to recalculate normals. Defaults to True.

  • fh (IO[bytes] | None) – Optional pre-opened binary file handle.

  • mode (Mode) – Format mode. Defaults to Mode.AUTOMATIC.

  • speedups (bool) – Use Cython speedups when available.

  • **kwargs (Any) – Additional arguments passed to the Mesh constructor.

Yields:

Mesh instances, one per solid block.

Return type:

Generator[Self, None, None]

Example

>>> from stl import mesh
>>> # Single-solid file yields one mesh
>>> solids = list(
...     mesh.Mesh.from_multi_file('tests/stl_ascii/HalfDonut.stl')
... )
>>> len(solids) >= 1
True

Note

Multi-solid loading only works with ASCII STL files. Binary STL files always contain a single solid.

classmethod from_ply_file(filename, calculate_normals=True, fh=None, **kwargs)

Load a mesh from a PLY file.

Supports ASCII and binary PLY formats (little-endian and big-endian).

Parameters:
  • filename (str) – Path to the .ply file.

  • calculate_normals (bool) – Whether to recalculate normals. Defaults to True.

  • fh (IO[bytes] | None) – Optional pre-opened binary file handle.

  • **kwargs (Any) – Additional arguments passed to the Mesh constructor.

Returns:

A Mesh instance.

Return type:

Self

Example

>>> from stl import mesh
>>> m = mesh.Mesh.from_ply_file('tests/ply_ascii/Cube.ply')
>>> len(m.data) == 12
True
get(k[, d]) D[k] if k in D, else d.  d defaults to None.
get_header(name)

Build the 80-byte binary STL header string.

Parameters:

name (_Name) – Solid name to embed in the header.

Returns:

Header string truncated to 80 bytes.

Return type:

str

get_mass_properties()

Compute volume, center of gravity, and inertia.

Uses the polyhedral mass properties algorithm from Eberly (Geometric Tools).

Returns:

  • volume – Mesh volume as float32.

  • center_of_gravity – COG as (3,) array.

  • inertia – Inertia tensor as (3, 3) array expressed at the COG.

Return type:

A tuple of (volume, center_of_gravity, inertia)

Example

>>> from stl import mesh
>>> m = mesh.Mesh.from_file('tests/stl_binary/HalfDonut.stl')
>>> vol, cog, inertia = m.get_mass_properties()
>>> float(vol) > 0
True

Warning

These values are only meaningful for closed (watertight) meshes. This method checks via check(exact=True) and logs a warning for open meshes, but still computes and returns the (then unreliable) values. Use is_closed() to verify beforehand.

get_mass_properties_with_density(density)

Compute mass properties with a given density.

Like get_mass_properties() but scales volume to mass using the provided density.

Parameters:

density (float) – Material density in consistent units (e.g., kg/m^3 when mesh units are meters).

Returns:

  • volume – Mesh volume.

  • mass – Volume * density.

  • cog – Center of gravity as (3,) array.

  • inertia – Inertia tensor as (3, 3) array.

Return type:

A tuple of (volume, mass, cog, inertia)

Warning

These values are only meaningful for closed (watertight) meshes. This method checks via check(exact=True) and logs a warning for open meshes, but still computes and returns the (then unreliable) values.

get_unit_normals()

Return a copy of normals normalized to unit length.

Unlike the units property, this method always recomputes from the current normals array.

Returns:

Array of shape (N, 3) with unit-length normals. Zero-length normals remain as zeros.

Return type:

ndarray[tuple[int, int], dtype[float32]]

is_closed(exact=False)

Check whether the mesh is watertight.

A closed mesh has every edge shared by exactly two triangles with consistent winding.

Parameters:

exact (bool) – If True, checks directed edges for matching pairs. If False, uses a faster normal-sum heuristic.

Returns:

True if the mesh is closed, False otherwise.

Return type:

bool

Warning

The non-exact check (exact=False) can give false positives and false negatives for certain mesh geometries. Use exact=True for reliable results. See #198.

is_convex()

Return True if the mesh is convex.

Tests whether every vertex lies on or behind every face plane.

Returns:

True if convex, False otherwise.

Return type:

bool

items() a set-like object providing a view on D's items
keys() a set-like object providing a view on D's keys
classmethod load(fh, mode=Mode.AUTOMATIC, speedups=True)

Load mesh data from an open STL file handle.

Auto-detects binary vs ASCII format unless mode is explicitly set.

Parameters:
  • fh (IO[Any]) – Open binary file handle.

  • mode (Mode | int) – Force a specific format or use Mode.AUTOMATIC (default). Plain ints are accepted and converted to Mode.

  • speedups (bool) – Use Cython speedups for ASCII parsing. Defaults to True.

Returns:

A (name, data) tuple, or None if the file is empty.

Raises:

ValueError – If mode is not a valid Mode value.

Return type:

tuple[bytes, _data_1d] | Any

property max_: ndarray[tuple[int], dtype[float32]]

Bounding box maximum corner, shape (3,).

Lazily computed and cached. Call update_max() after modifying vertices to refresh.

Warning

This value is cached on first access. If you modify vertices, call update_max() to refresh.

property min_: ndarray[tuple[int], dtype[float32]]

Bounding box minimum corner, shape (3,).

Lazily computed and cached. Call update_min() after modifying vertices to refresh.

Warning

This value is cached on first access. If you modify vertices, call update_min() to refresh.

property normals: ndarray[tuple[int, int], dtype[float32]]

Per-triangle normal vectors, shape (N, 3).

Lazily computed on first access. Call update_normals() after modifying vertices to refresh.

Example

>>> import numpy as np
>>> from stl.base import BaseMesh
>>> data = np.zeros(1, dtype=BaseMesh.dtype)
>>> data['vectors'][0] = [[0, 0, 0], [1, 0, 0], [0, 1, 0]]
>>> m = BaseMesh(data, remove_empty_areas=False)
>>> m.normals[0].tolist()
[0.0, 0.0, 1.0]
property points: ndarray[tuple[int, int], dtype[float32]]

All vertices flattened as (N, 9) array.

static remove_duplicate_polygons(data, value=RemoveDuplicates.SINGLE)

Remove duplicate triangles from mesh data.

Parameters:
Returns:

Filtered mesh data array.

Return type:

ndarray[tuple[int], dtype[void]]

Note

ALL has a safety fallback: when removing every duplicated polygon would drop half the mesh or more, a single copy of each duplicate is kept instead (the SINGLE behavior).

static remove_empty_areas(data)

Remove triangles with zero surface area.

Filters out degenerate triangles where all three vertices are collinear or coincident.

Parameters:

data (ndarray[tuple[int], dtype[void]]) – Structured mesh array.

Returns:

Filtered mesh data array with zero-area triangles removed.

Return type:

ndarray[tuple[int], dtype[void]]

rotate(axis, theta=0, point=None)

Rotate the mesh around an axis.

Parameters:
  • axis (_ToAxis) – Axis to rotate around as [x, y, z].

  • theta (float) – Rotation angle in radians. Use math.radians() to convert from degrees.

  • point (_ToPoint | None) – Optional point to rotate around. If None, rotates around the origin.

Return type:

None

Example

>>> import math
>>> import numpy as np
>>> from stl.base import BaseMesh
>>> data = np.zeros(1, dtype=BaseMesh.dtype)
>>> data['vectors'][0] = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
>>> m = BaseMesh(data, remove_empty_areas=False)
>>> m.rotate([0, 0, 1], math.radians(90))

Warning

In older versions, the point parameter was accidentally inverted. If you relied on the old behavior, pass -point instead.

rotate_using_matrix(rotation_matrix, point=None)

Rotate using a pre-computed rotation matrix.

Parameters:
  • rotation_matrix (_f32_2d | _f64_2d) – A (3, 3) rotation matrix.

  • point (_ToPoint | None) – Optional point to rotate around. If None, rotates around the origin.

Return type:

None

Warning

This method produces clockwise rotations for positive angles, which is arguably incorrect but retained for backwards compatibility. See #166.

static rotation_matrix(axis, theta)

Generate a 3x3 rotation matrix.

Uses the Euler-Rodrigues formula for fast rotation matrix construction.

Parameters:
  • axis (_ToAxis) – Axis to rotate around as [x, y, z].

  • theta (float) – Rotation angle in radians. Use math.radians() to convert from degrees.

Returns:

A (3, 3) rotation matrix. Returns the identity matrix if the axis is zero.

Return type:

ndarray[tuple[int, int], dtype[float64]]

save(filename, fh=None, mode=Mode.AUTOMATIC, update_normals=True)

Save the mesh to an STL file.

If mode is Mode.AUTOMATIC, writes binary unless the output is a TTY.

Parameters:
  • filename (_Name) – Output file path. Required even when fh is provided (used for STL header).

  • fh (IO[bytes] | None) – Optional pre-opened binary file handle.

  • mode (Mode | int) – Output format. Defaults to Mode.AUTOMATIC.

  • update_normals (bool) – Whether to recalculate normals before saving. Defaults to True.

Raises:
  • TypeError – If fh is a text-mode handle.

  • ValueError – If mode is not a valid Mode value.

Return type:

None

Example

>>> import numpy as np
>>> from stl import mesh
>>> data = np.zeros(1, dtype=mesh.Mesh.dtype)
>>> data['vectors'][0] = [[0, 0, 0], [1, 0, 0], [0, 1, 0]]
>>> m = mesh.Mesh(data, remove_empty_areas=False)
>>> m.save('/tmp/_numpy_stl_test.stl')

Warning

Even for ASCII output, the file handle must be opened in binary mode ('wb'). A text-mode handle raises TypeError.

save_ply(filename, fh=None, mode='binary_little_endian', update_normals=True)

Save the mesh to a PLY file.

Parameters:
  • filename (str) – Output file path.

  • fh (IO[bytes] | None) – Optional pre-opened binary file handle.

  • mode (str) – PLY format. One of 'ascii', 'binary_little_endian' (default), 'binary_big_endian'.

  • update_normals (bool) – Whether to recalculate normals before saving. Defaults to True.

Return type:

None

Example

>>> from stl import mesh
>>> m = mesh.Mesh.from_file('tests/stl_binary/HalfDonut.stl')
>>> m.save_ply('/tmp/_numpy_stl_test.ply')
transform(matrix)

Apply a 4x4 transformation matrix.

The upper-left 3x3 submatrix is the rotation. The rightmost column (0:3, 3) is the translation.

Parameters:

matrix (ndarray[tuple[int, int], dtype[float32]] | ndarray[tuple[int, int], dtype[float64]]) – A (4, 4) transformation matrix. The rotation part must have unit determinant.

Raises:

AssertionError – If matrix shape is not (4, 4) or rotation determinant is not 1.0.

Return type:

None

translate(translation)

Translate (move) the mesh.

Parameters:

translation (_ToTranslation) – Translation vector [x, y, z].

Raises:

AssertionError – If translation is not length 3.

Return type:

None

Example

>>> import numpy as np
>>> from stl.base import BaseMesh
>>> data = np.zeros(1, dtype=BaseMesh.dtype)
>>> data['vectors'][0] = [[0, 0, 0], [1, 0, 0], [0, 1, 0]]
>>> m = BaseMesh(data, remove_empty_areas=False)
>>> m.translate([10, 20, 30])
>>> float(m.v0[0][0])
10.0
property units: ndarray[tuple[int, int], dtype[float32]]

Per-triangle unit normal vectors, shape (N, 3).

Lazily computed and cached. Call update_units() after modifying vertices.

Example

>>> import numpy as np
>>> from stl.base import BaseMesh
>>> data = np.zeros(1, dtype=BaseMesh.dtype)
>>> data['vectors'][0] = [[0, 0, 0], [1, 0, 0], [0, 1, 0]]
>>> m = BaseMesh(data, remove_empty_areas=False)
>>> m.units[0].tolist()
[0.0, 0.0, 1.0]
update_areas(normals=None)

Refresh the cached per-triangle areas.

Parameters:

normals (ndarray[tuple[int, int], dtype[float32]] | None) – Pre-computed cross products. If None, recomputes from current vertices.

Return type:

None

update_centroids()

Refresh the cached per-triangle centroids.

Return type:

None

update_max()

Refresh the cached bounding box maximum.

Return type:

None

update_min()

Refresh the cached bounding box minimum.

Return type:

None

update_normals(update_areas=True, update_centroids=True)

Recalculate normals from current vertex positions.

Also refreshes areas and centroids by default.

Parameters:
  • update_areas (bool) – Whether to also refresh cached areas. Defaults to True.

  • update_centroids (bool) – Whether to also refresh cached centroids. Defaults to True.

Return type:

None

update_units()

Refresh the cached unit normal vectors.

Return type:

None

property v0: ndarray[tuple[int, int], dtype[float32]]

First vertex of each triangle, shape (N, 3).

property v1: ndarray[tuple[int, int], dtype[float32]]

Second vertex of each triangle, shape (N, 3).

property v2: ndarray[tuple[int, int], dtype[float32]]

Third vertex of each triangle, shape (N, 3).

values() an object providing a view on D's values
property vectors: ndarray[tuple[int, int, int], dtype[float32]]

Triangle vertices as (N, 3, 3) array.

property x: ndarray[tuple[int, int], dtype[float32]]

X coordinates by vertex, shape (N, 3).

property y: ndarray[tuple[int, int], dtype[float32]]

Y coordinates by vertex, shape (N, 3).

property z: ndarray[tuple[int, int], dtype[float32]]

Z coordinates by vertex, shape (N, 3).