Added Poly_MergeNodesTool tool for merging nodes within triangulation.
Added RWStl_Reader::MergeAngle() property managing merging behavior.
#include <Message_ProgressRange.hxx>
#include <OSD_OpenFile.hxx>
#include <Poly_Connect.hxx>
+#include <Poly_MergeNodesTool.hxx>
#include <TopExp_Explorer.hxx>
#include <TopTools_MapIteratorOfMapOfShape.hxx>
#include <BRep_CurveRepresentation.hxx>
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 (...)
"\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);
}
Poly_MakeLoops.cxx
Poly_MakeLoops.hxx
Poly_MeshPurpose.hxx
+Poly_MergeNodesTool.cxx
+Poly_MergeNodesTool.hxx
Poly_Polygon2D.cxx
Poly_Polygon2D.hxx
Poly_Polygon3D.cxx
--- /dev/null
+// 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 <Poly_MergeNodesTool.hxx>
+
+#include <NCollection_IncAllocator.hxx>
+
+#include <algorithm>
+
+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<int>
+{
+public:
+ //! Constructor.
+ DataMapNode (const NCollection_Vec3<float>& thePos,
+ const NCollection_Vec3<float>& theNorm,
+ int theItem, NCollection_ListNode* theNext)
+ : NCollection_TListNode<int> (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<float>& thePos,
+ const NCollection_Vec3<float>& theNorm,
+ const int theUpper) const
+{
+ (void )theNorm;
+ if (myInvTol <= 0.0f)
+ {
+ return ::HashCode (::HashCodes ((Standard_CString )&thePos, sizeof(NCollection_Vec3<float>)), theUpper);
+ }
+
+ const CellVec3i anIndex = vec3ToCell (thePos);
+ return vec3iHashCode (anIndex, theUpper);
+}
+
+// =======================================================================
+// function : MergedNodesMap::vec3AreEqual
+// purpose :
+// =======================================================================
+inline bool Poly_MergeNodesTool::MergedNodesMap::vec3AreEqual (const NCollection_Vec3<float>& theKey1,
+ const NCollection_Vec3<float>& 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<float>& thePos2,
+ const NCollection_Vec3<float>& 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<float>& thePos,
+ const NCollection_Vec3<float>& 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<int> 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();
+}
--- /dev/null
+// 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 <NCollection_DataMap.hxx>
+#include <NCollection_Map.hxx>
+#include <NCollection_Vec4.hxx>
+#include <Poly_Triangulation.hxx>
+
+//! 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<float> 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<float> 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<float> 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<float> Pos; //!< position
+ NCollection_Vec3<float> Norm; //!< normal to the element
+
+ Vec3AndNormal (const NCollection_Vec3<float>& thePos,
+ const NCollection_Vec3<float>& 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<int64_t> 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<float>& thePos,
+ const NCollection_Vec3<float>& 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<float>& 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<float>& thePos,
+ const NCollection_Vec3<float>& 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<float>& theKey1,
+ const NCollection_Vec3<float>& theKey2) const;
+
+ //! Compare two nodes.
+ Standard_EXPORT bool isEqual (const Vec3AndNormal& theKey1,
+ const NCollection_Vec3<float>& thePos2,
+ const NCollection_Vec3<float>& 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<int>& 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<int>& theKey1, const NCollection_Vec4<int>& theKey2)
+ {
+ return theKey1.IsEqual (theKey2);
+ }
+ };
+
+private:
+
+ Handle(Poly_Triangulation) myPolyData; //!< output triangulation
+ MergedNodesMap myNodeIndexMap; //!< map of merged nodes
+ NCollection_Map<NCollection_Vec4<int>, MergedElemHasher>
+ myElemMap; //!< map of elements
+ NCollection_Vec4<int> myNodeInds; //!< current element indexes
+ NCollection_Vec3<float> 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
}
//=============================================================================
-//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
//! 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.
#include <FSD_BinaryFile.hxx>
#include <OSD_FileSystem.hxx>
#include <OSD_Timer.hxx>
+#include <Poly_MergeNodesTool.hxx>
#include <Precision.hxx>
#include <Standard_CLocaleSentry.hxx>
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<gp_XYZ, Standard_Integer, MergeNodeTool> myMap;
+ NCollection_DataMap<Standard_Integer, Standard_Integer> myNodeIndexMap;
};
//! Read a Little Endian 32 bits float
}
//==============================================================================
-//function : Read
+//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)
{
}
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
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"
// 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
// 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();
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.
//! 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
{
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]);
++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] << "'";
{
// 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;
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",
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
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
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
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
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
--- /dev/null
+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
--- /dev/null
+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