pyvista-trimesh
A PyVista accessor for Trimesh. Point-in-mesh, proximity queries, sampling, repair, smoothing, voxelization, and ICP are all reachable as mesh.trimesh.<op> on any pv.PolyData.
import pyvista as pv
cube = pv.Cube()
cube.trimesh.is_watertight # True
cube.trimesh.volume # 1.0
cube.trimesh.signed_distance([[0, 0, 0]]) # array([-0.5])
Once installed, the accessor registers itself via PyVista's plugin entry-point system. There is nothing to import.
Why
PyVista wraps VTK, and VTK is brittle for several common triangle-mesh operations: vtkSelectEnclosedPoints misclassifies points on non-trivial inputs, there is no proximity or signed-distance API, and repair is awkward. Trimesh has working implementations of all of these. This package wires them up as a single .trimesh accessor that converts on demand, caches the default Trimesh conversion on the dataset, runs the operation, and hands back a fresh pv.PolyData.
mesh.trimesh.select_enclosed_points(...) mirrors pv.PolyData.select_enclosed_points and routes through Trimesh's contains (rtree-accelerated ray cast). It is the implementation originally proposed in pyvista/pyvista#6898.
Install
pip install pyvista-trimesh
For the rtree-backed contains, GLB / GLTF / 3MF readers, voxel grids, and quadric decimation:
pip install "pyvista-trimesh[extras]"
Requires Python 3.10+ and PyVista 0.48+.
Quick start
import numpy as np
import pyvista as pv
# Point-in-mesh on a 3D grid
sphere = pv.Sphere(radius=1.0)
grid = pv.ImageData(dimensions=(21, 21, 21), spacing=(0.1, 0.1, 0.1), origin=(-1, -1, -1))
result = sphere.trimesh.select_enclosed_points(grid)
inside = result.threshold(0.5, scalars='SelectedPoints')
# Signed distance from a query cloud to the surface
queries = np.random.default_rng(0).standard_normal((100, 3))
sdf = sphere.trimesh.signed_distance(queries) # negative inside, positive outside
# Even Poisson-style surface samples
samples = sphere.trimesh.sample_surface(500, even=True, seed=0)
# Voxelize into a PyVista ImageData
vox = sphere.trimesh.voxelized(pitch=0.1)
# Iterative closest point against another mesh
moved = sphere.copy()
moved.translate((0.5, 0.0, 0.0), inplace=True)
matrix, registered, cost = moved.trimesh.register(sphere)
# Drop down to the raw Trimesh when you need it
tmesh = sphere.trimesh.to_trimesh()
Output chains with the rest of PyVista:
finished = cube.trimesh.subdivide(2).clean().compute_normals()
The accessor
mesh.trimesh is a per-instance accessor. It converts the PolyData into a trimesh.Trimesh on demand, caches the default conversion until the dataset is modified (tracked by VTK's MTime), runs the operation, and converts the result back. The input is left untouched.
mesh.trimesh # accessor instance, cached on the dataset
mesh.trimesh.to_trimesh() # raw trimesh.Trimesh
mesh.trimesh.<operation>(...) # any method below
The default conversion triangulates the input. PyVista primitives like pv.Cube and pv.Cylinder work directly.
Point-in-mesh and proximity
| Method | Returns |
|---|---|
select_enclosed_points(points, check_surface=True, inside_out=False) |
copy of points with a SelectedPoints mask |
contains(points) |
boolean array, True where the point is inside the closed surface |
signed_distance(points) |
signed distance per query (negative inside, positive outside) |
closest_point(points) |
(closest, distance, triangle_id) tuple |
Sampling
| Method | Returns |
|---|---|
sample_surface(count, even=False, seed=None) |
vertex-only PolyData on the surface |
sample_volume(count, seed=None) |
vertex-only PolyData inside the volume |
Repair and smoothing
| Method | Notes |
|---|---|
repair(fill_holes=True, fix_normals=True, fix_winding=True, fix_inversion=True) |
hole filling, winding and inversion fixes |
fill_holes() |
patch boundary holes only |
smooth(method='taubin', iterations=10, lamb=0.5, nu=0.5) |
'taubin', 'humphrey', or 'laplacian' |
Subdivision and decimation
| Method | Notes |
|---|---|
subdivide(iterations=1) |
Loop midpoint subdivision |
subdivide_to_size(max_edge, max_iter=10) |
adaptive, edge-length driven |
simplify_quadric(face_count=None, percent=None, aggression=7) |
quadric edge-collapse decimation |
simplify_quadric requires the optional fast-simplification dependency, included in [extras].
Splits and slicing
| Method | Returns |
|---|---|
split(only_watertight=False) |
list of disconnected components |
section(normal, origin=None) |
polylines along one slice |
section_multiplane(plane_origin, plane_normal, heights) |
one PolyData per requested slice height |
Hulls and bounding shapes
| Method | Returns |
|---|---|
convex_hull() |
triangulated convex hull |
bounding_box(oriented=False) |
AABB or minimum-volume oriented box |
bounding_sphere() |
smallest enclosing sphere |
bounding_cylinder() |
minimum-volume bounding cylinder |
Voxelization
| Method | Returns |
|---|---|
voxelized(pitch, fill=True) |
binary pv.ImageData (filled cell-data); fill=False keeps shell |
Ray tracing
| Method | Notes |
|---|---|
ray_intersections(origins, directions, multiple_hits=True) |
NumPy ray engine, no Embree required |
Registration and transforms
| Method | Notes |
|---|---|
register(other, max_iterations=20) |
ICP alignment; returns (matrix, registered, cost) tuple |
apply_transform(matrix) |
apply a 4x4 transform via Trimesh |
other is a pv.DataSet, a trimesh.Trimesh, or an (N, 3) point-cloud array.
Properties
| Property | Returns |
|---|---|
is_watertight, is_winding_consistent, is_volume |
structural validity flags |
volume, area |
mass properties |
euler_number |
V - E + F |
center_mass, moment_inertia |
volumetric centroid and inertia tensor |
principal_inertia_components |
eigenvalues of the inertia tensor |
Module-level helpers
For inputs that do not start from an existing PolyData:
from pyvista_trimesh import (
convex_hull,
load_mesh,
primitive_annulus,
primitive_box,
primitive_capsule,
primitive_icosphere,
)
hull = convex_hull(points) # (N, 3) point cloud
mesh = load_mesh('model.glb') # any format Trimesh supports
ball = primitive_icosphere(subdivisions=4) # uniform-area triangulation
washer = primitive_annulus(r_min=0.4, r_max=1.0, height=0.2)
load_mesh handles GLB, GLTF, DAE, 3MF, OFF, and the rest of Trimesh's reader set. The result is a single pv.PolyData (scenes are concatenated).
For everything that already has a PyVista equivalent (pv.Cube, pv.Sphere, pv.Cylinder, ...), use PyVista directly and chain through .trimesh.
Conversion utilities
The accessor handles conversion automatically. Reach for these when you want a one-shot conversion outside the accessor:
from pyvista_trimesh import from_trimesh, to_trimesh
tmesh = to_trimesh(polydata, triangulate=True) # PolyData -> Trimesh
poly = from_trimesh(tmesh) # Trimesh -> PolyData
These wrap PyVista's built-in pv.to_trimesh / pv.from_trimesh (added in PyVista 0.47). Attribute mappings (vertex / face attributes, metadata, user_dict) match exactly.
Caveats
- Point-in-mesh requires a closed surface.
select_enclosed_pointsraisesRuntimeErrorif the input is not watertight. Passcheck_surface=Falseto skip the check. signed_distancerequires a watertight surface. For open meshes, useclosest_pointand take the unsigned distance.- Optional dependencies enable extra features.
voxelizedandselect_enclosed_pointsusertreewhen available,simplify_quadricrequiresfast-simplification, and the GLB / GLTF readers needshapelyand friends.[extras]installs all of them. - Cell data is dropped by the cached default conversion. Pass
pass_data=Truetoto_trimesh(ormesh.trimesh.to_trimesh(pass_data=True)) when you need round-tripping.
Development
git clone https://github.com/pyvista/pyvista-trimesh
cd pyvista-trimesh
just sync # uv sync --extra dev
just test # pytest with coverage
just lint # pre-commit run --all-files
just typecheck # mypy
just build # wheel + sdist
Acknowledgements
- Trimesh by Michael Dawson-Haggerty and contributors.
- PyVista for the accessor system and the rest of the visualization stack.
- The point-in-mesh implementation is the port of pyvista/pyvista#6898.
License
MIT.
Metadata
Release files for pyvista-trimesh 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyvista_trimesh-0.1.0.tar.gz | 1.9 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyvista_trimesh-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.9 MB
Release files / pyvista_trimesh-0.1.0.tar.gz
| Download URL | pyvista_trimesh-0.1.0.tar.gz |
|---|---|
| Size | 1.9 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ecf6aa4979ba2275aaf1a79a97da8388953cc89b10b51ba867dabc50835ac153
|
|
BLAKE2b-256 checksum How to use checksums |
9f2296019cde1587280f64b2693797573bdfbdf16a8b1327325d6f917b45f41e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 4, 2026.
Transparency logRelease files / pyvista_trimesh-0.1.0-py3-none-any.whl
| Download URL | pyvista_trimesh-0.1.0-py3-none-any.whl |
|---|---|
| Size | 20.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1cd2eb25e12a0b42cfb0e39e41476bc554d4e7058bd3667ab9ac688b2ec44609
|
|
BLAKE2b-256 checksum How to use checksums |
2a34f903edcc63775334e08dd43d6c23e9da8944bfd20341b5eea02750f51374
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 4, 2026.
Transparency log