mesh_repair/lib.rs
1//! Triangle mesh repair and processing utilities.
2//!
3//! This crate provides comprehensive tools for loading, validating, repairing, and
4//! transforming triangle meshes. It's designed for 3D printing pipelines, mesh
5//! processing, and geometry operations.
6//!
7//! # Features
8//!
9//! - **File I/O**: Load and save STL, OBJ, 3MF, and PLY formats
10//! - **Validation**: Check for non-manifold edges, holes, self-intersections, winding issues
11//! - **Repair**: Fill holes, fix winding, remove degenerates, weld vertices
12//! - **Analysis**: Component detection, wall thickness measurement, volume/surface area
13//! - **Transformation**: Decimation, subdivision, isotropic remeshing
14//!
15//! # Units and Scale
16//!
17//! **This library assumes millimeter (mm) units.**
18//!
19//! - Default hole filling skips holes larger than 500 edges (adjustable via params)
20//! - Default vertex welding tolerance is 1e-6 (sub-micron precision)
21//! - Wall thickness analysis defaults: 1mm minimum for FDM, 0.4mm for SLA
22//! - Ray casting max distance defaults to 1000mm (1 meter)
23//!
24//! If your mesh uses different units, scale accordingly:
25//! - **Meters → mm**: Multiply all coordinates by 1000
26//! - **Inches → mm**: Multiply all coordinates by 25.4
27//! - **Microns → mm**: Divide all coordinates by 1000
28//!
29//! # Coordinate System
30//!
31//! The library uses a **right-handed coordinate system**:
32//! - X: typically width (left/right)
33//! - Y: typically depth (front/back)
34//! - Z: typically height (up/down)
35//!
36//! Face winding is **counter-clockwise (CCW) when viewed from outside** the mesh.
37//! This means normals point outward by the right-hand rule.
38//!
39//! # Quick Start
40//!
41//! ```no_run
42//! use mesh_repair::Mesh;
43//!
44//! // Load a mesh from any supported format
45//! let mut mesh = Mesh::load("model.stl").unwrap();
46//!
47//! // Validate and check for issues
48//! let report = mesh.validate();
49//! println!("{}", report);
50//!
51//! // Repair common issues
52//! mesh.repair().unwrap();
53//!
54//! // Save to any supported format
55//! mesh.save("repaired.3mf").unwrap();
56//! ```
57//!
58//! # Common Workflows
59//!
60//! ## 3D Printing Pipeline
61//!
62//! ```no_run
63//! use mesh_repair::{Mesh, RepairParams, ThicknessParams};
64//!
65//! let mut mesh = Mesh::load("scan.stl").unwrap();
66//!
67//! // Use printing-optimized repair settings
68//! mesh.repair_with_config(&RepairParams::for_printing()).unwrap();
69//!
70//! // Check printability requirements
71//! let report = mesh.validate();
72//! if report.is_printable() {
73//! println!("Mesh is ready for printing!");
74//! } else {
75//! if !report.is_watertight {
76//! println!("Has {} boundary edges", report.boundary_edge_count);
77//! }
78//! if !report.is_manifold {
79//! println!("Has {} non-manifold edges", report.non_manifold_edge_count);
80//! }
81//! if report.is_inside_out {
82//! println!("Normals are inverted");
83//! }
84//! }
85//!
86//! // Check wall thickness for FDM printing
87//! let thickness = mesh.analyze_thickness(&ThicknessParams::for_printing());
88//! if thickness.has_thin_regions() {
89//! println!("Warning: {} thin regions below 0.8mm", thickness.thin_regions.len());
90//! }
91//!
92//! mesh.save("print_ready.3mf").unwrap();
93//! ```
94//!
95//! ## Processing 3D Scans (with RepairBuilder)
96//!
97//! ```no_run
98//! use mesh_repair::{Mesh, RepairBuilder};
99//!
100//! let mesh = Mesh::load("scan.ply").unwrap();
101//!
102//! // Use fluent builder API for repair operations
103//! let result = RepairBuilder::new(mesh)
104//! .for_scans() // Use scan-optimized settings
105//! .remove_small_components(100) // Remove debris < 100 faces
106//! .build()
107//! .unwrap();
108//!
109//! println!("Welded {} vertices, removed {} degenerates",
110//! result.vertices_welded, result.degenerates_removed);
111//!
112//! result.mesh.save("processed_scan.obj").unwrap();
113//! ```
114//!
115//! ## Processing 3D Scans (with params)
116//!
117//! ```no_run
118//! use mesh_repair::{Mesh, RepairParams};
119//!
120//! let mut mesh = Mesh::load("scan.ply").unwrap();
121//!
122//! // Remove small debris/noise components
123//! let removed = mesh.remove_small_components(100); // Remove components < 100 faces
124//! println!("Removed {} noise components", removed);
125//!
126//! // Use scan-optimized repair (smaller hole filling, more aggressive cleanup)
127//! mesh.repair_with_config(&RepairParams::for_scans()).unwrap();
128//!
129//! // Remesh for uniform triangle quality
130//! let remeshed = mesh.remesh_with_edge_length(2.0); // 2mm target edge length
131//!
132//! remeshed.mesh.save("processed_scan.obj").unwrap();
133//! ```
134//!
135//! ## CAD Model Cleanup
136//!
137//! ```no_run
138//! use mesh_repair::{Mesh, RepairParams};
139//!
140//! let mut mesh = Mesh::load("cad_export.stl").unwrap();
141//!
142//! // CAD models often have precise vertices that shouldn't be welded aggressively
143//! mesh.repair_with_config(&RepairParams::for_cad()).unwrap();
144//!
145//! // Check for self-intersections (common in boolean operation results)
146//! let intersections = mesh.detect_self_intersections();
147//! if !intersections.is_clean() {
148//! println!("Warning: {} self-intersecting triangle pairs", intersections.intersection_count);
149//! }
150//!
151//! mesh.save("cleaned.stl").unwrap();
152//! ```
153//!
154//! ## Mesh Simplification
155//!
156//! ```no_run
157//! use mesh_repair::{Mesh, DecimateParams};
158//!
159//! let mesh = Mesh::load("high_poly.obj").unwrap();
160//!
161//! // Decimate to 25% of original triangles
162//! let result = mesh.decimate_with_params(&DecimateParams::with_target_ratio(0.25));
163//! println!("Reduced from {} to {} triangles", result.original_triangles, result.final_triangles);
164//!
165//! // Or decimate to a specific count
166//! let result = mesh.decimate_to_count(10000);
167//!
168//! result.mesh.save("low_poly.obj").unwrap();
169//! ```
170//!
171//! # Error Handling
172//!
173//! Most operations return `MeshResult<T>`, which is `Result<T, MeshError>`.
174//!
175//! ```
176//! use mesh_repair::{Mesh, MeshError};
177//!
178//! fn process_mesh(path: &str) -> Result<(), MeshError> {
179//! let mut mesh = Mesh::load(path)?;
180//! mesh.repair()?;
181//! mesh.save("output.stl")?;
182//! Ok(())
183//! }
184//!
185//! // Handle specific errors
186//! match Mesh::load("nonexistent.stl") {
187//! Ok(_) => println!("Loaded successfully"),
188//! Err(MeshError::IoRead { path, source }) => {
189//! println!("Failed to read {:?}: {}", path, source);
190//! }
191//! Err(MeshError::ParseError { path, details }) => {
192//! println!("Failed to parse {:?}: {}", path, details);
193//! }
194//! Err(MeshError::UnsupportedFormat { extension }) => {
195//! println!("Unsupported format: {:?}", extension);
196//! }
197//! Err(e) => println!("Other error: {}", e),
198//! }
199//! ```
200//!
201//! # Troubleshooting
202//!
203//! ## "Mesh appears inside-out"
204//!
205//! This means face normals point inward instead of outward. Fix with:
206//! ```
207//! use mesh_repair::Mesh;
208//! let mut mesh = Mesh::new();
209//! // ... load or create mesh
210//! mesh.fix_winding().unwrap();
211//! ```
212//!
213//! ## "Mesh has holes / not watertight"
214//!
215//! Boundary edges indicate gaps in the surface. Fill holes with:
216//! ```
217//! use mesh_repair::Mesh;
218//! let mut mesh = Mesh::new();
219//! // ... load or create mesh
220//! let filled = mesh.fill_holes().unwrap();
221//! println!("Filled {} holes", filled);
222//! ```
223//!
224//! ## "Non-manifold edges detected"
225//!
226//! This means some edges have more than 2 adjacent faces. Use full repair:
227//! ```
228//! use mesh_repair::{Mesh, RepairParams};
229//! let mut mesh = Mesh::new();
230//! // ... load or create mesh
231//! let mut params = RepairParams::default();
232//! params.fix_non_manifold = true;
233//! mesh.repair_with_config(¶ms).unwrap();
234//! ```
235//!
236//! ## "Scale seems wrong"
237//!
238//! Check the mesh dimensions and scale if needed:
239//! ```
240//! use mesh_repair::Mesh;
241//! let mesh = Mesh::new();
242//! // ... load or create mesh
243//! if let Some((min, max)) = mesh.bounds() {
244//! let dims = max - min;
245//! println!("Dimensions: {:.1} x {:.1} x {:.1} mm", dims.x, dims.y, dims.z);
246//! }
247//! // If dimensions are in meters, they'll be 1000x too small
248//! // If dimensions are in inches, they'll be ~25x too small
249//! ```
250//!
251//! ## "Multiple disconnected parts"
252//!
253//! Keep only the main component or split into separate meshes:
254//! ```
255//! use mesh_repair::Mesh;
256//! let mut mesh = Mesh::new();
257//! // ... load or create mesh
258//! // Option 1: Keep only largest component
259//! let removed = mesh.keep_largest_component();
260//! println!("Removed {} small components", removed);
261//!
262//! // Option 2: Split into separate meshes
263//! let parts = mesh.split_components();
264//! for (i, part) in parts.iter().enumerate() {
265//! println!("Component {}: {} faces", i, part.face_count());
266//! }
267//! ```
268//!
269//! # Supported Formats
270//!
271//! | Format | Extension | Load | Save | Index Preservation | Notes |
272//! |--------|-----------|------|------|-------------------|-------|
273//! | STL | `.stl` | ✓ | ✓ | ✗ | Binary & ASCII, common for printing |
274//! | OBJ | `.obj` | ✓ | ✓ | ✓ | ASCII, preserves vertex order |
275//! | 3MF | `.3mf` | ✓ | ✓ | ✓ | ZIP-compressed XML, modern standard |
276//! | PLY | `.ply` | ✓ | ✓ | ✓ | ASCII & binary, supports colors/normals |
277//!
278//! Note: STL format does not preserve vertex indices because it stores triangles
279//! independently. OBJ, 3MF, and PLY use indexed storage and preserve vertex order.
280
281mod builder;
282mod error;
283mod fitting;
284mod pipeline;
285pub mod tracing_ext;
286mod types;
287
288#[cfg(test)]
289mod edge_cases;
290
291pub mod adjacency;
292pub mod assembly;
293pub mod boolean;
294pub mod components;
295pub mod decimate;
296pub mod holes;
297pub mod intersect;
298pub mod io;
299pub mod lattice;
300pub mod measure;
301pub mod morph;
302pub mod multiscan;
303pub mod pointcloud;
304pub mod printability;
305pub mod progress;
306pub mod region;
307pub mod registration;
308pub mod remesh;
309pub mod repair;
310pub mod scan;
311pub mod slice;
312pub mod subdivide;
313pub mod template;
314pub mod thickness;
315pub mod validate;
316pub mod winding;
317
318// STEP export (feature-gated)
319#[cfg(feature = "step")]
320pub mod step;
321
322// Re-export STEP types when feature is enabled
323#[cfg(feature = "step")]
324pub use step::{StepExportParams, StepExportResult, export_step, export_step_to_string};
325
326// Re-export core types at crate root
327pub use error::{
328 ErrorCode, IssueSeverity, MeshError, MeshResult, RecoverySuggestion, ValidationIssue,
329};
330pub use types::{Mesh, Triangle, Vertex, VertexColor};
331
332// Re-export adjacency at crate root for convenience
333pub use adjacency::MeshAdjacency;
334
335// Re-export commonly used functions
336pub use io::{
337 // 3MF Beam Lattice Extension types
338 Beam,
339 BeamCap,
340 BeamLatticeData,
341 BeamSet,
342 // 3MF Color Group types
343 ColorGroup,
344 MeshFormat,
345 ThreeMfExportParams,
346 ThreeMfLoadResult,
347 TriangleColors,
348 load_3mf_with_materials,
349 load_mesh,
350 save_3mf,
351 // 3MF extended export with all extensions
352 save_3mf_extended,
353 // 3MF with materials support
354 save_3mf_with_materials,
355 save_mesh,
356 save_obj,
357 save_ply,
358 save_ply_ascii,
359 save_stl,
360};
361pub use repair::{
362 RepairParams, compute_vertex_normals, fix_inverted_faces, fix_non_manifold_edges,
363 remove_degenerate_triangles, remove_degenerate_triangles_enhanced, remove_duplicate_faces,
364 remove_unreferenced_vertices, repair_mesh, repair_mesh_with_config, weld_vertices,
365};
366
367// Builder API
368pub use builder::{RepairBuilder, RepairResult};
369pub use fitting::{FittingBuilder, FittingResult};
370pub use pipeline::{IntoPipeline, Pipeline, PipelineResult};
371
372// Pipeline serialization (requires pipeline-config feature)
373pub use components::{
374 ComponentAnalysis, find_connected_components, keep_largest_component, remove_small_components,
375 split_into_components,
376};
377pub use decimate::{DecimateParams, DecimateResult, decimate_mesh, decimate_mesh_with_progress};
378pub use holes::{BoundaryLoop, detect_holes, fill_holes, fill_holes_with_max_edges};
379pub use intersect::{IntersectionParams, SelfIntersectionResult, detect_self_intersections};
380#[cfg(feature = "pipeline-config")]
381pub use pipeline::{PipelineConfig, PipelineConfigError, PipelineStep};
382pub use remesh::{
383 CurvatureResult, FeatureEdge, FeatureEdgeResult, RemeshParams, RemeshResult, VertexCurvature,
384 compute_curvature, detect_feature_edges, remesh_adaptive, remesh_anisotropic, remesh_isotropic,
385 remesh_isotropic_with_progress,
386};
387pub use subdivide::{SubdivideParams, SubdivideResult, subdivide_mesh};
388pub use thickness::{ThicknessParams, ThicknessResult, ThinRegion, analyze_thickness};
389pub use validate::{
390 DataValidationResult, MeshReport, ValidationOptions, validate_mesh, validate_mesh_data,
391 validate_mesh_data_strict,
392};
393pub use winding::fix_winding_order;
394
395// Re-export morphing and registration types
396pub use morph::{Constraint, MorphAlgorithm, MorphParams, MorphResult, RbfKernel, morph_mesh};
397pub use registration::{
398 Landmark, NonRigidParams, NonRigidRegistrationResult, RegistrationAlgorithm,
399 RegistrationParams, RegistrationResult, RigidTransform, align_meshes, non_rigid_align,
400};
401pub use template::{
402 ControlRegion, FitParams, FitResult, FitStage, FitTemplate, Measurement, MeasurementType,
403 RegionDefinition,
404};
405
406// Re-export region types for variable thickness and material zones
407pub use region::{
408 FloodFillCriteria, MaterialProperties, MaterialZone, MeshRegion, RegionMap, RegionSelector,
409 ThicknessMap,
410};
411
412// Re-export assembly types for multi-part management
413pub use assembly::{
414 Assembly, AssemblyExportFormat, AssemblyValidation, BillOfMaterials, BomItem, ClearanceResult,
415 Connection, ConnectionParams, ConnectionType, InterferenceResult, Part,
416};
417
418// Re-export lattice types for infill generation
419pub use lattice::{
420 DensityMap, InfillParams, InfillResult, LatticeParams, LatticeResult, LatticeType,
421 generate_infill, generate_lattice,
422};
423
424// Re-export boolean types for CSG operations
425pub use boolean::{
426 BooleanOp, BooleanParams, BooleanResult, BooleanStats, CoplanarStrategy, boolean_operation,
427 boolean_operation_with_progress,
428};
429
430// Re-export scan processing types
431pub use scan::{
432 DenoiseMethod, DenoiseParams, DenoiseResult, HoleFillParams, HoleFillResult, HoleFillStrategy,
433 OutlierRemovalParams, ScanCleanupParams, ScanCleanupResult, cleanup_scan, denoise_mesh,
434 fill_holes_advanced, remove_outliers,
435};
436
437// Re-export multi-scan alignment and merging types
438pub use multiscan::{
439 MergeParams, MergeResult, MultiAlignmentParams, MultiAlignmentResult, OverlapHandling,
440 OverlapRegion, align_multiple_scans, align_multiple_scans_with_params, merge_scans,
441};
442
443// Re-export printability/manufacturing types
444pub use printability::{
445 IssueSeverity as PrintIssueSeverity, OrientParams, OrientResult, OverhangRegion, PrintIssue,
446 PrintIssueType, PrintTechnology, PrintValidation, PrinterConfig, SupportAnalysis,
447 SupportRegion, ThinWallRegion, auto_orient_for_printing, detect_support_regions,
448 validate_for_printing,
449};
450
451// Re-export measurement types
452pub use measure::{
453 CrossSection, Dimensions, DistanceMeasurement, OrientedBoundingBox, circumference_at_height,
454 closest_point_on_mesh, cross_section, cross_sections, dimensions, measure_distance,
455 oriented_bounding_box,
456};
457
458// Re-export slicing types for 3D print preview
459pub use slice::{
460 Contour, FdmParams, FdmValidationResult, GapIssue, Layer, LayerBounds, LayerStats, SlaParams,
461 SlaValidationResult, SliceParams, SliceResult, SmallFeatureIssue, SvgExportParams,
462 ThinWallIssue, calculate_layer_stats, export_3mf_slices, export_layer_svg, export_slices_svg,
463 slice_mesh, slice_preview, validate_for_fdm, validate_for_sla,
464};
465
466// Re-export point cloud types for scanner data processing
467pub use pointcloud::{
468 CloudPoint, PointCloud, PointCloudFormat, ReconstructionAlgorithm, ReconstructionParams,
469 ReconstructionResult,
470};
471
472// Re-export progress tracking types for long-running operations
473pub use progress::{
474 OperationEstimate, OperationType, Progress, ProgressCallback, ProgressReporter,
475 ProgressTracker, SharedProgressTracker, estimate_operation_time,
476};
477
478// Re-export tracing extensions for structured logging and performance monitoring
479pub use tracing_ext::{
480 OperationTimer, log_io_operation, log_mesh_stats, log_mesh_stats_detailed, log_perf_section,
481 log_progress, log_repair_result, log_validation_result,
482};
483
484// Convenience methods on Mesh
485impl Mesh {
486 /// Load a mesh from a file, auto-detecting format from extension.
487 pub fn load(path: impl AsRef<std::path::Path>) -> MeshResult<Self> {
488 io::load_mesh(path.as_ref())
489 }
490
491 /// Save the mesh to a file, auto-detecting format from extension.
492 pub fn save(&self, path: impl AsRef<std::path::Path>) -> MeshResult<()> {
493 io::save_mesh(self, path.as_ref())
494 }
495
496 /// Validate the mesh and return a report of any issues.
497 pub fn validate(&self) -> MeshReport {
498 validate::validate_mesh(self)
499 }
500
501 /// Repair common mesh issues using default parameters.
502 ///
503 /// For more control, use `repair_with_config`.
504 pub fn repair(&mut self) -> MeshResult<()> {
505 repair::repair_mesh(self)
506 }
507
508 /// Repair common mesh issues with custom parameters.
509 ///
510 /// # Example
511 ///
512 /// ```
513 /// use mesh_repair::{Mesh, RepairParams};
514 ///
515 /// let mut mesh = Mesh::new();
516 /// // Use scan-optimized parameters
517 /// mesh.repair_with_config(&RepairParams::for_scans()).unwrap();
518 /// ```
519 pub fn repair_with_config(&mut self, params: &repair::RepairParams) -> MeshResult<()> {
520 repair::repair_mesh_with_config(self, params)
521 }
522
523 /// Compute vertex normals from face normals (area-weighted average).
524 pub fn compute_normals(&mut self) {
525 repair::compute_vertex_normals(self)
526 }
527
528 /// Fix inconsistent face winding to ensure all faces have consistent orientation.
529 pub fn fix_winding(&mut self) -> MeshResult<()> {
530 winding::fix_winding_order(self)
531 }
532
533 /// Fill holes in the mesh.
534 pub fn fill_holes(&mut self) -> MeshResult<usize> {
535 holes::fill_holes(self)
536 }
537
538 /// Find connected components in the mesh.
539 pub fn find_components(&self) -> components::ComponentAnalysis {
540 components::find_connected_components(self)
541 }
542
543 /// Split the mesh into separate meshes, one per connected component.
544 pub fn split_components(&self) -> Vec<Mesh> {
545 components::split_into_components(self)
546 }
547
548 /// Keep only the largest connected component, removing all others.
549 /// Returns the number of components removed.
550 pub fn keep_largest_component(&mut self) -> usize {
551 components::keep_largest_component(self)
552 }
553
554 /// Remove components with fewer than `min_faces` faces.
555 /// Returns the number of components removed.
556 pub fn remove_small_components(&mut self, min_faces: usize) -> usize {
557 components::remove_small_components(self, min_faces)
558 }
559
560 /// Check for self-intersecting triangles.
561 pub fn detect_self_intersections(&self) -> intersect::SelfIntersectionResult {
562 intersect::detect_self_intersections(self, &intersect::IntersectionParams::default())
563 }
564
565 /// Check for self-intersecting triangles with custom parameters.
566 pub fn detect_self_intersections_with_params(
567 &self,
568 params: &intersect::IntersectionParams,
569 ) -> intersect::SelfIntersectionResult {
570 intersect::detect_self_intersections(self, params)
571 }
572
573 /// Decimate the mesh to reduce triangle count using edge collapse.
574 ///
575 /// Uses default parameters (50% reduction, preserve boundary).
576 /// For more control, use `decimate_with_params`.
577 pub fn decimate(&self) -> decimate::DecimateResult {
578 decimate::decimate_mesh(self, &decimate::DecimateParams::default())
579 }
580
581 /// Decimate the mesh with custom parameters.
582 ///
583 /// # Example
584 ///
585 /// ```
586 /// use mesh_repair::{Mesh, Vertex, DecimateParams};
587 ///
588 /// // Create a simple test mesh
589 /// let mut mesh = Mesh::new();
590 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
591 /// mesh.vertices.push(Vertex::from_coords(1.0, 0.0, 0.0));
592 /// mesh.vertices.push(Vertex::from_coords(0.5, 1.0, 0.0));
593 /// mesh.faces.push([0, 1, 2]);
594 ///
595 /// let result = mesh.decimate_with_params(&DecimateParams::with_target_ratio(0.25));
596 /// println!("Reduced from {} to {} triangles", result.original_triangles, result.final_triangles);
597 /// ```
598 pub fn decimate_with_params(
599 &self,
600 params: &decimate::DecimateParams,
601 ) -> decimate::DecimateResult {
602 decimate::decimate_mesh(self, params)
603 }
604
605 /// Decimate the mesh to a target triangle count.
606 ///
607 /// # Example
608 ///
609 /// ```
610 /// use mesh_repair::{Mesh, Vertex};
611 ///
612 /// // Create a simple test mesh
613 /// let mut mesh = Mesh::new();
614 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
615 /// mesh.vertices.push(Vertex::from_coords(1.0, 0.0, 0.0));
616 /// mesh.vertices.push(Vertex::from_coords(0.5, 1.0, 0.0));
617 /// mesh.faces.push([0, 1, 2]);
618 ///
619 /// let result = mesh.decimate_to_count(1);
620 /// // With a single triangle, decimation is limited
621 /// assert!(result.original_triangles == 1);
622 /// ```
623 pub fn decimate_to_count(&self, target: usize) -> decimate::DecimateResult {
624 decimate::decimate_mesh(
625 self,
626 &decimate::DecimateParams::with_target_triangles(target),
627 )
628 }
629
630 /// Subdivide the mesh to increase triangle count and smooth the surface.
631 ///
632 /// Uses Loop subdivision with default parameters (1 iteration).
633 /// For more control, use `subdivide_with_params`.
634 ///
635 /// # Example
636 ///
637 /// ```
638 /// use mesh_repair::{Mesh, Vertex};
639 ///
640 /// let mut mesh = Mesh::new();
641 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
642 /// mesh.vertices.push(Vertex::from_coords(1.0, 0.0, 0.0));
643 /// mesh.vertices.push(Vertex::from_coords(0.5, 1.0, 0.0));
644 /// mesh.faces.push([0, 1, 2]);
645 ///
646 /// let result = mesh.subdivide();
647 /// assert_eq!(result.final_triangles, 4); // 1 triangle becomes 4
648 /// ```
649 pub fn subdivide(&self) -> subdivide::SubdivideResult {
650 subdivide::subdivide_mesh(self, &subdivide::SubdivideParams::default())
651 }
652
653 /// Subdivide the mesh with custom parameters.
654 ///
655 /// # Example
656 ///
657 /// ```
658 /// use mesh_repair::{Mesh, Vertex, SubdivideParams};
659 ///
660 /// let mut mesh = Mesh::new();
661 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
662 /// mesh.vertices.push(Vertex::from_coords(1.0, 0.0, 0.0));
663 /// mesh.vertices.push(Vertex::from_coords(0.5, 1.0, 0.0));
664 /// mesh.faces.push([0, 1, 2]);
665 ///
666 /// // Two iterations: 1 -> 4 -> 16 triangles
667 /// let result = mesh.subdivide_with_params(&SubdivideParams::with_iterations(2));
668 /// assert_eq!(result.final_triangles, 16);
669 /// ```
670 pub fn subdivide_with_params(
671 &self,
672 params: &subdivide::SubdivideParams,
673 ) -> subdivide::SubdivideResult {
674 subdivide::subdivide_mesh(self, params)
675 }
676
677 /// Subdivide the mesh a specific number of times.
678 ///
679 /// Each iteration roughly quadruples the triangle count.
680 ///
681 /// # Example
682 ///
683 /// ```
684 /// use mesh_repair::{Mesh, Vertex};
685 ///
686 /// let mut mesh = Mesh::new();
687 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
688 /// mesh.vertices.push(Vertex::from_coords(1.0, 0.0, 0.0));
689 /// mesh.vertices.push(Vertex::from_coords(0.5, 1.0, 0.0));
690 /// mesh.faces.push([0, 1, 2]);
691 ///
692 /// let result = mesh.subdivide_n(3);
693 /// // 1 -> 4 -> 16 -> 64
694 /// assert_eq!(result.final_triangles, 64);
695 /// ```
696 pub fn subdivide_n(&self, iterations: usize) -> subdivide::SubdivideResult {
697 subdivide::subdivide_mesh(
698 self,
699 &subdivide::SubdivideParams::with_iterations(iterations),
700 )
701 }
702
703 /// Remesh the mesh to achieve uniform edge lengths and improve triangle quality.
704 ///
705 /// Uses isotropic remeshing with default parameters (auto-detect target edge length).
706 /// For more control, use `remesh_with_params`.
707 ///
708 /// # Example
709 ///
710 /// ```
711 /// use mesh_repair::{Mesh, Vertex};
712 ///
713 /// let mut mesh = Mesh::new();
714 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
715 /// mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0));
716 /// mesh.vertices.push(Vertex::from_coords(5.0, 8.66, 0.0));
717 /// mesh.faces.push([0, 1, 2]);
718 ///
719 /// let result = mesh.remesh();
720 /// println!("Remeshed from {} to {} triangles", result.original_triangles, result.final_triangles);
721 /// ```
722 pub fn remesh(&self) -> remesh::RemeshResult {
723 remesh::remesh_isotropic(self, &remesh::RemeshParams::default())
724 }
725
726 /// Remesh the mesh with custom parameters.
727 ///
728 /// # Example
729 ///
730 /// ```
731 /// use mesh_repair::{Mesh, Vertex, RemeshParams};
732 ///
733 /// let mut mesh = Mesh::new();
734 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
735 /// mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0));
736 /// mesh.vertices.push(Vertex::from_coords(5.0, 8.66, 0.0));
737 /// mesh.faces.push([0, 1, 2]);
738 ///
739 /// let result = mesh.remesh_with_params(&RemeshParams::with_target_edge_length(2.0));
740 /// println!("Remeshed to {} triangles", result.final_triangles);
741 /// ```
742 pub fn remesh_with_params(&self, params: &remesh::RemeshParams) -> remesh::RemeshResult {
743 remesh::remesh_isotropic(self, params)
744 }
745
746 /// Remesh the mesh with a specific target edge length.
747 ///
748 /// # Example
749 ///
750 /// ```
751 /// use mesh_repair::{Mesh, Vertex};
752 ///
753 /// let mut mesh = Mesh::new();
754 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
755 /// mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0));
756 /// mesh.vertices.push(Vertex::from_coords(5.0, 8.66, 0.0));
757 /// mesh.faces.push([0, 1, 2]);
758 ///
759 /// let result = mesh.remesh_with_edge_length(2.0);
760 /// assert!(result.final_triangles > 1);
761 /// ```
762 pub fn remesh_with_edge_length(&self, target_edge_length: f64) -> remesh::RemeshResult {
763 remesh::remesh_isotropic(
764 self,
765 &remesh::RemeshParams::with_target_edge_length(target_edge_length),
766 )
767 }
768
769 /// Remesh with curvature-adaptive edge lengths.
770 ///
771 /// Creates smaller triangles in high-curvature regions and larger triangles
772 /// in flat regions.
773 ///
774 /// # Example
775 ///
776 /// ```
777 /// use mesh_repair::{Mesh, Vertex};
778 ///
779 /// let mut mesh = Mesh::new();
780 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
781 /// mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0));
782 /// mesh.vertices.push(Vertex::from_coords(5.0, 8.66, 0.0));
783 /// mesh.faces.push([0, 1, 2]);
784 ///
785 /// let result = mesh.remesh_adaptive(2.0);
786 /// println!("Adaptive remeshing: {} triangles", result.final_triangles);
787 /// ```
788 pub fn remesh_adaptive(&self, target_edge_length: f64) -> remesh::RemeshResult {
789 remesh::remesh_adaptive(self, &remesh::RemeshParams::adaptive(target_edge_length))
790 }
791
792 /// Remesh with anisotropic triangles aligned to surface curvature.
793 ///
794 /// Creates elongated triangles that follow principal curvature directions,
795 /// useful for cylindrical or ridge-like surfaces.
796 ///
797 /// # Arguments
798 /// * `target_edge_length` - Base target edge length
799 /// * `anisotropy_ratio` - Ratio of max to min edge length (e.g., 2.0 for 2:1)
800 ///
801 /// # Example
802 ///
803 /// ```
804 /// use mesh_repair::{Mesh, Vertex};
805 ///
806 /// let mut mesh = Mesh::new();
807 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
808 /// mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0));
809 /// mesh.vertices.push(Vertex::from_coords(5.0, 8.66, 0.0));
810 /// mesh.faces.push([0, 1, 2]);
811 ///
812 /// let result = mesh.remesh_anisotropic(2.0, 3.0);
813 /// println!("Anisotropic remeshing: {} triangles", result.final_triangles);
814 /// ```
815 pub fn remesh_anisotropic(
816 &self,
817 target_edge_length: f64,
818 anisotropy_ratio: f64,
819 ) -> remesh::RemeshResult {
820 remesh::remesh_anisotropic(
821 self,
822 &remesh::RemeshParams::anisotropic_with_ratio(target_edge_length, anisotropy_ratio),
823 )
824 }
825
826 /// Detect feature edges (sharp edges and boundaries) in the mesh.
827 ///
828 /// # Arguments
829 /// * `angle_threshold` - Dihedral angle threshold in radians (e.g., PI/3 for 60 degrees)
830 ///
831 /// # Example
832 ///
833 /// ```
834 /// use mesh_repair::{Mesh, Vertex};
835 /// use std::f64::consts::PI;
836 ///
837 /// let mut mesh = Mesh::new();
838 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
839 /// mesh.vertices.push(Vertex::from_coords(1.0, 0.0, 0.0));
840 /// mesh.vertices.push(Vertex::from_coords(0.5, 1.0, 0.0));
841 /// mesh.faces.push([0, 1, 2]);
842 ///
843 /// let result = mesh.detect_feature_edges(PI / 3.0);
844 /// println!("Found {} boundary edges", result.boundary_edges.len());
845 /// ```
846 pub fn detect_feature_edges(&self, angle_threshold: f64) -> remesh::FeatureEdgeResult {
847 remesh::detect_feature_edges(self, angle_threshold)
848 }
849
850 /// Compute per-vertex curvature information.
851 ///
852 /// # Example
853 ///
854 /// ```
855 /// use mesh_repair::{Mesh, Vertex};
856 ///
857 /// let mut mesh = Mesh::new();
858 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
859 /// mesh.vertices.push(Vertex::from_coords(1.0, 0.0, 0.0));
860 /// mesh.vertices.push(Vertex::from_coords(0.5, 1.0, 0.0));
861 /// mesh.faces.push([0, 1, 2]);
862 ///
863 /// let curvature = mesh.compute_curvature();
864 /// println!("Mean curvature range: {} to {}", curvature.min_mean_curvature, curvature.max_mean_curvature);
865 /// ```
866 pub fn compute_curvature(&self) -> remesh::CurvatureResult {
867 remesh::compute_curvature(self)
868 }
869
870 /// Morph the mesh using RBF with the given constraints.
871 ///
872 /// This is a convenience method for simple morphing operations.
873 /// For more control, use `morph_with_params`.
874 ///
875 /// # Example
876 ///
877 /// ```
878 /// use mesh_repair::{Mesh, Vertex, Constraint};
879 /// use nalgebra::{Point3, Vector3};
880 ///
881 /// let mut mesh = Mesh::new();
882 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
883 /// mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0));
884 /// mesh.vertices.push(Vertex::from_coords(5.0, 8.66, 0.0));
885 /// mesh.vertices.push(Vertex::from_coords(5.0, 2.89, 8.16));
886 /// mesh.faces.push([0, 2, 1]);
887 /// mesh.faces.push([0, 1, 3]);
888 /// mesh.faces.push([1, 2, 3]);
889 /// mesh.faces.push([2, 0, 3]);
890 ///
891 /// let constraints = vec![
892 /// Constraint::displacement(Point3::new(5.0, 2.89, 8.16), Vector3::new(0.0, 0.0, 2.0)),
893 /// ];
894 /// let result = mesh.morph(&constraints).unwrap();
895 /// ```
896 pub fn morph(&self, constraints: &[morph::Constraint]) -> MeshResult<morph::MorphResult> {
897 let params = morph::MorphParams::rbf().with_constraints(constraints.to_vec());
898 morph::morph_mesh(self, ¶ms)
899 }
900
901 /// Morph the mesh with custom parameters.
902 ///
903 /// # Example
904 ///
905 /// ```
906 /// use mesh_repair::{Mesh, Vertex, MorphParams, Constraint};
907 /// use nalgebra::Point3;
908 ///
909 /// let mut mesh = Mesh::new();
910 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
911 /// mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0));
912 /// mesh.vertices.push(Vertex::from_coords(5.0, 8.66, 0.0));
913 /// mesh.vertices.push(Vertex::from_coords(5.0, 2.89, 8.16));
914 /// mesh.faces.push([0, 2, 1]);
915 /// mesh.faces.push([0, 1, 3]);
916 /// mesh.faces.push([1, 2, 3]);
917 /// mesh.faces.push([2, 0, 3]);
918 ///
919 /// let constraints = vec![
920 /// Constraint::point(Point3::new(5.0, 2.89, 8.16), Point3::new(5.0, 2.89, 10.0)),
921 /// ];
922 /// let params = MorphParams::ffd().with_constraints(constraints);
923 /// let result = mesh.morph_with_params(¶ms).unwrap();
924 /// ```
925 pub fn morph_with_params(&self, params: &morph::MorphParams) -> MeshResult<morph::MorphResult> {
926 morph::morph_mesh(self, params)
927 }
928
929 /// Align this mesh to a target mesh using ICP.
930 ///
931 /// # Example
932 ///
933 /// ```
934 /// use mesh_repair::{Mesh, Vertex};
935 ///
936 /// let mut source = Mesh::new();
937 /// source.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
938 /// source.vertices.push(Vertex::from_coords(1.0, 0.0, 0.0));
939 /// source.vertices.push(Vertex::from_coords(0.5, 1.0, 0.0));
940 /// source.faces.push([0, 1, 2]);
941 ///
942 /// let target = source.clone();
943 /// let result = source.align_to(&target).unwrap();
944 /// assert!(result.converged);
945 /// ```
946 pub fn align_to(&self, target: &Mesh) -> MeshResult<registration::RegistrationResult> {
947 registration::align_meshes(self, target, ®istration::RegistrationParams::icp())
948 }
949
950 /// Align this mesh to a target mesh with custom parameters.
951 pub fn align_to_with_params(
952 &self,
953 target: &Mesh,
954 params: ®istration::RegistrationParams,
955 ) -> MeshResult<registration::RegistrationResult> {
956 registration::align_meshes(self, target, params)
957 }
958
959 /// Register this mesh to a target using landmarks.
960 ///
961 /// # Example
962 ///
963 /// ```
964 /// use mesh_repair::{Mesh, Vertex, Landmark};
965 /// use nalgebra::Point3;
966 ///
967 /// let mut source = Mesh::new();
968 /// source.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
969 /// source.vertices.push(Vertex::from_coords(1.0, 0.0, 0.0));
970 /// source.vertices.push(Vertex::from_coords(0.5, 1.0, 0.0));
971 /// source.faces.push([0, 1, 2]);
972 ///
973 /// let target = source.clone();
974 /// let landmarks = vec![
975 /// Landmark::new(Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 0.0, 0.0)),
976 /// Landmark::new(Point3::new(1.0, 0.0, 0.0), Point3::new(1.0, 0.0, 0.0)),
977 /// Landmark::new(Point3::new(0.5, 1.0, 0.0), Point3::new(0.5, 1.0, 0.0)),
978 /// ];
979 /// let result = source.register_to(&target, &landmarks).unwrap();
980 /// ```
981 pub fn register_to(
982 &self,
983 target: &Mesh,
984 landmarks: &[registration::Landmark],
985 ) -> MeshResult<registration::RegistrationResult> {
986 let params = registration::RegistrationParams::landmark_based(landmarks.to_vec());
987 registration::align_meshes(self, target, ¶ms)
988 }
989
990 /// Define a region on this mesh using a selector.
991 ///
992 /// Returns a `MeshRegion` containing the selected vertices and faces.
993 ///
994 /// # Example
995 ///
996 /// ```
997 /// use mesh_repair::{Mesh, Vertex, RegionSelector};
998 /// use nalgebra::Point3;
999 ///
1000 /// let mut mesh = Mesh::new();
1001 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
1002 /// mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0));
1003 /// mesh.vertices.push(Vertex::from_coords(5.0, 10.0, 0.0));
1004 /// mesh.faces.push([0, 1, 2]);
1005 ///
1006 /// // Define a region for the lower half of the mesh
1007 /// let lower_region = mesh.define_region("lower", RegionSelector::bounds(
1008 /// Point3::new(-1.0, -1.0, -1.0),
1009 /// Point3::new(11.0, 5.0, 1.0),
1010 /// ));
1011 /// ```
1012 pub fn define_region(
1013 &self,
1014 name: impl Into<String>,
1015 selector: region::RegionSelector,
1016 ) -> region::MeshRegion {
1017 region::MeshRegion::from_selector(self, name, selector)
1018 }
1019
1020 /// Create a `RegionMap` for this mesh.
1021 ///
1022 /// This is a convenience method to start defining multiple regions.
1023 pub fn create_region_map(&self) -> region::RegionMap {
1024 region::RegionMap::new()
1025 }
1026
1027 /// Create a `ThicknessMap` for this mesh.
1028 ///
1029 /// The thickness map starts with a uniform default thickness.
1030 ///
1031 /// # Example
1032 ///
1033 /// ```
1034 /// use mesh_repair::{Mesh, Vertex, RegionSelector};
1035 /// use nalgebra::Point3;
1036 ///
1037 /// let mut mesh = Mesh::new();
1038 /// mesh.vertices.push(Vertex::from_coords(0.0, 0.0, 0.0));
1039 /// mesh.vertices.push(Vertex::from_coords(10.0, 0.0, 0.0));
1040 /// mesh.vertices.push(Vertex::from_coords(5.0, 10.0, 0.0));
1041 /// mesh.faces.push([0, 1, 2]);
1042 ///
1043 /// // Create a thickness map with 2mm default
1044 /// let mut thickness = mesh.create_thickness_map(2.0);
1045 ///
1046 /// // Set different thickness for a region
1047 /// let top_region = mesh.define_region("top", RegionSelector::bounds(
1048 /// Point3::new(-1.0, 5.0, -1.0),
1049 /// Point3::new(11.0, 11.0, 1.0),
1050 /// ));
1051 /// thickness.set_region_thickness(&top_region, 3.5);
1052 /// ```
1053 pub fn create_thickness_map(&self, default_thickness: f64) -> region::ThicknessMap {
1054 region::ThicknessMap::new(default_thickness)
1055 }
1056
1057 /// Export the mesh to STEP format for CAD interchange.
1058 ///
1059 /// STEP (ISO 10303) is a widely-supported format for exchanging geometry
1060 /// with CAD systems. Triangle meshes are exported as faceted B-rep geometry.
1061 ///
1062 /// # Example
1063 ///
1064 /// ```no_run
1065 /// # #[cfg(feature = "step")]
1066 /// # fn main() -> mesh_repair::MeshResult<()> {
1067 /// use mesh_repair::Mesh;
1068 ///
1069 /// let mesh = Mesh::load("model.stl")?;
1070 /// mesh.save_step("model.step")?;
1071 /// # Ok(())
1072 /// # }
1073 /// # #[cfg(not(feature = "step"))]
1074 /// # fn main() {}
1075 /// ```
1076 #[cfg(feature = "step")]
1077 pub fn save_step(
1078 &self,
1079 path: impl AsRef<std::path::Path>,
1080 ) -> MeshResult<step::StepExportResult> {
1081 step::export_step(self, path, &step::StepExportParams::default())
1082 }
1083
1084 /// Export the mesh to STEP format with custom parameters.
1085 ///
1086 /// # Example
1087 ///
1088 /// ```no_run
1089 /// # #[cfg(feature = "step")]
1090 /// # fn main() -> mesh_repair::MeshResult<()> {
1091 /// use mesh_repair::{Mesh, StepExportParams};
1092 ///
1093 /// let mesh = Mesh::load("model.stl")?;
1094 /// let params = StepExportParams::default()
1095 /// .with_description("My CAD model")
1096 /// .with_author("Jane Doe", "ACME Corp");
1097 /// mesh.save_step_with_params("model.step", ¶ms)?;
1098 /// # Ok(())
1099 /// # }
1100 /// # #[cfg(not(feature = "step"))]
1101 /// # fn main() {}
1102 /// ```
1103 #[cfg(feature = "step")]
1104 pub fn save_step_with_params(
1105 &self,
1106 path: impl AsRef<std::path::Path>,
1107 params: &step::StepExportParams,
1108 ) -> MeshResult<step::StepExportResult> {
1109 step::export_step(self, path, params)
1110 }
1111}