Mesh - Fix periodic seam constraints (#1338)

The base mesh algorithm created constraint links for all pcurves of an
  edge, including duplicate representations at periodic seams.
Register nodes and mesh indices for all pcurves so polygon-on-triangulation
  data remains valid, but add links only for the pcurve of the wire occurrence.
This commit is contained in:
Pasukhin Dmitry
2026-07-13 13:42:10 +01:00
committed by GitHub
parent d3042539c7
commit aa3e65da8d
5 changed files with 254 additions and 33 deletions

View File

@@ -86,40 +86,59 @@ bool BRepMesh_BaseMeshAlgo::initDataStructure()
for (int aEdgeIt = 0; aEdgeIt < aDWire->EdgesNb(); ++aEdgeIt)
{
const IMeshData::IEdgeHandle aDEdge = aDWire->GetEdge(aEdgeIt);
const IMeshData::ICurveHandle& aCurve = aDEdge->GetCurve();
const IMeshData::ListOfInteger& aListOfPCurves = aDEdge->GetPCurves(myDFace.get());
const IMeshData::IEdgeHandle aDEdge = aDWire->GetEdge(aEdgeIt);
const IMeshData::ICurveHandle& aCurve = aDEdge->GetCurve();
// A wire defines one topological occurrence of an edge. Registering every
// pcurve attached to its face would create duplicate constraints at a periodic seam.
const IMeshData::IPCurveHandle& aPCurve =
aDEdge->GetPCurve(myDFace.get(), aDWire->GetEdgeOrientation(aEdgeIt));
const TopAbs_Orientation aOri = fixSeamEdgeOrientation(aDEdge, aPCurve);
int aPrevNodeIndex = -1;
const int aLastPoint = aPCurve->ParametersNb() - 1;
for (int aPointIndex = 0; aPointIndex <= aLastPoint; ++aPointIndex)
{
const int aNodeIndex = registerNode(aCurve->GetPoint(aPointIndex),
aPCurve->GetPoint(aPointIndex),
BRepMesh_Frontier,
false);
aPCurve->GetIndex(aPointIndex) = aNodeIndex;
if (aPrevNodeIndex != -1 && aPrevNodeIndex != aNodeIndex)
{
const int aLinksNb = myStructure->NbLinks();
const int aLinkIndex = addLinkToMesh(aPrevNodeIndex, aNodeIndex, aOri);
if (aWireIt != 0 && aLinkIndex <= aLinksNb)
{
// Prevent holes around wire of zero area.
BRepMesh_Edge& aLink = const_cast<BRepMesh_Edge&>(myStructure->GetLink(aLinkIndex));
aLink.SetMovability(BRepMesh_Fixed);
}
}
aPrevNodeIndex = aNodeIndex;
}
// Every pcurve needs node indices for Poly_PolygonOnTriangulation, but only
// the pcurve belonging to this wire occurrence defines a mesh constraint.
const IMeshData::ListOfInteger& aListOfPCurves = aDEdge->GetPCurves(myDFace.get());
for (IMeshData::ListOfInteger::Iterator aPCurveIt(aListOfPCurves); aPCurveIt.More();
aPCurveIt.Next())
{
const IMeshData::IPCurveHandle& aPCurve = aDEdge->GetPCurve(aPCurveIt.Value());
const TopAbs_Orientation aOri = fixSeamEdgeOrientation(aDEdge, aPCurve);
int aPrevNodeIndex = -1;
const int aLastPoint = aPCurve->ParametersNb() - 1;
for (int aPointIndex = 0; aPointIndex <= aLastPoint; ++aPointIndex)
const IMeshData::IPCurveHandle& anOtherPCurve = aDEdge->GetPCurve(aPCurveIt.Value());
if (anOtherPCurve == aPCurve)
{
const int aNodeIndex = registerNode(aCurve->GetPoint(aPointIndex),
aPCurve->GetPoint(aPointIndex),
BRepMesh_Frontier,
false);
continue;
}
aPCurve->GetIndex(aPointIndex) = aNodeIndex;
if (aPrevNodeIndex != -1 && aPrevNodeIndex != aNodeIndex)
{
const int aLinksNb = myStructure->NbLinks();
const int aLinkIndex = addLinkToMesh(aPrevNodeIndex, aNodeIndex, aOri);
if (aWireIt != 0 && aLinkIndex <= aLinksNb)
{
// Prevent holes around wire of zero area.
BRepMesh_Edge& aLink = const_cast<BRepMesh_Edge&>(myStructure->GetLink(aLinkIndex));
aLink.SetMovability(BRepMesh_Fixed);
}
}
aPrevNodeIndex = aNodeIndex;
const int anOtherLastPoint = anOtherPCurve->ParametersNb() - 1;
for (int aPointIndex = 0; aPointIndex <= anOtherLastPoint; ++aPointIndex)
{
anOtherPCurve->GetIndex(aPointIndex) = registerNode(aCurve->GetPoint(aPointIndex),
anOtherPCurve->GetPoint(aPointIndex),
BRepMesh_Frontier,
false);
}
}
}

View File

@@ -11,18 +11,37 @@
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
#include <BRepAdaptor_Surface.hxx>
#include <BRepAlgoAPI_Fuse.hxx>
#include <BRep_Builder.hxx>
#include <BRep_Tool.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepCheck_Analyzer.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <BRepPrimAPI_MakeCylinder.hxx>
#include <BRepPrimAPI_MakeRevol.hxx>
#include <Geom_CylindricalSurface.hxx>
#include <IMeshTools_Parameters.hxx>
#include <Poly_PolygonOnTriangulation.hxx>
#include <Poly_Triangulation.hxx>
#include <Precision.hxx>
#include <TopLoc_Location.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <gp_Ax3.hxx>
#include <gp_Ax1.hxx>
#include <gp_Ax2.hxx>
#include <gp_Circ.hxx>
#include <gp_Pln.hxx>
#include <gp_Pnt.hxx>
#include <gp_Vec.hxx>
#include <TopoDS_Edge.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Vertex.hxx>
#include <TopoDS_Wire.hxx>
@@ -71,3 +90,184 @@ TEST(BRepMesh_IncrementalMeshTest, OCC26407_PlanarPolygonMeshStatus)
EXPECT_EQ(aMesher.GetStatusFlags(), 0)
<< "Meshing of the planar polygon face should succeed (status 0)";
}
TEST(BRepMesh_IncrementalMeshTest, TrimmedCylinder_RespectsUVRangeAndDeflection)
{
constexpr double THE_RADIUS = 5.0;
constexpr double THE_FIRST_U = 0.4;
constexpr double THE_LAST_U = 5.7;
constexpr double THE_FIRST_V = -2.0;
constexpr double THE_LAST_V = 7.0;
constexpr double THE_DEFLECTION = 0.05;
occ::handle<Geom_CylindricalSurface> aCylinder =
new Geom_CylindricalSurface(gp_Ax3(gp::Origin(), gp::DZ()), THE_RADIUS);
BRepBuilderAPI_MakeFace aFaceBuilder(aCylinder,
THE_FIRST_U,
THE_LAST_U,
THE_FIRST_V,
THE_LAST_V,
Precision::Confusion());
ASSERT_TRUE(aFaceBuilder.IsDone());
const TopoDS_Face aFace = aFaceBuilder.Face();
BRepMesh_IncrementalMesh aMesher(aFace, THE_DEFLECTION);
ASSERT_TRUE(aMesher.IsDone());
TopLoc_Location aLocation;
const occ::handle<Poly_Triangulation> aTriangulation = BRep_Tool::Triangulation(aFace, aLocation);
ASSERT_FALSE(aTriangulation.IsNull());
ASSERT_TRUE(aTriangulation->HasUVNodes());
ASSERT_GT(aTriangulation->NbTriangles(), 0);
for (int aNodeIdx = 1; aNodeIdx <= aTriangulation->NbNodes(); ++aNodeIdx)
{
const gp_Pnt2d aUV = aTriangulation->UVNode(aNodeIdx);
EXPECT_GE(aUV.X(), THE_FIRST_U - Precision::PConfusion());
EXPECT_LE(aUV.X(), THE_LAST_U + Precision::PConfusion());
EXPECT_GE(aUV.Y(), THE_FIRST_V - Precision::PConfusion());
EXPECT_LE(aUV.Y(), THE_LAST_V + Precision::PConfusion());
const gp_Pnt anExpectedPoint = aCylinder->Value(aUV.X(), aUV.Y());
const gp_Pnt anActualPoint = aTriangulation->Node(aNodeIdx).Transformed(aLocation);
EXPECT_LE(anActualPoint.Distance(anExpectedPoint), Precision::Confusion());
}
for (int aTriangleIdx = 1; aTriangleIdx <= aTriangulation->NbTriangles(); ++aTriangleIdx)
{
int aNode1 = 0;
int aNode2 = 0;
int aNode3 = 0;
aTriangulation->Triangle(aTriangleIdx).Get(aNode1, aNode2, aNode3);
const gp_Pnt2d aUV1 = aTriangulation->UVNode(aNode1);
const gp_Pnt2d aUV2 = aTriangulation->UVNode(aNode2);
const gp_Pnt2d aUV3 = aTriangulation->UVNode(aNode3);
const gp_Pnt anExpectedPoint = aCylinder->Value((aUV1.X() + aUV2.X() + aUV3.X()) / 3.0,
(aUV1.Y() + aUV2.Y() + aUV3.Y()) / 3.0);
const gp_Pnt aPoint1 = aTriangulation->Node(aNode1).Transformed(aLocation);
const gp_Pnt aPoint2 = aTriangulation->Node(aNode2).Transformed(aLocation);
const gp_Pnt aPoint3 = aTriangulation->Node(aNode3).Transformed(aLocation);
const gp_Pnt anActualPoint((aPoint1.XYZ() + aPoint2.XYZ() + aPoint3.XYZ()) / 3.0);
EXPECT_LE(anActualPoint.Distance(anExpectedPoint), THE_DEFLECTION);
}
}
TEST(BRepMesh_IncrementalMeshTest, BooleanTrimmedTorus_CrossingSeamProducesDenseMesh)
{
BRepPrimAPI_MakeBox aBoxBuilder(gp_Pnt(-187.0, -20.0, -67.5), gp_Pnt(187.0, 0.0, 67.5));
const TopoDS_Shape aBox = aBoxBuilder.Shape();
ASSERT_TRUE(aBoxBuilder.IsDone());
const gp_Circ aCircle(gp_Ax2(gp_Pnt(200.0, -20.0, 0.0), gp_Dir(0.0, -1.0, 0.0)), 17.5);
BRepBuilderAPI_MakeEdge anEdgeBuilder(aCircle);
const TopoDS_Edge anEdge = anEdgeBuilder.Edge();
ASSERT_TRUE(anEdgeBuilder.IsDone());
BRepBuilderAPI_MakeWire aWireBuilder(anEdge);
const TopoDS_Wire aWire = aWireBuilder.Wire();
ASSERT_TRUE(aWireBuilder.IsDone());
BRepBuilderAPI_MakeFace aFaceBuilder(aWire, true);
const TopoDS_Face aProfile = aFaceBuilder.Face();
ASSERT_TRUE(aFaceBuilder.IsDone());
const gp_Ax1 anAxis(gp_Pnt(0.0, -20.0, 0.0), gp_Dir(0.0, 0.0, -1.0));
BRepPrimAPI_MakeRevol aRingBuilder(aProfile, anAxis, 2.0 * M_PI);
const TopoDS_Shape aRing = aRingBuilder.Shape();
ASSERT_TRUE(aRingBuilder.IsDone());
BRepAlgoAPI_Fuse aFuse(aBox, aRing);
aFuse.Build();
ASSERT_TRUE(aFuse.IsDone());
const TopoDS_Shape aFused = aFuse.Shape();
ASSERT_FALSE(aFused.IsNull());
EXPECT_TRUE(BRepCheck_Analyzer(aFused).IsValid());
IMeshTools_Parameters aParameters;
aParameters.Deflection = 0.1;
aParameters.Angle = 0.4;
BRepMesh_IncrementalMesh aMesher(aFused, aParameters);
ASSERT_TRUE(aMesher.IsDone());
int aTorusFaces = 0;
int aTorusNodes = 0;
int aTorusTriangles = 0;
for (TopExp_Explorer anExplorer(aFused, TopAbs_FACE); anExplorer.More(); anExplorer.Next())
{
const TopoDS_Face& aFace = TopoDS::Face(anExplorer.Current());
BRepAdaptor_Surface aSurface(aFace);
if (aSurface.GetType() != GeomAbs_Torus)
{
continue;
}
++aTorusFaces;
TopLoc_Location aLocation;
const occ::handle<Poly_Triangulation> aTriangulation =
BRep_Tool::Triangulation(aFace, aLocation);
ASSERT_FALSE(aTriangulation.IsNull());
aTorusNodes += aTriangulation->NbNodes();
aTorusTriangles += aTriangulation->NbTriangles();
}
ASSERT_GT(aTorusFaces, 0);
// The regression produced fewer than 450 triangles for the whole fused shape.
EXPECT_GT(aTorusNodes, 1000);
EXPECT_GT(aTorusTriangles, 1000);
}
TEST(BRepMesh_IncrementalMeshTest, ClosedCylinder_SeamPolygonsReferenceValidMeshNodes)
{
BRepPrimAPI_MakeCylinder aCylinderBuilder(5.0, 10.0);
const TopoDS_Shape aCylinder = aCylinderBuilder.Shape();
ASSERT_TRUE(aCylinderBuilder.IsDone());
BRepMesh_IncrementalMesh aMesher(aCylinder, 0.1);
ASSERT_TRUE(aMesher.IsDone());
int aSeamEdges = 0;
for (TopExp_Explorer aFaceExplorer(aCylinder, TopAbs_FACE); aFaceExplorer.More();
aFaceExplorer.Next())
{
const TopoDS_Face& aFace = TopoDS::Face(aFaceExplorer.Current());
TopLoc_Location aLocation;
const occ::handle<Poly_Triangulation> aTriangulation =
BRep_Tool::Triangulation(aFace, aLocation);
ASSERT_FALSE(aTriangulation.IsNull());
for (TopExp_Explorer anEdgeExplorer(aFace, TopAbs_EDGE); anEdgeExplorer.More();
anEdgeExplorer.Next())
{
const TopoDS_Edge& anEdge = TopoDS::Edge(anEdgeExplorer.Current());
if (!BRep_Tool::IsClosed(anEdge, aFace))
{
continue;
}
const occ::handle<Poly_PolygonOnTriangulation>& aForwardPolygon =
BRep_Tool::PolygonOnTriangulation(anEdge, aTriangulation, aLocation);
const TopoDS_Edge aReversedEdge = TopoDS::Edge(anEdge.Reversed());
const occ::handle<Poly_PolygonOnTriangulation>& aReversedPolygon =
BRep_Tool::PolygonOnTriangulation(aReversedEdge, aTriangulation, aLocation);
ASSERT_FALSE(aForwardPolygon.IsNull());
ASSERT_FALSE(aReversedPolygon.IsNull());
for (int aNodeIndex = 1; aNodeIndex <= aForwardPolygon->NbNodes(); ++aNodeIndex)
{
EXPECT_GE(aForwardPolygon->Node(aNodeIndex), 1);
EXPECT_LE(aForwardPolygon->Node(aNodeIndex), aTriangulation->NbNodes());
}
for (int aNodeIndex = 1; aNodeIndex <= aReversedPolygon->NbNodes(); ++aNodeIndex)
{
EXPECT_GE(aReversedPolygon->Node(aNodeIndex), 1);
EXPECT_LE(aReversedPolygon->Node(aNodeIndex), aTriangulation->NbNodes());
}
++aSeamEdges;
}
}
EXPECT_GT(aSeamEdges, 0);
}

