From: dish Date: Tue, 2 Apr 2024 18:53:19 +0000 (+0000) Subject: [bos #41409][FORUM] (2024) kindOfShape() bug for CONE2D X-Git-Tag: V9_13_0a1~2 X-Git-Url: http://git.salome-platform.org/gitweb/?a=commitdiff_plain;h=refs%2Ftlpr%2F19%2Fhead;p=modules%2Fgeom.git [bos #41409][FORUM] (2024) kindOfShape() bug for CONE2D Make KindOfShape() work correctly in cases, when substrate surface is cone and contour-wire is arbitrary (tested with a wire, composed of lines and 2-order curves). --- diff --git a/doc/salome/examples/kind_of_shape_cone.py b/doc/salome/examples/kind_of_shape_cone.py new file mode 100644 index 000000000..5f1acd04b --- /dev/null +++ b/doc/salome/examples/kind_of_shape_cone.py @@ -0,0 +1,137 @@ +# Sample: KindOfShape method for faces, which are results of partitioning of a conical surface with a prism with complex base. +# Faces of the prism are not perpendicular to cone axis, therefore contour-wires of resulting cone fragments are composed of lines and 2-order curves. + +import sys +import salome + +salome.salome_init() +import salome_notebook +notebook = salome_notebook.NoteBook() + +### +### GEOM component +### + +import GEOM +from salome.geom import geomBuilder +import math +import SALOMEDS + + +def approximatelyEqual(a, b, epsilon = 1e-5): + return abs(a - b) <= ((abs(b) if (abs(a) < abs(b)) else abs(a)) * epsilon) + + +def assertShapeKindEquals(iShapeInfo, iKind): + assert (len(iShapeInfo) > 0), "Yielded data array is empty." + assert (iShapeInfo[0] == iKind), f"Expected shape kind is {iKind}, but yielded kind is {iShapeInfo[0]}." + + +def assertConePropsEqual(iShapeName, iShapeInfo, iExpectedShapeInfo): + assertShapeKindEquals(iShapeInfo, geompy.kind.CONE2D) + assert (len(iShapeInfo) == len(iExpectedShapeInfo)), f"{iShapeName}: Yielded data array is of unexpected length." + for idx in range(1, len(iShapeInfo)): + assert (approximatelyEqual(iShapeInfo[idx], iExpectedShapeInfo[idx])), f"{iShapeName}: Yielded data array element is not equal to the expected value." + + +def assertConeInfoEquals(iFace, iExpectedShapeInfo, iAddRestoredConeToStudy = False): + ShapeInfo = geompy.KindOfShape(iFace) + print("ShapeInfo of " + iFace.GetName() + " = ", end = "") + print(ShapeInfo, ', ') + assertConePropsEqual(iFace.GetName(), ShapeInfo, iExpectedShapeInfo) + + if (iAddRestoredConeToStudy): + BottomLidCenter = geompy.MakeVertex(ShapeInfo[1], ShapeInfo[2], ShapeInfo[3]) + AxisAuxPnt = geompy.MakeVertex(ShapeInfo[1] + ShapeInfo[4], ShapeInfo[2] + ShapeInfo[5], ShapeInfo[3] + ShapeInfo[6]) + Axis = geompy.MakeVector(BottomLidCenter, AxisAuxPnt) + R1 = ShapeInfo[7] # Bottom lid radius. + R2 = ShapeInfo[8] # Top lid radius. + H = ShapeInfo[9] + RestoredCone = geompy.MakeCone(BottomLidCenter, Axis, R1, R2, H) + geompy.addToStudy(RestoredCone, iFace.GetName() + '__RestoredCone') + + +# iExpectedConeFragmentShapeInfos is a dictionary of [IndexOfFace, ExpectedShapeInfoOfFace]. IndexOfFace is zero-based index, not one-based one as in Shaper GUI! +def partitionConeAndAssertShapeInfosEqual(iCone, iPartitioningShape, iExpectedConeFragmentShapeInfos, iAddResultsToStudy): + PartitionedCone = geompy.MakePartition([iCone], [iPartitioningShape], [], [], geompy.ShapeType["FACE"], 0, [], 0) + if (iAddResultsToStudy): + geompy.addToStudy(PartitionedCone, "Partitioned" + iCone.GetName()) + + ConeFragments = geompy.ExtractShapes(PartitionedCone, geompy.ShapeType["FACE"], True) + ConeFragmentsIdxs = iExpectedConeFragmentShapeInfos.keys() + for ConeFragmentIdx in ConeFragmentsIdxs: + assert (ConeFragmentIdx < len(ConeFragments)), f"Num of faces, {iCone.GetName()} is partitioned into, <= {ConeFragmentIdx} (zero-based index)." + ConeFragment = ConeFragments[ConeFragmentIdx] + ConeFragmentName = f"Partitioned{iCone.GetName()}_Face_{ConeFragmentIdx+1}" # Add index to a name as Shaper GUI does. + + if (iAddResultsToStudy): + geompy.addToStudyInFather(PartitionedCone, ConeFragment, ConeFragmentName) + else: + ConeFragment.SetName(ConeFragmentName) + + assertConeInfoEquals(ConeFragment, iExpectedConeFragmentShapeInfos[ConeFragmentIdx], iAddResultsToStudy) + + +geompy = geomBuilder.New() + +OriginalConeBaseCenter = geompy.MakeVertex(100, 130, -60) +OriginalConeAxisAuxPnt = geompy.MakeVertex(100, 230, 40) +OriginalConeAxis = geompy.MakeVector(OriginalConeBaseCenter, OriginalConeAxisAuxPnt) +OriginalCone = geompy.MakeCone(OriginalConeBaseCenter, OriginalConeAxis, 100, 50, 300) +PrismSubstrateCenter = geompy.MakeVertex(100, 1000, 50) +PrismDirAuxPnt = geompy.MakeVertex(100, 950, 50) +PrismDir = geompy.MakeVector(PrismSubstrateCenter, PrismDirAuxPnt) +PrismSubstrate = geompy.MakeDiskPntVecR(PrismSubstrateCenter, PrismDir, 100) +sk = geompy.Sketcher2D() +sk.addPoint(0.395986, 43.346713) +sk.addSegmentAbsolute(66.321537, 41.733575) +sk.addSegmentAbsolute(80.619408, -2.852314) +sk.addSegmentAbsolute(67.641539, -38.565150) +sk.addSegmentAbsolute(22.193602, -56.632163) +sk.addSegmentAbsolute(-19.060136, -51.084351) +sk.addSegmentAbsolute(-60.823572, 34.825751) +sk.addSegmentAbsolute(-13.047004, 55.727527) +sk.close() +PrismBase = sk.wire(PrismSubstrate) +Prism = geompy.MakePrismVecH(PrismBase, PrismDir, 1400) +geompy.addToStudy( OriginalConeBaseCenter, 'OriginalConeBaseCenter' ) +geompy.addToStudy( OriginalConeAxisAuxPnt, 'OriginalConeAxisAuxPnt' ) +geompy.addToStudy( OriginalConeAxis, 'OriginalConeAxis' ) +geompy.addToStudy( OriginalCone, 'OriginalCone' ) +geompy.addToStudy( PrismSubstrateCenter, 'PrismSubstrateCenter' ) +geompy.addToStudy( PrismDirAuxPnt, 'PrismDirAuxPnt' ) +geompy.addToStudy( PrismDir, 'PrismDir' ) +geompy.addToStudy( PrismSubstrate, 'PrismSubstrate' ) +geompy.addToStudy( PrismBase, 'PrismBase' ) +geompy.addToStudy( Prism, 'Prism' ) + +# Test on the original cone +ExpectedOriginalConeFragmentsShapeInfos = { + 3: ["CONE2D", 100.0, 215.76160602318674, 25.761606023186744, 0.0, 0.7071067811865475, 0.7071067811865475, 79.7857956051852, 54.62305376134459, 150.9764510630437], + 5: ["CONE2D", 100.0, 129.99999999999753, -60.000000000002466, 0.0, 0.7071067811865475, 0.7071067811865475, 100.00000000000058, 69.82277418813575, 181.06335487118898], + 11: ["CONE2D", 100.0, 216.57653245407857, 26.57653245407856, 0.0, 0.7071067811865475, 0.7071067811865475, 79.59371560336794, 52.95933239773038, 159.80629923382543] +} +partitionConeAndAssertShapeInfosEqual(OriginalCone, Prism, ExpectedOriginalConeFragmentsShapeInfos, True) + +# Test on isotropically scaled cone. Non-isotropical scaling does not preserve shape kind - it is desired behavior. +ScaledCone = geompy.MakeScaleTransform(OriginalCone, OriginalConeAxisAuxPnt, 2) +ScaledCone.SetName('ScaledCone') +ExpectedScaledConeFragmentsShapeInfos = { + 4: ["CONE2D", 100.0, 29.9999999999999, -160.00000000000009, 0.0, 0.7071067811865475, 0.7071067811865475, 200.00000000000003, 162.64508449690112, 224.1294930185934], + 6: ["CONE2D", 100.0, 262.09898500769475, 72.09898500769472, 0.0, 0.7071067811865475, 0.7071067811865475, 145.2937445981814, 120.13428858458612, 150.95673608157182], + 12: ["CONE2D", 100.0, 262.8999708414969, 72.8999708414969, 0.0, 0.7071067811865475, 0.7071067811865475, 145.10495042660943, 117.46838914559419, 165.8193676860916] +} +partitionConeAndAssertShapeInfosEqual(ScaledCone, Prism, ExpectedScaledConeFragmentsShapeInfos, False) + +# Test on a cone, mirrored relative to a point. +PntMirroredCone = geompy.MakeMirrorByPoint(OriginalCone, OriginalConeAxisAuxPnt) +PntMirroredCone.SetName('PntMirroredCone') +ExpectedPntMirroredConeFragmentsShapeInfos = { + 2: ["CONE2D", 100.0, 229.8712015945071, 39.87120159450711, -0.0, -0.7071067811865475, -0.7071067811865475, 76.39941588513841, 51.25530645152799, 150.8646566016625], + 7: ["CONE2D", 100.0, 330.0, 140.0, -0.0, -0.7071067811865475, -0.7071067811865475, 100.0, 71.73019727352477, 169.61881635885143], + 10: ["CONE2D", 100.0, 249.15532313133338, 59.15532313133339, -0.0, -0.7071067811865475, -0.7071067811865475, 80.9447269211102, 51.428754043115056, 177.09583726797095] +} +partitionConeAndAssertShapeInfosEqual(PntMirroredCone, Prism, ExpectedPntMirroredConeFragmentsShapeInfos, False) + +if salome.sg.hasDesktop(): + salome.sg.updateObjBrowser() diff --git a/doc/salome/examples/tests.set b/doc/salome/examples/tests.set index 606c3a2a9..467f2029e 100644 --- a/doc/salome/examples/tests.set +++ b/doc/salome/examples/tests.set @@ -76,6 +76,7 @@ SET(GOOD_TESTS get_non_blocks.py import_export.py inertia.py + kind_of_shape_cone.py min_distance.py curvature_face.py normal_face.py diff --git a/doc/salome/gui/GEOM/input/tui_kind_of_shape.doc b/doc/salome/gui/GEOM/input/tui_kind_of_shape.doc index f8219bb5e..157bc4389 100644 --- a/doc/salome/gui/GEOM/input/tui_kind_of_shape.doc +++ b/doc/salome/gui/GEOM/input/tui_kind_of_shape.doc @@ -4,4 +4,6 @@ \tui_script{kind_of_shape.py} +\tui_script{kind_of_shape_cone.py} + */ diff --git a/src/GEOMAlgo/GEOMAlgo_ShapeInfoFiller.cxx b/src/GEOMAlgo/GEOMAlgo_ShapeInfoFiller.cxx index 7885f7b6b..e5b61b286 100644 --- a/src/GEOMAlgo/GEOMAlgo_ShapeInfoFiller.cxx +++ b/src/GEOMAlgo/GEOMAlgo_ShapeInfoFiller.cxx @@ -336,7 +336,7 @@ void GEOMAlgo_ShapeInfoFiller::FillFace(const TopoDS_Shape& aS) aP0=aPln.Location(); aAx3=aPln.Position(); // - aInfo.SetKindOfShape(GEOMAlgo_KS_PLANE); + aInfo.SetKindOfShape(GEOMAlgo_KS_PLANE); aInfo.SetKindOfName(GEOMAlgo_KN_PLANE); aInfo.SetKindOfClosed(GEOMAlgo_KC_NOTCLOSED); aInfo.SetLocation(aP0); @@ -420,38 +420,52 @@ void GEOMAlgo_ShapeInfoFiller::FillFace(const TopoDS_Shape& aS) //|||||||||||||||||||||||||||||||||||||||||||||||||||||||||| // 4. Cone else if (aST==GeomAbs_Cone) { - Standard_Real aSemiAngle; - gp_Cone aCone; - // - aCone=aGAS.Cone(); - aP0=aCone.Location(); - aAx3=aCone.Position(); + const gp_Cone aCone=aGAS.Cone(); // aInfo.SetKindOfShape(GEOMAlgo_KS_CONE); aInfo.SetKindOfName(GEOMAlgo_KN_CONE); - aInfo.SetLocation(aP0); - aInfo.SetPosition(aAx3); // BRepTools::UVBounds(aF, aUMin, aUMax, aVMin, aVMax); + bInfU1=Precision::IsNegativeInfinite(aUMin); bInfU2=Precision::IsPositiveInfinite(aUMax); bInfV1=Precision::IsNegativeInfinite(aVMin); bInfV2=Precision::IsPositiveInfinite(aVMax); // - bInf=(bInfU1 || bInfU2 || bInfV1 || bInfV2); + bInf=bInfV1 || bInfV2; if (bInf) { + aP0=aAx3.Location(); + aAx3=aCone.Position(); + aInfo.SetLocation(aP0); + aInfo.SetPosition(aAx3); aInfo.SetKindOfBounds(GEOMAlgo_KB_INFINITE); return; } // aInfo.SetKindOfBounds(GEOMAlgo_KB_TRIMMED); // - aSemiAngle=fabs(aCone.SemiAngle()); - dV=(aVMax-aVMin)*cos(aSemiAngle); - - aInfo.SetHeight(dV); - // - FillDetails(aF, aCone); + const Standard_Real aSemiAngle = aCone.SemiAngle(); + + dV = aVMax - aVMin; + Standard_Real H = dV * std::cos(aSemiAngle); + + aAx3 = aCone.Position(); + Standard_Real aShiftAlongAxisLength = aVMin * std::cos(aSemiAngle); // Required, because R1 does not equal to gp_Cone.RefRadius() in general case, and gp_Cone.Location() corresponds to the latter one. + auto aShiftAlongAxis = gp_Vec(aAx3.Direction().XYZ()); + aShiftAlongAxis *= aShiftAlongAxisLength; + aAx3.Translate(aShiftAlongAxis); + + aP0=aAx3.Location(); + aInfo.SetLocation(aP0); + aInfo.SetPosition(aAx3); + + aR1 = aCone.RefRadius() + aVMin * std::sin(aSemiAngle); + aR2 = aCone.RefRadius() + aVMax * std::sin(aSemiAngle); + + aInfo.SetRadius1(aR1); + aInfo.SetRadius2(aR2); + aInfo.SetHeight(H); + aInfo.SetKindOfDef(GEOMAlgo_KD_SPECIFIED); } // //|||||||||||||||||||||||||||||||||||||||||||||||||||||||||| diff --git a/src/GEOMAlgo/GEOMAlgo_ShapeInfoFiller.hxx b/src/GEOMAlgo/GEOMAlgo_ShapeInfoFiller.hxx index 6b9480e03..1943c698f 100644 --- a/src/GEOMAlgo/GEOMAlgo_ShapeInfoFiller.hxx +++ b/src/GEOMAlgo/GEOMAlgo_ShapeInfoFiller.hxx @@ -112,9 +112,6 @@ class GEOMAlgo_ShapeInfoFiller : public GEOMAlgo_Algo Standard_EXPORT void FillDetails(const TopoDS_Face& aF,const gp_Cylinder& aCyl) ; - Standard_EXPORT - void FillDetails(const TopoDS_Face& aF,const gp_Cone& aCone) ; - Standard_EXPORT void FillDetails(const TopoDS_Face& aF,const gp_Torus& aTorus) ; diff --git a/src/GEOMAlgo/GEOMAlgo_ShapeInfoFiller_1.cxx b/src/GEOMAlgo/GEOMAlgo_ShapeInfoFiller_1.cxx index 6731ee2ca..50ed77127 100644 --- a/src/GEOMAlgo/GEOMAlgo_ShapeInfoFiller_1.cxx +++ b/src/GEOMAlgo/GEOMAlgo_ShapeInfoFiller_1.cxx @@ -103,13 +103,13 @@ void GEOMAlgo_ShapeInfoFiller::FillDetails(const TopoDS_Solid& aSd) aKD=aInfoF.KindOfDef(); } if (aKD!=GEOMAlgo_KD_SPECIFIED) { - aInfo.SetKindOfName(GEOMAlgo_KN_SOLID); + aInfo.SetKindOfName(GEOMAlgo_KN_SOLID); return; } // aNbShells=GEOMAlgo_ShapeInfoFiller::NbShells(aSd); if (aNbShells>1) { - aInfo.SetKindOfName(GEOMAlgo_KN_SOLID); + aInfo.SetKindOfName(GEOMAlgo_KN_SOLID); return; } // @@ -396,7 +396,7 @@ void GEOMAlgo_ShapeInfoFiller::FillDetails(const TopoDS_Face& aF, if (bSegment) { // 2. may be it is TRIANGLE, POLYGON, QUADRANGLE, RECTANGLE aInfo.SetKindOfDef(GEOMAlgo_KD_SPECIFIED); - aInfo.SetKindOfName(GEOMAlgo_KN_POLYGON); + aInfo.SetKindOfName(GEOMAlgo_KN_POLYGON); // if (aNbV==3 && aNbE==3) { aInfo.SetKindOfName(GEOMAlgo_KN_TRIANGLE); @@ -480,7 +480,7 @@ void GEOMAlgo_ShapeInfoFiller::FillDetails(const TopoDS_Face& aF, // aLength=aD1; aWidth =aD0; - + if (aD0>aD1) { aLength=aD0; aWidth =aD1; @@ -515,7 +515,7 @@ void GEOMAlgo_ShapeInfoFiller::FillDetails(const TopoDS_Face& aF, void GEOMAlgo_ShapeInfoFiller::FillDetails(const TopoDS_Face& aF, const gp_Sphere& )//aSph) { - + Standard_Integer aNbV, aNbE, aNbSE, aNbDE; TopoDS_Edge aE; TopExp_Explorer aExp; @@ -523,7 +523,7 @@ void GEOMAlgo_ShapeInfoFiller::FillDetails(const TopoDS_Face& aF, GEOMAlgo_KindOfShape aKSE;//, aKSE; // GEOMAlgo_ShapeInfo& aInfo=myMapInfo.ChangeFromKey(aF); - // + // aInfo.SetKindOfDef(GEOMAlgo_KD_ARBITRARY); aNbV=aInfo.NbSubShapes(TopAbs_VERTEX); aNbE=aInfo.NbSubShapes(TopAbs_EDGE); @@ -557,7 +557,7 @@ void GEOMAlgo_ShapeInfoFiller::FillDetails(const TopoDS_Face& aF, //======================================================================= void GEOMAlgo_ShapeInfoFiller::FillDetails(const TopoDS_Face& aF, const gp_Cylinder& aCyl) - + { Standard_Integer aNbV, aNbE, aNbCE, aNbSE; Standard_Real aT0, aT1, aHeight; @@ -627,130 +627,10 @@ void GEOMAlgo_ShapeInfoFiller::FillDetails(const TopoDS_Face& aF, //function : FillDetails //purpose : //======================================================================= -void GEOMAlgo_ShapeInfoFiller::FillDetails(const TopoDS_Face& aF, - const gp_Cone& aCone) -{ - Standard_Integer aNbV, aNbE, aNbCE, aNbSE, aNbDE, i; - Standard_Real aR[3], aHeight, aRmin, aRmax; - gp_Pnt aPC[3], aPD, aPc, aPX[3]; - TopoDS_Vertex aVD; - TopoDS_Edge aE; - TopoDS_Iterator aIt; - TopExp_Explorer aExp; - TopTools_MapOfShape aM; - GEOMAlgo_KindOfShape aKSE; - GEOMAlgo_KindOfName aKNE; - GEOMAlgo_KindOfClosed aKCE; - // - GEOMAlgo_ShapeInfo& aInfo=myMapInfo.ChangeFromKey(aF); - // - aInfo.SetKindOfDef(GEOMAlgo_KD_ARBITRARY); - // - aNbV=aInfo.NbSubShapes(TopAbs_VERTEX); - aNbE=aInfo.NbSubShapes(TopAbs_EDGE); - if (aNbV==2 && aNbE==3) { - i=0; - aNbCE=0; - aNbSE=0; - aNbDE=0; - aExp.Init(aF, TopAbs_EDGE); - for (; aExp.More(); aExp.Next()) { - aE=TopoDS::Edge(aExp.Current()); - if(aM.Add(aE)) { - const GEOMAlgo_ShapeInfo& aInfoE=myMapInfo.FindFromKey(aE); - aKNE=aInfoE.KindOfName(); - aKCE=aInfoE.KindOfClosed(); - aKSE=aInfoE.KindOfShape(); - if (aKNE==GEOMAlgo_KN_CIRCLE && aKCE==GEOMAlgo_KC_CLOSED) { - aPC[i]=aInfoE.Location(); - aR[i]=aInfoE.Radius1(); - // - aIt.Initialize(aE); - if (aIt.More()) { - aVD=*((TopoDS_Vertex*)&aIt.Value()); - } - aPX[i]=BRep_Tool::Pnt(aVD); - // - ++i; - ++aNbCE; - } - else if (aKNE==GEOMAlgo_KN_SEGMENT) { - if (BRep_Tool::IsClosed(aE, aF)) { - ++aNbSE; - } - } - else if (aKSE==GEOMAlgo_KS_DEGENERATED) { - aIt.Initialize(aE); - if (aIt.More()) { - aVD=*((TopoDS_Vertex*)&aIt.Value()); - } - // - aPD=BRep_Tool::Pnt(aVD); - // - ++aNbDE; - } - } - } - // - if ((aNbCE==2 || (aNbCE==1 && aNbDE==1)) && aNbSE==1) { - if (aNbDE==1) { - aPC[1]=aPD; - aR[1]=0.; - } - // - aHeight=aPC[0].Distance(aPC[1]); - // - - gp_Ax2 aAx2new; - // - if (aR[0]>aR[1]) { - aRmin=aR[1]; - aRmax=aR[0]; - aPc=aPC[0]; - gp_Vec aVz(aPC[0], aPC[1]); - gp_Vec aVx(aPC[0], aPX[0]); - gp_Dir aDz(aVz); - gp_Dir aDx(aVx); - gp_Ax2 aAx2(aPc, aDz, aDx); - aAx2new=aAx2; - } - else { - aRmin=aR[0]; - aRmax=aR[1]; - aPc=aPC[1]; - gp_Vec aVz(aPC[1], aPC[0]); - gp_Vec aVx(aPC[1], aPX[1]); - gp_Dir aDz(aVz); - gp_Dir aDx(aVx); - gp_Ax2 aAx2(aPc, aDz, aDx); - aAx2new=aAx2; - } - // - gp_Ax3 aAx3(aAx2new); - aInfo.SetLocation(aPc); - aInfo.SetPosition(aAx3); - aInfo.SetRadius1(aRmax); - aInfo.SetRadius2(aRmin); - aInfo.SetHeight(aHeight); - // - aInfo.SetKindOfDef(GEOMAlgo_KD_SPECIFIED); - return; - }//if ((aNbCE==2 || (aNbCE==1 && aNbDE==1)) && aNbSE==1) { - }//if (aNbV==2 && aNbE==3) { - // - aInfo.SetRadius1 (aCone.RefRadius()); - // - aRmin=0.; // ZZ - aInfo.SetRadius2(aRmin); -} -//======================================================================= -//function : FillDetails -//purpose : -//======================================================================= void GEOMAlgo_ShapeInfoFiller::FillDetails(const TopoDS_Face& aF, const gp_Torus& ) { - + Standard_Integer aNbV, aNbE, aNbSE; TopoDS_Edge aE; TopExp_Explorer aExp; @@ -764,10 +644,10 @@ void GEOMAlgo_ShapeInfoFiller::FillDetails(const TopoDS_Face& aF, if (aKS!=GEOMAlgo_KS_TORUS) { return; } - + aNbV=aInfo.NbSubShapes(TopAbs_VERTEX); - aNbE=aInfo.NbSubShapes(TopAbs_EDGE); - + aNbE=aInfo.NbSubShapes(TopAbs_EDGE); + if (aNbV==1 && aNbE==2) { aNbSE=0; aExp.Init(aF, TopAbs_EDGE);