3D Mesh Repair
Command-line tool for automatic mesh hole detection and filling
MeshRepair CLI provides batch processing capabilities for detecting and filling holes in triangulated 3D meshes. This guide covers installation, usage, and configuration options.

Basic mesh repair with default settings:
meshrepair input.obj output.obj
This command will:
A mesh hole is a boundary loop where the surface is discontinuous. Holes are defined by a sequence of border edges that form a closed loop without adjacent faces on one side.
| Source | Characteristics |
|---|---|
| 3D Scanning | Irregular boundaries from occlusion or limited scanner coverage |
| Boolean Operations | Clean geometric openings from CSG operations |
| Incomplete Modeling | Intentionally or accidentally omitted faces |
| File Conversion | Data loss during format translation |
| Data Corruption | Random missing faces from file damage |
MeshRepair processes meshes through four sequential stages:
Topology cleanup operations:
| Operation | Description |
|---|---|
| Duplicate Merging | Combines vertices at identical positions |
| Non-Manifold Removal | Removes edges with >2 adjacent faces and vertices with non-disk neighborhoods |
| 3-Face Fan Collapse | Simplifies degenerate vertex configurations |
| Isolated Vertex Removal | Deletes vertices with no face connections |
| Small Component Removal | Removes disconnected mesh fragments below threshold |
Identifies all boundary loops by traversing border halfedges:
For each detected hole:
Each hole is triangulated using the Liepa algorithm:
The repaired mesh is written to the specified output file.
The CLI uses partitioned parallel filling by default (--no-partition switches to the legacy single-mesh pipeline). Holes are grouped into partitions and filled in parallel; results are merged automatically. Partition count is capped by the number of holes and a minimum edge budget (--min-edges) to avoid oversharding tiny holes. The --holes_only flag is supported only in partitioned mode.
meshrepair-windows.zip from the Releases pageC:\Tools\MeshRepair\)setx PATH "%PATH%;C:\Tools\MeshRepair"
wget https://github.com/your-repo/meshrepair/releases/latest/meshrepair-linux.tar.gz
tar -xzf meshrepair-linux.tar.gz
sudo mv meshrepair /usr/local/bin/
wget https://github.com/your-repo/meshrepair/releases/latest/meshrepair-macos.tar.gz
tar -xzf meshrepair-macos.tar.gz
sudo mv meshrepair /usr/local/bin/
meshrepair --help
meshrepair model.obj repaired.obj
meshrepair model.obj repaired.obj -v 2
Example output:
[INFO] Loading mesh: model.obj
[INFO] Vertices: 45,230 Faces: 89,456
[INFO] Preprocessing mesh...
- Duplicates merged: 12
- Non-manifold removed: 3
[INFO] Detecting holes...
- Found 4 holes
[INFO] Filling holes...
- Hole 1/4: 23 boundary vertices - filled (45 faces added)
- Hole 2/4: 156 boundary vertices - filled (312 faces added)
- Hole 3/4: 8 boundary vertices - filled (12 faces added)
- Hole 4/4: 67 boundary vertices - filled (134 faces added)
[INFO] Saving: repaired.obj
[INFO] Complete. Time: 2.34s
# OBJ to PLY (binary)
meshrepair model.obj model.ply
# PLY to OBJ
meshrepair scan.ply scan.obj
# PLY binary to ASCII
meshrepair model.ply output.ply --ascii-ply
The --continuity parameter controls surface smoothness at filled regions:
meshrepair model.obj fixed.obj --continuity 0
meshrepair model.obj fixed.obj --continuity 1
meshrepair model.obj fixed.obj --continuity 2
Control which holes are processed based on size:
# Maximum boundary vertices (skip larger holes)
meshrepair model.obj fixed.obj --max-boundary 500
# Maximum diameter as ratio of mesh bounding box
meshrepair model.obj fixed.obj --max-diameter 0.05
Notes:
--max-diameter is measured against the full mesh bounding-box diagonal; the value is cached before partitioning so submeshes use the same reference.# Skip all preprocessing
meshrepair model.obj fixed.obj --no-preprocess
# Selective preprocessing
meshrepair model.obj fixed.obj --no-remove-non-manifold --no-remove-3facefan
# Remove polygons with long edges (relative to bbox)
meshrepair model.obj fixed.obj --remove-long-edges 0.25
--remove-long-edges <r> removes any polygon whose edge length exceeds r times the mesh bounding-box diagonal. The diagonal is computed once on the full mesh before partitioning so all threads and partitions use the same reference. Use small ratios (e.g. 0.1–0.3) to aggressively prune stray “spike” triangles or extremely stretched polygons; leave this option off for CAD-like meshes where long edges are intentional.
# Disable 2D triangulation (use 3D only)
meshrepair model.obj fixed.obj --no-2d-cdt
# Disable mesh refinement
meshrepair model.obj fixed.obj --no-refine
# Skip cubic search algorithm (faster, may reduce quality)
meshrepair model.obj fixed.obj --skip-cubic
# Legacy (non-partitioned) pipeline
meshrepair model.obj fixed.obj --no-partition
# Holes-only output (partitioned mode only)
meshrepair model.obj fixed.obj --holes_only
# Specify thread count
meshrepair large_model.obj fixed.obj --threads 4
# Auto-detect (default)
meshrepair large_model.obj fixed.obj --threads 0
meshrepair problem.obj fixed.obj --temp-dir ./debug -v 4
Generated files:
debug_00_original_loaded.ply - Input mesh after loadingdebug_06_partition_*.ply - Partitioned submeshesdebug_07_partition_*_filled.ply - Submeshes after fillingdebug_08_final_merged.ply - Final merged resultNotes for partitioned mode:
--min-edges) to avoid overhead on tiny holes.debug_07*) now reflect the holes actually patched inside each partition.--holes_only is honored only when partitioned mode is enabled (default).3D scans present specific challenges for hole filling.
# Heavy preprocessing for scan artifacts
meshrepair raw_scan.ply cleaned.ply -v 2
# High-quality filling with larger size limits
meshrepair cleaned.ply final.ply --continuity 2 --max-boundary 2000 --max-diameter 0.2
For multi-million polygon scans:
# Use all available threads
meshrepair large_scan.ply repaired.ply --threads 0
# Binary PLY for faster I/O
meshrepair large_scan.ply repaired.ply
# Maximum quality settings
meshrepair artifact.ply restored.ply \
--continuity 2 \
--max-diameter 0.15 \
-v 2
| Format | Extension | Read | Write | Notes |
|---|---|---|---|---|
| Wavefront OBJ | .obj |
Yes | Yes | ASCII format, wide compatibility |
| Stanford PLY | .ply |
Yes | Yes | Binary (default) or ASCII |
| Object File Format | .off |
Yes | Yes | CGAL native format |
--ascii-ply flag# Force ASCII PLY output
meshrepair model.obj output.ply --ascii-ply
Possible causes:
Solutions:
# Check with verbose output
meshrepair model.obj fixed.obj -v 2
# Increase size limits
meshrepair model.obj fixed.obj --max-boundary 5000 --max-diameter 0.5
Possible causes:
Solutions:
# Try different triangulation
meshrepair model.obj fixed.obj --no-2d-cdt
# Enable all preprocessing
meshrepair model.obj fixed.obj -v 2
Solutions:
# Reduce quality for faster processing
meshrepair large.obj fixed.obj --continuity 0 --skip-cubic --no-refine
# Verify thread utilization
meshrepair large.obj fixed.obj -v 2 --threads 0
For meshes exceeding available RAM:
meshrepair <input> <output> [options]
meshrepair --engine [engine-options]
| Option | Default | Description |
|---|---|---|
-h, --help |
- | Display help message |
-v, --verbose <0-4> |
1 | Verbosity level |
--validate |
off | Validate mesh topology |
--temp-dir <path> |
- | Debug output directory |
| Option | Default | Description |
|---|---|---|
--continuity <0\|1\|2> |
1 | Surface continuity level |
--max-boundary <n> |
1000 | Maximum hole boundary vertices |
--max-diameter <r> |
0.1 | Maximum hole diameter ratio |
--no-refine |
off | Disable patch refinement |
--no-2d-cdt |
off | Disable 2D triangulation |
--no-3d-delaunay |
off | Disable 3D triangulation fallback |
--skip-cubic |
off | Skip cubic search algorithm |
--holes_only |
off | Return only patched holes (partitioned mode only) |
--min-edges <n> |
100 | Minimum boundary edges per partition (partitioned mode) |
| Option | Default | Description |
|---|---|---|
--no-preprocess |
off | Skip all preprocessing |
--no-remove-duplicates |
off | Keep duplicate vertices |
--no-remove-non-manifold |
off | Keep non-manifold geometry |
--no-remove-3facefan |
off | Keep 3-face fan configurations |
--no-remove-isolated |
off | Keep isolated vertices |
--no-remove-small |
off | Keep small components |
| Option | Default | Description |
|---|---|---|
--threads <n> |
0 (auto) | Worker thread count |
--queue-size <n> |
10 | Pipeline queue size (legacy mode) |
--no-partition |
off | Disable partitioned processing (use legacy pipeline) |
--cgal-loader |
off | Force CGAL OBJ loader |
Partitioned mode caps partitions to the number of holes and a minimum edge budget (--min-edges) to avoid oversharding tiny holes; lowering --min-edges increases parallelism on very small holes at the cost of overhead.
| Option | Default | Description |
|---|---|---|
--ascii-ply |
off | Write PLY in ASCII format |
Hole filling algorithm:
Peter Liepa. “Filling Holes in Meshes.” Eurographics Symposium on Geometry Processing, 2003.
Fairing algorithm:
Mario Botsch et al. “On Linear Variational Surface Deformation Methods.” IEEE Transactions on Visualization and Computer Graphics, 2008.