View File

@@ -1,4 +1,4 @@
puts "TODO OCC30286 Linux: Error : The length of result shape is 3025.32, expected 3025.36"
puts "TODO OCC30286 Linux: Error : The length of result shape is 3025.4"
puts "==========================================="
puts "OCC25813: regression in Hidden Line Removal"
@@ -6,7 +6,7 @@ puts "==========================================="
puts ""
set viewname ""
set length 3025.36
set length 3025.49
restore [locate_data_file bug25813_hlr-bus1-draw-Fusion001.brep] a
COMPUTE_HLR $viewname $algotype

View File

@@ -1,12 +1,14 @@
puts "TODO OCC30286 Linux: Error : The length of result shape is 302.183, expected 302.238"
puts "TODO OCC30286 Linux: Error : The length of result shape is 302"
puts "==========================================="
puts "OCC25813: regression in Hidden Line Removal"
puts "==========================================="
puts ""
set viewname ""
set length 302.238
set length 301.999
ptorus a 30 10

View File

@@ -4,7 +4,7 @@ puts "==========================================="
puts ""
set viewname ""
set length 1196.25
set length 1196.29
restore [locate_data_file bug25813_hlr-test_normandc1m1-draw-Cut001.brep] a
COMPUTE_HLR $viewname $algotype