1
0
mirror of https://git.dev.opencascade.org/repos/occt.git synced 2025-08-24 13:50:49 +03:00

0028840: Data Exchange - rewrite the STL Reader/Writer

STL Reader and Writer tools have been refactored to improve performance and usability:
- Basic reading of STL file is separated to abstract class RWStl_Reader which is not bound to particular data structures; the target data model can be bound via inheritance.
- RWStl package uses class Poly_Triangulation to represent triangular mesh.
- Obsolete data structures and tools (packages StlMesh and StlTransfer) are removed.
This commit is contained in:
aml
2017-06-14 08:07:26 +03:00
committed by bugmaster
parent c5b39011b1
commit 4178b3531b
44 changed files with 1168 additions and 2692 deletions

View File

@@ -1,2 +1,4 @@
RWStl.cxx
RWStl.hxx
RWStl_Reader.cxx
RWStl_Reader.hxx

View File

@@ -1,7 +1,6 @@
// Created on: 1994-10-13
// Created by: Marc LEGAY
// Copyright (c) 1994-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
// Created on: 2017-06-13
// Created by: Alexander MALYSHEV
// Copyright (c) 2017 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
@@ -14,272 +13,289 @@
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
#include <BRepBuilderAPI_CellFilter.hxx>
#include <BRepBuilderAPI_VertexInspector.hxx>
#include <gp.hxx>
#include <gp_Vec.hxx>
#include <gp_XYZ.hxx>
#include <Message.hxx>
#include <Message_Messenger.hxx>
#include <Message_ProgressIndicator.hxx>
#include <Message_ProgressSentry.hxx>
#include <OSD.hxx>
#include <OSD_File.hxx>
#include <OSD_Host.hxx>
#include <OSD_OpenFile.hxx>
#include <OSD_Path.hxx>
#include <OSD_Protection.hxx>
#include <Precision.hxx>
#include <RWStl.hxx>
#include <Standard_NoMoreObject.hxx>
#include <Standard_TypeMismatch.hxx>
#include <StlMesh_Mesh.hxx>
#include <StlMesh_MeshExplorer.hxx>
#include <TCollection_AsciiString.hxx>
#include <stdio.h>
// A static method adding nodes to a mesh and keeping coincident (sharing) nodes.
static Standard_Integer AddVertex(Handle(StlMesh_Mesh)& mesh,
BRepBuilderAPI_CellFilter& filter,
BRepBuilderAPI_VertexInspector& inspector,
const gp_XYZ& p)
#include <Message_ProgressSentry.hxx>
#include <NCollection_Vector.hxx>
#include <OSD_File.hxx>
#include <OSD_OpenFile.hxx>
#include <RWStl_Reader.hxx>
namespace
{
Standard_Integer index;
inspector.SetCurrent(p);
gp_XYZ minp = inspector.Shift(p, -Precision::Confusion());
gp_XYZ maxp = inspector.Shift(p, +Precision::Confusion());
filter.Inspect(minp, maxp, inspector);
const TColStd_ListOfInteger& indices = inspector.ResInd();
if (indices.IsEmpty() == Standard_False)
static const Standard_Integer THE_STL_SIZEOF_FACET = 50;
static const Standard_Integer IND_THRESHOLD = 1000; // increment the indicator every 1k triangles
//! Writing a Little Endian 32 bits integer
inline static void convertInteger (const Standard_Integer theValue,
Standard_Character* theResult)
{
index = indices.First(); // it should be only one
inspector.ClearResList();
union
{
Standard_Integer i;
Standard_Character c[4];
} anUnion;
anUnion.i = theValue;
theResult[0] = anUnion.c[0];
theResult[1] = anUnion.c[1];
theResult[2] = anUnion.c[2];
theResult[3] = anUnion.c[3];
}
else
//! Writing a Little Endian 32 bits float
inline static void convertDouble (const Standard_Real theValue,
Standard_Character* theResult)
{
index = mesh->AddVertex(p.X(), p.Y(), p.Z());
filter.Add(index, p);
inspector.Add(p);
union
{
Standard_ShortReal i;
Standard_Character c[4];
} anUnion;
anUnion.i = (Standard_ShortReal)theValue;
theResult[0] = anUnion.c[0];
theResult[1] = anUnion.c[1];
theResult[2] = anUnion.c[2];
theResult[3] = anUnion.c[3];
}
return index;
class Reader : public RWStl_Reader
{
public:
//! Add new node
virtual Standard_Integer AddNode (const gp_XYZ& thePnt) Standard_OVERRIDE
{
myNodes.Append (thePnt);
return myNodes.Size();
}
//! Add new triangle
virtual void AddTriangle (Standard_Integer theNode1, Standard_Integer theNode2, Standard_Integer theNode3) Standard_OVERRIDE
{
myTriangles.Append (Poly_Triangle (theNode1, theNode2, theNode3));
}
//! Creates Poly_Triangulation from collected data
Handle(Poly_Triangulation) GetTriangulation()
{
Handle(Poly_Triangulation) aPoly = new Poly_Triangulation (myNodes.Length(), myTriangles.Length(), Standard_False);
for (Standard_Integer aNodeIter = 0; aNodeIter < myNodes.Size(); ++aNodeIter)
{
aPoly->ChangeNode (aNodeIter + 1) = myNodes (aNodeIter);
}
for (Standard_Integer aTriIter = 0; aTriIter < myTriangles.Size(); ++aTriIter)
{
aPoly->ChangeTriangle (aTriIter + 1) = myTriangles (aTriIter);
}
return aPoly;
}
private:
NCollection_Vector<gp_XYZ> myNodes;
NCollection_Vector<Poly_Triangle> myTriangles;
};
}
// constants
static const size_t HEADER_SIZE = 84;
static const size_t SIZEOF_STL_FACET = 50;
static const size_t ASCII_LINES_PER_FACET = 7;
static const int IND_THRESHOLD = 1000; // increment the indicator every 1k triangles
//=======================================================================
//function : WriteInteger
//purpose : writing a Little Endian 32 bits integer
//=======================================================================
inline static void WriteInteger(OSD_File& ofile,const Standard_Integer value)
//=============================================================================
//function : Read
//purpose :
//=============================================================================
Handle(Poly_Triangulation) RWStl::ReadFile (const Standard_CString theFile,
const Handle(Message_ProgressIndicator)& theProgress)
{
union {
Standard_Integer i;// don't be afraid, this is just an unsigned int
char c[4];
} bidargum;
bidargum.i = value;
Standard_Integer entier;
entier = bidargum.c[0] & 0xFF;
entier |= (bidargum.c[1] & 0xFF) << 0x08;
entier |= (bidargum.c[2] & 0xFF) << 0x10;
entier |= (bidargum.c[3] & 0xFF) << 0x18;
ofile.Write((char *)&entier,sizeof(bidargum.c));
Reader aReader;
if (!aReader.Read (theFile, theProgress))
{
return Handle(Poly_Triangulation)();
}
return aReader.GetTriangulation();
}
//=======================================================================
//function : WriteDouble2Float
//purpose : writing a Little Endian 32 bits float
//=======================================================================
inline static void WriteDouble2Float(OSD_File& ofile,Standard_Real value)
//=============================================================================
//function : ReadFile
//purpose :
//=============================================================================
Handle(Poly_Triangulation) RWStl::ReadFile (const OSD_Path& theFile,
const Handle(Message_ProgressIndicator)& theProgress)
{
union {
Standard_ShortReal f;
char c[4];
} bidargum;
OSD_File aFile(theFile);
if (!aFile.Exists())
{
return Handle(Poly_Triangulation)();
}
bidargum.f = (Standard_ShortReal)value;
Standard_Integer entier;
entier = bidargum.c[0] & 0xFF;
entier |= (bidargum.c[1] & 0xFF) << 0x08;
entier |= (bidargum.c[2] & 0xFF) << 0x10;
entier |= (bidargum.c[3] & 0xFF) << 0x18;
ofile.Write((char *)&entier,sizeof(bidargum.c));
TCollection_AsciiString aPath;
theFile.SystemName (aPath);
return ReadFile (aPath.ToCString(), theProgress);
}
//=======================================================================
//function : readFloat2Double
//purpose : reading a Little Endian 32 bits float
//=======================================================================
inline static Standard_Real ReadFloat2Double(OSD_File &aFile)
//=============================================================================
//function : ReadBinary
//purpose :
//=============================================================================
Handle(Poly_Triangulation) RWStl::ReadBinary (const OSD_Path& theFile,
const Handle(Message_ProgressIndicator)& theProgress)
{
union {
uint32_t i;
float f;
}bidargum;
OSD_File aFile(theFile);
if (!aFile.Exists())
{
return Handle(Poly_Triangulation)();
}
char c[4];
Standard_Address adr;
adr = (Standard_Address)c;
Standard_Integer lread;
aFile.Read(adr,4,lread);
bidargum.i = c[0] & 0xFF;
bidargum.i |= (c[1] & 0xFF) << 0x08;
bidargum.i |= (c[2] & 0xFF) << 0x10;
bidargum.i |= (c[3] & 0xFF) << 0x18;
TCollection_AsciiString aPath;
theFile.SystemName (aPath);
return (Standard_Real)(bidargum.f);
std::filebuf aBuf;
OSD_OpenStream (aBuf, aPath, std::ios::in | std::ios::binary);
if (!aBuf.is_open())
{
return Handle(Poly_Triangulation)();
}
Standard_IStream aStream (&aBuf);
Reader aReader;
if (!aReader.ReadBinary (aStream, theProgress))
{
return Handle(Poly_Triangulation)();
}
return aReader.GetTriangulation();
}
//=============================================================================
//function : ReadAscii
//purpose :
//=============================================================================
Handle(Poly_Triangulation) RWStl::ReadAscii (const OSD_Path& theFile,
const Handle(Message_ProgressIndicator)& theProgress)
{
OSD_File aFile (theFile);
if (!aFile.Exists())
{
return Handle(Poly_Triangulation)();
}
TCollection_AsciiString aPath;
theFile.SystemName (aPath);
//=======================================================================
//function : WriteBinary
//purpose : write a binary STL file in Little Endian format
//=======================================================================
std::filebuf aBuf;
OSD_OpenStream (aBuf, aPath, std::ios::in | std::ios::binary);
if (!aBuf.is_open())
{
return Handle(Poly_Triangulation)();
}
Standard_IStream aStream (&aBuf);
Standard_Boolean RWStl::WriteBinary (const Handle(StlMesh_Mesh)& theMesh,
// get length of file to feed progress indicator
aStream.seekg (0, aStream.end);
std::streampos theEnd = aStream.tellg();
aStream.seekg (0, aStream.beg);
Reader aReader;
if (!aReader.ReadAscii (aStream, theEnd, theProgress))
{
return Handle(Poly_Triangulation)();
}
return aReader.GetTriangulation();
}
//=============================================================================
//function : Write
//purpose :
//=============================================================================
Standard_Boolean RWStl::WriteBinary (const Handle(Poly_Triangulation)& aMesh,
const OSD_Path& thePath,
const Handle(Message_ProgressIndicator)& theProgInd)
{
OSD_File aFile (thePath);
aFile.Build (OSD_WriteOnly, OSD_Protection());
TCollection_AsciiString aPath;
thePath.SystemName (aPath);
Standard_Real x1, y1, z1;
Standard_Real x2, y2, z2;
Standard_Real x3, y3, z3;
// writing 80 bytes of the trash?
char sval[80];
aFile.Write ((Standard_Address)sval,80);
WriteInteger (aFile, theMesh->NbTriangles());
int dum=0;
StlMesh_MeshExplorer aMexp (theMesh);
// create progress sentry for domains
Standard_Integer aNbDomains = theMesh->NbDomains();
Message_ProgressSentry aDPS (theProgInd, "Mesh domains", 0, aNbDomains, 1);
for (Standard_Integer nbd = 1; nbd <= aNbDomains && aDPS.More(); nbd++, aDPS.Next())
FILE* aFile = OSD_OpenFile (aPath, "wb");
if (aFile == NULL)
{
// create progress sentry for triangles in domain
Message_ProgressSentry aTPS (theProgInd, "Triangles", 0,
theMesh->NbTriangles (nbd), IND_THRESHOLD);
Standard_Integer aTriangleInd = 0;
for (aMexp.InitTriangle (nbd); aMexp.MoreTriangle(); aMexp.NextTriangle())
{
aMexp.TriangleVertices (x1,y1,z1,x2,y2,z2,x3,y3,z3);
//pgo aMexp.TriangleOrientation (x,y,z);
gp_XYZ Vect12 ((x2-x1), (y2-y1), (z2-z1));
gp_XYZ Vect13 ((x3-x1), (y3-y1), (z3-z1));
gp_XYZ Vnorm = Vect12 ^ Vect13;
Standard_Real Vmodul = Vnorm.Modulus ();
if (Vmodul > gp::Resolution())
{
Vnorm.Divide(Vmodul);
}
else
{
// si Vnorm est quasi-nul, on le charge a 0 explicitement
Vnorm.SetCoord (0., 0., 0.);
}
WriteDouble2Float (aFile, Vnorm.X());
WriteDouble2Float (aFile, Vnorm.Y());
WriteDouble2Float (aFile, Vnorm.Z());
WriteDouble2Float (aFile, x1);
WriteDouble2Float (aFile, y1);
WriteDouble2Float (aFile, z1);
WriteDouble2Float (aFile, x2);
WriteDouble2Float (aFile, y2);
WriteDouble2Float (aFile, z2);
WriteDouble2Float (aFile, x3);
WriteDouble2Float (aFile, y3);
WriteDouble2Float (aFile, z3);
aFile.Write (&dum, 2);
// update progress only per 1k triangles
if (++aTriangleInd % IND_THRESHOLD == 0)
{
if (!aTPS.More())
break;
aTPS.Next();
}
}
return Standard_False;
}
aFile.Close();
Standard_Boolean isInterrupted = !aDPS.More();
return !isInterrupted;
}
//=======================================================================
//function : WriteAscii
//purpose : write an ASCII STL file
//=======================================================================
Standard_Boolean RWStl::WriteAscii (const Handle(StlMesh_Mesh)& theMesh,
Standard_Boolean isOK = writeBinary (aMesh, aFile, theProgInd);
fclose (aFile);
return isOK;
}
//=============================================================================
//function : Write
//purpose :
//=============================================================================
Standard_Boolean RWStl::WriteAscii (const Handle(Poly_Triangulation)& theMesh,
const OSD_Path& thePath,
const Handle(Message_ProgressIndicator)& theProgInd)
{
OSD_File theFile (thePath);
theFile.Build(OSD_WriteOnly,OSD_Protection());
TCollection_AsciiString buf ("solid\n");
theFile.Write (buf,buf.Length());buf.Clear();
TCollection_AsciiString aPath;
thePath.SystemName (aPath);
Standard_Real x1, y1, z1;
Standard_Real x2, y2, z2;
Standard_Real x3, y3, z3;
char sval[512];
// create progress sentry for domains
Standard_Integer aNbDomains = theMesh->NbDomains();
Message_ProgressSentry aDPS (theProgInd, "Mesh domains", 0, aNbDomains, 1);
StlMesh_MeshExplorer aMexp (theMesh);
for (Standard_Integer nbd = 1; nbd <= aNbDomains && aDPS.More(); nbd++, aDPS.Next())
FILE* aFile = OSD_OpenFile (aPath, "w");
if (aFile == NULL)
{
// create progress sentry for triangles in domain
Message_ProgressSentry aTPS (theProgInd, "Triangles", 0,
theMesh->NbTriangles (nbd), IND_THRESHOLD);
Standard_Integer aTriangleInd = 0;
for (aMexp.InitTriangle (nbd); aMexp.MoreTriangle(); aMexp.NextTriangle())
return Standard_False;
}
Standard_Boolean isOK = writeASCII (theMesh, aFile, theProgInd);
fclose (aFile);
return isOK;
}
//=============================================================================
//function : writeASCII
//purpose :
//=============================================================================
Standard_Boolean RWStl::writeASCII (const Handle(Poly_Triangulation)& theMesh,
FILE* theFile,
const Handle(Message_ProgressIndicator)& theProgInd)
{
// note that space after 'solid' is necessary for many systems
if (fwrite ("solid \n", 1, 7, theFile) != 7)
{
return Standard_False;
}
char aBuffer[512];
memset (aBuffer, 0, sizeof(aBuffer));
Message_ProgressSentry aPS (theProgInd, "Triangles", 0,
theMesh->NbTriangles(), IND_THRESHOLD);
const TColgp_Array1OfPnt& aNodes = theMesh->Nodes();
const Poly_Array1OfTriangle& aTriangles = theMesh->Triangles();
const Standard_Integer NBTriangles = theMesh->NbTriangles();
Standard_Integer anElem[3] = {0, 0, 0};
for (Standard_Integer aTriIter = 1; aTriIter <= NBTriangles; ++aTriIter)
{
const Poly_Triangle& aTriangle = aTriangles (aTriIter);
aTriangle.Get (anElem[0], anElem[1], anElem[2]);
const gp_Pnt aP1 = aNodes (anElem[0]);
const gp_Pnt aP2 = aNodes (anElem[1]);
const gp_Pnt aP3 = aNodes (anElem[2]);
const gp_Vec aVec1 (aP1, aP2);
const gp_Vec aVec2 (aP1, aP3);
gp_Vec aVNorm = aVec1.Crossed (aVec2);
if (aVNorm.SquareMagnitude() > gp::Resolution())
{
aMexp.TriangleVertices (x1,y1,z1,x2,y2,z2,x3,y3,z3);
aVNorm.Normalize();
}
else
{
aVNorm.SetCoord (0.0, 0.0, 0.0);
}
// Standard_Real x, y, z;
// aMexp.TriangleOrientation (x,y,z);
gp_XYZ Vect12 ((x2-x1), (y2-y1), (z2-z1));
gp_XYZ Vect23 ((x3-x2), (y3-y2), (z3-z2));
gp_XYZ Vnorm = Vect12 ^ Vect23;
Standard_Real Vmodul = Vnorm.Modulus ();
if (Vmodul > gp::Resolution())
{
Vnorm.Divide (Vmodul);
}
else
{
// si Vnorm est quasi-nul, on le charge a 0 explicitement
Vnorm.SetCoord (0., 0., 0.);
}
Sprintf (sval,
Sprintf (aBuffer,
" facet normal % 12e % 12e % 12e\n"
" outer loop\n"
" vertex % 12e % 12e % 12e\n"
@@ -287,270 +303,132 @@ Standard_Boolean RWStl::WriteAscii (const Handle(StlMesh_Mesh)& theMesh,
" vertex % 12e % 12e % 12e\n"
" endloop\n"
" endfacet\n",
Vnorm.X(), Vnorm.Y(), Vnorm.Z(),
x1, y1, z1,
x2, y2, z2,
x3, y3, z3);
buf += sval;
theFile.Write (buf, buf.Length()); buf.Clear();
aVNorm.X(), aVNorm.Y(), aVNorm.Z(),
aP1.X(), aP1.Y(), aP1.Z(),
aP2.X(), aP2.Y(), aP2.Z(),
aP3.X(), aP3.Y(), aP3.Z());
// update progress only per 1k triangles
if (++aTriangleInd % IND_THRESHOLD == 0)
{
if (!aTPS.More())
break;
aTPS.Next();
}
if (fprintf (theFile, "%s", aBuffer) < 0)
{
return Standard_False;
}
}
buf += "endsolid\n";
theFile.Write (buf, buf.Length()); buf.Clear();
theFile.Close();
Standard_Boolean isInterrupted = !aDPS.More();
return !isInterrupted;
}
//=======================================================================
//function : ReadFile
//Design :
//Warning :
//=======================================================================
Handle(StlMesh_Mesh) RWStl::ReadFile (const OSD_Path& thePath,
const Handle(Message_ProgressIndicator)& theProgInd)
{
OSD_File file (thePath);
file.Open(OSD_ReadOnly,OSD_Protection(OSD_RWD,OSD_RWD,OSD_RWD,OSD_RWD));
Standard_Boolean IsAscii;
unsigned char str[128];
Standard_Integer lread,i;
Standard_Address ach;
ach = (Standard_Address)str;
// we skip the header which is in Ascii for both modes
file.Read(ach,HEADER_SIZE,lread);
// we read 128 characters to detect if we have a non-ascii char
file.Read(ach,sizeof(str),lread);
IsAscii = Standard_True;
for (i = 0; i< lread && IsAscii; ++i) {
if (str[i] > '~') {
IsAscii = Standard_False;
}
}
#ifdef OCCT_DEBUG
cout << (IsAscii ? "ascii\n" : "binary\n");
#endif
file.Close();
return IsAscii ? RWStl::ReadAscii (thePath, theProgInd)
: RWStl::ReadBinary (thePath, theProgInd);
}
//=======================================================================
//function : ReadBinary
//Design :
//Warning :
//=======================================================================
Handle(StlMesh_Mesh) RWStl::ReadBinary (const OSD_Path& thePath,
const Handle(Message_ProgressIndicator)& /*theProgInd*/)
{
Standard_Integer ifacet;
Standard_Real fx,fy,fz,fx1,fy1,fz1,fx2,fy2,fz2,fx3,fy3,fz3;
Standard_Integer i1,i2,i3,lread;
char buftest[5];
Standard_Address adr;
adr = (Standard_Address)buftest;
// Open the file
OSD_File theFile (thePath);
theFile.Open(OSD_ReadOnly,OSD_Protection(OSD_RWD,OSD_RWD,OSD_RWD,OSD_RWD));
// the size of the file (minus the header size)
// must be a multiple of SIZEOF_STL_FACET
// compute file size
Standard_Size filesize = theFile.Size();
// don't trust the number of triangles which is coded in the file sometimes it is wrong
Standard_Integer NBFACET = (Standard_Integer)((filesize - HEADER_SIZE) / SIZEOF_STL_FACET);
if (NBFACET < 1)
{
throw Standard_NoMoreObject("RWStl::ReadBinary (wrong file size)");
}
theFile.Seek (80, OSD_FromBeginning);
theFile.Read (adr, 4, lread);
Standard_Integer aNbTrisInHeader = (((char* )buftest)[3] << 24) | (((Standard_Byte* )buftest)[2] << 16)
| (((Standard_Byte* )buftest)[1] << 8 ) | (((Standard_Byte* )buftest)[0] << 0 );
if (NBFACET < aNbTrisInHeader)
{
Message::DefaultMessenger()->Send (TCollection_AsciiString ("RWStl - Binary STL file defines more triangles (") + aNbTrisInHeader
+ ") that can be read (" + NBFACET + ") - probably corrupted file",
Message_Warning);
}
else if (NBFACET > aNbTrisInHeader)
{
Message::DefaultMessenger()->Send (TCollection_AsciiString ("RWStl - Binary STL file defines less triangles (") + aNbTrisInHeader
+ ") that can be read (" + NBFACET + ") - probably corrupted file",
Message_Warning);
}
else if ((filesize - HEADER_SIZE) % SIZEOF_STL_FACET != 0)
{
Message::DefaultMessenger()->Send (TCollection_AsciiString ("RWStl - Binary STL file has unidentified tail"),
Message_Warning);
}
// skip the header
theFile.Seek(HEADER_SIZE,OSD_FromBeginning);
// create the StlMesh_Mesh object
Handle(StlMesh_Mesh) ReadMesh = new StlMesh_Mesh ();
ReadMesh->AddDomain ();
// Filter unique vertices to share the nodes of the mesh.
BRepBuilderAPI_CellFilter uniqueVertices(Precision::Confusion());
BRepBuilderAPI_VertexInspector inspector(Precision::Confusion());
for (ifacet=1; ifacet<=NBFACET; ++ifacet) {
// read normal coordinates
fx = ReadFloat2Double(theFile);
fy = ReadFloat2Double(theFile);
fz = ReadFloat2Double(theFile);
// read vertex 1
fx1 = ReadFloat2Double(theFile);
fy1 = ReadFloat2Double(theFile);
fz1 = ReadFloat2Double(theFile);
// read vertex 2
fx2 = ReadFloat2Double(theFile);
fy2 = ReadFloat2Double(theFile);
fz2 = ReadFloat2Double(theFile);
// read vertex 3
fx3 = ReadFloat2Double(theFile);
fy3 = ReadFloat2Double(theFile);
fz3 = ReadFloat2Double(theFile);
// Add vertices.
i1 = AddVertex(ReadMesh, uniqueVertices, inspector, gp_XYZ(fx1, fy1, fz1));
i2 = AddVertex(ReadMesh, uniqueVertices, inspector, gp_XYZ(fx2, fy2, fz2));
i3 = AddVertex(ReadMesh, uniqueVertices, inspector, gp_XYZ(fx3, fy3, fz3));
// Add triangle.
ReadMesh->AddTriangle (i1,i2,i3,fx,fy,fz);
// skip extra bytes
theFile.Read(adr,2,lread);
}
theFile.Close ();
return ReadMesh;
}
//=======================================================================
//function : ReadAscii
//Design :
//Warning :
//=======================================================================
Handle(StlMesh_Mesh) RWStl::ReadAscii (const OSD_Path& thePath,
const Handle(Message_ProgressIndicator)& theProgInd)
{
TCollection_AsciiString filename;
long ipos;
Standard_Integer nbLines = 0;
Standard_Integer nbTris = 0;
Standard_Integer iTri;
Standard_Integer i1,i2,i3;
Handle(StlMesh_Mesh) ReadMesh;
thePath.SystemName (filename);
// Open the file
FILE* file = OSD_OpenFile(filename.ToCString(),"r");
fseek(file,0L,SEEK_END);
long filesize = ftell(file);
rewind(file);
// count the number of lines
for (ipos = 0; ipos < filesize; ++ipos) {
if (getc(file) == '\n')
nbLines++;
}
// compute number of triangles
nbTris = (nbLines / ASCII_LINES_PER_FACET);
// go back to the beginning of the file
rewind(file);
// skip header
while (getc(file) != '\n');
#ifdef OCCT_DEBUG
cout << "start mesh\n";
#endif
ReadMesh = new StlMesh_Mesh();
ReadMesh->AddDomain();
// Filter unique vertices to share the nodes of the mesh.
BRepBuilderAPI_CellFilter uniqueVertices(Precision::Confusion());
BRepBuilderAPI_VertexInspector inspector(Precision::Confusion());
// main reading
Message_ProgressSentry aPS (theProgInd, "Triangles", 0, (nbTris - 1) * 1.0 / IND_THRESHOLD, 1);
for (iTri = 0; iTri < nbTris && aPS.More();)
{
char x[256]="", y[256]="", z[256]="";
// reading the facet normal
if (3 != fscanf(file,"%*s %*s %80s %80s %80s\n", x, y, z))
break; // error should be properly reported
gp_XYZ aN (Atof(x), Atof(y), Atof(z));
// skip the keywords "outer loop"
if (fscanf(file,"%*s %*s") < 0)
break;
// reading vertex
if (3 != fscanf(file,"%*s %80s %80s %80s\n", x, y, z))
break; // error should be properly reported
gp_XYZ aV1 (Atof(x), Atof(y), Atof(z));
if (3 != fscanf(file,"%*s %80s %80s %80s\n", x, y, z))
break; // error should be properly reported
gp_XYZ aV2 (Atof(x), Atof(y), Atof(z));
if (3 != fscanf(file,"%*s %80s %80s %80s\n", x, y, z))
break; // error should be properly reported
gp_XYZ aV3 (Atof(x), Atof(y), Atof(z));
// here the facet must be built and put in the mesh datastructure
i1 = AddVertex(ReadMesh, uniqueVertices, inspector, aV1);
i2 = AddVertex(ReadMesh, uniqueVertices, inspector, aV2);
i3 = AddVertex(ReadMesh, uniqueVertices, inspector, aV3);
ReadMesh->AddTriangle (i1, i2, i3, aN.X(), aN.Y(), aN.Z());
// skip the keywords "endloop"
if (fscanf(file,"%*s") < 0)
break;
// skip the keywords "endfacet"
if (fscanf(file,"%*s") < 0)
break;
// update progress only per 1k triangles
if (++iTri % IND_THRESHOLD == 0)
if ((aTriIter % IND_THRESHOLD) == 0)
{
aPS.Next();
}
}
#ifdef OCCT_DEBUG
cout << "end mesh\n";
#endif
fclose(file);
return ReadMesh;
if (fwrite ("endsolid\n", 1, 9, theFile) != 9)
{
return Standard_False;
}
return Standard_True;
}
//=============================================================================
//function : writeBinary
//purpose :
//=============================================================================
Standard_Boolean RWStl::writeBinary (const Handle(Poly_Triangulation)& theMesh,
FILE* theFile,
const Handle(Message_ProgressIndicator)& theProgInd)
{
char aHeader[80] = "STL Exported by OpenCASCADE [www.opencascade.com]";
if (fwrite (aHeader, 1, 80, theFile) != 80)
{
return Standard_False;
}
Message_ProgressSentry aPS (theProgInd, "Triangles", 0,
theMesh->NbTriangles(), IND_THRESHOLD);
const Standard_Size aNbChunkTriangles = 4096;
const Standard_Size aChunkSize = aNbChunkTriangles * THE_STL_SIZEOF_FACET;
NCollection_Array1<Standard_Character> aData (1, aChunkSize);
Standard_Character* aDataChunk = &aData.ChangeFirst();
const TColgp_Array1OfPnt& aNodes = theMesh->Nodes();
const Poly_Array1OfTriangle& aTriangles = theMesh->Triangles();
const Standard_Integer aNBTriangles = theMesh->NbTriangles();
Standard_Character aConv[4];
convertInteger (aNBTriangles, aConv);
if (fwrite (aConv, 1, 4, theFile) != 4)
{
return Standard_False;
}
Standard_Size aByteCount = 0;
for (Standard_Integer aTriIter = 1; aTriIter <= aNBTriangles; ++aTriIter)
{
Standard_Integer id[3];
const Poly_Triangle& aTriangle = aTriangles (aTriIter);
aTriangle.Get (id[0], id[1], id[2]);
const gp_Pnt aP1 = aNodes (id[0]);
const gp_Pnt aP2 = aNodes (id[1]);
const gp_Pnt aP3 = aNodes (id[2]);
gp_Vec aVec1 (aP1, aP2);
gp_Vec aVec2 (aP1, aP3);
gp_Vec aVNorm = aVec1.Crossed(aVec2);
if (aVNorm.SquareMagnitude() > gp::Resolution())
{
aVNorm.Normalize();
}
else
{
aVNorm.SetCoord (0.0, 0.0, 0.0);
}
convertDouble (aVNorm.X(), &aDataChunk[aByteCount]); aByteCount += 4;
convertDouble (aVNorm.Y(), &aDataChunk[aByteCount]); aByteCount += 4;
convertDouble (aVNorm.Z(), &aDataChunk[aByteCount]); aByteCount += 4;
convertDouble (aP1.X(), &aDataChunk[aByteCount]); aByteCount += 4;
convertDouble (aP1.Y(), &aDataChunk[aByteCount]); aByteCount += 4;
convertDouble (aP1.Z(), &aDataChunk[aByteCount]); aByteCount += 4;
convertDouble (aP2.X(), &aDataChunk[aByteCount]); aByteCount += 4;
convertDouble (aP2.Y(), &aDataChunk[aByteCount]); aByteCount += 4;
convertDouble (aP2.Z(), &aDataChunk[aByteCount]); aByteCount += 4;
convertDouble (aP3.X(), &aDataChunk[aByteCount]); aByteCount += 4;
convertDouble (aP3.Y(), &aDataChunk[aByteCount]); aByteCount += 4;
convertDouble (aP3.Z(), &aDataChunk[aByteCount]); aByteCount += 4;
aDataChunk[aByteCount] = 0; aByteCount += 1;
aDataChunk[aByteCount] = 0; aByteCount += 1;
// Chunk is filled. Dump it to the file.
if (aByteCount == aChunkSize)
{
if (fwrite (aDataChunk, 1, aChunkSize, theFile) != aChunkSize)
{
return Standard_False;
}
aByteCount = 0;
}
// update progress only per 1k triangles
if ((aTriIter % IND_THRESHOLD) == 0)
{
aPS.Next();
}
}
// Write last part if necessary.
if (aByteCount != aChunkSize)
{
if (fwrite (aDataChunk, 1, aByteCount, theFile) != aByteCount)
{
return Standard_False;
}
}
return Standard_True;
}

View File

@@ -1,7 +1,6 @@
// Created on: 1994-10-13
// Created by: Marc LEGAY
// Copyright (c) 1994-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
// Created on: 2017-06-13
// Created by: Alexander MALYSHEV
// Copyright (c) 2017 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
@@ -17,85 +16,61 @@
#ifndef _RWStl_HeaderFile
#define _RWStl_HeaderFile
#include <Standard.hxx>
#include <Standard_DefineAlloc.hxx>
#include <Standard_Handle.hxx>
#include <Message_ProgressIndicator.hxx>
#include <OSD_Path.hxx>
#include <Poly_Triangulation.hxx>
#include <Standard_Macro.hxx>
class StlMesh_Mesh;
class OSD_Path;
//! This package contains the methods to be used in
//! the Stereo Lithograpy Application. The main
//! features of this application are ,starting from a
//! Shape :
//! - mesh this shape with a maximun tolerance,
//! - display the meshing,
//! - write the meshing in a file (binary or ascii),
//! - read of file (binary or ascii) and display it,
//! - translate a binary file to an ascii file,
//! - translate an ascii file to an binary file.
class RWStl
//! This class provides methods to read and write triangulation from / to the STL files.
class RWStl
{
public:
DEFINE_STANDARD_ALLOC
//! write the meshing in a file following the
//! Write triangulation to binary STL file.
//! binary format of an STL file.
//! Returns false if the cannot be opened;
Standard_EXPORT static Standard_Boolean WriteBinary (const Handle(StlMesh_Mesh)& aMesh, const OSD_Path& aPath, const Handle(Message_ProgressIndicator)& aProgInd = NULL);
Standard_EXPORT static Standard_Boolean WriteBinary (const Handle(Poly_Triangulation)& theMesh,
const OSD_Path& thePath,
const Handle(Message_ProgressIndicator)& theProgInd = Handle(Message_ProgressIndicator)());
//! write the meshing in a file following the
//! Ascii format of an STL file.
//! Returns false if the cannot be opened;
Standard_EXPORT static Standard_Boolean WriteAscii (const Handle(StlMesh_Mesh)& aMesh, const OSD_Path& aPath, const Handle(Message_ProgressIndicator)& aProgInd = NULL);
Standard_EXPORT static Standard_Boolean WriteAscii (const Handle(Poly_Triangulation)& theMesh,
const OSD_Path& thePath,
const Handle(Message_ProgressIndicator)& theProgInd = Handle(Message_ProgressIndicator)());
//! This method will chwck if the file is a binary
//! file or an AsciiFile testing the 5 first
//! characters of the file wich are :"solid" in an
//! ascii file. If we do not find that word we assume
//! that it is a binary file.
Standard_EXPORT static Handle(StlMesh_Mesh) ReadFile (const OSD_Path& aPath, const Handle(Message_ProgressIndicator)& aProgInd = NULL);
//! 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 Handle(Message_ProgressIndicator)& aProgInd = Handle(Message_ProgressIndicator)());
//! 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 Standard_CString theFile,
const Handle(Message_ProgressIndicator)& aProgInd = Handle(Message_ProgressIndicator)());
//! Read triangulation from a binary STL file
//! In case of error, returns Null handle.
Standard_EXPORT static Handle(Poly_Triangulation) ReadBinary (const OSD_Path& thePath,
const Handle(Message_ProgressIndicator)& theProgInd = Handle(Message_ProgressIndicator)());
//! Read a meshing from a binary file
//! Raises NoMoreObject from Standard if a statement
//! does not contain the right number of tokens
//! Raises TypeMisMatch if a token has not the good
//! type (often real)
Standard_EXPORT static Handle(StlMesh_Mesh) ReadBinary (const OSD_Path& aPath, const Handle(Message_ProgressIndicator)& aProgInd = NULL);
//! Read a meshing from a binary file
//! Raises NoMoreObject from Standard if a statement
//! does not contain the right number of tokens
//! Raises TypeMisMatch if a token has not the good
//! type (often real)
//! Raises MoMoreObject if a file is finished before
//! having found the word endsolid;
Standard_EXPORT static Handle(StlMesh_Mesh) ReadAscii (const OSD_Path& aPath, const Handle(Message_ProgressIndicator)& aProgInd = NULL);
protected:
//! Read triangulation from an Ascii STL file
//! In case of error, returns Null handle.
Standard_EXPORT static Handle(Poly_Triangulation) ReadAscii (const OSD_Path& thePath,
const Handle(Message_ProgressIndicator)& theProgInd = Handle(Message_ProgressIndicator)());
private:
//! Write ASCII version.
static Standard_Boolean writeASCII (const Handle(Poly_Triangulation)& theMesh,
FILE *theFile,
const Handle(Message_ProgressIndicator)& theProgInd);
//! Write binary version.
static Standard_Boolean writeBinary (const Handle(Poly_Triangulation)& theMesh,
FILE *theFile,
const Handle(Message_ProgressIndicator)& theProgInd);
};
#endif // _RWStl_HeaderFile
#endif

407
src/RWStl/RWStl_Reader.cxx Normal file
View File

@@ -0,0 +1,407 @@
// Created: 2016-05-01
// Author: Andrey Betenev
// Copyright: Open CASCADE 2016
//
// 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 <RWStl_Reader.hxx>
#include <gp_XY.hxx>
#include <Message.hxx>
#include <Message_Messenger.hxx>
#include <Message_ProgressSentry.hxx>
#include <NCollection_DataMap.hxx>
#include <NCollection_IncAllocator.hxx>
#include <FSD_BinaryFile.hxx>
#include <OSD_OpenFile.hxx>
#include <OSD_Timer.hxx>
#include <Precision.hxx>
#include <Standard_CLocaleSentry.hxx>
#include <algorithm>
#include <limits>
IMPLEMENT_STANDARD_RTTIEXT(RWStl_Reader, Standard_Transient)
namespace
{
// Binary STL sizes
static const size_t THE_STL_HEADER_SIZE = 84;
static const size_t THE_STL_SIZEOF_FACET = 50;
static const size_t THE_STL_MIN_FILE_SIZE = THE_STL_HEADER_SIZE + THE_STL_SIZEOF_FACET;
//! Auxiliary tool for merging nodes during STL reading.
class MergeNodeTool
{
public:
//! Constructor
MergeNodeTool (RWStl_Reader* theReader)
: myReader (theReader),
myMap (1024, new NCollection_IncAllocator (1024 * 1024))
{
}
//! Add new triangle
int AddNode (double theX, double theY, double theZ)
{
// use existing node if found at the same point
gp_XYZ aPnt (theX, theY, theZ);
Standard_Integer anIndex = -1;
if (myMap.Find (aPnt, anIndex))
{
return anIndex;
}
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();
}
static Standard_Integer HashCode (const gp_XYZ& thePnt, Standard_Integer theUpper)
{
return ::HashCode (thePnt.X() * M_LN10 + thePnt.Y() * M_PI + thePnt.Z() * M_E, theUpper);
}
private:
RWStl_Reader* myReader;
NCollection_DataMap<gp_XYZ, Standard_Integer, MergeNodeTool> myMap;
};
//! Read a Little Endian 32 bits float
inline static float readStlFloat (const char* theData)
{
#if OCCT_BINARY_FILE_DO_INVERSE
// on big-endian platform, map values byte-per-byte
union
{
uint32_t i;
float f;
} bidargum;
bidargum.i = theData[0] & 0xFF;
bidargum.i |= (theData[1] & 0xFF) << 0x08;
bidargum.i |= (theData[2] & 0xFF) << 0x10;
bidargum.i |= (theData[3] & 0xFF) << 0x18;
return bidargum.f;
#else
// on little-endian platform, use plain cast
return *reinterpret_cast<const float*>(theData);
#endif
}
//! Read a Little Endian 32 bits float
inline static gp_XYZ readStlFloatVec3 (const char* theData)
{
return gp_XYZ (readStlFloat (theData),
readStlFloat (theData + sizeof(float)),
readStlFloat (theData + sizeof(float) * 2));
}
}
//==============================================================================
//function : Read
//purpose :
//==============================================================================
Standard_Boolean RWStl_Reader::Read (const char* theFile,
const Handle(Message_ProgressIndicator)& theProgress)
{
std::filebuf aBuf;
OSD_OpenStream (aBuf, theFile, std::ios::in | std::ios::binary);
if (!aBuf.is_open())
{
return Standard_False;
}
Standard_IStream aStream (&aBuf);
if (IsAscii (aStream))
{
// get length of file to feed progress indicator
aStream.seekg (0, aStream.end);
std::streampos theEnd = aStream.tellg();
aStream.seekg (0, aStream.beg);
return ReadAscii (aStream, theEnd, theProgress);
}
else
{
return ReadBinary (aStream, theProgress);
}
}
//==============================================================================
//function : IsAscii
//purpose :
//==============================================================================
Standard_Boolean RWStl_Reader::IsAscii (Standard_IStream& theStream)
{
// read first 134 bytes to detect file format
char aBuffer[THE_STL_MIN_FILE_SIZE];
std::streamsize aNbRead = theStream.read (aBuffer, THE_STL_MIN_FILE_SIZE).gcount();
if (!theStream)
{
Message::DefaultMessenger()->Send ("Error: Cannot read file", Message_Fail);
return false;
}
// put back the read symbols
for (std::streamsize aByteIter = aNbRead; aByteIter > 0; --aByteIter)
{
theStream.unget();
}
// if file is shorter than size of binary file with 1 facet, it must be ascii
if (aNbRead < std::streamsize(THE_STL_MIN_FILE_SIZE))
{
return true;
}
// otherwise, detect binary format by presence of non-ascii symbols in first 128 bytes
// (note that binary STL file may start with the same bytes "solid " as Ascii one)
for (Standard_Integer aByteIter = 0; aByteIter < aNbRead; ++aByteIter)
{
if ((unsigned char )aBuffer[aByteIter] > (unsigned char )'~')
{
return false;
}
}
return true;
}
// adapted from Standard_CString.cxx
#ifdef __APPLE__
// There are a lot of *_l functions availalbe on Mac OS X - we use them
#define SAVE_TL()
#elif defined(_MSC_VER)
// MSVCRT has equivalents with slightly different syntax
#define SAVE_TL()
#define sscanf_l(theBuffer, theLocale, theFormat, ...) _sscanf_s_l(theBuffer, theFormat, theLocale, __VA_ARGS__)
#else
// glibc provides only limited xlocale implementation:
// strtod_l/strtol_l/strtoll_l functions with explicitly specified locale
// and newlocale/uselocale/freelocale to switch locale within current thread only.
// So we switch to C locale temporarily
#define SAVE_TL() Standard_CLocaleSentry aLocaleSentry;
#define sscanf_l(theBuffer, theLocale, theFormat, ...) sscanf(theBuffer, theFormat, __VA_ARGS__)
#endif
// Macro to get 64-bit position of the file from streampos
#if defined(_MSC_VER)
#define GETPOS(aPos) aPos.seekpos()
#else
#define GETPOS(aPos) ((int64_t)aPos)
#endif
static inline bool str_starts_with (const char* theStr, const char* theWord, int theN)
{
while (isspace (*theStr) && *theStr != '\0') theStr++;
return !strncmp (theStr, theWord, theN);
}
//==============================================================================
//function : ReadAscii
//purpose :
//==============================================================================
Standard_Boolean RWStl_Reader::ReadAscii (Standard_IStream& theStream,
const std::streampos theUntilPos,
const Handle(Message_ProgressIndicator)& theProgress)
{
// use method seekpos() to get true 64-bit offset to enable
// handling of large files (VS 2010 64-bit)
const int64_t aStartPos = GETPOS(theStream.tellg());
const int64_t aEndPos = (theUntilPos > 0 ? GETPOS(theUntilPos) : std::numeric_limits<int64_t>::max());
// skip header "solid ..."
theStream.ignore (aEndPos - aStartPos, '\n');
if (!theStream)
{
Message::DefaultMessenger()->Send ("Error: premature end of file", Message_Fail);
return false;
}
MergeNodeTool aMergeTool (this);
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
// report progress every 1 MiB of read data
const int aStepB = 1024 * 1024;
const Standard_Integer aNbSteps = 1 + Standard_Integer((theUntilPos - aStartPos) / aStepB);
Message_ProgressSentry aPSentry (theProgress, "Reading text STL file", 0, aNbSteps, 1);
int64_t aProgressPos = aStartPos + aStepB;
const int64_t LINELEN = 1024;
int aNbLine = 1;
char aLine1[LINELEN], aLine2[LINELEN], aLine3[LINELEN];
while (aPSentry.More())
{
if (GETPOS(theStream.tellg()) > aProgressPos)
{
aPSentry.Next();
aProgressPos += aStepB;
}
char facet[LINELEN], outer[LINELEN];
theStream.getline (facet, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); // "facet normal nx ny nz"
if (str_starts_with (facet, "endsolid", 8))
{
// end of STL code
break;
}
theStream.getline (outer, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg()))); // "outer loop"
if (!str_starts_with (facet, "facet", 5) || !str_starts_with (outer, "outer", 5))
{
TCollection_AsciiString aStr ("Error: unexpected format of facet at line ");
aStr += aNbLine + 1;
Message::DefaultMessenger()->Send (aStr, Message_Fail);
return false;
}
theStream.getline (aLine1, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg())));
theStream.getline (aLine2, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg())));
theStream.getline (aLine3, std::min (LINELEN, aEndPos - GETPOS(theStream.tellg())));
// stop reading if end of file is reached;
// note that well-formatted file never ends by the vertex line
if (theStream.eof() || GETPOS(theStream.tellg()) >= aEndPos)
{
break;
}
if (!theStream)
{
Message::DefaultMessenger()->Send ("Error: premature end of file", Message_Fail);
return false;
}
aNbLine += 5;
Standard_Real x1, y1, z1, x2, y2, z2, x3, y3, z3;
Standard_Integer aReadCount = // read 3 lines "vertex x y z"
sscanf_l (aLine1, aLocale, "%*s %lf %lf %lf", &x1, &y1, &z1) +
sscanf_l (aLine2, aLocale, "%*s %lf %lf %lf", &x2, &y2, &z2) +
sscanf_l (aLine3, aLocale, "%*s %lf %lf %lf", &x3, &y3, &z3);
if (aReadCount != 9)
{
TCollection_AsciiString aStr ("Error: cannot read vertex co-ordinates at line ");
aStr += aNbLine;
Message::DefaultMessenger()->Send(aStr, Message_Fail);
return false;
}
// add triangle
int n1 = aMergeTool.AddNode (x1, y1, z1);
int n2 = aMergeTool.AddNode (x2, y2, z2);
int n3 = aMergeTool.AddNode (x3, y3, z3);
if (n1 != n2 && n2 != n3 && n3 != n1)
{
AddTriangle (n1, n2, n3);
}
theStream.ignore (aEndPos - GETPOS(theStream.tellg()), '\n'); // skip "endloop"
theStream.ignore (aEndPos - GETPOS(theStream.tellg()), '\n'); // skip "endfacet"
aNbLine += 2;
}
return aPSentry.More();
}
//==============================================================================
//function : readStlBinary
//purpose :
//==============================================================================
Standard_Boolean RWStl_Reader::ReadBinary (Standard_IStream& theStream,
const Handle(Message_ProgressIndicator)& theProgress)
{
/*
// the size of the file (minus the header size)
// must be a multiple of SIZEOF_STL_FACET
if ((theFileLen - THE_STL_HEADER_SIZE) % THE_STL_SIZEOF_FACET != 0
|| (theFileLen < THE_STL_MIN_FILE_SIZE))
{
Message::DefaultMessenger()->Send ("Error: Corrupted binary STL file (inconsistent file size)!", Message_Fail);
return Standard_False;
}
const Standard_Integer aNbFacets = Standard_Integer((theFileLen - THE_STL_HEADER_SIZE) / THE_STL_SIZEOF_FACET);
*/
// read file header at first
char aHeader[THE_STL_HEADER_SIZE + 1];
if (theStream.read (aHeader, THE_STL_HEADER_SIZE).gcount() != std::streamsize(THE_STL_HEADER_SIZE))
{
Message::DefaultMessenger()->Send ("Error: Corrupted binary STL file!", Message_Fail);
return false;
}
// number of facets is stored as 32-bit integer at position 80
const Standard_Integer aNbFacets = *(int32_t*)(aHeader + 80);
MergeNodeTool aMergeTool (this);
// 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
Message_ProgressSentry aPSentry (theProgress, "Reading binary STL file", 0, aNbFacets, 1);
Standard_Integer aNbRead = 0;
// allocate buffer for 80 triangles
const int THE_CHUNK_NBFACETS = 80;
char aBuffer[THE_STL_SIZEOF_FACET * THE_CHUNK_NBFACETS];
// normal + 3 nodes + 2 extra bytes
const size_t aVec3Size = sizeof(float) * 3;
const size_t aFaceDataLen = aVec3Size * 4 + 2;
const char* aBufferPtr = aBuffer;
Standard_Integer aNbFacesInBuffer = 0;
for (Standard_Integer aNbFacetRead = 0; aNbFacetRead < aNbFacets && aPSentry.More();
++aNbFacetRead, ++aNbRead, --aNbFacesInBuffer, aBufferPtr += aFaceDataLen, aPSentry.Next())
{
// read more data
if (aNbFacesInBuffer <= 0)
{
aNbFacesInBuffer = Min (THE_CHUNK_NBFACETS, aNbFacets - aNbFacetRead);
const std::streamsize aDataToRead = aNbFacesInBuffer * aFaceDataLen;
if (theStream.read (aBuffer, aDataToRead).gcount() != aDataToRead)
{
Message::DefaultMessenger()->Send ("Error: read filed", Message_Fail);
return false;
}
aBufferPtr = aBuffer;
}
// 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)
{
AddTriangle (n1, n2, n3);
}
}
return true;
}

