Self-hosted service
mikedh/trimesh avatar
mikedh/trimesh

trimesh: a mesh library where the interesting decisions are caching and watertightness

Python library for loading and using triangular meshes.

3,685 stars674 forksPythonMIT

At a glance

What is it?
trimesh loads, transforms and measures triangular meshes in Python with numpy as its only required dependency. The design centers on watertight surfaces and on invalidating cached values correctly when you edit vertices in place.
Who is it for?
trimesh earns its place when your program needs to answer questions about a solid rather than render it: is this watertight, what is its volume, does this face belong to that shell, does this ray hit anything. Installing it costs one pip command and numpy, and the optional extras are chosen by the feature you need rather than all at once.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 16 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One required dependency, and extras chosen by feature

Keeping the install small is stated as a core goal, and the only hard dependency is numpy. The README draws a line between what the base install can do and what each extra adds: the minimal install can load STL, PLY, OBJ and GLTF/GLB into `numpy.ndarray` values, and everything past that is gated on a soft dependency.

bash
pip install trimesh

The mapping from capability to package is specific enough to be useful when you are writing a requirements file. Convex hulls come from `scipy`, graph operations from `networkx`, fast ray queries from `embreex`, vector path handling from `shapely` and `rtree`, XML formats such as 3DXML, XAML and 3MF from `lxml`, preview windows from `pyglet`, and faster cache checks from `xxhash`.

bash
pip install trimesh[easy]

The README notes that the `easy` extra is the set of packages that generally install cleanly from binaries on Linux x86_64, MacOS ARM and Windows x86_64. It then gives advice that matters if you are packaging rather than experimenting: if you support a different platform or freeze dependencies for an application, depend on `trimesh scipy` style lists rather than extras. Extras resolve at install time for whoever installs the package, whereas a frozen application needs the pins spelled out.

Loading a GLB and asking whether it encloses a volume

The quick start opens with a mesh built from raw vertex and face arrays, then moves to a real file. It attaches a logger first so trimesh messages reach the console.

python
import numpy as np
import trimesh

# attach to logger so trimesh messages will be printed to console
trimesh.util.attach_to_log()

# mesh objects can be created from existing faces and vertex data
mesh = trimesh.Trimesh(vertices=[[0, 0, 0], [0, 0, 1], [0, 1, 0]], faces=[[0, 1, 2]])

The properties the example reaches for are the ones that separate a mesh library from a file parser. `mesh.is_watertight` answers whether the surface closes on itself. `mesh.euler_number` gives a topological invariant. `mesh.volume` divided by `mesh.convex_hull.volume` gives a compactness measure, and the convex hull is itself another `Trimesh` object exposed as a property. `mesh.moment_inertia` returns the 3 by 3 matrix, which is the kind of number a robotics or physics caller actually wants.

One warning in that snippet is easy to skim past and expensive to ignore. Loading is described as concatenating irreversibly, so a format such as `glb` that can hold several meshes with several instances comes back as one flat mesh. If instance information matters, the README points at `load_scene` and a `Scene` object instead. Choosing between `load_mesh` and `load_scene` at the call site is cheap now and painful to undo later.

Why apply_translation is not the same as subtracting from vertices

By default, constructing a `Trimesh` runs a light processing step that removes NaN values and merges vertices that share a position. Passing `process=False` skips it. That flag changes what your vertex array means, so it belongs in the places where index correspondence matters.

python
# by default, Trimesh will do a light processing, which will
# remove any NaN values and merge vertices that share position
# if you want to not do this on load, you can pass `process=False`
mesh = trimesh.Trimesh(
    vertices=[[0, 0, 0], [0, 0, 1], [0, 1, 0]], faces=[[0, 1, 2]], process=False
)

The more interesting half of that section is the cache. The README notes that altering vertices directly, for example `mesh.vertices -= mesh.center_mass`, completely clears the cache including face normals, because the library cannot know they are still valid. `apply_translation()` is the supported path, and it tries to preserve cached values that survive a translation.

That distinction is the most useful sentence in this documentation. Properties like normals, areas and volumes are computed and then remembered, so a fast property access after a mutation can hand you a stale number. Reach for the method that matches the operation, and the difference shows up as correctness rather than as speed.

The same care applies to hashing. The feature list mentions automatic hashing from a subclassed numpy array for change tracking using MD5, zlib CRC or xxhash, along with internal caching of expensive values. Hashing is what makes cache invalidation knowable, which is why `xxhash` shows up among the optional dependencies.

Splitting by connectivity, grouping facets, bounding volumes

Once a mesh is loaded, the useful operations are decomposition and measurement. `mesh.split()` separates a mesh into its connected components of face adjacency, so a model containing several bodies comes back as a list of meshes. The example notes that a single watertight body yields a list of one, which is a useful reminder that this is a real decomposition rather than a guess.

Facets are groups of coplanar adjacent faces, and the example colors each one with `trimesh.visual.random_color()`, writing into `mesh.visual.face_colors` as 8 bit RGBA by default in an `(n, 4)` `np.uint8` array. Facet detection is what makes per-surface work possible on scanned or imported geometry where the original face boundaries carry no meaning.

