From c22a8bdd278caac4ea542e5f1825d08fc516b5e3 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Sun, 5 Apr 2026 12:21:41 +0300 Subject: [PATCH 1/6] TEST( MRMesh, HoleFillPlan ) --- source/MRTest/MRFillHoleTests.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/source/MRTest/MRFillHoleTests.cpp b/source/MRTest/MRFillHoleTests.cpp index a3fe7621aead..f1895106030c 100644 --- a/source/MRTest/MRFillHoleTests.cpp +++ b/source/MRTest/MRFillHoleTests.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include namespace MR @@ -117,4 +118,24 @@ TEST( MRMesh, makeBridgeEdge ) EXPECT_FALSE( x.valid() ); } +TEST( MRMesh, HoleFillPlan ) +{ + Mesh mesh; + const auto e = mesh.addSeparateEdgeLoop + ( { + { 0, -1, 0 }, + { 2, 0, 0 }, + { 0, 1, 0 }, + { -2, 0, 0 } + } ); + auto p0 = getPlanarHoleFillPlan( mesh, e ); + auto p1 = getPlanarHoleFillPlan( mesh, e.sym() ); + + executeHoleFillPlan( mesh, e, p0 ); + executeHoleFillPlan( mesh, e.sym(), p1 ); + EXPECT_EQ( mesh.topology.numValidFaces(), 4 ); + EXPECT_TRUE( mesh.topology.isClosed() ); + EXPECT_TRUE( hasMultipleEdges( mesh.topology ) ); +} + } //namespace MR From 4f946d2ac8345b647a2baf1218488203caac8755 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Sun, 5 Apr 2026 13:00:55 +0300 Subject: [PATCH 2/6] HoleFillPlan3 --- source/MRTest/MRFillHoleTests.cpp | 40 ++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/source/MRTest/MRFillHoleTests.cpp b/source/MRTest/MRFillHoleTests.cpp index f1895106030c..4fe1a8c3223a 100644 --- a/source/MRTest/MRFillHoleTests.cpp +++ b/source/MRTest/MRFillHoleTests.cpp @@ -118,7 +118,34 @@ TEST( MRMesh, makeBridgeEdge ) EXPECT_FALSE( x.valid() ); } -TEST( MRMesh, HoleFillPlan ) +TEST( MRMesh, HoleFillPlan3 ) +{ + Mesh mesh; + const auto e = mesh.addSeparateEdgeLoop + ( { + { 0, -1, 0 }, + { 2, 0, 0 }, + { 0, 1, 0 } + } ); + + auto p0 = getPlanarHoleFillPlan( mesh, e ); + EXPECT_EQ( p0.items.size(), 0 ); + EXPECT_EQ( p0.numTris, 1 ); + + auto p1 = getPlanarHoleFillPlan( mesh, e.sym() ); + EXPECT_EQ( p1.items.size(), 0 ); + EXPECT_EQ( p1.numTris, 1 ); + + executeHoleFillPlan( mesh, e, p0 ); + EXPECT_EQ( mesh.topology.numValidFaces(), 1 ); + EXPECT_FALSE( mesh.topology.isClosed() ); + + executeHoleFillPlan( mesh, e.sym(), p1 ); + EXPECT_EQ( mesh.topology.numValidFaces(), 2 ); + EXPECT_TRUE( mesh.topology.isClosed() ); +} + +TEST( MRMesh, HoleFillPlan4 ) { Mesh mesh; const auto e = mesh.addSeparateEdgeLoop @@ -128,13 +155,24 @@ TEST( MRMesh, HoleFillPlan ) { 0, 1, 0 }, { -2, 0, 0 } } ); + auto p0 = getPlanarHoleFillPlan( mesh, e ); + EXPECT_EQ( p0.items.size(), 1 ); + EXPECT_EQ( p0.numTris, 2 ); + auto p1 = getPlanarHoleFillPlan( mesh, e.sym() ); + EXPECT_EQ( p1.items.size(), 1 ); + EXPECT_EQ( p1.numTris, 2 ); executeHoleFillPlan( mesh, e, p0 ); + EXPECT_EQ( mesh.topology.numValidFaces(), 2 ); + EXPECT_FALSE( mesh.topology.isClosed() ); + EXPECT_FALSE( hasMultipleEdges( mesh.topology ) ); + executeHoleFillPlan( mesh, e.sym(), p1 ); EXPECT_EQ( mesh.topology.numValidFaces(), 4 ); EXPECT_TRUE( mesh.topology.isClosed() ); + // independently produced plans can result in multiple edges after execution: EXPECT_TRUE( hasMultipleEdges( mesh.topology ) ); } From 756f897797fb7d9032baa2d228719951870a4690 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Sun, 5 Apr 2026 13:05:05 +0300 Subject: [PATCH 3/6] plan to fill the second hole is prepared after the first hole is filled --- source/MRTest/MRFillHoleTests.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/source/MRTest/MRFillHoleTests.cpp b/source/MRTest/MRFillHoleTests.cpp index 4fe1a8c3223a..abbe283683be 100644 --- a/source/MRTest/MRFillHoleTests.cpp +++ b/source/MRTest/MRFillHoleTests.cpp @@ -169,11 +169,22 @@ TEST( MRMesh, HoleFillPlan4 ) EXPECT_FALSE( mesh.topology.isClosed() ); EXPECT_FALSE( hasMultipleEdges( mesh.topology ) ); + auto mesh1 = mesh; + executeHoleFillPlan( mesh, e.sym(), p1 ); EXPECT_EQ( mesh.topology.numValidFaces(), 4 ); EXPECT_TRUE( mesh.topology.isClosed() ); // independently produced plans can result in multiple edges after execution: EXPECT_TRUE( hasMultipleEdges( mesh.topology ) ); + + // if the plan to fill the second hole is prepared after the first hole is filled, no multiple edges appear + auto p11 = getPlanarHoleFillPlan( mesh1, e.sym() ); + EXPECT_EQ( p11.items.size(), 1 ); + EXPECT_EQ( p11.numTris, 2 ); + executeHoleFillPlan( mesh1, e.sym(), p11 ); + EXPECT_EQ( mesh1.topology.numValidFaces(), 4 ); + EXPECT_TRUE( mesh1.topology.isClosed() ); + EXPECT_FALSE( hasMultipleEdges( mesh1.topology ) ); } } //namespace MR From acae515a0efee58f12b546ce7594c38fa444afe0 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Sun, 5 Apr 2026 14:24:31 +0300 Subject: [PATCH 4/6] doesFillingMultipleEdgeFree --- source/MRMesh/MRMeshFillHole.cpp | 21 +++++++++++++++++++++ source/MRMesh/MRMeshFillHole.h | 13 +++++++++---- source/MRTest/MRFillHoleTests.cpp | 7 ++++++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/source/MRMesh/MRMeshFillHole.cpp b/source/MRMesh/MRMeshFillHole.cpp index 756a800940f0..48dbb73c589a 100644 --- a/source/MRMesh/MRMeshFillHole.cpp +++ b/source/MRMesh/MRMeshFillHole.cpp @@ -587,6 +587,27 @@ void executeHoleFillPlan( Mesh & mesh, EdgeId a0, HoleFillPlan & plan, FaceBitSe assert( plan.numTris == int( fsz - fsz0 + ( f0 ? 1 : 0 ) ) ); } +bool doesFillingMultipleEdgeFree( const MeshTopology & topology, const HoleFillPlan & plan ) +{ + if ( plan.items.empty() ) + return true; + + auto getVert = [&]( int code ) + { + while ( code < 0 ) + code = plan.items[ -(code+1) ].edgeCode1; + return topology.org( EdgeId( code ) ); + }; + for ( int i = 0; i < plan.items.size(); ++i ) + { + auto v1 = getVert( plan.items[i].edgeCode1 ); + auto v2 = getVert( plan.items[i].edgeCode2 ); + if ( topology.findEdge( v1, v2 ) ) + return false; + } + return true; +} + /// this class allows you to prepare fill plans for several holes with no new memory allocations on /// second and subsequent calls class HoleFillPlanner diff --git a/source/MRMesh/MRMeshFillHole.h b/source/MRMesh/MRMeshFillHole.h index fdae2f4922a2..235f7d71634a 100644 --- a/source/MRMesh/MRMeshFillHole.h +++ b/source/MRMesh/MRMeshFillHole.h @@ -173,23 +173,28 @@ struct HoleFillPlan /// prepares the plan how to triangulate the face or hole to the left of (e) (not filling it immediately), /// several getHoleFillPlan can work in parallel -MRMESH_API HoleFillPlan getHoleFillPlan( const Mesh& mesh, EdgeId e, const FillHoleParams& params = {} ); +[[nodiscard]] MRMESH_API HoleFillPlan getHoleFillPlan( const Mesh& mesh, EdgeId e, const FillHoleParams& params = {} ); /// prepares the plans how to triangulate the faces or holes, each given by a boundary edge (with filling target to the left), /// the plans are prepared in parallel with minimal memory allocation compared to manual calling of several getHoleFillPlan(), but it can inefficient when some holes are very complex -MRMESH_API std::vector getHoleFillPlans( const Mesh& mesh, const std::vector& holeRepresentativeEdges, const FillHoleParams& params = {} ); +[[nodiscard]] MRMESH_API std::vector getHoleFillPlans( const Mesh& mesh, const std::vector& holeRepresentativeEdges, const FillHoleParams& params = {} ); /// prepares the plan how to triangulate the planar face or planar hole to the left of (e) (not filling it immediately), /// several getPlanarHoleFillPlan can work in parallel -MRMESH_API HoleFillPlan getPlanarHoleFillPlan( const Mesh& mesh, EdgeId e ); +[[nodiscard]] MRMESH_API HoleFillPlan getPlanarHoleFillPlan( const Mesh& mesh, EdgeId e ); /// prepares the plans how to triangulate the planar faces or holes, each given by a boundary edge (with filling target to the left), /// the plans are prepared in parallel with minimal memory allocation compared to manual calling of several getPlanarHoleFillPlan(), but it can inefficient when some holes are very complex -MRMESH_API std::vector getPlanarHoleFillPlans( const Mesh& mesh, const std::vector& holeRepresentativeEdges ); +[[nodiscard]] MRMESH_API std::vector getPlanarHoleFillPlans( const Mesh& mesh, const std::vector& holeRepresentativeEdges ); /// quickly triangulates the face or hole to the left of (e) given the plan (quickly compared to fillHole function) MRMESH_API void executeHoleFillPlan( Mesh & mesh, EdgeId a0, HoleFillPlan & plan, FaceBitSet * outNewFaces = nullptr ); +/// returns true if executeHoleFillPlan() with the same topology and plan +/// does not introduce any edge with the same end-vertices as any existed edge in the mesh; +/// note: this function can be used for checking filling plans generated independently before executing them one by one +[[nodiscard]] MRMESH_API bool doesFillingMultipleEdgeFree( const MeshTopology & topology, const HoleFillPlan & plan ); + /** \brief Triangulates face of hole in mesh trivially\n * \ingroup FillHoleGroup * diff --git a/source/MRTest/MRFillHoleTests.cpp b/source/MRTest/MRFillHoleTests.cpp index abbe283683be..1580179d49dd 100644 --- a/source/MRTest/MRFillHoleTests.cpp +++ b/source/MRTest/MRFillHoleTests.cpp @@ -136,10 +136,12 @@ TEST( MRMesh, HoleFillPlan3 ) EXPECT_EQ( p1.items.size(), 0 ); EXPECT_EQ( p1.numTris, 1 ); + EXPECT_TRUE( doesFillingMultipleEdgeFree( mesh.topology, p0 ) ); executeHoleFillPlan( mesh, e, p0 ); EXPECT_EQ( mesh.topology.numValidFaces(), 1 ); EXPECT_FALSE( mesh.topology.isClosed() ); + EXPECT_TRUE( doesFillingMultipleEdgeFree( mesh.topology, p1 ) ); executeHoleFillPlan( mesh, e.sym(), p1 ); EXPECT_EQ( mesh.topology.numValidFaces(), 2 ); EXPECT_TRUE( mesh.topology.isClosed() ); @@ -164,6 +166,7 @@ TEST( MRMesh, HoleFillPlan4 ) EXPECT_EQ( p1.items.size(), 1 ); EXPECT_EQ( p1.numTris, 2 ); + EXPECT_TRUE( doesFillingMultipleEdgeFree( mesh.topology, p0 ) ); executeHoleFillPlan( mesh, e, p0 ); EXPECT_EQ( mesh.topology.numValidFaces(), 2 ); EXPECT_FALSE( mesh.topology.isClosed() ); @@ -171,16 +174,18 @@ TEST( MRMesh, HoleFillPlan4 ) auto mesh1 = mesh; + // independently produced plans can result in multiple edges after execution: + EXPECT_FALSE( doesFillingMultipleEdgeFree( mesh.topology, p1 ) ); executeHoleFillPlan( mesh, e.sym(), p1 ); EXPECT_EQ( mesh.topology.numValidFaces(), 4 ); EXPECT_TRUE( mesh.topology.isClosed() ); - // independently produced plans can result in multiple edges after execution: EXPECT_TRUE( hasMultipleEdges( mesh.topology ) ); // if the plan to fill the second hole is prepared after the first hole is filled, no multiple edges appear auto p11 = getPlanarHoleFillPlan( mesh1, e.sym() ); EXPECT_EQ( p11.items.size(), 1 ); EXPECT_EQ( p11.numTris, 2 ); + EXPECT_TRUE( doesFillingMultipleEdgeFree( mesh1.topology, p11 ) ); executeHoleFillPlan( mesh1, e.sym(), p11 ); EXPECT_EQ( mesh1.topology.numValidFaces(), 4 ); EXPECT_TRUE( mesh1.topology.isClosed() ); From dc4dd11d1f139a3ed50a22ce653d5f5c11890d1d Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Mon, 6 Apr 2026 11:11:18 +0300 Subject: [PATCH 5/6] fix CR --- source/MRMesh/MRMeshFillHole.cpp | 2 +- source/MRMesh/MRMeshFillHole.h | 4 ++-- source/MRTest/MRFillHoleTests.cpp | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/source/MRMesh/MRMeshFillHole.cpp b/source/MRMesh/MRMeshFillHole.cpp index 48dbb73c589a..6a703f898c2c 100644 --- a/source/MRMesh/MRMeshFillHole.cpp +++ b/source/MRMesh/MRMeshFillHole.cpp @@ -587,7 +587,7 @@ void executeHoleFillPlan( Mesh & mesh, EdgeId a0, HoleFillPlan & plan, FaceBitSe assert( plan.numTris == int( fsz - fsz0 + ( f0 ? 1 : 0 ) ) ); } -bool doesFillingMultipleEdgeFree( const MeshTopology & topology, const HoleFillPlan & plan ) +bool isFillingMultipleEdgeFree( const MeshTopology & topology, const HoleFillPlan & plan ) { if ( plan.items.empty() ) return true; diff --git a/source/MRMesh/MRMeshFillHole.h b/source/MRMesh/MRMeshFillHole.h index 235f7d71634a..fbc0440cba03 100644 --- a/source/MRMesh/MRMeshFillHole.h +++ b/source/MRMesh/MRMeshFillHole.h @@ -192,8 +192,8 @@ MRMESH_API void executeHoleFillPlan( Mesh & mesh, EdgeId a0, HoleFillPlan & plan /// returns true if executeHoleFillPlan() with the same topology and plan /// does not introduce any edge with the same end-vertices as any existed edge in the mesh; -/// note: this function can be used for checking filling plans generated independently before executing them one by one -[[nodiscard]] MRMESH_API bool doesFillingMultipleEdgeFree( const MeshTopology & topology, const HoleFillPlan & plan ); +/// note: this function can be used for checking a fill plan generated before filling other holes +[[nodiscard]] MRMESH_API bool isFillingMultipleEdgeFree( const MeshTopology & topology, const HoleFillPlan & plan ); /** \brief Triangulates face of hole in mesh trivially\n * \ingroup FillHoleGroup diff --git a/source/MRTest/MRFillHoleTests.cpp b/source/MRTest/MRFillHoleTests.cpp index 1580179d49dd..5ba620cade07 100644 --- a/source/MRTest/MRFillHoleTests.cpp +++ b/source/MRTest/MRFillHoleTests.cpp @@ -136,12 +136,12 @@ TEST( MRMesh, HoleFillPlan3 ) EXPECT_EQ( p1.items.size(), 0 ); EXPECT_EQ( p1.numTris, 1 ); - EXPECT_TRUE( doesFillingMultipleEdgeFree( mesh.topology, p0 ) ); + EXPECT_TRUE( isFillingMultipleEdgeFree( mesh.topology, p0 ) ); executeHoleFillPlan( mesh, e, p0 ); EXPECT_EQ( mesh.topology.numValidFaces(), 1 ); EXPECT_FALSE( mesh.topology.isClosed() ); - EXPECT_TRUE( doesFillingMultipleEdgeFree( mesh.topology, p1 ) ); + EXPECT_TRUE( isFillingMultipleEdgeFree( mesh.topology, p1 ) ); executeHoleFillPlan( mesh, e.sym(), p1 ); EXPECT_EQ( mesh.topology.numValidFaces(), 2 ); EXPECT_TRUE( mesh.topology.isClosed() ); @@ -166,7 +166,7 @@ TEST( MRMesh, HoleFillPlan4 ) EXPECT_EQ( p1.items.size(), 1 ); EXPECT_EQ( p1.numTris, 2 ); - EXPECT_TRUE( doesFillingMultipleEdgeFree( mesh.topology, p0 ) ); + EXPECT_TRUE( isFillingMultipleEdgeFree( mesh.topology, p0 ) ); executeHoleFillPlan( mesh, e, p0 ); EXPECT_EQ( mesh.topology.numValidFaces(), 2 ); EXPECT_FALSE( mesh.topology.isClosed() ); @@ -175,7 +175,7 @@ TEST( MRMesh, HoleFillPlan4 ) auto mesh1 = mesh; // independently produced plans can result in multiple edges after execution: - EXPECT_FALSE( doesFillingMultipleEdgeFree( mesh.topology, p1 ) ); + EXPECT_FALSE( isFillingMultipleEdgeFree( mesh.topology, p1 ) ); executeHoleFillPlan( mesh, e.sym(), p1 ); EXPECT_EQ( mesh.topology.numValidFaces(), 4 ); EXPECT_TRUE( mesh.topology.isClosed() ); @@ -185,7 +185,7 @@ TEST( MRMesh, HoleFillPlan4 ) auto p11 = getPlanarHoleFillPlan( mesh1, e.sym() ); EXPECT_EQ( p11.items.size(), 1 ); EXPECT_EQ( p11.numTris, 2 ); - EXPECT_TRUE( doesFillingMultipleEdgeFree( mesh1.topology, p11 ) ); + EXPECT_TRUE( isFillingMultipleEdgeFree( mesh1.topology, p11 ) ); executeHoleFillPlan( mesh1, e.sym(), p11 ); EXPECT_EQ( mesh1.topology.numValidFaces(), 4 ); EXPECT_TRUE( mesh1.topology.isClosed() ); From 1e55e011c5bb648d24564b201df5724634b173c4 Mon Sep 17 00:00:00 2001 From: Fedor Chelnokov Date: Mon, 6 Apr 2026 11:54:49 +0300 Subject: [PATCH 6/6] a better comment --- source/MRMesh/MRMeshFillHole.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/MRMesh/MRMeshFillHole.h b/source/MRMesh/MRMeshFillHole.h index fbc0440cba03..132d0f406e7d 100644 --- a/source/MRMesh/MRMeshFillHole.h +++ b/source/MRMesh/MRMeshFillHole.h @@ -192,7 +192,7 @@ MRMESH_API void executeHoleFillPlan( Mesh & mesh, EdgeId a0, HoleFillPlan & plan /// returns true if executeHoleFillPlan() with the same topology and plan /// does not introduce any edge with the same end-vertices as any existed edge in the mesh; -/// note: this function can be used for checking a fill plan generated before filling other holes +/// note: this function can be used for checking a fill plan that was generated before filling other holes [[nodiscard]] MRMESH_API bool isFillingMultipleEdgeFree( const MeshTopology & topology, const HoleFillPlan & plan ); /** \brief Triangulates face of hole in mesh trivially\n