Getting started
Install
uv add triwarp
or with pip:
pip install triwarp
Requires Python ≥ 3.11 and warp-lang ≥ 1.17. A CUDA-capable GPU is recommended but not
required — every function also runs on Warp's CPU backend, same code, same results. Mesh file
I/O via meshio is an optional extra:
pip install triwarp[io].
The five-minute mental model
triwarp has no scene graph, no viewer, and no mandatory mesh object. A mesh is two
warp.array buffers — vertex
positions and a flat triangle index buffer — and every function is a plain call that takes
arrays and returns arrays:
import triwarp as tw
vertices, faces = tw.creation.icosphere(subdivisions=4)
# vertices: wp.array[wp.vec3], shape (2562,)
# faces: wp.array[wp.int32], shape (15360,) -- flat (3 * n_faces,), not (n_faces, 3)
That flat, (3 * n_faces,) face layout (rather than (n_faces, 3)) is the one convention that
differs from most mesh libraries you may already know, and it's worth internalizing early —
Concepts explains why, and Migrating from another library
maps the functions you already reach for onto their triwarp equivalents.
An optional Trimesh class wraps that same pair of arrays and caches
derived quantities (normals, adjacency, boundary loops, manifoldness predicates) the first time
each is asked for:
mesh = tw.Trimesh(vertices, faces)
print(mesh.area) # 12.551353454589844
print(mesh.is_watertight) # True
print(mesh.euler_characteristic) # 2
Nothing about the free functions requires it — reach for Trimesh when an object is more
convenient than threading (vertices, faces) through every call, and drop back to arrays when a
pipeline benefits from staying explicit about what's being computed.
A first pipeline
A realistic input is rarely as clean as a freshly generated icosphere. The snippet below builds a stand-in for one — punches a hole in the sphere, then bolts on a small piece of unrelated debris — and runs it through the same repair-then-remesh pipeline you'd point at a raw scan:
import numpy as np
import warp as wp
import triwarp as tw
vertices, faces = tw.creation.icosphere(subdivisions=3)
# Punch a hole (drop faces whose centroid sits in a small polar cap)...
centroids = tw.triangles.face_centroids(vertices, faces).numpy()
keep_mask = wp.array(centroids[:, 2] < 0.9, dtype=wp.bool, device=vertices.device)
holed_vertices, holed_faces = tw.selection.submesh_from_face_mask(vertices, faces, keep_mask)
# ...and glue on an unrelated speck of debris, the way a scanner might pick up a stray fragment.
debris_vertices, debris_faces = tw.creation.tetrahedron(device=vertices.device)
debris_vertices = wp.array(
debris_vertices.numpy() * 0.05 + np.array([3.0, 0.0, 0.0], dtype=np.float32),
dtype=wp.vec3,
device=vertices.device,
)
broken_vertices, broken_faces = tw.combine.concatenate(
[(holed_vertices, holed_faces), (debris_vertices, debris_faces)]
)
print(tw.validation.is_watertight(broken_vertices, broken_faces)) # False
# One call: drop small components, close the hole, and clean up what's left.
repaired_vertices, repaired_faces = tw.repair.make_solid(
broken_vertices, broken_faces, keep_largest=True
)
print(tw.validation.is_watertight(repaired_vertices, repaired_faces)) # True
# Feature-preserving isotropic remeshing (split / collapse / flip / smooth / reproject).
remeshed_vertices, remeshed_faces = tw.remesh.isotropic_remesh(
repaired_vertices, repaired_faces, target_length=0.2
)
Every intermediate value here is a wp.array on whatever device the input arrived on (CUDA if
one is present) — nothing crossed back to the host between submesh_from_face_mask,
concatenate, make_solid, and isotropic_remesh. That's the "device follows the data" design
principle in practice; see Concepts for the rest of it.
Where to next
- Concepts — the four ideas worth understanding before writing anything nontrivial: arrays in/arrays out, the device follows the data, host syncs are budgeted, and everything is measured rather than assumed.
- Cookbook — task-oriented recipes: cleaning a scan, point clouds to watertight surfaces, geodesic distance fields, aligning two scans.
- Migrating from another library — already know trimesh, libigl, Open3D, MeshLab, potpourri3d, or PyTorch3D? Start here for a function-by-function map.
- API Reference — every public module, generated from its own docstrings.