Triangle mesh repair and processing utilities.
This crate provides comprehensive tools for loading, validating, repairing, and transforming triangle meshes. It's designed for 3D printing pipelines, mesh processing, and geometry operations.
Features
- File I/O: Load and save STL, OBJ, 3MF, and PLY formats
- Validation: Check for non-manifold edges, holes, self-intersections, winding issues
- Repair: Fill holes, fix winding, remove degenerates, weld vertices
- Analysis: Component detection, wall thickness measurement, volume/surface area
- Transformation: Decimation, subdivision, isotropic remeshing
Units and Scale
This library assumes millimeter (mm) units.
- Default hole filling skips holes larger than 500 edges (adjustable via params)
- Default vertex welding tolerance is 1e-6 (sub-micron precision)
- Wall thickness analysis defaults: 1mm minimum for FDM, 0.4mm for SLA
- Ray casting max distance defaults to 1000mm (1 meter)
If your mesh uses different units, scale accordingly:
- Meters → mm: Multiply all coordinates by 1000
- Inches → mm: Multiply all coordinates by 25.4
- Microns → mm: Divide all coordinates by 1000
Coordinate System
The library uses a right-handed coordinate system:
- X: typically width (left/right)
- Y: typically depth (front/back)
- Z: typically height (up/down)
Face winding is counter-clockwise (CCW) when viewed from outside the mesh. This means normals point outward by the right-hand rule.
Quick Start
use mesh_repair::Mesh;
// Load a mesh from any supported format
let mut mesh = Mesh::load("model.stl").unwrap();
// Validate and check for issues
let report = mesh.validate();
println!("{}", report);
// Repair common issues
mesh.repair().unwrap();
// Save to any supported format
mesh.save("repaired.3mf").unwrap();
Common Workflows
3D Printing Pipeline
use mesh_repair::{Mesh, RepairParams, ThicknessParams};
let mut mesh = Mesh::load("scan.stl").unwrap();
// Use printing-optimized repair settings
mesh.repair_with_config(&RepairParams::for_printing()).unwrap();
// Check printability requirements
let report = mesh.validate();
if report.is_printable() {
println!("Mesh is ready for printing!");
} else {
if !report.is_watertight {
println!("Has {} boundary edges", report.boundary_edge_count);
}
if !report.is_manifold {
println!("Has {} non-manifold edges", report.non_manifold_edge_count);
}
if report.is_inside_out {
println!("Normals are inverted");
}
}
// Check wall thickness for FDM printing
let thickness = mesh.analyze_thickness(&ThicknessParams::for_printing());
if thickness.has_thin_regions() {
println!("Warning: {} thin regions below 0.8mm", thickness.thin_regions.len());
}
mesh.save("print_ready.3mf").unwrap();
Processing 3D Scans (with RepairBuilder)
use mesh_repair::{Mesh, RepairBuilder};
let mesh = Mesh::load("scan.ply").unwrap();
// Use fluent builder API for repair operations
let result = RepairBuilder::new(mesh)
.for_scans() // Use scan-optimized settings
.remove_small_components(100) // Remove debris < 100 faces
.build()
.unwrap();
println!("Welded {} vertices, removed {} degenerates",
result.vertices_welded, result.degenerates_removed);
result.mesh.save("processed_scan.obj").unwrap();
Processing 3D Scans (with params)
use mesh_repair::{Mesh, RepairParams};
let mut mesh = Mesh::load("scan.ply").unwrap();
// Remove small debris/noise components
let removed = mesh.remove_small_components(100); // Remove components < 100 faces
println!("Removed {} noise components", removed);
// Use scan-optimized repair (smaller hole filling, more aggressive cleanup)
mesh.repair_with_config(&RepairParams::for_scans()).unwrap();
// Remesh for uniform triangle quality
let remeshed = mesh.remesh_with_edge_length(2.0); // 2mm target edge length
remeshed.mesh.save("processed_scan.obj").unwrap();
CAD Model Cleanup
use mesh_repair::{Mesh, RepairParams};
let mut mesh = Mesh::load("cad_export.stl").unwrap();
// CAD models often have precise vertices that shouldn't be welded aggressively
mesh.repair_with_config(&RepairParams::for_cad()).unwrap();
// Check for self-intersections (common in boolean operation results)
let intersections = mesh.detect_self_intersections();
if !intersections.is_clean() {
println!("Warning: {} self-intersecting triangle pairs", intersections.intersection_count);
}
mesh.save("cleaned.stl").unwrap();
Mesh Simplification
use mesh_repair::{Mesh, DecimateParams};
let mesh = Mesh::load("high_poly.obj").unwrap();
// Decimate to 25% of original triangles
let result = mesh.decimate_with_params(&DecimateParams::with_target_ratio(0.25));
println!("Reduced from {} to {} triangles", result.original_triangles, result.final_triangles);
// Or decimate to a specific count
let result = mesh.decimate_to_count(10000);
result.mesh.save("low_poly.obj").unwrap();
Error Handling
Most operations return MeshResult<T>, which is Result<T, MeshError>.
use ;
// Handle specific errors
match load
Troubleshooting
"Mesh appears inside-out"
This means face normals point inward instead of outward. Fix with:
use Mesh;
let mut mesh = new;
// ... load or create mesh
mesh.fix_winding.unwrap;
"Mesh has holes / not watertight"
Boundary edges indicate gaps in the surface. Fill holes with:
use Mesh;
let mut mesh = new;
// ... load or create mesh
let filled = mesh.fill_holes.unwrap;
println!;
"Non-manifold edges detected"
This means some edges have more than 2 adjacent faces. Use full repair:
use ;
let mut mesh = new;
// ... load or create mesh
let mut params = default;
params.fix_non_manifold = true;
mesh.repair_with_config.unwrap;
"Scale seems wrong"
Check the mesh dimensions and scale if needed:
use Mesh;
let mesh = new;
// ... load or create mesh
if let Some = mesh.bounds
// If dimensions are in meters, they'll be 1000x too small
// If dimensions are in inches, they'll be ~25x too small
"Multiple disconnected parts"
Keep only the main component or split into separate meshes:
use Mesh;
let mut mesh = new;
// ... load or create mesh
// Option 1: Keep only largest component
let removed = mesh.keep_largest_component;
println!;
// Option 2: Split into separate meshes
let parts = mesh.split_components;
for in parts.iter.enumerate
Supported Formats
| Format | Extension | Load | Save | Index Preservation | Notes |
|---|---|---|---|---|---|
| STL | .stl |
✓ | ✓ | ✗ | Binary & ASCII, common for printing |
| OBJ | .obj |
✓ | ✓ | ✓ | ASCII, preserves vertex order |
| 3MF | .3mf |
✓ | ✓ | ✓ | ZIP-compressed XML, modern standard |
| PLY | .ply |
✓ | ✓ | ✓ | ASCII & binary, supports colors/normals |
Note: STL format does not preserve vertex indices because it stores triangles independently. OBJ, 3MF, and PLY use indexed storage and preserve vertex order.