refactoring

This commit is contained in:
LarsBrubaker 2020-04-01 20:48:59 -07:00
parent cf6b02c8eb
commit aec26a2292
12 changed files with 356 additions and 226 deletions

View file

@ -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();
}
}
}
}

View file

@ -149,7 +149,7 @@ namespace MatterHackers.Plugins.EditorTools
}
}
CollisionVolume = rotationHandle.CreateTraceData();
CollisionVolume = rotationHandle.CreateBVHData();
InteractionContext.GuiSurface.AfterDraw += InteractionLayer_AfterDraw;
}

View file

@ -110,7 +110,7 @@ namespace MatterHackers.Plugins.EditorTools
minXminYMesh = PlatonicSolids.CreateCube(selectCubeSize, selectCubeSize, selectCubeSize);
CollisionVolume = minXminYMesh.CreateTraceData();
CollisionVolume = minXminYMesh.CreateBVHData();
InteractionContext.GuiSurface.AfterDraw += InteractionLayer_AfterDraw;
}

View file

@ -139,7 +139,7 @@ namespace MatterHackers.Plugins.EditorTools
topScaleMesh = PlatonicSolids.CreateCube(selectCubeSize, selectCubeSize, selectCubeSize);
CollisionVolume = topScaleMesh.CreateTraceData();
CollisionVolume = topScaleMesh.CreateBVHData();
InteractionContext.GuiSurface.AfterDraw += InteractionLayer_AfterDraw;
}

View file

@ -55,7 +55,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
{
foreach (var child in sceneContext.Scene.Children)
{
world.RenderDebugAABB(e.Graphics2D, child.TraceData().GetAxisAlignedBoundingBox());
world.RenderDebugAABB(e.Graphics2D, child.GetBVHData().GetAxisAlignedBoundingBox());
world.RenderDebugAABB(e.Graphics2D, child.GetAxisAlignedBoundingBox());
}
}

View file

@ -61,7 +61,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
{
var center = item.GetAxisAlignedBoundingBox().Center;
var traceData = item.TraceData();
var traceData = item.GetBVHData();
var xy = traceData.Contains(center);

View file

@ -56,7 +56,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
public void Draw(GuiWidget sender, DrawEventArgs e, Matrix4X4 itemMaxtrix, WorldView world)
{
// RenderSceneTraceData
var bvhIterator = new BvhIterator(scene?.TraceData(), decentFilter: (x) =>
var bvhIterator = new BvhIterator(scene?.GetBVHData(), decentFilter: (x) =>
{
var center = x.Bvh.GetCenter();
var worldCenter = Vector3Ex.Transform(center, x.TransformToWorld);

View file

@ -142,7 +142,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
upArrowMesh = StlProcessing.Load(arrowStream, CancellationToken.None);
}
CollisionVolume = upArrowMesh.CreateTraceData();
CollisionVolume = upArrowMesh.CreateBVHData();
InteractionContext.GuiSurface.AfterDraw += InteractionLayer_AfterDraw;
}

View file

@ -190,7 +190,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
if (nameFound)
{
AxisAlignedBoundingBox bounds = child.TraceData().GetAxisAlignedBoundingBox();
AxisAlignedBoundingBox bounds = child.GetBVHData().GetAxisAlignedBoundingBox();
RectangleDouble screenBoundsOfObject3D = RectangleDouble.ZeroIntersection;
for (int i = 0; i < 4; i++)

View file

@ -188,7 +188,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
connections.Add(new ConnectedFaces(1, 1, 2, 3, 1, 0));
connections.Add(new ConnectedFaces(1, -1, 1, 3, 2, 0));
cubeTraceData = cube.CreateTraceData();
cubeTraceData = cube.CreateBVHData();
});
MouseLeave += (s, e) =>

View file

@ -913,7 +913,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
foundTriangleInSelectionBounds = false;
// Filter the IPrimitive trace data finding matches as defined in InSelectionBounds
var filteredResults = item.TraceData().Filter(InSelectionBounds);
var filteredResults = item.GetBVHData().Filter(InSelectionBounds);
// Accumulate all matching BvhIterator results for debug rendering
allResults.AddRange(filteredResults);
@ -1461,7 +1461,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
{
foreach (Object3D object3D in selectedItem.Children)
{
if (object3D.TraceData().Contains(info.HitPosition))
if (object3D.GetBVHData().Contains(info.HitPosition))
{
CurrentSelectInfo.PlaneDownHitPos = info.HitPosition;
CurrentSelectInfo.LastMoveDelta = default(Vector3);
@ -1711,12 +1711,12 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
Vector2 meshViewerWidgetScreenPosition = this.InteractionLayer.TransformFromParentSpace(this, screenPosition);
Ray ray = sceneContext.World.GetRayForLocalBounds(meshViewerWidgetScreenPosition);
intersectionInfo = Scene.TraceData().GetClosestIntersection(ray);
intersectionInfo = Scene.GetBVHData().GetClosestIntersection(ray);
if (intersectionInfo != null)
{
foreach (Object3D object3D in Scene.Children)
{
if (object3D.TraceData().Contains(intersectionInfo.HitPosition))
if (object3D.GetBVHData().Contains(intersectionInfo.HitPosition))
{
CurrentSelectInfo.PlaneDownHitPos = intersectionInfo.HitPosition;
CurrentSelectInfo.LastMoveDelta = default(Vector3);

@ -1 +1 @@
Subproject commit b3588348683eeca5d1308dc9a0c7179c2488835f
Subproject commit 5d3c5e0290472cddbe0abc95ad8ef99559ea82b4