@ -1,41 +1,21 @@
/ *
Copyright ( c ) 2018 , Lars Brubaker , John Lewin
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
All rights reserved .
Redistribution and use in source and binary forms , with or without
modification , are permitted provided that the following conditions are met :
1. Redistributions of source code must retain the above copyright notice , this
list of conditions and the following disclaimer .
2. Redistributions in binary form must reproduce the above copyright notice ,
this list of conditions and the following disclaimer in the documentation
and / or other materials provided with the distribution .
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES
( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ;
LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT
( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
The views and conclusions contained in the software and documentation are those
of the authors and should not be interpreted as representing official policies ,
either expressed or implied , of the FreeBSD Project .
* /
using System ;
using System.ComponentModel ;
using System.Threading ;
using System.Threading.Tasks ;
using g3 ;
using gs ;
using MatterHackers.DataConverters3D ;
using MatterHackers.Localizations ;
using MatterHackers.MatterControl.DesignTools.Operations ;
using MatterHackers.PolygonMesh ;
using System.ComponentModel ;
using System.Threading ;
using System.Threading.Tasks ;
using static gs . MeshAutoRepair ;
namespace MatterHackers.MatterControl.DesignTools
{
@ -46,9 +26,19 @@ namespace MatterHackers.MatterControl.DesignTools
Name = "Repair" . Localize ( ) ;
}
[Description(" Ensure that each reduced point is on the surface of the original mesh. This is not normally required and slows the computation significantly .")]
[Description(" Make all the faces have a consistent orientation .")]
public bool FaceOrientation { get ; set ; } = true ;
[Description("Repair any small cracks or bad seams in the model.")]
public bool WeldEdges { get ; set ; } = true ;
[Description("Try to fill in any holes that are in the model.")]
public bool FillHoles { get ; set ; } = true ;
[Description("Remove interior faces and bodies.")]
public RemoveModes RemoveMode { get ; set ; } = RemoveModes . None ;
public override Task Rebuild ( )
{
this . DebugDepth ( "Rebuild" ) ;
@ -95,218 +85,358 @@ namespace MatterHackers.MatterControl.DesignTools
public Mesh Repair ( Mesh inMesh , CancellationToken cancellationToken )
{
var mesh = inMesh . ToDMesh3 ( ) ;
if ( FaceOrientation )
try
{
var repaired = new MeshRepairOrientation ( mesh ) ;
repaired . OrientComponents ( ) ;
mesh = repaired . Mesh ;
}
var mesh = inMesh . ToDMesh3 ( ) ;
int repeatCount = 0 ;
int erosionIterations = 5 ;
double repairTolerance = MathUtil . ZeroTolerancef ;
double minEdgeLengthTol = 0.0001 ;
{
int repeat_count = 0 ;
repeat_all :
repeat_all :
// 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...
do_remove_inside ( mesh ) ;
if ( cancellationToken . IsCancellationRequested )
if ( FaceOrientation )
{
return inMesh ;
// make sure orientation of connected components is consistent
// TODO: what about mobius strip problems?
RepairOrientation ( mesh , cancellationToken , true ) ;
}
// make sure orientation of connected components is consistent
// TODO: what about mobius strip problems?
repair_orientation ( mesh , cancellationToken , false ) ;
if ( cancellationToken . IsCancellationRequested )
if ( RemoveMode ! = RemoveModes . None )
{
return inMesh ;
// 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)...
RremoveInside ( mesh ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
}
// Do safe close-cracks to handle easy cases
repair_cracks ( true , RepairTolerance ) ;
if ( Mesh . IsClosed ( ) ) goto all_done ;
if ( cancellationToken . IsCancellationRequested )
if ( WeldEdges | | FillHoles )
{
return inMesh ;
}
// Do safe close-cracks to handle easy cases
RepairCracks ( mesh , true , repairTolerance ) ;
// Collapse tiny edges and then try easy cases again, and
// then allow for handling of ambiguous cases
collapse_all_degenerate_edges ( RepairTolerance * 0.5 , true ) ;
if ( cancellationToken . IsCancellationRequested )
{
return inMesh ;
}
repair_cracks ( true , 2 * RepairTolerance ) ;
if ( cancellationToken . IsCancellationRequested )
{
return inMesh ;
}
repair_cracks ( false , 2 * RepairTolerance ) ;
if ( cancellationToken . IsCancellationRequested )
{
return inMesh ;
}
if ( Mesh . IsClosed ( ) ) goto all_done ;
// Possibly we have joined regions with different orientation (is it?), fix that
// TODO: mobius strips again
repair_orientation ( false ) ;
if ( cancellationToken . IsCancellationRequested )
{
return inMesh ;
}
// get rid of any remaining single-triangles before we start filling holes
remove_loners ( ) ;
// Ok, fill simple holes.
int nRemainingBowties = 0 ;
int nHoles ; bool bSawSpans ;
fill_trivial_holes ( out nHoles , out bSawSpans ) ;
if ( cancellationToken . IsCancellationRequested )
{
return inMesh ;
}
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.
fill_any_holes ( out nHoles , out bSawSpans ) ;
if ( Cancelled ( ) ) return false ;
if ( bSawSpans )
{
disconnect_bowties ( out nRemainingBowties ) ;
fill_any_holes ( out nHoles , out bSawSpans ) ;
}
if ( cancellationToken . IsCancellationRequested )
{
return inMesh ;
}
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.
disconnect_bowties ( out nRemainingBowties ) ;
if ( cancellationToken . IsCancellationRequested )
{
return inMesh ;
}
// If the mesh is not closed, we will do one more round to try again.
if ( repeat_count = = 0 & & mesh . IsClosed ( ) = = false )
{
repeat_count + + ;
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 ( repeat_count < = ErosionIterations & & Mesh . IsClosed ( ) = = false )
{
repeat_count + + ;
MeshFaceSelection bdry_faces = new MeshFaceSelection ( Mesh ) ;
foreach ( int eid in MeshIterators . BoundaryEdges ( Mesh ) )
if ( mesh . IsClosed ( ) )
{
bdry_faces . SelectEdgeTris ( eid ) ;
goto all_done ;
}
MeshEditor . RemoveTriangles ( Mesh , bdry_faces , true ) ;
goto repeat_all ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
// 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 ( ) ;
RepairCracks ( mesh , true , 2 * repairTolerance ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
RepairCracks ( mesh , false , 2 * repairTolerance ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
if ( mesh . IsClosed ( ) )
{
goto all_done ;
}
// 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 ) ;
}
all_done :
// Remove tiny edges
if ( MinEdgeLengthTol > 0 )
if ( FillHoles )
{
collapse_all_degenerate_edges ( MinEdgeLengthTol , false ) ;
// 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 ;
}
}
if ( cancellationToken . IsCancellationRequested )
all_done :
// and do a final clean up of the model
if ( FillHoles )
{
return inMesh ;
// Remove tiny edges
if ( minEdgeLengthTol > 0 )
{
CollapseAllDegenerateEdges ( mesh , cancellationToken , minEdgeLengthTol , false ) ;
}
cancellationToken . ThrowIfCancellationRequested ( ) ;
// finally do global orientation
RepairOrientation ( mesh , cancellationToken , true ) ;
cancellationToken . ThrowIfCancellationRequested ( ) ;
}
// finally do global orientation
repair_orientation ( mesh , cancellationToken , true ) ;
if ( cancellationToken . IsCancellationRequested )
{
return inMesh ;
}
return mesh . ToMesh ( ) ;
}
return mesh . ToMesh ( ) ;
}
void repair_orientation ( DMesh3 Mesh , CancellationToken cancellationToken , bool bGlobal )
{
MeshRepairOrientation orient = new MeshRepairOrientation ( Mesh ) ;
orient . OrientComponents ( ) ;
if ( cancellationToken . IsCancellationRequested )
catch ( OperationCanceledException )
{
return ;
return inMesh ;
}
if ( bGlobal )
{
orient . SolveGlobalOrientation ( ) ;
}
}
bool remove_interior ( DMesh3 Mesh , out int nRemoved )
{
RemoveOccludedTriangles remove = new RemoveOccludedTriangles ( Mesh ) ;
remove . PerVertex = true ;
remove . InsideMode = RemoveOccludedTriangles . CalculationMode . FastWindingNumber ;
remove . Apply ( ) ;
nRemoved = remove . RemovedT . Count ( ) ;
return true ;
}
bool remove_occluded ( DMesh3 Mesh , out int nRemoved )
{
RemoveOccludedTriangles remove = new RemoveOccludedTriangles ( Mesh ) ;
remove . PerVertex = true ;
remove . InsideMode = RemoveOccludedTriangles . CalculationMode . SimpleOcclusionTest ;
remove . Apply ( ) ;
nRemoved = remove . RemovedT . Count ( ) ;
return true ;
}
bool do_remove_inside ( DMesh3 Mesh )
{
int nRemoved = 0 ;
if ( RemoveMode = = RemoveModes . Interior )
{
return remove_interior ( Mesh , out nRemoved ) ;
}
else if ( RemoveMode = = RemoveModes . Occluded )
{
return remove_occluded ( Mesh , out nRemoved ) ;
}
return true ;
}
public void UpdateControls ( PublicPropertyChange change )
{
//if (change.Context.GetEditRow(nameof(TargetPercent)) is GuiWidget percentWidget)
//{
// percentWidget.Visible = Mode == ReductionMode.Polygon_Percent;
//}
// 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 )
{
repeat = false ;
}
}
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 )
{
continue ;
}
bool isBoundaryEdge = mesh . IsBoundaryEdge ( eid ) ;
if ( bBoundaryOnly & & isBoundaryEdge = = false )
{
continue ;
}
Index2i ev = mesh . GetEdgeV ( eid ) ;
Vector3d a = mesh . GetVertex ( ev . a ) , b = mesh . GetVertex ( ev . b ) ;
if ( a . Distance ( b ) < minLength )
{
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 )
{
+ + collapseCount ;
if ( mesh . IsBoundaryVertex ( keep ) = = false | | isBoundaryEdge )
{
mesh . SetVertex ( keep , ( a + b ) * 0.5 ) ;
}
}
}
}
return true ;
}
private bool DisconnectBowties ( DMesh3 mesh , out int nRemaining )
{
var editor = new MeshEditor ( mesh ) ;
nRemaining = editor . DisconnectAllBowties ( ) ;
return true ;
}
private bool RremoveInside ( DMesh3 mesh )
{
if ( RemoveMode = = RemoveModes . Interior )
{
return RemoveInterior ( mesh , out _ ) ;
}
else if ( RemoveMode = = RemoveModes . Occluded )
{
return RemoveOccluded ( mesh , out _ ) ;
}
return true ;
}
private void FillAnyHoles ( 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 )
{
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 ( ) ;
}
}
}
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 )
{
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 ;
}
}
}
private bool RemoveInterior ( DMesh3 mesh , out int nRemoved )
{
var remove = new RemoveOccludedTriangles ( mesh )
{
PerVertex = true ,
InsideMode = RemoveOccludedTriangles . CalculationMode . FastWindingNumber
} ;
remove . Apply ( ) ;
nRemoved = remove . RemovedT . Count ;
return true ;
}
private bool RemoveOccluded ( DMesh3 mesh , out int nRemoved )
{
var remove = new RemoveOccludedTriangles ( mesh )
{
PerVertex = true ,
InsideMode = RemoveOccludedTriangles . CalculationMode . SimpleOcclusionTest
} ;
remove . Apply ( ) ;
nRemoved = remove . RemovedT . Count ;
return true ;
}
private bool RepairCracks ( DMesh3 mesh , bool bUniqueOnly , double mergeDist )
{
try
{
var merge = new MergeCoincidentEdges ( mesh )
{
OnlyUniquePairs = bUniqueOnly ,
MergeDistance = mergeDist
} ;
return merge . Apply ( ) ;
}
catch ( Exception /*e*/ )
{
// ??
return false ;
}
}
private void RepairOrientation ( DMesh3 mesh ,
CancellationToken cancellationToken ,
bool bGlobal )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
var orient = new MeshRepairOrientation ( mesh ) ;
orient . OrientComponents ( ) ;
if ( bGlobal )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
orient . SolveGlobalOrientation ( ) ;
}
}
}
}