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:
@@ -1,2 +1,4 @@
|
||||
RWStl.cxx
|
||||
RWStl.hxx
|
||||
RWStl_Reader.cxx
|
||||
RWStl_Reader.hxx
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
407
src/RWStl/RWStl_Reader.cxx
Normal 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;
|
||||
}
|
77
src/RWStl/RWStl_Reader.hxx
Normal file
77
src/RWStl/RWStl_Reader.hxx
Normal 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
|
Reference in New Issue
Block a user