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(&params).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, &params)
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(&params).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, &registration::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: &registration::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, &params)
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", &params)?;
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}