View File

@@ -0,0 +1,77 @@
// Created: 2016-05-01
// Author: Andrey Betenev
// Copyright: Open CASCADE 2016
//
// 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 _RWStl_Reader_HeaderFile
#define _RWStl_Reader_HeaderFile
#include <Message_ProgressIndicator.hxx>
#include <gp_XYZ.hxx>
//! An abstract class implementing procedure to read STL file.
//!
//! This class is not bound to particular data structure and can be used to read the file directly into arbitrary data model.
//! To use it, create descendant class and implement methods addNode() and addTriangle().
//!
//! Call method Read() to read the file. In the process of reading, the tool will call methods addNode() and addTriangle() to fill the mesh data structure.
//!
//! The nodes with equal coordinates are merged automatically on the fly.
class RWStl_Reader : public Standard_Transient
{
DEFINE_STANDARD_RTTIEXT(RWStl_Reader, Standard_Transient)
public:
//! Reads data from STL file (either binary or Ascii).
//! Unicode paths can be given in UTF-8 encoding.
//! Format is recognized automatically by analysis of the file header.
//! Returns true if success, false on error or user break.
Standard_EXPORT Standard_Boolean Read (const char* theFile,
const Handle(Message_ProgressIndicator)& theProgress);
//! Guess whether the stream is an Ascii STL file, by analysis of the first bytes (~200).
//! The function attempts to put back the read symbols to the stream which thus must support ungetc().
//! Returns true if the stream seems to contain Ascii STL.
Standard_EXPORT Standard_Boolean IsAscii (Standard_IStream& theStream);
//! Reads STL data from binary stream.
//! The stream must be opened in binary mode.
//! Stops after reading the number of triangles recorded in the file header.
//! Returns true if success, false on error or user break.
Standard_EXPORT Standard_Boolean ReadBinary (Standard_IStream& theStream,
const Handle(Message_ProgressIndicator)& theProgress);
//! Reads data from the stream assumed to contain Ascii STL data.
//! The stream can be opened either in binary or in Ascii mode.
//! Reading stops at the position specified by theUntilPos,
//! or end of file is reached, or when keyword "endsolid" is found.
//! Empty lines are not supported and will read to reading failure.
//! If theUntilPos is non-zero, reads not more than until that position.
//! Returns true if success, false on error or user break.
Standard_EXPORT Standard_Boolean ReadAscii (Standard_IStream& theStream,
const std::streampos theUntilPos,
const Handle(Message_ProgressIndicator)& theProgress);
public:
//! Callback function to be implemented in descendant.
//! Should create new node with specified coordinates in the target model, and return its ID as integer.
virtual Standard_Integer AddNode (const gp_XYZ& thePnt) = 0;
//! Callback function to be implemented in descendant.
//! 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;
};
#endif