internal static void test_MeshAutoRepair() { Console.Write("test_MeshAutoRepair..."); // b1 will has edges that are overlapping, but disconnected, it's the result of a merge // I guess autorepair should weld it back DMesh3 b1 = TestUtil.LoadTestInputMesh("NeedHealing.obj"); // b2 is the same mesh, but we weld it var b2 = new DMesh3(b1, true); MergeCoincidentEdges merg = new MergeCoincidentEdges(b2); merg.Apply(); MeshAutoRepair autoRepair = new MeshAutoRepair(b1); autoRepair.Apply(); var allOk = b1.VertexCount == b2.VertexCount && b1.EdgeCount == b2.EdgeCount && b1.TriangleCount == b2.TriangleCount; if (!allOk) { var outF = TestUtil.WriteTestOutputMesh(b1, "MeshOps_MeshAutoRepair.obj"); Console.WriteLine($"Error, shape written to: {outF}"); } else { Console.WriteLine("Ok"); } }
public static void basic_tests() { DMesh3 mesh = TestUtil.LoadTestInputMesh("three_edge_crack.obj"); MergeCoincidentEdges merge = new MergeCoincidentEdges(mesh); merge.Apply(); Util.gDevAssert(mesh.BoundaryEdgeIndices().Count() == 0); mesh.CheckValidity(true, FailMode.DebugAssert); TestUtil.WriteTestOutputMesh(mesh, "three_edge_crack_merged.obj"); DMesh3 mesh2 = TestUtil.LoadTestInputMesh("crack_loop.obj"); MergeCoincidentEdges merge2 = new MergeCoincidentEdges(mesh2); merge2.Apply(); Util.gDevAssert(mesh2.BoundaryEdgeIndices().Count() == 0); mesh2.CheckValidity(true, FailMode.DebugAssert); TestUtil.WriteTestOutputMesh(mesh2, "crack_loop_merged.obj"); DMesh3 mesh3 = TestUtil.LoadTestInputMesh("cracks_many.obj"); MergeCoincidentEdges merge3 = new MergeCoincidentEdges(mesh3); merge3.Apply(); Util.gDevAssert(mesh3.BoundaryEdgeIndices().Count() == 0); mesh3.CheckValidity(true, FailMode.DebugAssert); TestUtil.WriteTestOutputMesh(mesh3, "cracks_many_merged.obj"); DMesh3 mesh4 = TestUtil.LoadTestInputMesh("cracks_duplicate_edge.obj"); MergeCoincidentEdges merge4 = new MergeCoincidentEdges(mesh4); merge4.Apply(); Util.gDevAssert(mesh4.BoundaryEdgeIndices().Count() == 0); mesh4.CheckValidity(true, FailMode.DebugAssert); TestUtil.WriteTestOutputMesh(mesh4, "cracks_duplicate_edge_merged.obj"); }
public static void test_auto_fill() { DMesh3 mesh = TestUtil.LoadTestInputMesh("autofill_cases.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("autofill_tempcase1.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("autofill_planarcase.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("autofill_box_edge_strip.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("autofill_box_edge_strip_2.obj"); //DMesh3 mesh = TestUtil.LoadTestInputMesh("crazyhole.obj"); double mine, maxe, avge; MeshQueries.EdgeLengthStats(mesh, out mine, out maxe, out avge); int ROUNDS = 1; for (int k = 0; k < ROUNDS; ++k) { MeshBoundaryLoops loops = new MeshBoundaryLoops(mesh); foreach (EdgeLoop loop in loops) { AutoHoleFill fill = new AutoHoleFill(mesh, loop); fill.TargetEdgeLength = avge; fill.Apply(); } if (k == ROUNDS - 1) { continue; } RemesherPro r = new RemesherPro(mesh); r.SetTargetEdgeLength(avge); for (int j = 0; j < 10; ++j) { r.FastSplitIteration(); } MergeCoincidentEdges merge = new MergeCoincidentEdges(mesh); merge.Apply(); } TestUtil.WriteTestOutputMesh(mesh, "autofill_result.obj"); //DMesh3 mesh = new DMesh3(); //SphericalFibonacciPointSet ps = new SphericalFibonacciPointSet(); //for (int k = 0; k < ps.N; ++k) { // MeshEditor.AppendBox(mesh, ps[k], ps[k], 0.05f); //} //Random r = new Random(31337); //Vector3d[] pts = TestUtil.RandomPoints3(10000, r, Vector3d.Zero); //foreach ( Vector3d pt in pts ) { // pt.Normalize(); // int nearest = ps.NearestPoint(pt, true); // Vector3d p = ps[nearest]; // MeshEditor.AppendLine(mesh, new Segment3d(p, p + 0.25f * pt), 0.01f); //} //TestUtil.WriteTestOutputMesh(mesh, "fibonacci.obj"); }
public static void hard_test() { //DMesh3 mesh = TestUtil.LoadTestInputMesh("three_edge_crack.obj"); DMesh3 mesh = TestUtil.LoadTestMesh("c:\\scratch\\VTX_Scan_removeocc.obj"); //MeshEditor editor = new MeshEditor(mesh); //editor.DisconnectAllBowties(100); mesh.CheckValidity(true, FailMode.DebugAssert); MergeCoincidentEdges merge = new MergeCoincidentEdges(mesh); merge.Apply(); mesh.CheckValidity(true, FailMode.DebugAssert); TestUtil.WriteTestOutputMesh(mesh, "vtx_scan_merged.obj"); }
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); } }
public static void test_write_solids() { //string FORMAT = ".obj"; string FORMAT = ".g3mesh"; string WRITEPATH = "E:\\Thingi10K\\closed\\"; string[] files = File.ReadAllLines("E:\\Thingi10K\\current\\thingi10k_closed.txt"); SafeListBuilder <string> failures = new SafeListBuilder <string>(); if (!Directory.Exists(WRITEPATH)) { Directory.CreateDirectory(WRITEPATH); } int k = 0; gParallel.ForEach(files, (filename) => { int i = k; Interlocked.Increment(ref k); if (i % 500 == 0) { System.Console.WriteLine("{0} : {1}", i, files.Length); } long start_ticks = DateTime.Now.Ticks; DMesh3Builder builder = new DMesh3Builder(); StandardMeshReader reader = new StandardMeshReader() { MeshBuilder = builder }; IOReadResult result = reader.Read(filename, ReadOptions.Defaults); if (result.code != IOCode.Ok) { System.Console.WriteLine("{0} FAILED!", filename); failures.SafeAdd(filename); return; } DMesh3 combineMesh = new DMesh3(); if (builder.Meshes.Count == 1) { combineMesh = builder.Meshes[0]; } else { foreach (DMesh3 mesh in builder.Meshes) { MeshEditor.Append(combineMesh, mesh); } } if (combineMesh.IsClosed() == false) { MergeCoincidentEdges closeCracks = new MergeCoincidentEdges(combineMesh); closeCracks.Apply(); } if (combineMesh.IsClosed() == false) { System.Console.WriteLine("NOT CLOSED: {0}", filename); return; } string outPath = Path.Combine(WRITEPATH, Path.GetFileNameWithoutExtension(filename) + FORMAT); StandardMeshWriter.WriteMesh(outPath, combineMesh, WriteOptions.Defaults); }); }
public static void test_specific_file() { //string filename = "F:\\Thingi10K\\raw_meshes\\1423009.stl"; //string filename = "E:\\Thingi10K\\raw_meshes\\99944.stl"; string filename = "E:\\Thingi10K\\raw_meshes\\57356.stl"; System.Console.WriteLine("reading {0}", filename); DMesh3Builder builder = new DMesh3Builder(); StandardMeshReader reader = new StandardMeshReader() { MeshBuilder = builder }; IOReadResult result = reader.Read(filename, ReadOptions.Defaults); if (result.code != IOCode.Ok) { System.Console.WriteLine("{0} FAILED!", filename); return; } System.Console.WriteLine("got {0} meshes", builder.Meshes.Count); bool is_open = false; bool loops_failed = false; bool is_empty = true; foreach (DMesh3 mesh in builder.Meshes) { if (mesh.TriangleCount > 0) { is_empty = false; } TestUtil.WriteTestOutputMesh(mesh, "thingi10k_specific_file_in.obj"); if (mesh.IsClosed() == false) { MergeCoincidentEdges closeCracks = new MergeCoincidentEdges(mesh); closeCracks.Apply(); } if (mesh.IsClosed() == false) { is_open = true; try { MeshBoundaryLoops loops = new MeshBoundaryLoops(mesh, false) { SpanBehavior = MeshBoundaryLoops.SpanBehaviors.Compute, FailureBehavior = MeshBoundaryLoops.FailureBehaviors.ConvertToOpenSpan }; loops.Compute(); } catch (Exception e) { System.Console.WriteLine("EXCEPTION: " + e.Message); loops_failed = true; } } System.Console.WriteLine("open: {0} loopsFailed: {1} empty: {2}", is_open, loops_failed, is_empty); TestUtil.WriteTestOutputMesh(mesh, "thingi10k_specific_file_out.obj"); } }
public static void test_repair_all() { //const string THINGIROOT = "D:\\meshes\\Thingi10K\\raw_meshes\\"; const string THINGIROOT = "E:\\Thingi10K\\raw_meshes\\"; string[] files = Directory.GetFiles(THINGIROOT); //files = File.ReadAllLines("C:\\git\\geometry3SharpDemos\\geometry3Test\\test_output\\thingi10k_open.txt"); SafeListBuilder <string> failures = new SafeListBuilder <string>(); SafeListBuilder <string> empty = new SafeListBuilder <string>(); SafeListBuilder <string> closed = new SafeListBuilder <string>(); SafeListBuilder <string> open = new SafeListBuilder <string>(); SafeListBuilder <string> boundaries_failed = new SafeListBuilder <string>(); SafeListBuilder <string> boundaries_spans_failed = new SafeListBuilder <string>(); SafeListBuilder <string> slow = new SafeListBuilder <string>(); SafeListBuilder <string> veryslow = new SafeListBuilder <string>(); int k = 0; int MAX_NUM_FILES = 10000; gParallel.ForEach(files, (filename) => { if (k > MAX_NUM_FILES) { return; } int i = k; Interlocked.Increment(ref k); System.Console.WriteLine("{0} : {1}", i, filename); long start_ticks = DateTime.Now.Ticks; DMesh3Builder builder = new DMesh3Builder(); StandardMeshReader reader = new StandardMeshReader() { MeshBuilder = builder }; IOReadResult result = reader.Read(filename, ReadOptions.Defaults); if (result.code != IOCode.Ok) { System.Console.WriteLine("{0} FAILED!", filename); failures.SafeAdd(filename); return; } bool is_open = false; bool loops_failed = false; bool loops_spans_failed = false; bool is_empty = true; foreach (DMesh3 mesh in builder.Meshes) { if (mesh.TriangleCount > 0) { is_empty = false; } if (mesh.IsClosed() == false) { MergeCoincidentEdges closeCracks = new MergeCoincidentEdges(mesh); closeCracks.Apply(); } if (mesh.IsClosed() == false) { is_open = true; try { MeshBoundaryLoops loops = new MeshBoundaryLoops(mesh, false) { SpanBehavior = MeshBoundaryLoops.SpanBehaviors.ThrowException, FailureBehavior = MeshBoundaryLoops.FailureBehaviors.ThrowException }; loops.Compute(); } catch { loops_failed = true; } if (loops_failed) { try { MeshBoundaryLoops loops = new MeshBoundaryLoops(mesh, false) { SpanBehavior = MeshBoundaryLoops.SpanBehaviors.Compute, FailureBehavior = MeshBoundaryLoops.FailureBehaviors.ConvertToOpenSpan }; loops.Compute(); } catch { loops_spans_failed = true; } } } } TimeSpan elapsed = new TimeSpan(DateTime.Now.Ticks - start_ticks); if (elapsed.TotalSeconds > 60) { veryslow.SafeAdd(filename); } else if (elapsed.TotalSeconds > 10) { slow.SafeAdd(filename); } if (is_empty) { empty.SafeAdd(filename); } else if (is_open) { open.SafeAdd(filename); if (loops_failed) { boundaries_failed.SafeAdd(filename); } if (loops_spans_failed) { boundaries_spans_failed.SafeAdd(filename); } } else { closed.SafeAdd(filename); } }); foreach (string failure in failures.Result) { System.Console.WriteLine("FAIL: {0}", failure); } TestUtil.WriteTestOutputStrings(make_strings(failures), "thingi10k_failures.txt"); TestUtil.WriteTestOutputStrings(make_strings(empty), "thingi10k_empty.txt"); TestUtil.WriteTestOutputStrings(make_strings(closed), "thingi10k_closed.txt"); TestUtil.WriteTestOutputStrings(make_strings(open), "thingi10k_open.txt"); TestUtil.WriteTestOutputStrings(make_strings(boundaries_failed), "thingi10k_boundaries_failed.txt"); TestUtil.WriteTestOutputStrings(make_strings(boundaries_spans_failed), "thingi10k_boundaries_spans_failed.txt"); TestUtil.WriteTestOutputStrings(make_strings(slow), "thingi10k_slow.txt"); TestUtil.WriteTestOutputStrings(make_strings(veryslow), "thingi10k_veryslow.txt"); }
protected void merge_loops(DMesh3 mesh, EdgeLoop cutLoop, EdgeLoop connectorLoop, bool is_outer) { /* * To join the loops, we are going to first make a circle, then snap both * open loops to that circle. Then we sample a set of vertices on the circle * and remesh the loops while also snapping them to the circle vertices. * The result is two perfectly-matching edge loops. */ //AxisAlignedBox3d cutLoopBounds = // BoundsUtil.Bounds(cutLoop.Vertices, (vid) => { return mesh.GetVertex(vid); }); AxisAlignedBox3d cutLoopBounds = cutLoop.GetBounds(); AxisAlignedBox3d connectorLoopBounds = connectorLoop.GetBounds(); Vector3d midPt = (cutLoopBounds.Center + connectorLoopBounds.Center) * 0.5; double midY = midPt.y; // this mess construcst the circle and the sampled version Frame3f circFrame = new Frame3f(midPt); Circle3d circ = new Circle3d(circFrame, connectorLoopBounds.Width * 0.5, 1); DistPoint3Circle3 dist = new DistPoint3Circle3(Vector3d.Zero, circ); DCurve3 sampled = new DCurve3(); double target_edge_len = TargetMeshEdgeLength; int N = (int)(circ.ArcLength / target_edge_len); for (int k = 0; k < N; ++k) { sampled.AppendVertex(circ.SampleT((double)k / (double)N)); } MergeProjectionTarget circleTarget = new MergeProjectionTarget() { Mesh = mesh, CircleDist = dist, CircleLoop = sampled }; EdgeLoop[] loops = new EdgeLoop[2] { cutLoop, connectorLoop }; EdgeLoop[] outputLoops = new EdgeLoop[2]; // loops after this remeshing/etc (but might be missing some verts/edges!) for (int li = 0; li < 2; ++li) { EdgeLoop loop = loops[li]; // snap the loop verts onto the analytic circle foreach (int vid in loop.Vertices) { Vector3d v = mesh.GetVertex(vid); dist.Point = new Vector3d(v.x, midY, v.z); mesh.SetVertex(vid, dist.Compute().CircleClosest); } if (DebugStep <= 5) { continue; } // remesh around the edge loop while we snap it to the sampled circle verts EdgeLoopRemesher loopRemesh = new EdgeLoopRemesher(mesh, loop) { LocalSmoothingRings = 3 }; loopRemesh.EnableParallelProjection = false; loopRemesh.SetProjectionTarget(circleTarget); loopRemesh.SetTargetEdgeLength(TargetMeshEdgeLength); loopRemesh.SmoothSpeedT = 0.5f; for (int k = 0; k < 5; ++k) { loopRemesh.BasicRemeshPass(); } loopRemesh.SmoothSpeedT = 0; for (int k = 0; k < 2; ++k) { loopRemesh.BasicRemeshPass(); } EdgeLoop newLoop = loopRemesh.OutputLoop; outputLoops[li] = newLoop; if (DebugStep <= 6) { continue; } // hard snap the loop vertices to the sampled circle verts foreach (int vid in newLoop.Vertices) { Vector3d v = mesh.GetVertex(vid); v = circleTarget.Project(v, vid); mesh.SetVertex(vid, v); } // [TODO] we could re-order newLoop verts/edges to match the sampled verts order, // then the pair of loops would be consistently ordered (currently no guarantee) if (DebugStep <= 7) { continue; } // collapse any degenerate edges on loop (just in case) // DANGER: if this actually happens, then outputLoops[li] has some invalid verts/edges! foreach (int eid in newLoop.Edges) { if (mesh.IsEdge(eid)) { Index2i ev = mesh.GetEdgeV(eid); Vector3d a = mesh.GetVertex(ev.a), b = mesh.GetVertex(ev.b); if (a.Distance(b) < TargetMeshEdgeLength * 0.001) { DMesh3.EdgeCollapseInfo collapse; mesh.CollapseEdge(ev.a, ev.b, out collapse); } } } } if (DebugStep <= 7) { return; } /* * Ok now we want to merge the loops and make them nice */ // would be more efficient to find loops and stitch them... MergeCoincidentEdges merge = new MergeCoincidentEdges(mesh); merge.Apply(); // fill any fail-holes?? // remesh merge region MeshVertexSelection remesh_roi_v = new MeshVertexSelection(mesh); remesh_roi_v.Select(outputLoops[0].Vertices); remesh_roi_v.Select(outputLoops[1].Vertices); remesh_roi_v.ExpandToOneRingNeighbours(5); MeshFaceSelection remesh_roi = new MeshFaceSelection(mesh, remesh_roi_v, 1); remesh_roi.LocalOptimize(true, true); IProjectionTarget projTarget = null; if (is_outer) { projTarget = new NoPenetrationProjectionTarget() { Spatial = this.OuterOffsetMeshSpatial }; } else { projTarget = new NoPenetrationProjectionTarget() { Spatial = this.InnerOffsetMeshSpatial }; } RegionRemesher join_remesh = RegionRemesher.QuickRemesh(mesh, remesh_roi.ToArray(), TargetMeshEdgeLength, 0.5, 5, projTarget); if (DebugStep <= 8) { return; } if (false && is_outer) { Func <int, bool> filterF = (tid) => { return(mesh.GetTriCentroid(tid).y > connectorLoopBounds.Max.y); }; MeshFaceSelection tris = new MeshFaceSelection(mesh); foreach (int tid in join_remesh.CurrentBaseTriangles) { if (filterF(tid)) { tris.Select(tid); } } tris.ExpandToOneRingNeighbours(5, filterF); MeshVertexSelection verts = new MeshVertexSelection(mesh, tris); MeshIterativeSmooth smooth = new MeshIterativeSmooth(mesh, verts.ToArray(), true); smooth.Alpha = 1.0f; smooth.Rounds = 25; smooth.ProjectF = (v, n, vid) => { return(projTarget.Project(v)); }; smooth.Smooth(); } // [RMS] this smooths too far. we basically only want to smooth 'up' from top of socket... //LaplacianMeshSmoother.RegionSmooth(mesh, join_remesh.CurrentBaseTriangles, 1, 10); // need to post-enforce thickness, which we aren't doing above - we could though }