mattercontrol/MatterControlLib/DesignTools/Operations/RepairObject3D.cs
Lars Brubaker 9cfc85f90e Took out legacy csg code
refactoring
2021-11-26 12:49:50 -08:00

500 lines
No EOL
13 KiB
C#

/*
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.
*/
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using g3;
using gs;
using MatterHackers.Agg.UI;
using MatterHackers.DataConverters3D;
using MatterHackers.Localizations;
using MatterHackers.MatterControl.DesignTools.Operations;
using MatterHackers.PolygonMesh;
using MatterHackers.PolygonMesh.Processors;
using static gs.MeshAutoRepair;
namespace MatterHackers.MatterControl.DesignTools
{
public class RepairObject3D : OperationSourceContainerObject3D, IPropertyGridModifier
{
public RepairObject3D()
{
Name = "Repair".Localize();
}
public override bool Persistable => ApplicationController.Instance.UserHasPermission(this);
[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;
[Description("How distant a vertex must be to weld.")]
public double WeldTolerance { get; set; }
[Description("Make all the faces have a consistent orientation.")]
public bool FaceOrientation { get; set; } = false;
[Description("Repair any small cracks or bad seams in the model.")]
public bool WeldEdges { get; set; } = false;
[Description("Try to fill in any holes that are in the model.")]
public bool FillHoles { get; set; } = false;
[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.")]
public RemoveModes RemoveMode { get; set; } = RemoveModes.None;
[ReadOnly(true)]
public int FinalVertices { get; set; }
[ReadOnly(true)]
public int FinalFaces { get; set; }
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();
var inititialVertices = 0;
var inititialFaces = 0;
var finalVertices = 0;
var finalFaces = 0;
foreach (var sourceItem in SourceContainer.VisibleMeshes())
{
var originalMesh = sourceItem.Mesh;
inititialFaces += originalMesh.Faces.Count;
inititialVertices += originalMesh.Vertices.Count;
var repairedMesh = Repair(originalMesh, cancellationToken);
finalFaces += repairedMesh.Faces.Count;
finalVertices += repairedMesh.Vertices.Count;
var repairedChild = new Object3D()
{
Mesh = repairedMesh
};
repairedChild.CopyWorldProperties(sourceItem, this, Object3DPropertyFlags.All, false);
this.Children.Add(repairedChild);
}
this.InitialFaces = inititialFaces;
this.InitialVertices = inititialVertices;
this.FinalFaces = finalFaces;
this.FinalVertices = finalVertices;
SourceContainer.Visible = false;
UiThread.RunOnIdle(() =>
{
rebuildLocks.Dispose();
Invalidate(InvalidateType.DisplayValues);
Parent?.Invalidate(new InvalidateArgs(this, InvalidateType.Children));
});
return Task.CompletedTask;
});
}
public Mesh Repair(Mesh sourceMesh, CancellationToken cancellationToken)
{
var inMesh = sourceMesh;
try
{
if (WeldVertices)
{
inMesh = sourceMesh.Copy(cancellationToken);
if (WeldTolerance > 0)
{
inMesh.MergeVertices(.01);
}
else
{
inMesh.CleanAndMerge();
}
if (!FaceOrientation
&& RemoveMode == RemoveModes.None
&& !WeldEdges
&& !FillHoles)
{
return inMesh;
}
}
var mesh = inMesh.ToDMesh3();
int repeatCount = 0;
int erosionIterations = 5;
double repairTolerance = MathUtil.ZeroTolerancef;
double minEdgeLengthTol = 0.0001;
repeat_all:
if (FaceOrientation)
{
// make sure orientation of connected components is consistent
// TODO: what about mobius strip problems?
RepairOrientation(mesh, cancellationToken, true);
}
if (RemoveMode != RemoveModes.None)
{
// 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)...
RemoveInside(mesh);
cancellationToken.ThrowIfCancellationRequested();
}
if (WeldEdges || FillHoles)
{
// Do safe close-cracks to handle easy cases
RepairCracks(mesh, true, repairTolerance);
if (mesh.IsClosed())
{
goto all_done;
}
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);
}
if (FillHoles)
{
// 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;
}
}
all_done:
// and do a final clean up of the model
if (FillHoles)
{
// Remove tiny edges
if (minEdgeLengthTol > 0)
{
CollapseAllDegenerateEdges(mesh, cancellationToken, minEdgeLengthTol, false);
}
cancellationToken.ThrowIfCancellationRequested();
// finally do global orientation
RepairOrientation(mesh, cancellationToken, true);
cancellationToken.ThrowIfCancellationRequested();
}
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)
{
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 RemoveInside(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();
}
}
}
}