Import, export, process, analyze and view triangular meshes.
Comprehensive geometric analysis including mass properties, curvature analysis, mesh quality metrics, inertia calculations, and geometric measurements. These tools enable detailed mesh characterization and engineering analysis.
Calculate physical properties assuming uniform density.
@property
def mass_properties(self) -> dict:
"""
Calculate mass properties including center of mass and inertia tensor.
Returns:
dict with keys:
- 'volume': float, mesh volume
- 'mass': float, mass (volume * density)
- 'density': float, material density
- 'center_mass': (3,) center of mass
- 'inertia': (3, 3) inertia tensor
"""
@property
def moment_inertia(self) -> np.ndarray:
"""
Moment of inertia tensor around center of mass.
Returns:
(3, 3) inertia tensor matrix
"""
@property
def principal_inertia_components(self) -> np.ndarray:
"""
Principal moments of inertia (eigenvalues of inertia tensor).
Returns:
(3,) principal inertia values in ascending order
"""
@property
def principal_inertia_vectors(self) -> np.ndarray:
"""
Principal inertia axes (eigenvectors of inertia tensor).
Returns:
(3, 3) matrix where columns are principal axes
"""
@property
def center_mass(self) -> np.ndarray:
"""
Center of mass (volume-weighted centroid).
Returns:
(3,) center of mass coordinates
"""Analyze mesh curvature properties and surface characteristics.
def discrete_gaussian_curvature_measure(self) -> np.ndarray:
"""
Discrete Gaussian curvature at each vertex.
Returns:
(n,) Gaussian curvature values at vertices
"""
def discrete_mean_curvature_measure(self) -> np.ndarray:
"""
Discrete mean curvature at each vertex.
Returns:
(n,) mean curvature values at vertices
"""
def vertex_defects(self) -> np.ndarray:
"""
Angular defect at each vertex (2π - sum of incident angles).
Returns:
(n,) angular defects at vertices
"""
def face_angles(self) -> np.ndarray:
"""
Interior angles of each face.
Returns:
(m, 3) angles for each face corner
"""
def face_angles_sparse(self) -> np.ndarray:
"""
Sparse representation of face angles.
Returns:
(3*m,) flattened face angles
"""Basic geometric properties and measurements.
@property
def area(self) -> float:
"""Total surface area"""
@property
def area_faces(self) -> np.ndarray:
"""
Surface area of each face.
Returns:
(m,) area values for each face
"""
@property
def volume(self) -> float:
"""
Mesh volume (positive for watertight meshes).
Returns:
float, volume value
"""
@property
def bounds(self) -> np.ndarray:
"""
Axis-aligned bounding box.
Returns:
(2, 3) array: [[min_x, min_y, min_z], [max_x, max_y, max_z]]
"""
@property
def extents(self) -> np.ndarray:
"""
Size in each dimension.
Returns:
(3,) array: [width, height, depth]
"""
@property
def scale(self) -> float:
"""Scaling factor relative to unit cube"""
@property
def centroid(self) -> np.ndarray:
"""
Geometric centroid (average of vertices).
Returns:
(3,) centroid coordinates
"""Analyze mesh quality and identify potential issues.
@property
def euler_number(self) -> int:
"""
Euler characteristic (V - E + F).
Returns:
int, Euler number (2 for closed surfaces)
"""
@property
def is_watertight(self) -> bool:
"""True if mesh is watertight (manifold and closed)"""
@property
def is_winding_consistent(self) -> bool:
"""True if face winding is consistent"""
@property
def is_volume(self) -> bool:
"""True if mesh encloses a volume"""
@property
def face_adjacency(self) -> np.ndarray:
"""
Face adjacency matrix.
Returns:
(m, m) sparse matrix showing face connectivity
"""
@property
def face_adjacency_edges(self) -> np.ndarray:
"""
Edges between adjacent faces.
Returns:
(p, 2) array of face indices sharing edges
"""
def face_adjacency_convex(self, tolerance=0.0) -> np.ndarray:
"""
Check if adjacent faces form convex angles.
Parameters:
- tolerance: float, angle tolerance
Returns:
(p,) boolean array indicating convex adjacencies
"""
def face_normals_from_vertices(self) -> np.ndarray:
"""
Calculate face normals from vertex positions.
Returns:
(m, 3) face normal vectors
"""Compute statistical properties of mesh geometry.
def vertex_degree(self) -> np.ndarray:
"""
Number of faces incident to each vertex.
Returns:
(n,) vertex degrees
"""
def edge_lengths(self) -> np.ndarray:
"""
Length of each unique edge.
Returns:
(e,) edge length values
"""
def edge_lengths_histogram(self, bins=20) -> tuple:
"""
Histogram of edge lengths.
Parameters:
- bins: int, number of histogram bins
Returns:
tuple: (counts, bin_edges)
"""
def face_areas_histogram(self, bins=20) -> tuple:
"""
Histogram of face areas.
Parameters:
- bins: int, number of histogram bins
Returns:
tuple: (counts, bin_edges)
"""Compare meshes and compute similarity metrics.
def hausdorff_distance(self, other) -> float:
"""
Hausdorff distance to another mesh.
Parameters:
- other: Trimesh object
Returns:
float, maximum distance between meshes
"""
def symmetric_difference_volume(self, other) -> float:
"""
Volume of symmetric difference with another mesh.
Parameters:
- other: Trimesh object
Returns:
float, volume of non-overlapping regions
"""
def difference_volume(self, other) -> float:
"""
Volume difference with another mesh.
Parameters:
- other: Trimesh object
Returns:
float, volume difference
"""Specialized geometric analysis functions.
def integral_gaussian_curvature(self) -> float:
"""
Total Gaussian curvature (should equal 4π for closed surfaces).
Returns:
float, integrated Gaussian curvature
"""
def integral_mean_curvature(self) -> float:
"""
Total mean curvature over surface.
Returns:
float, integrated mean curvature
"""
def sphericity(self) -> float:
"""
Sphericity measure (how sphere-like the mesh is).
Returns:
float, sphericity value (1.0 for perfect sphere)
"""
def compactness(self) -> float:
"""
Compactness measure (surface area vs volume ratio).
Returns:
float, compactness value
"""import trimesh
import numpy as np
# Load mesh
mesh = trimesh.load('part.stl')
# Set material density (kg/m³)
mesh.density = 7850 # Steel density
# Get complete mass properties
props = mesh.mass_properties
print(f"Volume: {props['volume']:.6f} m³")
print(f"Mass: {props['mass']:.3f} kg")
print(f"Center of mass: {props['center_mass']}")
print(f"Inertia tensor:\n{props['inertia']}")
# Principal inertia analysis
principal_values = mesh.principal_inertia_components
principal_vectors = mesh.principal_inertia_vectors
print(f"Principal moments: {principal_values}")
print(f"Principal axes:\n{principal_vectors}")
# Transform to principal axes
transform = mesh.principal_inertia_transform
aligned_mesh = mesh.copy()
aligned_mesh.apply_transform(transform)# Calculate curvature properties
gaussian_curvature = mesh.discrete_gaussian_curvature_measure()
mean_curvature = mesh.discrete_mean_curvature_measure()
# Analyze curvature distribution
print(f"Gaussian curvature range: {gaussian_curvature.min():.4f} to {gaussian_curvature.max():.4f}")
print(f"Mean curvature range: {mean_curvature.min():.4f} to {mean_curvature.max():.4f}")
# Find high curvature regions
high_curvature_threshold = np.percentile(np.abs(mean_curvature), 90)
high_curvature_vertices = np.where(np.abs(mean_curvature) > high_curvature_threshold)[0]
print(f"High curvature vertices: {len(high_curvature_vertices)}")
# Visualize curvature
if mesh.visual.kind == 'vertex':
# Color vertices by curvature
curvature_normalized = (mean_curvature - mean_curvature.min()) / (mean_curvature.max() - mean_curvature.min())
colors = plt.cm.viridis(curvature_normalized)[:, :3] * 255
mesh.visual.vertex_colors = colors.astype(np.uint8)
mesh.show()# Basic quality checks
print(f"Is watertight: {mesh.is_watertight}")
print(f"Is winding consistent: {mesh.is_winding_consistent}")
print(f"Euler number: {mesh.euler_number}")
print(f"Vertex count: {len(mesh.vertices)}")
print(f"Face count: {len(mesh.faces)}")
# Geometric properties
print(f"Volume: {mesh.volume:.6f}")
print(f"Surface area: {mesh.area:.6f}")
print(f"Bounds: {mesh.bounds}")
print(f"Extents: {mesh.extents}")
# Quality metrics
print(f"Sphericity: {mesh.sphericity():.4f}")
print(f"Compactness: {mesh.compactness():.4f}")
# Edge and face statistics
edge_lengths = mesh.edge_lengths()
face_areas = mesh.area_faces
print(f"Edge length range: {edge_lengths.min():.6f} to {edge_lengths.max():.6f}")
print(f"Face area range: {face_areas.min():.6f} to {face_areas.max():.6f}")
print(f"Mean edge length: {edge_lengths.mean():.6f}")
print(f"Mean face area: {face_areas.mean():.6f}")# Load two meshes to compare
mesh1 = trimesh.load('original.stl')
mesh2 = trimesh.load('modified.stl')
# Hausdorff distance (measure of maximum deviation)
hausdorff_dist = mesh1.hausdorff_distance(mesh2)
print(f"Hausdorff distance: {hausdorff_dist:.6f}")
# Volume comparison
vol_diff = abs(mesh1.volume - mesh2.volume)
vol_percent = (vol_diff / mesh1.volume) * 100
print(f"Volume difference: {vol_diff:.6f} ({vol_percent:.2f}%)")
# Symmetric difference volume
sym_diff_vol = mesh1.symmetric_difference_volume(mesh2)
print(f"Symmetric difference volume: {sym_diff_vol:.6f}")
# Surface area comparison
area_diff = abs(mesh1.area - mesh2.area)
area_percent = (area_diff / mesh1.area) * 100
print(f"Surface area difference: {area_diff:.6f} ({area_percent:.2f}%)")import matplotlib.pyplot as plt
# Vertex degree distribution
vertex_degrees = mesh.vertex_degree()
print(f"Vertex degree range: {vertex_degrees.min()} to {vertex_degrees.max()}")
print(f"Mean vertex degree: {vertex_degrees.mean():.2f}")
# Edge length histogram
edge_lengths = mesh.edge_lengths()
counts, bin_edges = mesh.edge_lengths_histogram(bins=50)
plt.figure(figsize=(10, 6))
plt.subplot(1, 2, 1)
plt.hist(edge_lengths, bins=50, alpha=0.7)
plt.xlabel('Edge Length')
plt.ylabel('Count')
plt.title('Edge Length Distribution')
# Face area histogram
face_areas = mesh.area_faces
plt.subplot(1, 2, 2)
plt.hist(face_areas, bins=50, alpha=0.7)
plt.xlabel('Face Area')
plt.ylabel('Count')
plt.title('Face Area Distribution')
plt.tight_layout()
plt.show()
# Find outliers
edge_mean = edge_lengths.mean()
edge_std = edge_lengths.std()
edge_outliers = np.where(edge_lengths > edge_mean + 3*edge_std)[0]
print(f"Edge length outliers: {len(edge_outliers)} edges")Install with Tessl CLI
npx tessl i tessl/pypi-trimesh