Import, export, process, analyze and view triangular meshes.
Specialized functionality including voxelization, vector path handling, primitive shape generation, and advanced algorithms. These features extend trimesh's capabilities for specialized applications and research.
Convert meshes to voxel representations for volumetric analysis and processing.
def voxelized(self, pitch, method='subdivide', bounds=None) -> 'VoxelGrid':
"""
Convert mesh to voxel representation.
Parameters:
- pitch: float, voxel spacing/size
- method: str, voxelization method ('subdivide', 'binvox')
- bounds: (2, 3) custom bounds for voxelization
Returns:
VoxelGrid object
"""
class VoxelGrid:
"""3D voxel grid representation"""
@property
def shape(self) -> tuple:
"""Shape of voxel grid as (nx, ny, nz)"""
@property
def pitch(self) -> float:
"""Voxel spacing/size"""
@property
def bounds(self) -> np.ndarray:
"""Bounds of voxel grid"""
@property
def volume(self) -> float:
"""Total volume of filled voxels"""
def as_boxes(self, solid=True) -> 'Trimesh':
"""
Convert filled voxels to box meshes.
Parameters:
- solid: bool, create solid boxes or just faces
Returns:
Trimesh containing voxel boxes
"""
def marching_cubes(self, level=0.0) -> 'Trimesh':
"""
Extract mesh surface using marching cubes.
Parameters:
- level: float, iso-surface level
Returns:
Mesh representing voxel surface
"""
def fill_orthographic(self, direction=[0, 0, 1]) -> 'VoxelGrid':
"""
Fill voxels using orthographic projection.
Parameters:
- direction: (3,) projection direction
Returns:
Filled VoxelGrid
"""Create parametric geometric primitives.
def Sphere(radius=1.0, subdivisions=2, **kwargs) -> 'Trimesh':
"""
Create sphere mesh.
Parameters:
- radius: float, sphere radius
- subdivisions: int, subdivision level for smoothness
- **kwargs: additional mesh options
Returns:
Trimesh sphere
"""
def Box(extents=None, transform=None, **kwargs) -> 'Trimesh':
"""
Create box mesh.
Parameters:
- extents: (3,) box dimensions [width, height, depth]
- transform: (4, 4) transformation matrix
- **kwargs: additional mesh options
Returns:
Trimesh box
"""
def Cylinder(radius=1.0, height=1.0, sections=32, **kwargs) -> 'Trimesh':
"""
Create cylinder mesh.
Parameters:
- radius: float, cylinder radius
- height: float, cylinder height
- sections: int, number of circumferential sections
- **kwargs: additional mesh options
Returns:
Trimesh cylinder
"""
def Capsule(radius=1.0, height=1.0, **kwargs) -> 'Trimesh':
"""
Create capsule (cylinder with hemispherical caps).
Parameters:
- radius: float, capsule radius
- height: float, cylinder portion height
- **kwargs: additional mesh options
Returns:
Trimesh capsule
"""
def Cone(radius=1.0, height=1.0, sections=32, **kwargs) -> 'Trimesh':
"""
Create cone mesh.
Parameters:
- radius: float, base radius
- height: float, cone height
- sections: int, number of base sections
- **kwargs: additional mesh options
Returns:
Trimesh cone
"""
def Annulus(r_min=0.5, r_max=1.0, height=1.0, **kwargs) -> 'Trimesh':
"""
Create annulus (hollow cylinder).
Parameters:
- r_min: float, inner radius
- r_max: float, outer radius
- height: float, annulus height
- **kwargs: additional mesh options
Returns:
Trimesh annulus
"""Work with 2D and 3D vector paths, curves, and sketches.
class Path2D:
"""2D vector path representation"""
@property
def vertices(self) -> np.ndarray:
"""Path vertices as (n, 2) array"""
@property
def entities(self) -> list:
"""Path entities (lines, arcs, curves)"""
@property
def bounds(self) -> np.ndarray:
"""Path bounding box"""
@property
def length(self) -> float:
"""Total path length"""
@property
def is_closed(self) -> bool:
"""True if path forms closed loop"""
def extrude(self, height, **kwargs) -> 'Trimesh':
"""
Extrude path to create 3D mesh.
Parameters:
- height: float, extrusion height
- **kwargs: extrusion options
Returns:
Extruded Trimesh
"""
def buffer(self, distance, **kwargs) -> 'Path2D':
"""
Create offset path at specified distance.
Parameters:
- distance: float, offset distance
- **kwargs: buffer options
Returns:
Buffered Path2D
"""
class Path3D:
"""3D vector path representation"""
@property
def vertices(self) -> np.ndarray:
"""Path vertices as (n, 3) array"""
@property
def length(self) -> float:
"""Total path length"""
def to_planar(self, to_2D=None) -> 'Path2D':
"""
Convert to 2D path by projection.
Parameters:
- to_2D: (4, 4) transformation to 2D plane
Returns:
Projected Path2D
"""Advanced mesh parameterization and texture mapping.
def unwrap(self, method='angle_based') -> tuple:
"""
UV unwrap mesh for texture mapping.
Parameters:
- method: str, unwrapping method
Returns:
tuple: (uv_coordinates, face_index_map)
"""
def parameterize_spherical(self) -> np.ndarray:
"""
Spherical parameterization of mesh.
Returns:
(n, 2) spherical coordinates for vertices
"""
def parameterize_cylindrical(self, axis=[0, 0, 1]) -> np.ndarray:
"""
Cylindrical parameterization of mesh.
Parameters:
- axis: (3,) cylinder axis direction
Returns:
(n, 2) cylindrical coordinates for vertices
"""Create meshes from various data sources and mathematical functions.
def from_heightmap(heightmap, pitch=1.0, **kwargs) -> 'Trimesh':
"""
Create mesh from 2D heightmap data.
Parameters:
- heightmap: (h, w) height values
- pitch: float, spacing between height samples
- **kwargs: mesh generation options
Returns:
Trimesh representing heightmap surface
"""
def from_function(func, bounds, resolution=50, **kwargs) -> 'Trimesh':
"""
Create mesh from mathematical function z = f(x, y).
Parameters:
- func: function taking (x, y) and returning z
- bounds: ((x_min, x_max), (y_min, y_max)) function domain
- resolution: int, sampling resolution
- **kwargs: mesh options
Returns:
Trimesh representing function surface
"""
def from_points(points, method='delaunay', **kwargs) -> 'Trimesh':
"""
Create mesh from point cloud using surface reconstruction.
Parameters:
- points: (n, 3) point coordinates
- method: str, reconstruction method
- **kwargs: reconstruction options
Returns:
Reconstructed Trimesh
"""Specialized algorithms for complex mesh processing tasks.
def poisson_reconstruction(self, depth=8, **kwargs) -> 'Trimesh':
"""
Poisson surface reconstruction from point cloud with normals.
Parameters:
- depth: int, octree depth for reconstruction
- **kwargs: Poisson reconstruction options
Returns:
Reconstructed mesh surface
"""
def alpha_shape(points, alpha) -> 'Trimesh':
"""
Compute alpha shape of point set.
Parameters:
- points: (n, 3) point coordinates
- alpha: float, alpha parameter controlling shape
Returns:
Alpha shape mesh
"""
def medial_axis(self, **kwargs) -> tuple:
"""
Compute medial axis (skeleton) of mesh.
Parameters:
- **kwargs: medial axis computation options
Returns:
tuple: (skeleton_points, skeleton_radius)
"""
def geodesic_distance(self, start_vertices, **kwargs) -> np.ndarray:
"""
Compute geodesic distances on mesh surface.
Parameters:
- start_vertices: array of starting vertex indices
- **kwargs: geodesic computation options
Returns:
(n,) geodesic distances to each vertex
"""Advanced mesh optimization and quality improvement algorithms.
def optimize_vertex_order(self) -> 'Trimesh':
"""
Optimize vertex ordering for better cache performance.
Returns:
Mesh with optimized vertex order
"""
def optimize_face_order(self) -> 'Trimesh':
"""
Optimize face ordering for rendering efficiency.
Returns:
Mesh with optimized face order
"""
def isotropic_remeshing(self, target_edge_length, iterations=10) -> 'Trimesh':
"""
Isotropic remeshing for uniform triangle quality.
Parameters:
- target_edge_length: float, desired edge length
- iterations: int, number of remeshing iterations
Returns:
Remeshed Trimesh with improved quality
"""
def feature_preserving_smoothing(self, iterations=5, feature_angle=45.0) -> 'Trimesh':
"""
Smooth mesh while preserving sharp features.
Parameters:
- iterations: int, smoothing iterations
- feature_angle: float, angle threshold for feature detection
Returns:
Smoothed mesh with preserved features
"""import trimesh
import numpy as np
# Load mesh
mesh = trimesh.load('model.stl')
# Convert to voxels
voxel_pitch = 0.1 # 0.1 unit voxel size
voxel_grid = mesh.voxelized(pitch=voxel_pitch)
print(f"Voxel grid shape: {voxel_grid.shape}")
print(f"Original volume: {mesh.volume:.4f}")
print(f"Voxel volume: {voxel_grid.volume:.4f}")
print(f"Volume error: {abs(mesh.volume - voxel_grid.volume)/mesh.volume*100:.2f}%")
# Convert voxels back to mesh
voxel_mesh = voxel_grid.as_boxes()
print(f"Voxel mesh faces: {len(voxel_mesh.faces)}")
# Smooth voxel surface with marching cubes
smooth_mesh = voxel_grid.marching_cubes()
print(f"Marching cubes mesh faces: {len(smooth_mesh.faces)}")
# Visualize results
scene = trimesh.Scene([
mesh, # Original
voxel_mesh.apply_translation([5, 0, 0]), # Voxel boxes
smooth_mesh.apply_translation([10, 0, 0]) # Marching cubes
])
scene.show()# Create various primitive shapes
primitives = [
('Sphere', trimesh.primitives.Sphere(radius=1.0, subdivisions=3)),
('Box', trimesh.primitives.Box(extents=[2, 1, 0.5])),
('Cylinder', trimesh.primitives.Cylinder(radius=0.8, height=2.0, sections=16)),
('Cone', trimesh.primitives.Cone(radius=1.0, height=1.5, sections=12)),
('Capsule', trimesh.primitives.Capsule(radius=0.6, height=1.0)),
('Annulus', trimesh.primitives.Annulus(r_min=0.3, r_max=0.8, height=1.0))
]
# Position primitives in a grid
scene = trimesh.Scene()
for i, (name, primitive) in enumerate(primitives):
x_pos = (i % 3) * 3
y_pos = (i // 3) * 3
transform = trimesh.transformations.translation_matrix([x_pos, y_pos, 0])
# Color each primitive differently
colors = [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0], [1, 0, 1], [0, 1, 1]]
primitive.visual.face_colors = colors[i] + [1.0]
scene.add_geometry(primitive, transform=transform)
print(f"{name}: Volume={primitive.volume:.4f}, Area={primitive.area:.4f}")
scene.show()# Create 2D path from points
path_points = np.array([
[0, 0], [1, 0], [1.5, 0.5], [1, 1], [0, 1], [0, 0] # Closed path
])
path = trimesh.load_path(path_points)
print(f"Path length: {path.length:.4f}")
print(f"Path is closed: {path.is_closed}")
print(f"Path bounds: {path.bounds}")
# Extrude path to create 3D mesh
extruded_mesh = path.extrude(height=2.0)
print(f"Extruded mesh volume: {extruded_mesh.volume:.4f}")
# Create offset/buffer of path
buffered_path = path.buffer(distance=0.2)
buffered_mesh = buffered_path.extrude(height=1.0)
# Visualize original and buffered extrusions
scene = trimesh.Scene([
extruded_mesh,
buffered_mesh.apply_translation([3, 0, 0])
])
scene.show()
# Work with 3D paths
curve_points = np.array([
[np.cos(t), np.sin(t), t/5] for t in np.linspace(0, 4*np.pi, 100)
])
path_3d = trimesh.load_path(curve_points)
print(f"3D path length: {path_3d.length:.4f}")
# Convert 3D path to 2D by projection
path_2d = path_3d.to_planar()
path_2d_mesh = path_2d.extrude(height=0.5)# Generate mesh from mathematical function
def surface_function(x, y):
return 0.5 * np.sin(2*np.pi*x) * np.cos(2*np.pi*y) + 0.2 * np.sin(5*np.pi*x)
bounds = ((-1, 1), (-1, 1)) # x and y ranges
function_mesh = trimesh.creation.from_function(
surface_function,
bounds=bounds,
resolution=100
)
print(f"Function mesh: {len(function_mesh.vertices)} vertices, {len(function_mesh.faces)} faces")
function_mesh.show()
# Create mesh from heightmap
heightmap = np.random.rand(50, 50) * 0.5 # Random heightmap
heightmap += 2 * np.exp(-((np.arange(50)[:, None] - 25)**2 + (np.arange(50) - 25)**2) / 100) # Add peak
heightmap_mesh = trimesh.creation.from_heightmap(heightmap, pitch=0.1)
print(f"Heightmap mesh volume: {heightmap_mesh.volume:.4f}")
# Visualize heightmap with color mapping
heights = heightmap_mesh.vertices[:, 2]
normalized_heights = (heights - heights.min()) / (heights.max() - heights.min())
colors = plt.cm.terrain(normalized_heights)
heightmap_mesh.visual.vertex_colors = (colors * 255).astype(np.uint8)
heightmap_mesh.show()# Generate sample point cloud
n_points = 1000
theta = np.random.uniform(0, 2*np.pi, n_points)
phi = np.random.uniform(0, np.pi, n_points)
radius = 1 + 0.1 * np.random.randn(n_points) # Noisy sphere
points = np.column_stack([
radius * np.sin(phi) * np.cos(theta),
radius * np.sin(phi) * np.sin(theta),
radius * np.cos(phi)
])
# Create point cloud
point_cloud = trimesh.PointCloud(points)
print(f"Point cloud: {len(point_cloud.vertices)} points")
# Surface reconstruction
reconstructed_mesh = trimesh.creation.from_points(points, method='ball_pivoting')
if reconstructed_mesh is not None:
print(f"Reconstructed mesh: {len(reconstructed_mesh.faces)} faces")
print(f"Volume: {reconstructed_mesh.volume:.4f}")
# Visualize original points and reconstruction
scene = trimesh.Scene([
point_cloud.apply_translation([-3, 0, 0]),
reconstructed_mesh.apply_translation([3, 0, 0])
])
scene.show()
# Alpha shape reconstruction
alpha_mesh = trimesh.creation.alpha_shape(points, alpha=0.3)
if alpha_mesh is not None:
print(f"Alpha shape: {len(alpha_mesh.faces)} faces")
alpha_mesh.show()# Load complex mesh
mesh = trimesh.load('complex_model.stl')
# Compute geodesic distance from a starting vertex
start_vertex = 0 # Start from first vertex
geodesic_distances = mesh.geodesic_distance([start_vertex])
# Visualize geodesic distances with colors
normalized_distances = geodesic_distances / geodesic_distances.max()
colors = plt.cm.plasma(normalized_distances)
mesh.visual.vertex_colors = (colors * 255).astype(np.uint8)
mesh.show()
# Isotropic remeshing for better triangle quality
target_edge_length = mesh.edge_lengths().mean()
remeshed = mesh.isotropic_remeshing(target_edge_length, iterations=5)
print(f"Original: {len(mesh.faces)} faces")
print(f"Remeshed: {len(remeshed.faces)} faces")
# Compare triangle quality
original_angles = mesh.face_angles()
remeshed_angles = remeshed.face_angles()
print(f"Original angle std: {original_angles.std():.4f}")
print(f"Remeshed angle std: {remeshed_angles.std():.4f}")
# Feature-preserving smoothing
smoothed = mesh.feature_preserving_smoothing(iterations=3, feature_angle=30.0)
# Visualize original, remeshed, and smoothed
scene = trimesh.Scene([
mesh,
remeshed.apply_translation([5, 0, 0]),
smoothed.apply_translation([10, 0, 0])
])
scene.show()# Optimize mesh for rendering performance
mesh = trimesh.load('large_model.obj')
print(f"Original mesh: {len(mesh.vertices)} vertices, {len(mesh.faces)} faces")
# Optimize vertex and face ordering
optimized_mesh = mesh.copy()
optimized_mesh = optimized_mesh.optimize_vertex_order()
optimized_mesh = optimized_mesh.optimize_face_order()
print("Mesh optimized for cache performance")
# Simplify mesh while preserving important features
simplified = mesh.simplify_quadratic_decimation(face_count=len(mesh.faces)//2)
print(f"Simplified mesh: {len(simplified.faces)} faces ({100*len(simplified.faces)/len(mesh.faces):.1f}% of original)")
# Compare volumes
print(f"Original volume: {mesh.volume:.6f}")
print(f"Simplified volume: {simplified.volume:.6f}")
print(f"Volume error: {abs(mesh.volume - simplified.volume)/mesh.volume*100:.2f}%")
# Visualize comparison
scene = trimesh.Scene([
mesh.apply_translation([-3, 0, 0]),
simplified.apply_translation([3, 0, 0])
])
scene.show()Install with Tessl CLI
npx tessl i tessl/pypi-trimesh