rmesh
rmesh
is an experimental reimplementation of trimesh
in Rust using PyO3
. It is not currently released and may never be. The general idea of rmesh
is to be mostly API compatible and pass trimesh's large test suite with the exception of behavior changes made for quality reasons.
Background
Trimesh was originally a research codebase that grew organically to solve problems I care about. trimesh
is probably popular because it tries quite hard to avoid being annoying: pip install
always works, expensive values are cached automatically on the mesh object, and it uses occasionally convoluted indexing tricks to avoid Evil Python Behavior such as loops. trimesh
usually performs comparably to carefully written compiled mesh codebases for many common tasks, which is achieved with the careful use of numpy. It also is opinionated and maybe a little too magical sometimes, given my Very Strong Opinions that most applications that consume meshes should be like 10 lines.
trimesh
also predates type hints and has a larger than ideal number of optional dependencies, an upstream burden which requires effort to maintain. There's also been a revolution in the Python ecosystem where some great Python libraries are now written in Rust, thanks to the awesome work of PyO3
and cibuildwheel
. Rmesh is intended to package similarly to polars
, where it is written in Rust but usable from both Python and Rust. Rust is also a nice low-level language with great tooling and is fast enough to implement very sensitive iterative algorithms.
Project Status
This is experimental, and doesn't do anything at the moment. It hasn't been released to package indexes and may never be if it turns out to be a dead end. trimesh
isn't going anywhere as it has a ton of surface area. This won't be released to indexes until the following MVP feature list exists:
- Load an STL, OBJ, and PLY file from Python 3.0x faster than
trimesh
in Python. - Implement the following for a mesh object:
edges
,euler_number
,merge_vertices
,face_normals
,face_adjacency
,face_adjacency_angles
,extents
,bounds
,split
,is_watertight
,is_winding_consistant
,is_volume
,is_convex
, mass properties using the same algorithm,principal_inertia_components
(using nalgebrahermitian_eigen
),
Goals
- Targeting use as a Rust crate, a nicely type hinted Python module, and WASM. WASM is mostly because
wasm-pack
made it kind of easy, and keeping the build in CI from the start makes sure we don't add things that break WASM builds. - Be generally faster than trimesh and pass many-to-most of trimesh's unit tests.
- Have a relatively small number of carefully chosen dependencies, and prefer to vendor/re-write the rest in Rust. Generally try to keep it to major crates, like
nalgebra
,anyhow
,bytemuck
, although if there's a well-maintained implementation of something in pure Rust we should use it (i.e. earcut). - Build Python wheels for every platform using cibuildwheel.
Implementation Notes
- Caching
trimesh
uses hashes of numpy arrays and dirty flags on anndarray
subclass to save expensive values, likeface_normals
(very often the slowest thing in a profile).rmesh
objects are immutable and mutations produce new objects with a new cache. Cached values are saved to aRwLock
cache which can be accessed through convenience macros. TODO: does every value have to also be anArc
?
- Soft Dependencies
trimesh
tries to package high-quality preferably header-only upstream C codebases usingcibuildwheel
into their own Python package.rmesh
tries to use a small number of compile-time dependencies. Most mesh algorithms should be done inside the codebase to avoid chasing mysterious upstream dependencies if at all possible. The Python install should havenumpy
as the only Python dependency, as all heavy lifting will be implemented in Rust.
- Basic Data Types
trimesh
uses anp.ndarray
object for vertices (float64
) and faces (int64
)rmesh
has the choice betweenndarray
vsnalgebra
:ndarray::Array2<f64>
orVec<nalgebra::Point3<f64>>
rmesh
started with nalgebra, converted tondarray
on a branch, and then reverted back to nalgebra. Forum posts indicate ndarray doesn't win on performance like I kind of expected it to. The nalgebra objects have some nice properties: specifically you can constrain the number of columns (i.eVector3
vsVector4
) which I couldn't make work in ndarray although it's probably possible. I also generally preferred the nalgebra API but this is certainly a matter of taste.
- Vertex Attributes
trimesh
kind of squirrels these away in multiple places:mesh.visual.TextureVisuals.uv
which puts them inmesh.visual.vertex_attributes
but notmesh.vertex_attributes
. Which is a little weird. And what if you had multiple sets of UV's and colors?rmesh
intends to be more attribute-forward. Faces and vertices each get a flatVec
ofAttribute
, similar to the GLTF format. For instance if a function wanted vertex color they'd go through the vec and then take the first (or n-th)Color
attribute. We could have a lookup helper if we really wanted to but most meshes as loaded rarely have more than ~3 attributes (with a median of 0).
Project Layout: Crates
rmesh
is set up as a Cargo workspace which is a common choice for a complex project. The workspace crates (in ./crates
) are:
rmesh
- the basic crate where algorithms are implemented.
rmesh_macro
- all
proc macros
must be their own crate for Reasons. This as of writing only contains thecache_access
proc macro which handles some of the boilerplate for dealing with theRwLock
cache.
- all
rmesh_python
- The crate that builds to
pip install rmesh
, and includes a dependency onPyO3
and other Python plumbing. This should be 100% boilerplate for accessingrmesh
.
- The crate that builds to
rmesh_wasm
- The crate that builds to a WASM blob for use in Node and browsers.
rmesh_external
(proposed but not implemented)- For things that really have to be in C/C++, like accessing OpenCASCADE for STEP loading. This doesn't work with
wasm-pack
without a lot of plumbing work.
- For things that really have to be in C/C++, like accessing OpenCASCADE for STEP loading. This doesn't work with