Bounding volumes come as a graded set. `mesh.bounding_box.extents` is the axis aligned box. `mesh.bounding_box_oriented` is the minimum volume oriented box, and it carries a `.primitive` attribute with `.extents` and `.transform`, plus `.volume`. The README is precise about the guarantees: bounding spheres and cylinders are the minimum volume version of each, except in certain degenerate cases where they are no worse than a least squares fit of the primitive.

python
mesh.apply_transform(trimesh.transformations.random_rotation_matrix())
mesh.bounding_box_oriented.primitive.extents
mesh.bounding_box_oriented.primitive.transform

Two details are worth internalizing. The transform method takes a 4 by 4 matrix and applies it cleanly, which is the path for placing a mesh in world coordinates. And primitives are subclasses of `Trimesh` that generate faces and vertices lazily from the `primitive` attribute, which is why a `Box` can be added to a mesh with `mesh + mesh.bounding_box_oriented` and then shown in the viewer.

The 5.0 line changed what the library requires

The packaging metadata states the support window precisely. The project requires Python 3.10 or newer, declares a single dependency on numpy at `>=1.21`, and lists classifiers through Python 3.13.

toml
[project]
name = "trimesh"
requires-python = ">=3.10"
version = "5.1.0"
authors = [{name = "Michael Dawson-Haggerty", email = "[email protected]"}]
dependencies = ["numpy>=1.21"]

Release 5.0.0, published 2026-08-01, is where that floor moved: it dropped Python 3.8 and 3.9 because they had reached end of life, and modernized the type annotations. Several fixes in the same release came from outside the maintainer, including work on `Box(bounds=...)` centering when corners are not min-first, a `mesh_other` reflection bug when `reflection=False`, dropping faces that touch non-finite vertices, and winding on revolve-based primitives under a reflection transform.

Release 5.1.0 on 2026-08-31 continues the pattern with a batched r-tree query in the discrete mean curvature measure, a fix for smoothing that shifted mesh coordinates, stricter errors for a PLY property line appearing before any element and for an OFF file missing its count line, and a marked work-in-progress item on Draco support.

The README is direct about version policy: the API is mostly stable, but that should not be relied on and is not guaranteed, and anyone deploying should install a specific version. Given that a major release dropped Python versions less than two months ago, that advice is easy to agree with.

What trimesh leaves to other libraries

The feature list defines the boundary, and it is a geometry boundary rather than a rendering one. Import covers binary and ASCII STL, Wavefront OBJ, ASCII OFF, binary and ASCII PLY, GLTF/GLB 2.0, 3MF, XAML and 3DXML, with export to GLB/GLTF, binary STL, binary PLY, ASCII OFF, OBJ and COLLADA. Two dimensional work comes through vector paths with DXF and SVG. Analysis covers face adjacencies, face angles, vertex defects and convex hulls, plus cross sections for a 2D outline and slicing a mesh into a remainder mesh for 3D printing.

Rendering is the part it delegates. The preview is an OpenGL `pyglet` window, or in-line in a jupyter or marimo notebook through `three.js`. The examples directory shows how far that stretches: `offscreen_render.py`, `ray.py`, `raytrace.py`, `voxel.py`, `scan_register.py` and notebooks covering curvature, sections, texture and nearest-point queries. So the library covers rendering in the sense of giving you a viewer and ray casting, not in the sense of producing finished images.

The comparison the README makes is to Shapely, whose `Polygon` object trimesh's `Trimesh` is styled after. That is a fair framing for the ergonomics and a useful boundary too, since Shapely is 2D and trimesh is 3D with a large soft dependency surface. If you need a scene graph, materials and a pipeline for asset delivery, that is a different tool. If you need to know whether a downloaded model is a closed solid and what it weighs, this is the right one.

One more thing the tree says: the repository ships an `AI_PREFERENCE.md` at the root, which tells you something about how the maintainer expects contributions to be written.

Editorial conclusion

trimesh earns its place when your program needs to answer questions about a solid rather than render it: is this watertight, what is its volume, does this face belong to that shell, does this ray hit anything. Installing it costs one pip command and numpy, and the optional extras are chosen by the feature you need rather than all at once. The real decision to make is about your own code, because the cache is the trap: edit `mesh.vertices` directly and every derived property is invalidated, while `apply_translation()` tries to keep what stays valid. Pin a specific version if you deploy, as the README itself advises, and start from the quick start notebook rather than the feature list.

Frequently asked questions

What is a trimesh?

A triangular mesh is a surface made entirely of flat triangles, where each triangle references three shared vertices. A collection of triangles has no inherent interior, so a mesh only has a meaningful volume once the triangles join into a closed surface, which is what trimesh's `is_watertight` property checks for.

Is trimesh free to use?

Yes. GitHub reports an MIT license for the repository, and the license text sits in a `LICENSE.md` file at the root. MIT is permissive and puts no restriction on commercial use, though reading that file yourself is the only way to know exactly what it asks of you.

How do I install trimesh using pip?

`pip install trimesh` gives you numpy plus a working loader for STL, PLY, OBJ and GLTF/GLB. For convex hulls, graph operations, ray queries, vector paths or preview windows, add the extra with `pip install trimesh[easy]`, or depend on the individual packages as in `trimesh scipy` when you are freezing an application.

Official sources

  1. License: MIT
  2. mikedh/trimesh on GitHub
  3. Project website
  4. README
  5. Releases