1
0
mirror of https://git.dev.opencascade.org/repos/occt.git synced 2025-09-08 14:17:06 +03:00

0032580: Data Exchange, STL - add option splitting nodes at sharp corners

Added Poly_MergeNodesTool tool for merging nodes within triangulation.
Added RWStl_Reader::MergeAngle() property managing merging behavior.
This commit is contained in:
kgv
2021-09-21 11:56:09 +03:00
committed by smoskvin
parent 5dd92c395a
commit c983176406
16 changed files with 1190 additions and 62 deletions

View 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

View 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.

View 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
@@ -123,11 +121,21 @@ namespace
}
//==============================================================================
//function : RWStl_Reader
//purpose :
//==============================================================================
RWStl_Reader::RWStl_Reader()
: myMergeAngle (M_PI/2.0),
myMergeTolearance (0.0)
{
//
}
//==============================================================================
//function : Read
//purpose :
//==============================================================================
Standard_Boolean RWStl_Reader::Read (const char* theFile,
const Message_ProgressRange& theProgress)
{
@@ -301,6 +309,9 @@ Standard_Boolean RWStl_Reader::ReadAscii (Standard_IStream& theStream,
}
MergeNodeTool aMergeTool (this);
aMergeTool.SetMergeAngle (myMergeAngle);
aMergeTool.SetMergeTolerance (myMergeTolearance);
Standard_CLocaleSentry::clocale_t aLocale = Standard_CLocaleSentry::GetCLocale();
(void)aLocale; // to avoid warning on GCC where it is actually not used
SAVE_TL() // for GCC only, set C locale globally
@@ -373,13 +384,7 @@ Standard_Boolean RWStl_Reader::ReadAscii (Standard_IStream& theStream,
aNbLine += 5;
// add triangle
int n1 = aMergeTool.AddNode (aVertex[0].X(), aVertex[0].Y(), aVertex[0].Z());
int n2 = aMergeTool.AddNode (aVertex[1].X(), aVertex[1].Y(), aVertex[1].Z());
int n3 = aMergeTool.AddNode (aVertex[2].X(), aVertex[2].Y(), aVertex[2].Z());
if (n1 != n2 && n2 != n3 && n3 != n1)
{
AddTriangle (n1, n2, n3);
}
aMergeTool.AddTriangle (aVertex);
theBuffer.ReadLine (theStream, aLineLen); // skip "endloop"
theBuffer.ReadLine (theStream, aLineLen); // skip "endfacet"
@@ -421,7 +426,9 @@ Standard_Boolean RWStl_Reader::ReadBinary (Standard_IStream& theStream,
// number of facets is stored as 32-bit integer at position 80
const Standard_Integer aNbFacets = *(int32_t*)(aHeader + 80);
MergeNodeTool aMergeTool (this);
MergeNodeTool aMergeTool (this, aNbFacets);
aMergeTool.SetMergeAngle (myMergeAngle);
aMergeTool.SetMergeTolerance (myMergeTolearance);
// don't trust the number of triangles which is coded in the file
// sometimes it is wrong, and with this technique we don't need to swap endians for integer
@@ -455,18 +462,13 @@ Standard_Boolean RWStl_Reader::ReadBinary (Standard_IStream& theStream,
// get points from buffer
// readStlFloatVec3 (aBufferPtr); // skip normal
gp_XYZ aP1 = readStlFloatVec3 (aBufferPtr + aVec3Size);
gp_XYZ aP2 = readStlFloatVec3 (aBufferPtr + aVec3Size * 2);
gp_XYZ aP3 = readStlFloatVec3 (aBufferPtr + aVec3Size * 3);
// add triangle
int n1 = aMergeTool.AddNode (aP1.X(), aP1.Y(), aP1.Z());
int n2 = aMergeTool.AddNode (aP2.X(), aP2.Y(), aP2.Z());
int n3 = aMergeTool.AddNode (aP3.X(), aP3.Y(), aP3.Z());
if (n1 != n2 && n2 != n3 && n3 != n1)
gp_XYZ aTriNodes[3] =
{
AddTriangle (n1, n2, n3);
}
readStlFloatVec3 (aBufferPtr + aVec3Size),
readStlFloatVec3 (aBufferPtr + aVec3Size * 2),
readStlFloatVec3 (aBufferPtr + aVec3Size * 3)
};
aMergeTool.AddTriangle (aTriNodes);
}
return aPS.More();

View 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