2020-03-30 22:14:19 -07:00
/ *
2020-04-01 20:48:59 -07:00
Copyright ( c ) Ryan Schmidt ( rms @gradientspace . com ) - All Rights Reserved
Distributed under the Boost Software License , Version 1.0 . http : //www.boost.org/LICENSE_1_0.txt
Copyright ( c ) 2018 , Lars Brubaker
2020-03-30 22:14:19 -07:00
All rights reserved .
* /
2020-04-01 20:48:59 -07:00
using System ;
using System.ComponentModel ;
using System.Threading ;
using System.Threading.Tasks ;
2020-03-30 22:14:19 -07:00
using g3 ;
using gs ;
2021-05-03 17:58:03 -07:00
using MatterHackers.Agg.UI ;
2020-03-30 22:14:19 -07:00
using MatterHackers.DataConverters3D ;
using MatterHackers.Localizations ;
using MatterHackers.MatterControl.DesignTools.Operations ;
using MatterHackers.PolygonMesh ;
2021-11-26 12:49:42 -08:00
using MatterHackers.PolygonMesh.Processors ;
2020-04-01 20:48:59 -07:00
using static gs . MeshAutoRepair ;
2020-03-30 22:14:19 -07:00
namespace MatterHackers.MatterControl.DesignTools
{
public class RepairObject3D : OperationSourceContainerObject3D , IPropertyGridModifier
{
public RepairObject3D ( )
{
Name = "Repair" . Localize ( ) ;
}
2020-05-21 16:29:45 -07:00
public override bool Persistable = > ApplicationController . Instance . UserHasPermission ( this ) ;
2020-05-23 11:12:06 -07:00
[ReadOnly(true)]
public int InitialVertices { get ; set ; }
[ReadOnly(true)]
public int InitialFaces { get ; set ; }
[Description("Align and merge any vertices that are nearly coincident.")]
public bool WeldVertices { get ; set ; } = true ;
2021-11-08 18:02:58 -08:00
[Description("How distant a vertex must be to weld.")]
public double WeldTolerance { get ; set ; }
2020-04-01 20:48:59 -07:00
[Description("Make all the faces have a consistent orientation.")]
2020-05-23 11:12:06 -07:00
public bool FaceOrientation { get ; set ; } = false ;
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
[Description("Repair any small cracks or bad seams in the model.")]
2020-05-23 11:12:06 -07:00
public bool WeldEdges { get ; set ; } = false ;
2020-04-01 20:48:59 -07:00
[Description("Try to fill in any holes that are in the model.")]
2020-05-23 11:12:06 -07:00
public bool FillHoles { get ; set ; } = false ;
2020-04-01 20:48:59 -07:00
2020-04-02 15:32:24 -07:00
[Description("Remove interior faces and bodies. This should only be used if the interior bodies are separate from the external faces, otherwise it may remove requried faces.")]
2020-04-01 20:48:59 -07:00
public RemoveModes RemoveMode { get ; set ; } = RemoveModes . None ;
2020-05-23 11:12:06 -07:00
[ReadOnly(true)]
public int FinalVertices { get ; set ; }
[ReadOnly(true)]
public int FinalFaces { get ; set ; }
2020-03-30 22:14:19 -07:00
public override Task Rebuild ( )
{
this . DebugDepth ( "Rebuild" ) ;
var rebuildLocks = this . RebuilLockAll ( ) ;
var valuesChanged = false ;
// check if we have be initialized
return TaskBuilder (
"Repair" . Localize ( ) ,
( reporter , cancellationToken ) = >
{
SourceContainer . Visible = true ;
RemoveAllButSource ( ) ;
2020-05-23 11:12:06 -07:00
var inititialVertices = 0 ;
var inititialFaces = 0 ;
var finalVertices = 0 ;
var finalFaces = 0 ;
2020-03-30 22:14:19 -07:00
foreach ( var sourceItem in SourceContainer . VisibleMeshes ( ) )
{
var originalMesh = sourceItem . Mesh ;
2020-05-23 11:12:06 -07:00
inititialFaces + = originalMesh . Faces . Count ;
inititialVertices + = originalMesh . Vertices . Count ;
var repairedMesh = Repair ( originalMesh , cancellationToken ) ;
finalFaces + = repairedMesh . Faces . Count ;
finalVertices + = repairedMesh . Vertices . Count ;
2020-03-30 22:14:19 -07:00
2020-05-23 11:12:06 -07:00
var repairedChild = new Object3D ( )
2020-03-30 22:14:19 -07:00
{
2020-05-23 11:12:06 -07:00
Mesh = repairedMesh
2020-03-30 22:14:19 -07:00
} ;
2020-05-23 11:12:06 -07:00
repairedChild . CopyWorldProperties ( sourceItem , this , Object3DPropertyFlags . All , false ) ;
this . Children . Add ( repairedChild ) ;
2020-03-30 22:14:19 -07:00
}
2020-05-23 11:12:06 -07:00
this . InitialFaces = inititialFaces ;
this . InitialVertices = inititialVertices ;
this . FinalFaces = finalFaces ;
this . FinalVertices = finalVertices ;
2020-03-30 22:14:19 -07:00
SourceContainer . Visible = false ;
2021-05-03 17:58:03 -07:00
UiThread . RunOnIdle ( ( ) = >
2020-03-30 22:14:19 -07:00
{
2021-05-03 17:58:03 -07:00
rebuildLocks . Dispose ( ) ;
2021-10-01 12:28:06 -07:00
Invalidate ( InvalidateType . DisplayValues ) ;
2021-05-03 17:58:03 -07:00
Parent ? . Invalidate ( new InvalidateArgs ( this , InvalidateType . Children ) ) ;
} ) ;
2020-03-30 22:14:19 -07:00
return Task . CompletedTask ;
} ) ;
}
2020-05-23 11:12:06 -07:00
public Mesh Repair ( Mesh sourceMesh , CancellationToken cancellationToken )
2020-03-30 22:14:19 -07:00
{
2020-05-23 11:12:06 -07:00
var inMesh = sourceMesh ;
2020-04-01 20:48:59 -07:00
try
2020-03-30 22:14:19 -07:00
{
2020-05-23 11:12:06 -07:00
if ( WeldVertices )
{
inMesh = sourceMesh . Copy ( cancellationToken ) ;
2021-11-08 18:02:58 -08:00
if ( WeldTolerance > 0 )
{
inMesh . MergeVertices ( . 01 ) ;
}
else
{
inMesh . CleanAndMerge ( ) ;
}
2020-05-23 11:12:06 -07:00
if ( ! FaceOrientation
& & RemoveMode = = RemoveModes . None
& & ! WeldEdges
& & ! FillHoles )
{
return inMesh ;
}
}
2020-04-01 20:48:59 -07:00
var mesh = inMesh . ToDMesh3 ( ) ;
int repeatCount = 0 ;
int erosionIterations = 5 ;
double repairTolerance = MathUtil . ZeroTolerancef ;
double minEdgeLengthTol = 0.0001 ;
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
repeat_all :
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
if ( FaceOrientation )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
// make sure orientation of connected components is consistent
// TODO: what about mobius strip problems?
RepairOrientation ( mesh , cancellationToken , true ) ;
2020-03-30 22:14:19 -07:00
}
2020-04-01 20:48:59 -07:00
if ( RemoveMode ! = RemoveModes . None )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
// Remove parts of the mesh we don't want before we bother with anything else
// TODO: maybe we need to repair orientation first? if we want to use MWN (MeshWindingNumber)...
2021-02-08 11:53:15 -08:00
RemoveInside ( mesh ) ;
2020-04-01 20:48:59 -07:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
2020-03-30 22:14:19 -07:00
}
2020-04-01 20:48:59 -07:00
if ( WeldEdges | | FillHoles )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
// Do safe close-cracks to handle easy cases
RepairCracks ( mesh , true , repairTolerance ) ;
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
if ( mesh . IsClosed ( ) )
{
goto all_done ;
}
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
// Collapse tiny edges and then try easy cases again, and
// then allow for handling of ambiguous cases
CollapseAllDegenerateEdges ( mesh , cancellationToken , repairTolerance * 0.5 , true ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
RepairCracks ( mesh , true , 2 * repairTolerance ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
RepairCracks ( mesh , false , 2 * repairTolerance ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
if ( mesh . IsClosed ( ) )
{
goto all_done ;
}
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
// Possibly we have joined regions with different orientation (is it?), fix that
// TODO: mobius strips again
RepairOrientation ( mesh , cancellationToken , true ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
// get rid of any remaining single-triangles before we start filling holes
MeshEditor . RemoveIsolatedTriangles ( mesh ) ;
2020-03-30 22:14:19 -07:00
}
2020-04-01 20:48:59 -07:00
if ( FillHoles )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
// Ok, fill simple holes.
int nRemainingBowties = 0 ;
FillTrivialHoles ( mesh , cancellationToken , out int nHoles , out bool bSawSpans ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
if ( mesh . IsClosed ( ) )
{
goto all_done ;
}
// Now fill harder holes. If we saw spans, that means boundary loops could
// not be resolved in some cases, do we disconnect bowties and try again.
FillAnyHoles ( mesh , cancellationToken , out nHoles , out bSawSpans ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
if ( bSawSpans )
{
DisconnectBowties ( mesh , out nRemainingBowties ) ;
FillAnyHoles ( mesh , cancellationToken , out nHoles , out bSawSpans ) ;
}
cancellationToken . ThrowIfCancellationRequested ( ) ;
if ( mesh . IsClosed ( ) )
{
goto all_done ;
}
// We may have a closed mesh now but it might still have bowties (eg
// tetrahedra sharing vtx case). So disconnect those.
DisconnectBowties ( mesh , out nRemainingBowties ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
// If the mesh is not closed, we will do one more round to try again.
if ( repeatCount = = 0 & & mesh . IsClosed ( ) = = false )
{
repeatCount + + ;
goto repeat_all ;
}
// Ok, we didn't get anywhere on our first repeat. If we are still not
// closed, we will try deleting boundary triangles and repeating.
// Repeat this N times.
if ( repeatCount < = erosionIterations & & mesh . IsClosed ( ) = = false )
{
repeatCount + + ;
var bdry_faces = new MeshFaceSelection ( mesh ) ;
foreach ( int eid in MeshIterators . BoundaryEdges ( mesh ) )
{
bdry_faces . SelectEdgeTris ( eid ) ;
}
MeshEditor . RemoveTriangles ( mesh , bdry_faces , true ) ;
goto repeat_all ;
}
2020-03-30 22:14:19 -07:00
}
2020-04-01 20:48:59 -07:00
all_done :
// and do a final clean up of the model
if ( FillHoles )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
// Remove tiny edges
if ( minEdgeLengthTol > 0 )
{
CollapseAllDegenerateEdges ( mesh , cancellationToken , minEdgeLengthTol , false ) ;
}
cancellationToken . ThrowIfCancellationRequested ( ) ;
// finally do global orientation
RepairOrientation ( mesh , cancellationToken , true ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
2020-03-30 22:14:19 -07:00
}
2020-04-01 20:48:59 -07:00
return mesh . ToMesh ( ) ;
}
catch ( OperationCanceledException )
{
return inMesh ;
}
}
public void UpdateControls ( PublicPropertyChange change )
{
// if (change.Context.GetEditRow(nameof(TargetPercent)) is GuiWidget percentWidget)
// {
// percentWidget.Visible = Mode == ReductionMode.Polygon_Percent;
// }
}
private bool CollapseAllDegenerateEdges ( DMesh3 mesh ,
CancellationToken cancellationToken ,
double minLength ,
bool bBoundaryOnly )
{
bool repeat = true ;
while ( repeat )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
CollapseDegenerateEdges ( mesh , cancellationToken , minLength , bBoundaryOnly , out int collapseCount ) ;
if ( collapseCount = = 0 )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
repeat = false ;
2020-03-30 22:14:19 -07:00
}
2020-04-01 20:48:59 -07:00
}
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
return true ;
}
private bool CollapseDegenerateEdges ( DMesh3 mesh ,
CancellationToken cancellationToken ,
double minLength ,
bool bBoundaryOnly ,
out int collapseCount )
{
collapseCount = 0 ;
// don't iterate sequentially because there may be pathological cases
foreach ( int eid in MathUtil . ModuloIteration ( mesh . MaxEdgeID ) )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
if ( mesh . IsEdge ( eid ) = = false )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
continue ;
2020-03-30 22:14:19 -07:00
}
2020-04-01 20:48:59 -07:00
bool isBoundaryEdge = mesh . IsBoundaryEdge ( eid ) ;
if ( bBoundaryOnly & & isBoundaryEdge = = false )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
continue ;
2020-03-30 22:14:19 -07:00
}
2020-04-01 20:48:59 -07:00
Index2i ev = mesh . GetEdgeV ( eid ) ;
Vector3d a = mesh . GetVertex ( ev . a ) , b = mesh . GetVertex ( ev . b ) ;
if ( a . Distance ( b ) < minLength )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
int keep = mesh . IsBoundaryVertex ( ev . a ) ? ev . a : ev . b ;
int discard = ( keep = = ev . a ) ? ev . b : ev . a ;
MeshResult result = mesh . CollapseEdge ( keep , discard , out DMesh3 . EdgeCollapseInfo collapseInfo ) ;
if ( result = = MeshResult . Ok )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
+ + collapseCount ;
if ( mesh . IsBoundaryVertex ( keep ) = = false | | isBoundaryEdge )
{
mesh . SetVertex ( keep , ( a + b ) * 0.5 ) ;
}
2020-03-30 22:14:19 -07:00
}
}
2020-04-01 20:48:59 -07:00
}
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
return true ;
}
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
private bool DisconnectBowties ( DMesh3 mesh , out int nRemaining )
{
var editor = new MeshEditor ( mesh ) ;
nRemaining = editor . DisconnectAllBowties ( ) ;
return true ;
}
2020-03-30 22:14:19 -07:00
2021-02-08 11:53:15 -08:00
private bool RemoveInside ( DMesh3 mesh )
2020-04-01 20:48:59 -07:00
{
if ( RemoveMode = = RemoveModes . Interior )
{
return RemoveInterior ( mesh , out _ ) ;
}
else if ( RemoveMode = = RemoveModes . Occluded )
{
return RemoveOccluded ( mesh , out _ ) ;
2020-03-30 22:14:19 -07:00
}
2020-04-01 20:48:59 -07:00
return true ;
2020-03-30 22:14:19 -07:00
}
2020-04-01 20:48:59 -07:00
private void FillAnyHoles ( DMesh3 mesh ,
CancellationToken cancellationToken ,
out int nRemaining ,
out bool sawSpans )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
var loops = new MeshBoundaryLoops ( mesh ) ;
nRemaining = 0 ;
sawSpans = loops . SawOpenSpans ;
foreach ( var loop in loops )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
var filler = new MinimalHoleFill ( mesh , loop ) ;
bool filled = filler . Apply ( ) ;
if ( filled = = false )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
var fallback = new SimpleHoleFiller ( mesh , loop ) ;
fallback . Fill ( ) ;
}
2020-03-30 22:14:19 -07:00
}
2020-04-01 20:48:59 -07:00
}
2020-03-30 22:14:19 -07:00
2020-04-01 20:48:59 -07:00
private void FillTrivialHoles ( DMesh3 mesh ,
CancellationToken cancellationToken ,
out int nRemaining ,
out bool sawSpans )
{
var loops = new MeshBoundaryLoops ( mesh ) ;
nRemaining = 0 ;
sawSpans = loops . SawOpenSpans ;
foreach ( var loop in loops )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
bool filled = false ;
if ( loop . VertexCount = = 3 )
{
var filler = new SimpleHoleFiller ( mesh , loop ) ;
filled = filler . Fill ( ) ;
}
else if ( loop . VertexCount = = 4 )
{
var filler = new MinimalHoleFill ( mesh , loop ) ;
filled = filler . Apply ( ) ;
if ( filled = = false )
{
var fallback = new SimpleHoleFiller ( mesh , loop ) ;
filled = fallback . Fill ( ) ;
}
}
if ( filled = = false )
{
+ + nRemaining ;
}
2020-03-30 22:14:19 -07:00
}
}
2020-04-01 20:48:59 -07:00
private bool RemoveInterior ( DMesh3 mesh , out int nRemoved )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
var remove = new RemoveOccludedTriangles ( mesh )
{
PerVertex = true ,
InsideMode = RemoveOccludedTriangles . CalculationMode . FastWindingNumber
} ;
2020-03-30 22:14:19 -07:00
remove . Apply ( ) ;
2020-04-01 20:48:59 -07:00
nRemoved = remove . RemovedT . Count ;
2020-03-30 22:14:19 -07:00
return true ;
}
2020-04-01 20:48:59 -07:00
private bool RemoveOccluded ( DMesh3 mesh , out int nRemoved )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
var remove = new RemoveOccludedTriangles ( mesh )
{
PerVertex = true ,
InsideMode = RemoveOccludedTriangles . CalculationMode . SimpleOcclusionTest
} ;
2020-03-30 22:14:19 -07:00
remove . Apply ( ) ;
2020-04-01 20:48:59 -07:00
nRemoved = remove . RemovedT . Count ;
2020-03-30 22:14:19 -07:00
return true ;
}
2020-04-01 20:48:59 -07:00
private bool RepairCracks ( DMesh3 mesh , bool bUniqueOnly , double mergeDist )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
try
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
var merge = new MergeCoincidentEdges ( mesh )
{
OnlyUniquePairs = bUniqueOnly ,
MergeDistance = mergeDist
} ;
return merge . Apply ( ) ;
2020-03-30 22:14:19 -07:00
}
2020-04-01 20:48:59 -07:00
catch ( Exception /*e*/ )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
// ??
return false ;
2020-03-30 22:14:19 -07:00
}
}
2020-04-01 20:48:59 -07:00
private void RepairOrientation ( DMesh3 mesh ,
CancellationToken cancellationToken ,
bool bGlobal )
2020-03-30 22:14:19 -07:00
{
2020-04-01 20:48:59 -07:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
var orient = new MeshRepairOrientation ( mesh ) ;
orient . OrientComponents ( ) ;
if ( bGlobal )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
orient . SolveGlobalOrientation ( ) ;
}
2020-03-30 22:14:19 -07:00
}
}
}