]> OCCT Git - occt.git/commitdiff
0032580: Data Exchange, STL - add option splitting nodes at sharp corners
authorkgv <kgv@opencascade.com>
Tue, 21 Sep 2021 08:56:09 +0000 (11:56 +0300)
committersmoskvin <smoskvin@opencascade.com>
Wed, 22 Sep 2021 17:09:48 +0000 (20:09 +0300)
Added Poly_MergeNodesTool tool for merging nodes within triangulation.
Added RWStl_Reader::MergeAngle() property managing merging behavior.

16 files changed:
src/MeshTest/MeshTest.cxx
src/Poly/FILES
src/Poly/Poly_MergeNodesTool.cxx [new file with mode: 0644]
src/Poly/Poly_MergeNodesTool.hxx [new file with mode: 0644]
src/RWStl/RWStl.cxx
src/RWStl/RWStl.hxx
src/RWStl/RWStl_Reader.cxx
src/RWStl/RWStl_Reader.hxx
src/XSDRAWSTLVRML/XSDRAWSTLVRML.cxx
tests/de_mesh/stl_read/B11
tests/de_mesh/stl_read/B13
tests/de_mesh/stl_read/B14
tests/de_mesh/stl_read/B6
tests/de_mesh/stl_read/C5
tests/de_mesh/stl_read/D2 [new file with mode: 0644]
tests/de_mesh/stl_read/D3 [new file with mode: 0644]

index 9a30ab38e69e61b64c5097de34ea4454822c9bed..234081dad9aaecc42a9342fe47c0ec540571d4c8 100644 (file)
@@ -41,6 +41,7 @@
 #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>
@@ -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);
 }
index ca4cdf3de8a1e581a5e1cd5e9fbe56bfc3e4a830..46d643c57a25829c4f32f58f835e22ca781369cc 100755 (executable)
@@ -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 (file)
index 0000000..40778ad
--- /dev/null
@@ -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 <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();
+}
diff --git a/src/Poly/Poly_MergeNodesTool.hxx b/src/Poly/Poly_MergeNodesTool.hxx
new file mode 100644 (file)
index 0000000..6c3cae7
--- /dev/null
@@ -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 <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
index c830d7715c7e2e1fb3a93f05a04cc05fc48ac211..7febb29540ff0da74a0ae681914f98388e5d0010 100644 (file)
@@ -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
index 3fc8043ebd73471a634a8929d8f3cc0b5ec6b076..d513ce1a691f1812d123f88323496843b90d7e9e 100644 (file)
@@ -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.
index 69a70dcc3b3a1c838f5fc3486743a58682950984..85dc4f7b4d50476eafc43d4c602208f5eea50dd5 100644 (file)
@@ -24,6 +24,7 @@
 #include <FSD_BinaryFile.hxx>
 #include <OSD_FileSystem.hxx>
 #include <OSD_Timer.hxx>
+#include <Poly_MergeNodesTool.hxx>
 #include <Precision.hxx>
 #include <Standard_CLocaleSentry.hxx>
 
@@ -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<gp_XYZ, Standard_Integer, MergeNodeTool> myMap;
+    NCollection_DataMap<Standard_Integer, Standard_Integer> myNodeIndexMap;
   };
 
   //! Read a Little Endian 32 bits float
@@ -124,10 +122,20 @@ namespace
 }
 
 //==============================================================================
-//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)
 {
@@ -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();
index 671f79d66f616c71a34a3594f32010eb75098d04..0a6955b748a1ad6cc38475c50d902d27912a3f73 100644 (file)
@@ -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
index 2f96e14eb628d541cbc649f4e2deeaf255fc6e2a..b73ec8fbe80abc89f19294dc2024769fd2a43c84 100644 (file)
@@ -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",
index 71a2ff477e8549498ad7a8bb0c64236df361fa23..5f0764efa89fa15bfef190307c86290104175c4d 100644 (file)
@@ -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
index 32db62ffc29c29d5a01e5420029d28c895857fe0..301a7fc92c0b07d0481cedd3faf48601513caeb3 100644 (file)
@@ -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
index f4f08fd3b4b17fe1d4ffbbecfdf9d20514c82e2f..c8d32d589d3924756dce2d9952b34a0cb726d2fd 100644 (file)
@@ -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
index 040e6a862c585f2a1ed118158dff2e5bc09bd22b..86d2aefd0a460b1e414f8dd6cedc90e957429e61 100644 (file)
@@ -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
index e9b284dd85f616fa045d8714b04447b34ca39732..ff8c1d6fc45ecc6428518786dc6eebab7856a307 100644 (file)
@@ -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 (file)
index 0000000..bc62450
--- /dev/null
@@ -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 (file)
index 0000000..ffd5e0b
--- /dev/null
@@ -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