Metadata-Version: 2.4
Name: pyvista-trimesh
Version: 0.1.0
Summary: PyVista accessor for the Trimesh library, with robust point-in-mesh, repair, sampling, smoothing, proximity, and more.
Author-email: The PyVista Developers <info@pyvista.org>
License-Expression: MIT
Project-URL: Homepage, https://github.com/pyvista/pyvista-trimesh
Project-URL: Issues, https://github.com/pyvista/pyvista-trimesh/issues
Project-URL: Repository, https://github.com/pyvista/pyvista-trimesh
Keywords: 3d,pyvista,trimesh,mesh,geometry,repair,proximity
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Scientific/Engineering :: Visualization
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: pyvista>=0.48
Requires-Dist: trimesh>=4.0
Provides-Extra: extras
Requires-Dist: fast-simplification>=0.1; extra == "extras"
Requires-Dist: networkx>=3.0; extra == "extras"
Requires-Dist: rtree>=1.0; extra == "extras"
Requires-Dist: scipy>=1.10; extra == "extras"
Requires-Dist: shapely>=2.0; extra == "extras"
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: pytest-pyvista>=0.1; extra == "dev"
Requires-Dist: ruff>=0.15; extra == "dev"
Requires-Dist: pyvista-trimesh[extras]; extra == "dev"
Dynamic: license-file

# pyvista-trimesh

![banner](assets/banner.png)

A PyVista accessor for [Trimesh](https://github.com/mikedh/trimesh). Point-in-mesh, proximity queries, sampling, repair, smoothing, voxelization, and ICP are all reachable as `mesh.trimesh.<op>` on any `pv.PolyData`.

```python
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](https://github.com/pyvista/pyvista/pull/6898).

## Install

```bash
pip install pyvista-trimesh
```

For the rtree-backed `contains`, GLB / GLTF / 3MF readers, voxel grids, and quadric decimation:

```bash
pip install "pyvista-trimesh[extras]"
```

Requires Python 3.10+ and PyVista 0.48+.

## Quick start

```python
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:

```python
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.

```python
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`](https://github.com/pyvista/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:

```python
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:

```python
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_points` raises `RuntimeError` if the input is not watertight. Pass `check_surface=False` to skip the check.
- **`signed_distance` requires a watertight surface.** For open meshes, use `closest_point` and take the unsigned distance.
- **Optional dependencies enable extra features.** `voxelized` and `select_enclosed_points` use `rtree` when available, `simplify_quadric` requires `fast-simplification`, and the GLB / GLTF readers need `shapely` and friends. `[extras]` installs all of them.
- **Cell data is dropped** by the cached default conversion. Pass `pass_data=True` to `to_trimesh` (or `mesh.trimesh.to_trimesh(pass_data=True)`) when you need round-tripping.

## Development

```bash
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](https://github.com/mikedh/trimesh) by Michael Dawson-Haggerty and contributors.
- [PyVista](https://github.com/pyvista/pyvista) for the accessor system and the rest of the visualization stack.
- The point-in-mesh implementation is the port of [pyvista/pyvista#6898](https://github.com/pyvista/pyvista/pull/6898).

## License

MIT.
