From c9831764060f4ef248cc2316cd5919bd424c33b0 Mon Sep 17 00:00:00 2001 From: kgv Date: Tue, 21 Sep 2021 11:56:09 +0300 Subject: [PATCH] 0032580: Data Exchange, STL - add option splitting nodes at sharp corners Added Poly_MergeNodesTool tool for merging nodes within triangulation. Added RWStl_Reader::MergeAngle() property managing merging behavior. --- src/MeshTest/MeshTest.cxx | 153 +++++++++ src/Poly/FILES | 2 + src/Poly/Poly_MergeNodesTool.cxx | 486 ++++++++++++++++++++++++++++ src/Poly/Poly_MergeNodesTool.hxx | 358 ++++++++++++++++++++ src/RWStl/RWStl.cxx | 4 +- src/RWStl/RWStl.hxx | 16 +- src/RWStl/RWStl_Reader.cxx | 104 +++--- src/RWStl/RWStl_Reader.hxx | 23 ++ src/XSDRAWSTLVRML/XSDRAWSTLVRML.cxx | 34 +- tests/de_mesh/stl_read/B11 | 2 +- tests/de_mesh/stl_read/B13 | 2 +- tests/de_mesh/stl_read/B14 | 2 +- tests/de_mesh/stl_read/B6 | 2 +- tests/de_mesh/stl_read/C5 | 2 +- tests/de_mesh/stl_read/D2 | 29 ++ tests/de_mesh/stl_read/D3 | 33 ++ 16 files changed, 1190 insertions(+), 62 deletions(-) create mode 100644 src/Poly/Poly_MergeNodesTool.cxx create mode 100644 src/Poly/Poly_MergeNodesTool.hxx create mode 100644 tests/de_mesh/stl_read/D2 create mode 100644 tests/de_mesh/stl_read/D3 diff --git a/src/MeshTest/MeshTest.cxx b/src/MeshTest/MeshTest.cxx index 9a30ab38e6..234081dad9 100644 --- a/src/MeshTest/MeshTest.cxx +++ b/src/MeshTest/MeshTest.cxx @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -1431,6 +1432,150 @@ static Standard_Integer triedgepoints(Draw_Interpretor& di, Standard_Integer nba return 0; } +//======================================================================= +//function : TrMergeNodes +//purpose : +//======================================================================= +static Standard_Integer TrMergeNodes (Draw_Interpretor& theDI, Standard_Integer theNbArgs, const char** theArgVec) +{ + if (theNbArgs < 2) + { + theDI << "Syntax error: not enough arguments"; + return 1; + } + + TopoDS_Shape aShape = DBRep::Get (theArgVec[1]); + if (aShape.IsNull()) + { + theDI << "Syntax error: '" << theArgVec[1] << "' is not a shape"; + return 1; + } + + Standard_Real aMergeAngle = M_PI / 4.0, aMergeToler = 0.0; + bool toForce = false; + TCollection_AsciiString aResFace; + for (Standard_Integer anArgIter = 2; anArgIter < theNbArgs; ++anArgIter) + { + TCollection_AsciiString anArgCase (theArgVec[anArgIter]); + anArgCase.LowerCase(); + if (anArgIter + 1 < theNbArgs + && (anArgCase == "-angle" + || anArgCase == "-smoothangle" + || anArgCase == "-mergeangle") + && Draw::ParseReal (theArgVec[anArgIter + 1], aMergeAngle)) + { + if (aMergeAngle < 0.0 || aMergeAngle > 90.0) + { + theDI << "Syntax error: angle should be within [0,90] range"; + return 1; + } + + ++anArgIter; + aMergeAngle = aMergeAngle * M_PI / 180.0; + } + else if (anArgIter + 1 < theNbArgs + && anArgCase == "-tolerance" + && Draw::ParseReal (theArgVec[anArgIter + 1], aMergeToler)) + { + if (aMergeToler < 0.0) + { + theDI << "Syntax error: tolerance should be within >=0"; + return 1; + } + + ++anArgIter; + } + else if (anArgCase == "-force") + { + toForce = Draw::ParseOnOffIterator (theNbArgs, theArgVec, anArgIter); + } + else if (anArgIter + 1 < theNbArgs + && anArgCase == "-oneface") + { + aResFace = theArgVec[++anArgIter]; + } + else + { + theDI << "Syntax error at '" << theArgVec[anArgIter] << "'"; + return 1; + } + } + + Standard_Integer aNbNodesOld = 0, aNbTrisOld = 0; + Standard_Integer aNbNodesNew = 0, aNbTrisNew = 0; + if (!aResFace.IsEmpty()) + { + TopLoc_Location aFaceLoc; + Poly_MergeNodesTool aMergeTool (aMergeAngle, aMergeToler); + for (TopExp_Explorer aFaceIter (aShape, TopAbs_FACE); aFaceIter.More(); aFaceIter.Next()) + { + const TopoDS_Face& aFace = TopoDS::Face (aFaceIter.Value()); + Handle(Poly_Triangulation) aTris = BRep_Tool::Triangulation (aFace, aFaceLoc); + if (aTris.IsNull() + || aTris->NbNodes() < 3 + || aTris->NbTriangles() < 1) + { + continue; + } + + aNbNodesOld += aTris->NbNodes(); + aNbTrisOld += aTris->NbTriangles(); + aMergeTool.AddTriangulation (aTris, aFaceLoc, aFace.Orientation() == TopAbs_REVERSED); + } + Handle(Poly_Triangulation) aNewTris = aMergeTool.Result(); + if (aNewTris.IsNull()) + { + theDI << "Error: empty result"; + return 0; + } + + aNbNodesNew += aNewTris->NbNodes(); + aNbTrisNew += aNewTris->NbTriangles(); + TopoDS_Face aFace; + BRep_Builder().MakeFace (aFace, aNewTris); + DBRep::Set (aResFace.ToCString(), aFace); + } + else + { + TopTools_MapOfShape aProcessedFaces; + TopLoc_Location aDummy; + for (TopExp_Explorer aFaceIter (aShape, TopAbs_FACE); aFaceIter.More(); aFaceIter.Next()) + { + const TopoDS_Face& aFace = TopoDS::Face (aFaceIter.Value()); + if (!aProcessedFaces.Add (aFace.Located (TopLoc_Location()))) + { + continue; + } + + Handle(Poly_Triangulation) aTris = BRep_Tool::Triangulation (aFace, aDummy); + if (aTris.IsNull() + || aTris->NbNodes() < 3 + || aTris->NbTriangles() < 1) + { + continue; + } + + aNbNodesOld += aTris->NbNodes(); + aNbTrisOld += aTris->NbTriangles(); + Poly_MergeNodesTool aMergeTool (aMergeAngle, aMergeToler, aTris->NbTriangles()); + aMergeTool.AddTriangulation (aTris); + if (toForce + || aMergeTool.NbNodes() != aTris->NbNodes() + || aMergeTool.NbElements() != aTris->NbTriangles()) + { + BRep_Builder().UpdateFace (aFace, aMergeTool.Result(), false); + } + + aTris = BRep_Tool::Triangulation (aFace, aDummy); + aNbNodesNew += aTris->NbNodes(); + aNbTrisNew += aTris->NbTriangles(); + } + } + theDI << "Old, Triangles: " << aNbTrisOld << ", Nodes: " << aNbNodesOld << "\n"; + theDI << "New, Triangles: " << aNbTrisNew << ", Nodes: " << aNbNodesNew << "\n"; + return 0; +} + //======================================================================= //function : correctnormals //purpose : Corrects normals in shape triangulation nodes (...) @@ -1499,5 +1644,13 @@ void MeshTest::Commands(Draw_Interpretor& theCommands) "\n\t\t: '-loadSingleExact' - make loaded and active ONLY exactly specified triangulation. All other triangulations" "\n\t\t: will be unloaded. If triangulation with such Index doesn't exist do nothing", __FILE__, TrLateLoad, g); + theCommands.Add("trmergenodes", + "trmergenodes shapeName" + "\n\t\t: [-angle Angle] [-tolerance Value] [-oneFace Result]" + "\n\t\t: Merging nodes within triangulation data." + "\n\t\t: -angle merge angle upper limit in degrees; 45 when unspecified" + "\n\t\t: -tolerance linear tolerance to merge nodes; 0.0 when unspecified" + "\n\t\t: -oneFace create a new single Face with specified name for the whole triangulation", + __FILE__, TrMergeNodes, g); theCommands.Add("correctnormals", "correctnormals shape",__FILE__, correctnormals, g); } diff --git a/src/Poly/FILES b/src/Poly/FILES index ca4cdf3de8..46d643c57a 100755 --- a/src/Poly/FILES +++ b/src/Poly/FILES @@ -22,6 +22,8 @@ Poly_ListOfTriangulation.hxx Poly_MakeLoops.cxx Poly_MakeLoops.hxx Poly_MeshPurpose.hxx +Poly_MergeNodesTool.cxx +Poly_MergeNodesTool.hxx Poly_Polygon2D.cxx Poly_Polygon2D.hxx Poly_Polygon3D.cxx diff --git a/src/Poly/Poly_MergeNodesTool.cxx b/src/Poly/Poly_MergeNodesTool.cxx new file mode 100644 index 0000000000..40778ad17f --- /dev/null +++ b/src/Poly/Poly_MergeNodesTool.cxx @@ -0,0 +1,486 @@ +// Copyright (c) 2015-2021 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include + +#include + +namespace +{ + //! Returns initial number of buckets for the map. + static int initialNbBuckets (int theNbFacets) + { + return theNbFacets > 0 + ? theNbFacets * 2 // consider ratio 1:2 (NbTriangles:MergedNodes) as expected + : 995329; // default initial value for mesh of unknown size + } +} + +IMPLEMENT_STANDARD_RTTIEXT(Poly_MergeNodesTool, Standard_Transient) + +//! Map node. +class Poly_MergeNodesTool::MergedNodesMap::DataMapNode : public NCollection_TListNode +{ +public: + //! Constructor. + DataMapNode (const NCollection_Vec3& thePos, + const NCollection_Vec3& theNorm, + int theItem, NCollection_ListNode* theNext) + : NCollection_TListNode (theItem, theNext), myKey (thePos, theNorm) {} + + //! Key. + const Poly_MergeNodesTool::Vec3AndNormal& Key() const { return myKey; } + + //! Static deleter to be passed to BaseMap + static void delNode (NCollection_ListNode* theNode, Handle(NCollection_BaseAllocator)& theAl) + { + ((DataMapNode* )theNode)->~DataMapNode(); + theAl->Free (theNode); + } + +private: + Poly_MergeNodesTool::Vec3AndNormal myKey; +}; + +// ======================================================================= +// function : MergedNodesMap +// purpose : +// ======================================================================= +Poly_MergeNodesTool::MergedNodesMap::MergedNodesMap (const int theNbBuckets) +: NCollection_BaseMap (theNbBuckets, true, new NCollection_IncAllocator()), + myTolerance(0.0f), + myInvTol (0.0f), + myAngle (1.0f), + myAngleCos (0.0f), + myToMergeOpposite (false) +{ + // +} + +// ======================================================================= +// function : MergedNodesMap::SetMergeTolerance +// purpose : +// ======================================================================= +void Poly_MergeNodesTool::MergedNodesMap::SetMergeTolerance (double theTolerance) +{ + myTolerance = (float )theTolerance; + myInvTol = 0.0f; + if (myTolerance > 0.0f) + { + myInvTol = float(1.0 / theTolerance); + } +} + +// ======================================================================= +// function : MergedNodesMap::hashCode +// purpose : +// ======================================================================= +inline int Poly_MergeNodesTool::MergedNodesMap::vec3iHashCode (const Poly_MergeNodesTool::MergedNodesMap::CellVec3i& theVec, + const int theUpper) +{ + // copied from NCollection_CellFilter + const uint64_t aShiftBits = (BITS(int64_t)-1) / 3; + uint64_t aHashCode = 0; + aHashCode = (aHashCode << aShiftBits) ^ theVec[0]; + aHashCode = (aHashCode << aShiftBits) ^ theVec[1]; + aHashCode = (aHashCode << aShiftBits) ^ theVec[2]; + return IntegerHashCode(aHashCode, 0x7fffffffffffffff, theUpper); +} + +// ======================================================================= +// function : MergedNodesMap::hashCode +// purpose : +// ======================================================================= +inline int Poly_MergeNodesTool::MergedNodesMap::hashCode (const NCollection_Vec3& thePos, + const NCollection_Vec3& theNorm, + const int theUpper) const +{ + (void )theNorm; + if (myInvTol <= 0.0f) + { + return ::HashCode (::HashCodes ((Standard_CString )&thePos, sizeof(NCollection_Vec3)), theUpper); + } + + const CellVec3i anIndex = vec3ToCell (thePos); + return vec3iHashCode (anIndex, theUpper); +} + +// ======================================================================= +// function : MergedNodesMap::vec3AreEqual +// purpose : +// ======================================================================= +inline bool Poly_MergeNodesTool::MergedNodesMap::vec3AreEqual (const NCollection_Vec3& theKey1, + const NCollection_Vec3& theKey2) const +{ + if (myInvTol <= 0.0f) + { + return theKey1.IsEqual (theKey2); + } + + /// tolerance should be smaller than triangle size to avoid artifacts + //const CellVec3i anIndex1 = vec3ToCell (theKey1); + //const CellVec3i anIndex2 = vec3ToCell (theKey2); + //return anIndex1.IsEqual (anIndex2); + + float aVal = theKey1.x() - theKey2.x(); + if (aVal < 0) { aVal = -aVal; } + if (aVal > myTolerance) { return false; } + aVal = theKey1.y() - theKey2.y(); + if (aVal < 0) { aVal = -aVal; } + if (aVal > myTolerance) { return false; } + aVal = theKey1.z() - theKey2.z(); + if (aVal < 0) { aVal = -aVal; } + if (aVal > myTolerance) { return false; } + return true; +} + +// ======================================================================= +// function : MergedNodesMap::isEqual +// purpose : +// ======================================================================= +inline bool Poly_MergeNodesTool::MergedNodesMap::isEqual (const Vec3AndNormal& theKey1, + const NCollection_Vec3& thePos2, + const NCollection_Vec3& theNorm2, + bool& theIsOpposite) const +{ + if (!vec3AreEqual (theKey1.Pos, thePos2)) + { + return false; + } + + const float aCosinus = theKey1.Norm.Dot (theNorm2); + if (aCosinus >= myAngleCos) + { + //theIsOpposite = false; + return true; + } + else if (myToMergeOpposite + && aCosinus <= -myAngleCos) + { + theIsOpposite = true; + return true; + } + return false; +} + +// ======================================================================= +// function : MergedNodesMap::Bind +// purpose : +// ======================================================================= +inline bool Poly_MergeNodesTool::MergedNodesMap::Bind (int& theIndex, + bool& theIsOpposite, + const NCollection_Vec3& thePos, + const NCollection_Vec3& theNorm) +{ + if (Resizable()) + { + ReSize (Extent()); + } + + DataMapNode** aData = (DataMapNode** )myData1; + const int aHash = hashCode (thePos, theNorm, NbBuckets()); + for (DataMapNode* aNodeIter = aData[aHash]; aNodeIter != NULL; + aNodeIter = (DataMapNode* )aNodeIter->Next()) + { + if (isEqual (aNodeIter->Key(), thePos, theNorm, theIsOpposite)) + { + theIndex = aNodeIter->ChangeValue(); + return false; + } + } + if (myInvTol > 0.0f) + { + static const CellVec3i THE_NEIGHBRS[26] = + { + CellVec3i(-1, 0, 0),CellVec3i( 1, 0, 0),CellVec3i( 0,-1, 0),CellVec3i( 0, 1, 0),CellVec3i( 0, 0,-1),CellVec3i( 0, 0, 1), + CellVec3i(-1,-1, 0),CellVec3i( 1,-1, 0),CellVec3i( 1, 1, 0),CellVec3i(-1, 1, 0), + CellVec3i( 0,-1,-1),CellVec3i( 0, 1,-1),CellVec3i( 0, 1, 1),CellVec3i( 0,-1, 1), + CellVec3i(-1, 0,-1),CellVec3i( 1, 0,-1),CellVec3i( 1, 0, 1),CellVec3i(-1, 0, 1), + CellVec3i(-1,-1,-1),CellVec3i( 1,-1,-1),CellVec3i(-1, 1,-1),CellVec3i( 1, 1,-1),CellVec3i(-1,-1, 1),CellVec3i( 1,-1, 1),CellVec3i(-1, 1, 1),CellVec3i(1, 1, 1) + }; + const CellVec3i anIndexCnt = vec3ToCell (thePos); + for (int aNeigIter = 0; aNeigIter < 26; ++aNeigIter) + { + const CellVec3i anIndex = anIndexCnt + THE_NEIGHBRS[aNeigIter]; + const int aHashEx = vec3iHashCode (anIndex, NbBuckets()); + for (DataMapNode* aNodeIter = aData[aHashEx]; aNodeIter != NULL; + aNodeIter = (DataMapNode* )aNodeIter->Next()) + { + if (isEqual (aNodeIter->Key(), thePos, theNorm, theIsOpposite)) + { + theIndex = aNodeIter->ChangeValue(); + return false; + } + } + } + } + //theIsOpposite = false; + aData[aHash] = new (this->myAllocator) DataMapNode (thePos, theNorm, theIndex, aData[aHash]); + Increment(); + return true; +} + +// ======================================================================= +// function : MergedNodesMap::ReSize +// purpose : +// ======================================================================= +inline void Poly_MergeNodesTool::MergedNodesMap::ReSize (const int theSize) +{ + NCollection_ListNode** aNewData = NULL; + NCollection_ListNode** aDummy = NULL; + int aNbNewBuck = 0; + if (BeginResize (theSize, aNbNewBuck, aNewData, aDummy)) + { + if (DataMapNode** anOldData = (DataMapNode** )myData1) + { + for (int anOldBuckIter = 0; anOldBuckIter <= NbBuckets(); ++anOldBuckIter) + { + for (DataMapNode* anOldNodeIter = anOldData[anOldBuckIter]; anOldNodeIter != NULL; ) + { + const Standard_Integer aNewHash = hashCode (anOldNodeIter->Key(), aNbNewBuck); + DataMapNode* aNextNode = (DataMapNode* )anOldNodeIter->Next(); + anOldNodeIter->Next() = aNewData[aNewHash]; + aNewData[aNewHash] = anOldNodeIter; + anOldNodeIter = aNextNode; + } + } + } + EndResize (theSize, aNbNewBuck, aNewData, aDummy); + } +} + +// ======================================================================= +// function : Poly_MergeNodesTool +// purpose : +// ======================================================================= +Poly_MergeNodesTool::Poly_MergeNodesTool (const double theSmoothAngle, + const double theMergeTolerance, + const int theNbFacets) +: myPolyData (new Poly_Triangulation()), + myNodeIndexMap ((theSmoothAngle > 0.0 + || theMergeTolerance > 0.0) + ? initialNbBuckets (theNbFacets) + : 1), + myNodeInds (0, 0, 0, -1), + myTriNormal (0.0f, 0.0f, 1.0f), + myUnitFactor (1.0), + myNbNodes (0), + myNbElems (0), + myNbDegenElems (0), + myNbMergedElems (0), + myToDropDegenerative (true), + myToMergeElems (false) +{ + SetMergeAngle (theSmoothAngle); + SetMergeTolerance (theMergeTolerance); +} + +// ======================================================================= +// function : AddElement +// purpose : +// ======================================================================= +void Poly_MergeNodesTool::AddElement (const gp_XYZ* theElemNodes, + int theNbNodes) +{ + if (theNbNodes != 3 + && theNbNodes != 4) + { + throw Standard_ProgramError ("Poly_MergeNodesTool::AddElement() - Internal error"); + } + + myPlaces[0] = theElemNodes[0]; + myPlaces[1] = theElemNodes[1]; + myPlaces[2] = theElemNodes[2]; + if (theNbNodes == 4) + { + myPlaces[3] = theElemNodes[3]; + } + PushLastElement (theNbNodes); +} + +// ======================================================================= +// function : PushLastElement +// purpose : +// ======================================================================= +void Poly_MergeNodesTool::PushLastElement (int theNbNodes) +{ + if (theNbNodes != 3 + && theNbNodes != 4) + { + throw Standard_ProgramError ("Poly_MergeNodesTool::PushLastElement() - Internal error"); + } + + bool isOpposite = false; + myNodeInds[3] = -1; + if (myNodeIndexMap.HasMergeAngle() + || myNodeIndexMap.HasMergeTolerance()) + { + if (!myNodeIndexMap.ToMergeAnyAngle()) + { + myTriNormal = computeTriNormal(); + } + + pushNodeCheck (isOpposite, 0); + pushNodeCheck (isOpposite, 1); + pushNodeCheck (isOpposite, 2); + if (theNbNodes == 4) + { + pushNodeCheck (isOpposite, 3); + } + } + else + { + pushNodeNoMerge (0); + pushNodeNoMerge (1); + pushNodeNoMerge (2); + if (theNbNodes == 4) + { + pushNodeNoMerge (3); + } + } + + if (myToDropDegenerative) + { + // warning - removing degenerate elements may produce unused nodes + if (myNodeInds[0] == myNodeInds[1] + || myNodeInds[0] == myNodeInds[2] + || myNodeInds[1] == myNodeInds[2]) + { + if (theNbNodes == 4) + { + // + } + else + { + ++myNbDegenElems; + return; + } + } + } + + if (myToMergeElems) + { + NCollection_Vec4 aSorted = myNodeInds; + std::sort (aSorted.ChangeData(), aSorted.ChangeData() + theNbNodes); + if (!myElemMap.Add (aSorted)) + { + ++myNbMergedElems; + return; + } + } + + ++myNbElems; + if (!myPolyData.IsNull()) + { + if (myPolyData->NbTriangles() < myNbElems) + { + myPolyData->ResizeTriangles (myNbElems * 2, true); + } + myPolyData->SetTriangle (myNbElems, Poly_Triangle (myNodeInds[0] + 1, myNodeInds[1] + 1, myNodeInds[2] + 1)); + if (theNbNodes == 4) + { + ++myNbElems; + if (myPolyData->NbTriangles() < myNbElems) + { + myPolyData->ResizeTriangles (myNbElems * 2, true); + } + myPolyData->SetTriangle (myNbElems, Poly_Triangle (myNodeInds[0] + 1, myNodeInds[2] + 1, myNodeInds[3] + 1)); + } + } +} + +// ======================================================================= +// function : AddTriangulation +// purpose : +// ======================================================================= +void Poly_MergeNodesTool::AddTriangulation (const Handle(Poly_Triangulation)& theTris, + const gp_Trsf& theTrsf, + const Standard_Boolean theToReverse) +{ + if (theTris.IsNull()) + { + return; + } + + if (!myPolyData.IsNull() + && myPolyData->NbNodes() == 0) + { + // preallocate optimistically + myPolyData->SetDoublePrecision (theTris->IsDoublePrecision()); + myPolyData->ResizeNodes (theTris->NbNodes(), false); + myPolyData->ResizeTriangles (theTris->NbTriangles(), false); + } + + for (int anElemIter = 1; anElemIter <= theTris->NbTriangles(); ++anElemIter) + { + Poly_Triangle anElem = theTris->Triangle (anElemIter); + if (theToReverse) + { + anElem = Poly_Triangle (anElem.Value (1), anElem.Value (3), anElem.Value (2)); + } + for (int aTriNodeIter = 0; aTriNodeIter < 3; ++aTriNodeIter) + { + const gp_Pnt aNode = theTris->Node (anElem.Value (aTriNodeIter + 1)).Transformed (theTrsf); + myPlaces[aTriNodeIter] = aNode.XYZ(); + } + PushLastTriangle(); + } +} + +// ======================================================================= +// function : Result +// purpose : +// ======================================================================= +Handle(Poly_Triangulation) Poly_MergeNodesTool::Result() +{ + if (myPolyData.IsNull()) + { + return Handle(Poly_Triangulation)(); + } + + // compress data + myPolyData->ResizeNodes (myNbNodes, true); + myPolyData->ResizeTriangles(myNbElems, true); + return myPolyData; +} + +// ======================================================================= +// function : MergeNodes +// purpose : +// ======================================================================= +Handle(Poly_Triangulation) Poly_MergeNodesTool::MergeNodes (const Handle(Poly_Triangulation)& theTris, + const gp_Trsf& theTrsf, + const Standard_Boolean theToReverse, + const double theSmoothAngle, + const double theMergeTolerance, + const bool theToForce) +{ + if (theTris.IsNull() + || theTris->NbNodes() < 3 + || theTris->NbTriangles() < 1) + { + return Handle(Poly_Triangulation)(); + } + + Poly_MergeNodesTool aMergeTool (theSmoothAngle, theMergeTolerance, theTris->NbTriangles()); + aMergeTool.AddTriangulation (theTris, theTrsf, theToReverse); + if (!theToForce + && aMergeTool.NbNodes() == theTris->NbNodes() + && aMergeTool.NbElements() == theTris->NbTriangles()) + { + return Handle(Poly_Triangulation)(); + } + return aMergeTool.Result(); +} diff --git a/src/Poly/Poly_MergeNodesTool.hxx b/src/Poly/Poly_MergeNodesTool.hxx new file mode 100644 index 0000000000..6c3cae76ef --- /dev/null +++ b/src/Poly/Poly_MergeNodesTool.hxx @@ -0,0 +1,358 @@ +// Copyright (c) 2015-2021 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _Poly_MergeNodesTool_HeaderFile +#define _Poly_MergeNodesTool_HeaderFile + +#include +#include +#include +#include + +//! Auxiliary tool for merging triangulation nodes for visualization purposes. +//! Tool tries to merge all nodes within input triangulation, but split the ones on sharp corners at specified angle. +class Poly_MergeNodesTool : public Standard_Transient +{ + DEFINE_STANDARD_RTTIEXT(Poly_MergeNodesTool, Standard_Transient) +public: + + //! Merge nodes of existing mesh and return the new mesh. + //! @param[in] theTris triangulation to add + //! @param[in] theTrsf transformation to apply + //! @param[in] theToReverse reverse triangle nodes order + //! @param[in] theSmoothAngle merge angle in radians + //! @param[in] theMergeTolerance linear merge tolerance + //! @param[in] theToForce return merged triangulation even if it's statistics is equal to input one + //! @return merged triangulation or NULL on no result + Standard_EXPORT static Handle(Poly_Triangulation) MergeNodes (const Handle(Poly_Triangulation)& theTris, + const gp_Trsf& theTrsf, + const Standard_Boolean theToReverse, + const double theSmoothAngle, + const double theMergeTolerance = 0.0, + const bool theToForce = true); + +public: + + //! Constructor + //! @param[in] theSmoothAngle smooth angle in radians or 0.0 to disable merging by angle + //! @param[in] theMergeTolerance node merging maximum distance + //! @param[in] theNbFacets estimated number of facets for map preallocation + Standard_EXPORT Poly_MergeNodesTool (const double theSmoothAngle, + const double theMergeTolerance = 0.0, + const int theNbFacets = -1); + + //! Return merge tolerance; 0.0 by default (only 3D points with exactly matching coordinates are merged). + double MergeTolerance() const { return myNodeIndexMap.MergeTolerance(); } + + //! Set merge tolerance. + void SetMergeTolerance (double theTolerance) { myNodeIndexMap.SetMergeTolerance (theTolerance); } + + //! Return merge angle in radians; 0.0 by default (normals with non-exact directions are not merged). + double MergeAngle() const { return myNodeIndexMap.MergeAngle(); } + + //! Set merge angle. + void SetMergeAngle (double theAngleRad) { myNodeIndexMap.SetMergeAngle (theAngleRad); } + + //! Return TRUE if nodes with opposite normals should be merged; FALSE by default. + bool ToMergeOpposite() const { return myNodeIndexMap.ToMergeOpposite(); } + + //! Set if nodes with opposite normals should be merged. + void SetMergeOpposite (bool theToMerge) { myNodeIndexMap.SetMergeOpposite (theToMerge); } + + //! Setup unit factor. + void SetUnitFactor (double theUnitFactor) { myUnitFactor = theUnitFactor; } + + //! Return TRUE if degenerate elements should be discarded; TRUE by default. + bool ToDropDegenerative() const { return myToDropDegenerative; } + + //! Set if degenerate elements should be discarded. + void SetDropDegenerative (bool theToDrop) { myToDropDegenerative = theToDrop; } + + //! Return TRUE if equal elements should be filtered; FALSE by default. + bool ToMergeElems() const { return myToMergeElems; } + + //! Set if equal elements should be filtered. + void SetMergeElems (bool theToMerge) { myToMergeElems = theToMerge; } + + //! Compute normal for the mesh element. + NCollection_Vec3 computeTriNormal() const + { + const gp_XYZ aVec01 = myPlaces[1] - myPlaces[0]; + const gp_XYZ aVec02 = myPlaces[2] - myPlaces[0]; + const gp_XYZ aCross = aVec01 ^ aVec02; + NCollection_Vec3 aNorm ((float )aCross.X(), (float )aCross.Y(), (float )aCross.Z()); + return aNorm.Normalized(); + } + +public: + + //! Add another triangulation to created one. + //! @param[in] theTris triangulation to add + //! @param[in] theTrsf transformation to apply + //! @param[in] theToReverse reverse triangle nodes order + Standard_EXPORT virtual void AddTriangulation (const Handle(Poly_Triangulation)& theTris, + const gp_Trsf& theTrsf = gp_Trsf(), + const Standard_Boolean theToReverse = false); + + //! Prepare and return result triangulation (temporary data will be truncated to result size). + Standard_EXPORT Handle(Poly_Triangulation) Result(); + +public: + + //! Add new triangle. + //! @param[in] theElemNodes 3 element nodes + void AddTriangle (const gp_XYZ theElemNodes[3]) + { + AddElement (theElemNodes, 3); + } + + //! Add new quad. + //! @param[in] theElemNodes 4 element nodes + void AddQuad (const gp_XYZ theElemNodes[4]) + { + AddElement (theElemNodes, 4); + } + + //! Add new triangle or quad. + //! @param[in] theElemNodes element nodes + //! @param[in] theNbNodes number of element nodes, should be 3 or 4 + Standard_EXPORT void AddElement (const gp_XYZ* theElemNodes, + int theNbNodes); + + //! Change node coordinates of element to be pushed. + //! @param[in] theIndex node index within current element, in 0..3 range + gp_XYZ& ChangeElementNode (int theIndex) { return myPlaces[theIndex]; } + + //! Add new triangle or quad with nodes specified by ChangeElementNode(). + Standard_EXPORT void PushLastElement (int theNbNodes); + + //! Add new triangle with nodes specified by ChangeElementNode(). + void PushLastTriangle() { PushLastElement (3); } + + //! Add new quad with nodes specified by ChangeElementNode(). + void PushLastQuad() { PushLastElement (4); } + + //! Return current element node index defined by PushLastElement(). + Standard_Integer ElementNodeIndex (int theIndex) const { return myNodeInds[theIndex]; } + + //! Return number of nodes. + int NbNodes() const { return myNbNodes; } + + //! Return number of elements. + int NbElements() const { return myNbElems; } + + //! Return number of discarded degenerate elements. + int NbDegenerativeElems() const { return myNbDegenElems; } + + //! Return number of merged equal elements. + int NbMergedElems() const { return myNbMergedElems; } + + //! Setup output triangulation for modifications. + //! When set to NULL, the tool could be used as a merge map for filling in external mesh structure. + Handle(Poly_Triangulation)& ChangeOutput() { return myPolyData; } + +private: + + //! Push triangle node with normal angle comparison. + void pushNodeCheck (bool& theIsOpposite, + const int theTriNode) + { + int aNodeIndex = myNbNodes; + const gp_XYZ& aPlace = myPlaces[theTriNode]; + const NCollection_Vec3 aVec3 ((float )aPlace.X(), (float )aPlace.Y(), (float )aPlace.Z()); + if (myNodeIndexMap.Bind (aNodeIndex, theIsOpposite, aVec3, myTriNormal)) + { + ++myNbNodes; + if (!myPolyData.IsNull()) + { + if (myPolyData->NbNodes() < myNbNodes) + { + myPolyData->ResizeNodes (myNbNodes * 2, true); + } + myPolyData->SetNode (myNbNodes, aPlace * myUnitFactor); + } + } + myNodeInds[theTriNode] = aNodeIndex; + } + + //! Push triangle node without merging vertices. + inline void pushNodeNoMerge (const int theTriNode) + { + int aNodeIndex = myNbNodes; + const gp_XYZ aPlace = myPlaces[theTriNode] * myUnitFactor; + + ++myNbNodes; + if (!myPolyData.IsNull()) + { + if (myPolyData->NbNodes() < myNbNodes) + { + myPolyData->ResizeNodes (myNbNodes * 2, true); + } + myPolyData->SetNode (myNbNodes, aPlace); + } + + myNodeInds[theTriNode] = aNodeIndex; + } + +private: + + //! Pair holding Vec3 and Normal to the triangle + struct Vec3AndNormal + { + NCollection_Vec3 Pos; //!< position + NCollection_Vec3 Norm; //!< normal to the element + + Vec3AndNormal (const NCollection_Vec3& thePos, + const NCollection_Vec3& theNorm) + : Pos (thePos), Norm (theNorm) {} + }; + + //! Custom map class with key as Node + element normal and value as Node index. + //! NCollection_DataMap is not used, as it requires Hasher to be defined as class template and not class field. + class MergedNodesMap : public NCollection_BaseMap + { + public: + typedef NCollection_Vec3 CellVec3i; + public: + //! Main constructor. + Standard_EXPORT MergedNodesMap (const int theNbBuckets); + + //! Return merge angle in radians; + double MergeAngle() const { return myAngle; } + + //! Set merge angle. + void SetMergeAngle (double theAngleRad) + { + myAngle = (float )theAngleRad; + myAngleCos = (float )Cos (theAngleRad); + } + + //! Return TRUE if merge angle is non-zero. + //! 0 angle means angles should much without a tolerance. + bool HasMergeAngle() const { return myAngle > 0.0f; } + + //! Return TRUE if merge angle comparison can be skipped (angle is close to 90 degrees). + bool ToMergeAnyAngle() const { return myAngleCos <= 0.01f; } + + //! Return TRUE if nodes with opposite normals should be merged; FALSE by default. + bool ToMergeOpposite() const { return myToMergeOpposite; } + + //! Set if nodes with opposite normals should be merged. + void SetMergeOpposite (bool theToMerge) { myToMergeOpposite = theToMerge; } + + //! Return merge tolerance. + double MergeTolerance() const { return myTolerance; } + + //! Set merge tolerance. + Standard_EXPORT void SetMergeTolerance (double theTolerance); + + //! Return TRUE if merge tolerance is non-zero. + bool HasMergeTolerance() const { return myTolerance > 0.0f; } + + //! Bind node to the map or find existing one. + //! @param theIndex [in,out] index of new key to add, or index of existing key, if already bound + //! @param theIsOpposite [out] flag indicating that existing (already bound) node has opposite direction + //! @param thePos [in] node position to add or find + //! @param theNorm [in] element normal for equality check + //! @return TRUE if node was not bound already + Standard_EXPORT bool Bind (int& theIndex, + bool& theIsOpposite, + const NCollection_Vec3& thePos, + const NCollection_Vec3& theNorm); + + //! ReSize the map. + Standard_EXPORT void ReSize (const int theSize); + + private: + + //! Return cell index for specified 3D point and inverted cell size. + CellVec3i vec3ToCell (const NCollection_Vec3& thePnt) const + { + return CellVec3i (thePnt * myInvTol); + } + + //! Hash code for integer vec3. + Standard_EXPORT static int vec3iHashCode (const Poly_MergeNodesTool::MergedNodesMap::CellVec3i& theVec, + const int theUpper); + + //! Compute hash code. + Standard_EXPORT int hashCode (const NCollection_Vec3& thePos, + const NCollection_Vec3& theNorm, + const int theUpper) const; + + //! Compute hash code. + int hashCode (const Vec3AndNormal& theKey, const int theUpper) const + { + return hashCode (theKey.Pos, theKey.Norm, theUpper); + } + + //! Compare two vectors with inversed tolerance. + Standard_EXPORT bool vec3AreEqual (const NCollection_Vec3& theKey1, + const NCollection_Vec3& theKey2) const; + + //! Compare two nodes. + Standard_EXPORT bool isEqual (const Vec3AndNormal& theKey1, + const NCollection_Vec3& thePos2, + const NCollection_Vec3& theNorm2, + bool& theIsOpposite) const; + private: + //! Map node. + class DataMapNode; + private: + float myTolerance; //!< linear tolerance for comparison + float myInvTol; //!< inversed linear tolerance for comparison + float myAngle; //!< angle for comparison + float myAngleCos; //!< angle cosine for comparison + bool myToMergeOpposite; //!< merge nodes with opposite normals + }; + + //! Hasher for merging equal elements (with pre-sorted indexes). + struct MergedElemHasher + { + static int HashCode (const NCollection_Vec4& theVec, const int theUpper) + { + unsigned int aHashCode = 0; + aHashCode = aHashCode ^ ::HashCode (theVec[0], theUpper); + aHashCode = aHashCode ^ ::HashCode (theVec[1], theUpper); + aHashCode = aHashCode ^ ::HashCode (theVec[2], theUpper); + aHashCode = aHashCode ^ ::HashCode (theVec[3], theUpper); + return ((aHashCode & 0x7fffffff) % theUpper) + 1; + } + + static bool IsEqual (const NCollection_Vec4& theKey1, const NCollection_Vec4& theKey2) + { + return theKey1.IsEqual (theKey2); + } + }; + +private: + + Handle(Poly_Triangulation) myPolyData; //!< output triangulation + MergedNodesMap myNodeIndexMap; //!< map of merged nodes + NCollection_Map, MergedElemHasher> + myElemMap; //!< map of elements + NCollection_Vec4 myNodeInds; //!< current element indexes + NCollection_Vec3 myTriNormal; //!< current triangle normal + gp_XYZ myPlaces[4]; //!< current triangle/quad coordinates to push + + Standard_Real myUnitFactor; //!< scale factor to apply + Standard_Integer myNbNodes; //!< number of output nodes + Standard_Integer myNbElems; //!< number of output elements + Standard_Integer myNbDegenElems; //!< number of degenerated elements + Standard_Integer myNbMergedElems; //!< number of merged elements + Standard_Boolean myToDropDegenerative; //!< flag to filter our degenerate elements + Standard_Boolean myToMergeElems; //!< flag to merge elements + +}; + +#endif // _Poly_MergeNodesTool_HeaderFile diff --git a/src/RWStl/RWStl.cxx b/src/RWStl/RWStl.cxx index c830d7715c..7febb29540 100644 --- a/src/RWStl/RWStl.cxx +++ b/src/RWStl/RWStl.cxx @@ -107,13 +107,15 @@ namespace } //============================================================================= -//function : Read +//function : ReadFile //purpose : //============================================================================= Handle(Poly_Triangulation) RWStl::ReadFile (const Standard_CString theFile, + const Standard_Real theMergeAngle, const Message_ProgressRange& theProgress) { Reader aReader; + aReader.SetMergeAngle (theMergeAngle); aReader.Read (theFile, theProgress); // note that returned bool value is ignored intentionally -- even if something went wrong, // but some data have been read, we at least will return these data diff --git a/src/RWStl/RWStl.hxx b/src/RWStl/RWStl.hxx index 3fc8043ebd..d513ce1a69 100644 --- a/src/RWStl/RWStl.hxx +++ b/src/RWStl/RWStl.hxx @@ -43,12 +43,24 @@ public: //! Read specified STL file and returns its content as triangulation. //! In case of error, returns Null handle. Standard_EXPORT static Handle(Poly_Triangulation) ReadFile (const OSD_Path& theFile, - const Message_ProgressRange& aProgInd = Message_ProgressRange()); + const Message_ProgressRange& theProgress = Message_ProgressRange()); //! Read specified STL file and returns its content as triangulation. //! In case of error, returns Null handle. + static Handle(Poly_Triangulation) ReadFile (const Standard_CString theFile, + const Message_ProgressRange& theProgress = Message_ProgressRange()) + { + return ReadFile (theFile, M_PI / 2.0, theProgress); + } + + //! Read specified STL file and returns its content as triangulation. + //! @param[in] theFile file path to read + //! @param[in] theMergeAngle maximum angle in radians between triangles to merge equal nodes; M_PI/2 means ignore angle + //! @param[in] theProgress progress indicator + //! @return result triangulation or NULL in case of error Standard_EXPORT static Handle(Poly_Triangulation) ReadFile (const Standard_CString theFile, - const Message_ProgressRange& aProgInd = Message_ProgressRange()); + const Standard_Real theMergeAngle, + const Message_ProgressRange& theProgress = Message_ProgressRange()); //! Read triangulation from a binary STL file //! In case of error, returns Null handle. diff --git a/src/RWStl/RWStl_Reader.cxx b/src/RWStl/RWStl_Reader.cxx index 69a70dcc3b..85dc4f7b4d 100644 --- a/src/RWStl/RWStl_Reader.cxx +++ b/src/RWStl/RWStl_Reader.cxx @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -43,53 +44,50 @@ namespace static const size_t THE_BUFFER_SIZE = 1024; //! Auxiliary tool for merging nodes during STL reading. - class MergeNodeTool + class MergeNodeTool : public Poly_MergeNodesTool { public: //! Constructor - MergeNodeTool (RWStl_Reader* theReader) - : myReader (theReader), - myMap (1024, new NCollection_IncAllocator (1024 * 1024)) + MergeNodeTool (RWStl_Reader* theReader, + const Standard_Integer theNbFacets = -1) + : Poly_MergeNodesTool (theReader->MergeAngle(), 0.0, theNbFacets), + myReader (theReader), + myNodeIndexMap (1024, new NCollection_IncAllocator (1024 * 1024)) { + // avoid redundant allocations as final triangulation is managed by RWStl_Reader subclass + ChangeOutput().Nullify(); } //! Add new triangle - int AddNode (double theX, double theY, double theZ) + void AddTriangle (const gp_XYZ theElemNodes[3]) { - // use existing node if found at the same point - gp_XYZ aPnt (theX, theY, theZ); + Poly_MergeNodesTool::AddTriangle (theElemNodes); - Standard_Integer anIndex = -1; - if (myMap.Find (aPnt, anIndex)) + // remap node indices returned by RWStl_Reader::AddNode(); + // this is a waste of time for most cases of sequential index adding, but preserved for keeping RWStl_Reader interface + int aNodesSrc[3] = { ElementNodeIndex (0), ElementNodeIndex (1), ElementNodeIndex (2) }; + int aNodesRes[3] = { -1, -1, -1 }; + for (int aNodeIter = 0; aNodeIter < 3; ++aNodeIter) { - return anIndex; + // use existing node if found at the same point + if (!myNodeIndexMap.Find (aNodesSrc[aNodeIter], aNodesRes[aNodeIter])) + { + aNodesRes[aNodeIter] = myReader->AddNode (theElemNodes[aNodeIter]); + myNodeIndexMap.Bind (aNodesSrc[aNodeIter], aNodesRes[aNodeIter]); + } + } + if (aNodesRes[0] != aNodesRes[1] + && aNodesRes[1] != aNodesRes[2] + && aNodesRes[2] != aNodesRes[0]) + { + myReader->AddTriangle (aNodesRes[0], aNodesRes[1], aNodesRes[2]); } - - anIndex = myReader->AddNode (aPnt); - myMap.Bind (aPnt, anIndex); - return anIndex; - } - - public: - - static Standard_Boolean IsEqual (const gp_XYZ& thePnt1, const gp_XYZ& thePnt2) - { - return (thePnt1 - thePnt2).SquareModulus() < Precision::SquareConfusion(); - } - - //! Computes a hash code for the point, in the range [1, theUpperBound] - //! @param thePoint the point which hash code is to be computed - //! @param theUpperBound the upper bound of the range a computing hash code must be within - //! @return a computed hash code, in the range [1, theUpperBound] - static Standard_Integer HashCode (const gp_XYZ& thePoint, const Standard_Integer theUpperBound) - { - return ::HashCode (thePoint.X() * M_LN10 + thePoint.Y() * M_PI + thePoint.Z() * M_E, theUpperBound); } private: RWStl_Reader* myReader; - NCollection_DataMap myMap; + NCollection_DataMap myNodeIndexMap; }; //! Read a Little Endian 32 bits float @@ -123,11 +121,21 @@ namespace } +//============================================================================== +//function : RWStl_Reader +//purpose : +//============================================================================== +RWStl_Reader::RWStl_Reader() +: myMergeAngle (M_PI/2.0), + myMergeTolearance (0.0) +{ + // +} + //============================================================================== //function : Read //purpose : //============================================================================== - Standard_Boolean RWStl_Reader::Read (const char* theFile, const Message_ProgressRange& theProgress) { @@ -301,6 +309,9 @@ Standard_Boolean RWStl_Reader::ReadAscii (Standard_IStream& theStream, } MergeNodeTool aMergeTool (this); + aMergeTool.SetMergeAngle (myMergeAngle); + aMergeTool.SetMergeTolerance (myMergeTolearance); + Standard_CLocaleSentry::clocale_t aLocale = Standard_CLocaleSentry::GetCLocale(); (void)aLocale; // to avoid warning on GCC where it is actually not used SAVE_TL() // for GCC only, set C locale globally @@ -373,13 +384,7 @@ Standard_Boolean RWStl_Reader::ReadAscii (Standard_IStream& theStream, aNbLine += 5; // add triangle - int n1 = aMergeTool.AddNode (aVertex[0].X(), aVertex[0].Y(), aVertex[0].Z()); - int n2 = aMergeTool.AddNode (aVertex[1].X(), aVertex[1].Y(), aVertex[1].Z()); - int n3 = aMergeTool.AddNode (aVertex[2].X(), aVertex[2].Y(), aVertex[2].Z()); - if (n1 != n2 && n2 != n3 && n3 != n1) - { - AddTriangle (n1, n2, n3); - } + aMergeTool.AddTriangle (aVertex); theBuffer.ReadLine (theStream, aLineLen); // skip "endloop" theBuffer.ReadLine (theStream, aLineLen); // skip "endfacet" @@ -421,7 +426,9 @@ Standard_Boolean RWStl_Reader::ReadBinary (Standard_IStream& theStream, // number of facets is stored as 32-bit integer at position 80 const Standard_Integer aNbFacets = *(int32_t*)(aHeader + 80); - MergeNodeTool aMergeTool (this); + MergeNodeTool aMergeTool (this, aNbFacets); + aMergeTool.SetMergeAngle (myMergeAngle); + aMergeTool.SetMergeTolerance (myMergeTolearance); // don't trust the number of triangles which is coded in the file // sometimes it is wrong, and with this technique we don't need to swap endians for integer @@ -455,18 +462,13 @@ Standard_Boolean RWStl_Reader::ReadBinary (Standard_IStream& theStream, // get points from buffer // readStlFloatVec3 (aBufferPtr); // skip normal - gp_XYZ aP1 = readStlFloatVec3 (aBufferPtr + aVec3Size); - gp_XYZ aP2 = readStlFloatVec3 (aBufferPtr + aVec3Size * 2); - gp_XYZ aP3 = readStlFloatVec3 (aBufferPtr + aVec3Size * 3); - - // add triangle - int n1 = aMergeTool.AddNode (aP1.X(), aP1.Y(), aP1.Z()); - int n2 = aMergeTool.AddNode (aP2.X(), aP2.Y(), aP2.Z()); - int n3 = aMergeTool.AddNode (aP3.X(), aP3.Y(), aP3.Z()); - if (n1 != n2 && n2 != n3 && n3 != n1) + gp_XYZ aTriNodes[3] = { - AddTriangle (n1, n2, n3); - } + readStlFloatVec3 (aBufferPtr + aVec3Size), + readStlFloatVec3 (aBufferPtr + aVec3Size * 2), + readStlFloatVec3 (aBufferPtr + aVec3Size * 3) + }; + aMergeTool.AddTriangle (aTriNodes); } return aPS.More(); diff --git a/src/RWStl/RWStl_Reader.hxx b/src/RWStl/RWStl_Reader.hxx index 671f79d66f..0a6955b748 100644 --- a/src/RWStl/RWStl_Reader.hxx +++ b/src/RWStl/RWStl_Reader.hxx @@ -35,6 +35,9 @@ class RWStl_Reader : public Standard_Transient DEFINE_STANDARD_RTTIEXT(RWStl_Reader, Standard_Transient) public: + //! Default constructor. + Standard_EXPORT RWStl_Reader(); + //! Reads data from STL file (either binary or Ascii). //! This function supports reading multi-domain STL files formed by concatenation of several "plain" files. //! The mesh nodes are not merged between domains. @@ -81,6 +84,26 @@ public: //! Should create new triangle built on specified nodes in the target model. virtual void AddTriangle (Standard_Integer theN1, Standard_Integer theN2, Standard_Integer theN3) = 0; +public: + + //! Return merge tolerance; M_PI/2 by default - all nodes are merged regardless angle between triangles. + Standard_Real MergeAngle() const { return myMergeAngle; } + + //! Set merge angle in radians. + //! Specify something like M_PI/4 (45 degrees) to avoid merge nodes between triangles at sharp corners. + void SetMergeAngle (Standard_Real theAngleRad) { myMergeAngle = theAngleRad; } + + //! Return linear merge tolerance; 0.0 by default (only 3D points with exactly matching coordinates are merged). + double MergeTolerance() const { return myMergeTolearance; } + + //! Set linear merge tolerance. + void SetMergeTolerance (double theTolerance) { myMergeTolearance = theTolerance; } + +protected: + + Standard_Real myMergeAngle; + Standard_Real myMergeTolearance; + }; #endif diff --git a/src/XSDRAWSTLVRML/XSDRAWSTLVRML.cxx b/src/XSDRAWSTLVRML/XSDRAWSTLVRML.cxx index 2f96e14eb6..b73ec8fbe8 100644 --- a/src/XSDRAWSTLVRML/XSDRAWSTLVRML.cxx +++ b/src/XSDRAWSTLVRML/XSDRAWSTLVRML.cxx @@ -574,6 +574,7 @@ static Standard_Integer readstl(Draw_Interpretor& theDI, { TCollection_AsciiString aShapeName, aFilePath; bool toCreateCompOfTris = false; + double aMergeAngle = M_PI / 2.0; for (Standard_Integer anArgIter = 1; anArgIter < theArgc; ++anArgIter) { TCollection_AsciiString anArg (theArgv[anArgIter]); @@ -595,6 +596,32 @@ static Standard_Integer readstl(Draw_Interpretor& theDI, ++anArgIter; } } + else if (anArg == "-mergeangle" + || anArg == "-smoothangle" + || anArg == "-nomergeangle" + || anArg == "-nosmoothangle") + { + if (anArg.StartsWith ("-no")) + { + aMergeAngle = M_PI / 2.0; + } + else + { + aMergeAngle = M_PI / 4.0; + if (anArgIter + 1 < theArgc + && Draw::ParseReal (theArgv[anArgIter + 1], aMergeAngle)) + { + if (aMergeAngle < 0.0 || aMergeAngle > 90.0) + { + theDI << "Syntax error: angle should be within [0,90] range"; + return 1; + } + + ++anArgIter; + aMergeAngle = aMergeAngle * M_PI / 180.0; + } + } + } else { Message::SendFail() << "Syntax error: unknown argument '" << theArgv[anArgIter] << "'"; @@ -612,7 +639,7 @@ static Standard_Integer readstl(Draw_Interpretor& theDI, { // Read STL file to the triangulation. Handle(Draw_ProgressIndicator) aProgress = new Draw_ProgressIndicator (theDI, 1); - Handle(Poly_Triangulation) aTriangulation = RWStl::ReadFile (aFilePath.ToCString(), aProgress->Start()); + Handle(Poly_Triangulation) aTriangulation = RWStl::ReadFile (aFilePath.ToCString(), aMergeAngle, aProgress->Start()); TopoDS_Face aFace; BRep_Builder aB; @@ -2073,10 +2100,11 @@ void XSDRAWSTLVRML::InitCommands (Draw_Interpretor& theCommands) theCommands.Add ("writevrml", "shape file [version VRML#1.0/VRML#2.0 (1/2): 2 by default] [representation shaded/wireframe/both (0/1/2): 1 by default]",__FILE__,writevrml,g); theCommands.Add ("writestl", "shape file [ascii/binary (0/1) : 1 by default] [InParallel (0/1) : 0 by default]",__FILE__,writestl,g); theCommands.Add ("readstl", - "readstl shape file [-brep]" + "readstl shape file [-brep] [-mergeAngle Angle]" "\n\t\t: Reads STL file and creates a new shape with specified name." "\n\t\t: When -brep is specified, creates a Compound of per-triangle Faces." - "\n\t\t: Single triangulation-only Face is created otherwise (default).", + "\n\t\t: Single triangulation-only Face is created otherwise (default)." + "\n\t\t: -mergeAngle specifies maximum angle in degrees between triangles to merge equal nodes; disabled by default.", __FILE__, readstl, g); theCommands.Add ("loadvrml" , "shape file",__FILE__,loadvrml,g); theCommands.Add ("ReadObj", diff --git a/tests/de_mesh/stl_read/B11 b/tests/de_mesh/stl_read/B11 index 71a2ff477e..5f0764efa8 100644 --- a/tests/de_mesh/stl_read/B11 +++ b/tests/de_mesh/stl_read/B11 @@ -1,7 +1,7 @@ readstl m [locate_data_file model_stl_026.stl] # Number of triangles check -checktrinfo m -tri 41113 -nod 18457 +checktrinfo m -tri 41113 -nod 18459 # Visual check checkview -display m -2d -path ${imagedir}/${test_image}.png \ No newline at end of file diff --git a/tests/de_mesh/stl_read/B13 b/tests/de_mesh/stl_read/B13 index 32db62ffc2..301a7fc92c 100644 --- a/tests/de_mesh/stl_read/B13 +++ b/tests/de_mesh/stl_read/B13 @@ -1,7 +1,7 @@ readstl m [locate_data_file model_stl_028.stl] # Number of triangles check -checktrinfo m -tri 10956 -nod 5238 +checktrinfo m -tri 10956 -nod 5240 # Visual check checkview -display m -2d -path ${imagedir}/${test_image}.png \ No newline at end of file diff --git a/tests/de_mesh/stl_read/B14 b/tests/de_mesh/stl_read/B14 index f4f08fd3b4..c8d32d589d 100644 --- a/tests/de_mesh/stl_read/B14 +++ b/tests/de_mesh/stl_read/B14 @@ -1,7 +1,7 @@ readstl m [locate_data_file model_stl_029.stl] # Number of triangles check -checktrinfo m -tri 33313 -nod 15442 +checktrinfo m -tri 33313 -nod 15444 # Visual check checkview -display m -2d -path ${imagedir}/${test_image}.png \ No newline at end of file diff --git a/tests/de_mesh/stl_read/B6 b/tests/de_mesh/stl_read/B6 index 040e6a862c..86d2aefd0a 100644 --- a/tests/de_mesh/stl_read/B6 +++ b/tests/de_mesh/stl_read/B6 @@ -1,7 +1,7 @@ readstl m [locate_data_file model_stl_021.stl] # Number of triangles check -checktrinfo m -tri 63268 -nod 31445 +checktrinfo m -tri 63268 -nod 31448 # Visual check checkview -display m -2d -path ${imagedir}/${test_image}.png \ No newline at end of file diff --git a/tests/de_mesh/stl_read/C5 b/tests/de_mesh/stl_read/C5 index e9b284dd85..ff8c1d6fc4 100644 --- a/tests/de_mesh/stl_read/C5 +++ b/tests/de_mesh/stl_read/C5 @@ -1,7 +1,7 @@ readstl m [locate_data_file model_stl_035.stl] # Number of triangles check -checktrinfo m -tri 21778 -nod 10963 +checktrinfo m -tri 21779 -nod 10964 # Visual check checkview -display m -2d -path ${imagedir}/${test_image}.png \ No newline at end of file diff --git a/tests/de_mesh/stl_read/D2 b/tests/de_mesh/stl_read/D2 new file mode 100644 index 0000000000..bc6245018c --- /dev/null +++ b/tests/de_mesh/stl_read/D2 @@ -0,0 +1,29 @@ +puts "========" +puts "0032580: Data Exchange, STL - add option splitting nodes at sharp corners" +puts "Test readstl with various merge nodes parameters" +puts "========" + +pload XDE VISUALIZATION MODELING +readstl s1 [locate_data_file shape.stl] +checktrinfo s1 -tri 494 -nod 249 +readstl s2 [locate_data_file shape.stl] -mergeAngle 45 +checktrinfo s2 -tri 494 -nod 413 + +vinit View1 +vdisplay -dispMode 1 s1 +vaspects s1 -faceBoundaryDraw 1 +vfit +vdump ${imagedir}/${casename}_mergeall.png + +vclear +vdisplay -dispMode 1 s2 +vaspects s2 -faceBoundaryDraw 1 +vdump ${imagedir}/${casename}_merge45.png + +tcopy -mesh s1 s3 +trmergenodes s3 -angle 60 +vclear +vdisplay -dispMode 1 s3 +vaspects s3 -faceBoundaryDraw 1 +vdump ${imagedir}/${casename}_merge60.png +checktrinfo s3 -tri 494 -nod 411 diff --git a/tests/de_mesh/stl_read/D3 b/tests/de_mesh/stl_read/D3 new file mode 100644 index 0000000000..ffd5e0b707 --- /dev/null +++ b/tests/de_mesh/stl_read/D3 @@ -0,0 +1,33 @@ +puts "========" +puts "0032580: Data Exchange, STL - add option splitting nodes at sharp corners" +puts "Test trmergenodes with various parameters" +puts "========" + +pload XDE VISUALIZATION MODELING +testreadstep [locate_data_file as1-oc-214-mat.stp] ss +incmesh ss 1.0 + +vinit View1 +vdisplay -dispMode 1 ss +vaspects ss -faceBoundaryDraw 1 +vfit +vdump ${imagedir}/${casename}_stp.png + +trmergenodes ss -angle 0 -oneFace m0 +trmergenodes ss -angle 45 -oneFace m45 +trmergenodes ss -angle 90 -oneFace m90 + +vclear +vdisplay -dispMode 1 m0 +vaspects m0 -faceBoundaryDraw 1 +vdump ${imagedir}/${casename}_m0.png + +vclear +vdisplay -dispMode 1 m45 +vaspects m45 -faceBoundaryDraw 1 +vdump ${imagedir}/${casename}_m45.png + +vclear +vdisplay -dispMode 1 m90 +vaspects m90 -faceBoundaryDraw 1 +vdump ${imagedir}/${casename}_m90.png