|
Open Cascade Data Exchange --- STL
eryar@163.com
摘要Abstract:介紹了三維數(shù)據(jù)交換格式STL的組成,以及Open Cascade中對(duì)STL的讀寫。并將Open Cascade讀進(jìn)來的STL的三角面片在OpenSceneGraph中顯示。
關(guān)鍵字Key Words:STL, Open Cascade, OpenSceneGraph, Data Exchange
STL(the Stereo Lithograpy)是快速原型系統(tǒng)所應(yīng)用的標(biāo)準(zhǔn)文件類型。它的目的是將幾何數(shù)據(jù)發(fā)送到可以讀取和解釋這些數(shù)據(jù)的機(jī)器,這種機(jī)器可將模型轉(zhuǎn)換成塑料的物理模型。STL是用三角網(wǎng)格來表示三維模型的。STL文件格式簡單,只能描述三維物體的幾何信息,不支持顏色、材質(zhì)等信息,是三維打印機(jī)支持的最常見的文件格式。由于STL文件的網(wǎng)格表示方法只能表示封閉的形狀,所以要轉(zhuǎn)換的形狀必須是實(shí)體,或封閉的面和體。STL文件有兩種:一種是明碼(ASCII)格式,一種是二進(jìn)制(Binary)格式。
一、STL的明碼(ASCII)格式
ASCII格式的STL文件逐行給出三角面片的幾何信息,每行以1個(gè)或2個(gè)關(guān)鍵字開頭。STL文件中的三角面片的信息單元facet是一個(gè)帶法向方向的三角面片,STL三維模型就是由這一系列的三角面片構(gòu)成。整個(gè)STL文件的首行給出了文件路徑及文件名。在一個(gè)STL文件中,每個(gè)facet由7行數(shù)據(jù)組成:facet normal是三角面片指向?qū)嶓w外部的單位法矢量;outer loop說明隨后的3行數(shù)據(jù)分別是三角面片的3個(gè)頂點(diǎn)坐標(biāo),3頂點(diǎn)沿指向?qū)嶓w外部的法矢量方向逆時(shí)針排列。
ASCII格式的STL文件結(jié)構(gòu)如下:
說明如下:
下面給出由Open Cascade中導(dǎo)出的一個(gè)長方體的STL文件:
長方體的尺寸為長200,寬150,高100,原點(diǎn)在一個(gè)角點(diǎn)上。
Figure 1.1 Box in Open Cascade
solid

facet normal -1.000000e+000 -0.000000e+000 -0.000000e+000

outer loop

vertex 0.000000e+000 1.500000e+002 1.000000e+002

vertex 0.000000e+000 1.500000e+002 0.000000e+000

vertex 0.000000e+000 0.000000e+000 1.000000e+002

endloop

endfacet

facet normal -1.000000e+000 0.000000e+000 0.000000e+000

outer loop

vertex 0.000000e+000 1.500000e+002 0.000000e+000

vertex 0.000000e+000 0.000000e+000 0.000000e+000

vertex 0.000000e+000 0.000000e+000 1.000000e+002

endloop

endfacet

facet normal 1.000000e+000 -0.000000e+000 0.000000e+000

outer loop

vertex 2.000000e+002 0.000000e+000 1.000000e+002

vertex 2.000000e+002 1.500000e+002 0.000000e+000

vertex 2.000000e+002 1.500000e+002 1.000000e+002

endloop

endfacet

facet normal 1.000000e+000 -0.000000e+000 0.000000e+000

outer loop

vertex 2.000000e+002 0.000000e+000 1.000000e+002

vertex 2.000000e+002 0.000000e+000 0.000000e+000

vertex 2.000000e+002 1.500000e+002 0.000000e+000

endloop

endfacet

facet normal 0.000000e+000 -1.000000e+000 0.000000e+000

outer loop

vertex 0.000000e+000 0.000000e+000 0.000000e+000

vertex 2.000000e+002 0.000000e+000 0.000000e+000

vertex 2.000000e+002 0.000000e+000 1.000000e+002

endloop

endfacet

facet normal 0.000000e+000 -1.000000e+000 0.000000e+000

outer loop

vertex 0.000000e+000 0.000000e+000 1.000000e+002

vertex 0.000000e+000 0.000000e+000 0.000000e+000

vertex 2.000000e+002 0.000000e+000 1.000000e+002

endloop

endfacet

facet normal 0.000000e+000 1.000000e+000 0.000000e+000

outer loop

vertex 2.000000e+002 1.500000e+002 1.000000e+002

vertex 2.000000e+002 1.500000e+002 0.000000e+000

vertex 0.000000e+000 1.500000e+002 0.000000e+000

endloop

endfacet

facet normal 0.000000e+000 1.000000e+000 -0.000000e+000

outer loop

vertex 2.000000e+002 1.500000e+002 1.000000e+002

vertex 0.000000e+000 1.500000e+002 0.000000e+000

vertex 0.000000e+000 1.500000e+002 1.000000e+002

endloop

endfacet

facet normal 0.000000e+000 0.000000e+000 -1.000000e+000

outer loop

vertex 0.000000e+000 0.000000e+000 0.000000e+000

vertex 0.000000e+000 1.500000e+002 0.000000e+000

vertex 2.000000e+002 1.500000e+002 0.000000e+000

endloop

endfacet

facet normal 0.000000e+000 0.000000e+000 -1.000000e+000

outer loop

vertex 2.000000e+002 0.000000e+000 0.000000e+000

vertex 0.000000e+000 0.000000e+000 0.000000e+000

vertex 2.000000e+002 1.500000e+002 0.000000e+000

endloop

endfacet

facet normal 0.000000e+000 0.000000e+000 1.000000e+000

outer loop

vertex 2.000000e+002 1.500000e+002 1.000000e+002

vertex 0.000000e+000 1.500000e+002 1.000000e+002

vertex 0.000000e+000 0.000000e+000 1.000000e+002

endloop

endfacet

facet normal -0.000000e+000 0.000000e+000 1.000000e+000

outer loop

vertex 2.000000e+002 1.500000e+002 1.000000e+002

vertex 0.000000e+000 0.000000e+000 1.000000e+002

vertex 2.000000e+002 0.000000e+000 1.000000e+002

endloop

endfacet

endsolid


由上面的STL明碼文件可知,上述數(shù)據(jù)將一個(gè)長方體的6個(gè)面用12個(gè)三角形來表示。在OpenSceneGraph中顯示效果如下圖所示,分別為此長方體的實(shí)體渲染模式和線框渲染模式:
Figure 1.2 Shaded and Wireframe box in OpenSceneGraph
二、STL的二進(jìn)制(Binary)格式
二進(jìn)制的STL文件用固定的字節(jié)數(shù)來給出三角面片的幾何信息。文件起始80個(gè)字節(jié)是文件頭,用于存貯零件名;緊接著4個(gè)字節(jié)的整數(shù)來描述模型的三角面片個(gè)數(shù);后面逐個(gè)給出每個(gè)三角面片的幾何信息。每個(gè)三角面片用固定的50個(gè)字節(jié),依次是表示三角面片的法矢量的3個(gè)4字節(jié)浮點(diǎn)數(shù);表示三角面片三個(gè)頂點(diǎn)的3x3個(gè)4字節(jié)浮點(diǎn)數(shù);最后2個(gè)字節(jié)用來描述三角面片的屬性信息。
三、OCC中STL文件的讀寫Read/Write STL in Open Cascade
在Open Cascade中STL文件的讀寫分別使用類:StlAPI_Reader/StlAPI_Writer來實(shí)現(xiàn)。查看源程序可知,寫STL文件的步驟如下:
l 遍歷一個(gè)TopoDS_Shape所有的面Face;
l 使用工具BRep_Tool::Triangulation將每個(gè)面Face三角面片化;
l 計(jì)算每個(gè)三角面片的法矢量;
l 將結(jié)果寫入文件。
類RWStl對(duì)STL的讀定也是有兩種格式,即ASCII格式和Binary格式:
n RWStl::WriteBinary
n RWStl::WriteAscii
n RWStl::ReadBinary
n RWStl::ReadAscii
程序的具體實(shí)現(xiàn)可以查看Open Cascade源代碼,將讀寫部分主要代碼RWStl.cxx列出如下:
// Created on: 1994-10-13
// Created by: Marc LEGAY
// Copyright (c) 1994-1999 Matra Datavision
// Copyright (c) 1999-2012 OPEN CASCADE SAS
//
// The content of this file is subject to the Open CASCADE Technology Public
// License Version 6.5 (the "License"). You may not use the content of this file
// except in compliance with the License. Please obtain a copy of the License
// at http://www.opencascade.org and read it completely before using this file.
//
// The Initial Developer of the Original Code is Open CASCADE S.A.S., having its
// main offices at: 1, place des Freres Montgolfier, 78280 Guyancourt, France.
//
// The Original Code and all software distributed under the License is
// distributed on an "AS IS" basis, without warranty of any kind, and the
// Initial Developer hereby disclaims all such warranties, including without
// limitation, any warranties of merchantability, fitness for a particular
// purpose or non-infringement. Please see the License for the specific terms
// and conditions governing the rights and limitations under the License.



#include <RWStl.ixx>
#include <OSD_Protection.hxx>
#include <OSD_File.hxx>
#include <Message_ProgressSentry.hxx>
#include <TCollection_AsciiString.hxx>
#include <Standard_NoMoreObject.hxx>
#include <Standard_TypeMismatch.hxx>
#include <Precision.hxx>
#include <StlMesh_MeshExplorer.hxx>
#include <OSD.hxx>
#include <OSD_Host.hxx>
#include <gp_XYZ.hxx>
#include <gp.hxx>
#include <stdio.h>
#include <gp_Vec.hxx>


// constants
static const int HEADER_SIZE = 84;
static const int SIZEOF_STL_FACET = 50;
static const int STL_MIN_FILE_SIZE = 284;
static const int 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)
  {
 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));
}

//=======================================================================
//function : WriteDouble2Float
//purpose : writing a Little Endian 32 bits float
//=======================================================================

inline static void WriteDouble2Float(OSD_File& ofile,Standard_Real value)
  {
 union {
Standard_ShortReal f;
char c[4];
} bidargum;

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));
}


//=======================================================================
//function : readFloat2Double
//purpose : reading a Little Endian 32 bits float
//=======================================================================

inline static Standard_Real ReadFloat2Double(OSD_File &aFile)
  {
 union {
Standard_Boolean i; // don't be afraid, this is just an unsigned int
Standard_ShortReal f;
}bidargum;

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;

return (Standard_Real)(bidargum.f);
}



//=======================================================================
//function : WriteBinary
//purpose : write a binary STL file in Little Endian format
//=======================================================================

Standard_Boolean RWStl::WriteBinary (const Handle(StlMesh_Mesh)& theMesh,
const OSD_Path& thePath,
const Handle(Message_ProgressIndicator)& theProgInd)
  {
OSD_File aFile (thePath);
aFile.Build (OSD_WriteOnly, OSD_Protection());

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())
 {
// 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();
}
}
}
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,
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();

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())
 {
// 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);

// 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,
" facet normal % 12e % 12e % 12e\n"
" outer loop\n"
" vertex % 12e % 12e % 12e\n"
" vertex % 12e % 12e % 12e\n"
" 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();

// update progress only per 1k triangles
if (++aTriangleInd % IND_THRESHOLD == 0)
 {
if (!aTPS.More())
break;
aTPS.Next();
}
}
}

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 DEB
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 NBFACET;
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_Integer filesize = theFile.Size();

if ( (filesize - HEADER_SIZE) % SIZEOF_STL_FACET !=0
 || (filesize < STL_MIN_FILE_SIZE)) {
Standard_NoMoreObject::Raise("RWStl::ReadBinary (wrong file size)");
}

// 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
NBFACET = ((filesize - HEADER_SIZE) / SIZEOF_STL_FACET);

// skip the header
theFile.Seek(HEADER_SIZE,OSD_FromBeginning);

// create the StlMesh_Mesh object
Handle(StlMesh_Mesh) ReadMesh = new StlMesh_Mesh ();
ReadMesh->AddDomain ();

 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);

i1 = ReadMesh->AddOnlyNewVertex (fx1,fy1,fz1);
i2 = ReadMesh->AddOnlyNewVertex (fx2,fy2,fz2);
i3 = ReadMesh->AddOnlyNewVertex (fx3,fy3,fz3);
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_ShortReal x[4],y[4],z[4];
Standard_Integer i1,i2,i3;
Handle(StlMesh_Mesh) ReadMesh;

thePath.SystemName (filename);

// Open the file
FILE* file = fopen(filename.ToCString(),"r");

fseek(file,0L,SEEK_END);

long filesize = ftell(file);

fclose(file);
file = fopen(filename.ToCString(),"r");



// 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
// fclose(file);
// file = fopen(filename.ToCString(),"r");
rewind(file);

// skip header
while (getc(file) != '\n');
#ifdef DEB
cout << "start mesh\n";
#endif
ReadMesh = new StlMesh_Mesh();
ReadMesh->AddDomain();

// main reading
Message_ProgressSentry aPS (theProgInd, "Triangles", 0, (nbTris - 1) * 1.0 / IND_THRESHOLD, 1);
for (iTri = 0; iTri < nbTris && aPS.More();)
 {
// reading the facet normal
fscanf(file,"%*s %*s %f %f %f\n",&x[0],&y[0],&z[0]);

// skip the keywords "outer loop"
fscanf(file,"%*s %*s");

// reading vertex
fscanf(file,"%*s %f %f %f\n",&x[1],&y[1],&z[1]);
fscanf(file,"%*s %f %f %f\n",&x[2],&y[2],&z[2]);
fscanf(file,"%*s %f %f %f\n",&x[3],&y[3],&z[3]);

// here the facet must be built and put in the mesh datastructure

i1 = ReadMesh->AddOnlyNewVertex ((Standard_Real)x[1],(Standard_Real)y[1],(Standard_Real)z[1]);
i2 = ReadMesh->AddOnlyNewVertex ((Standard_Real)x[2],(Standard_Real)y[2],(Standard_Real)z[2]);
i3 = ReadMesh->AddOnlyNewVertex ((Standard_Real)x[3],(Standard_Real)y[3],(Standard_Real)z[3]);
ReadMesh->AddTriangle (i1,i2,i3,(Standard_Real)x[0],(Standard_Real)y[0],(Standard_Real)z[0]);

// skip the keywords "endloop"
fscanf(file,"%*s");

// skip the keywords "endfacet"
fscanf(file,"%*s");

// update progress only per 1k triangles
if (++iTri % IND_THRESHOLD == 0)
aPS.Next();
}
#ifdef DEB
cout << "end mesh\n";
#endif
fclose(file);
return ReadMesh;

}

程序開始定義了一些常量:
// constants
static const int HEADER_SIZE = 84;
static const int SIZEOF_STL_FACET = 50;
static const int STL_MIN_FILE_SIZE = 284;
static const int ASCII_LINES_PER_FACET = 7;
static const int IND_THRESHOLD = 1000; // increment the indicator every 1k triangles

分別對(duì)應(yīng)二進(jìn)制文件中相關(guān)信息,即文件頭84個(gè)字節(jié),每個(gè)三角面片50個(gè)字節(jié),STL文件最小為284字節(jié)。ASCII的STL中每個(gè)三角面有7行。
在數(shù)據(jù)的讀寫過程中,對(duì)數(shù)據(jù)進(jìn)行了小端轉(zhuǎn)換。將double數(shù)據(jù)轉(zhuǎn)換成小端表示的代碼如下所示:
//=======================================================================
//function : WriteDouble2Float
//purpose : writing a Little Endian 32 bits float
//=======================================================================

inline static void WriteDouble2Float(OSD_File& ofile,Standard_Real value)
  {
 union {
Standard_ShortReal f;
char c[4];
} bidargum;

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));
}
使用聯(lián)合體(union)來處理顯得很優(yōu)雅。關(guān)于大端、小端的相關(guān)信息請(qǐng)參考:
http://www.shnenglu.com/tx7do/archive/2009/01/06/71276.html
四、在OpenSceneGraph中顯示STL
結(jié)合OpenCascade中對(duì)STL文件讀寫的功能和OpenSceneGraph的顯示功能,將STL讀取所得數(shù)據(jù)進(jìn)行顯示。源程序如下所示:

// Open Cascade
#include <gp_Vec.hxx>
#include <OSD_Path.hxx>
#include <RWStl.hxx>
#include <StlMesh_Mesh.hxx>
#include <StlMesh_MeshExplorer.hxx>

#pragma comment(lib, "TKernel.lib")
#pragma comment(lib, "TKMath.lib")
#pragma comment(lib, "TKSTL.lib")

// OpenSceneGraph
#include <osgDB/ReadFile>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include <osgGA/StateSetManipulator>

#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDbd.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")

osg::Node* readSTLFile(const std::string& fileName)
  {
osg::Group* root = new osg::Group();

OSD_Path stlFile(fileName.c_str());
Handle_StlMesh_Mesh stlMesh = RWStl::ReadFile(stlFile);
Standard_Integer nDomains = stlMesh->NbDomains();
StlMesh_MeshExplorer meshExplorer(stlMesh);

 Standard_Real x[3] = {0};
 Standard_Real y[3] = {0};
 Standard_Real z[3] = {0};
 Standard_Real n[3] = {0};

gp_XYZ p1;
gp_XYZ p2;
gp_XYZ p3;
gp_XYZ normal;
gp_Vec vecNormal;

for (int i = 1; i <= nDomains; i++)
 {
for (meshExplorer.InitTriangle(i); meshExplorer.MoreTriangle(); meshExplorer.NextTriangle())
 {
meshExplorer.TriangleVertices(x[0], y[0], z[0], x[1], y[1], z[1], x[2], y[2], z[2]);
meshExplorer.TriangleOrientation(n[0], n[1], n[2]);

p1.SetCoord(x[0], y[0], z[0]);
p2.SetCoord(x[1], y[1], z[1]);
p3.SetCoord(x[2], y[2], z[2]);
normal.SetCoord(n[0], n[1], n[2]);

//gp_Vec vec12((x[1] - x[0]), (y[1] - y[0]), (z[1] - z[0]));
//gp_Vec vec23((x[2] - x[1]), (y[2] - y[1]), (z[2] - z[1]));
//vecNormal = vec12.Crossed(vec23).Normalized();

osg::ref_ptr<osg::Geode> geode = new osg::Geode();
osg::ref_ptr<osg::Geometry> triGeom = new osg::Geometry();
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array();
osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array();
vertices->push_back(osg::Vec3(x[0], y[0], z[0]));
vertices->push_back(osg::Vec3(x[1], y[1], z[1]));
vertices->push_back(osg::Vec3(x[2], y[2], z[2]));

normals->push_back(osg::Vec3(n[0], n[1], n[2]));

triGeom->setVertexArray(vertices.get());
triGeom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, vertices->size()));

triGeom->setNormalArray(normals);
triGeom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE);

geode->addDrawable(triGeom);

root->addChild(geode);
}
}

return root;
}

int main(int argc, char* argv[])
  {
osgViewer::Viewer myViewer;
osg::ref_ptr<osg::Group> root = new osg::Group();

//root->addChild(readSTLFile("D:\\OpenCASCADE6.5.0\\data\\stl\\propeller.stl"));
root->addChild(readSTLFile("D:\\OpenCASCADE6.5.0\\data\\stl\\sh1.stl"));
//root->addChild(readSTLFile("D:\\OpenCASCADE6.5.0\\data\\stl\\motor.stl"));
//root->addChild(readSTLFile("D:\\box.stl"));
myViewer.setSceneData(root);

myViewer.addEventHandler(new osgGA::StateSetManipulator(myViewer.getCamera()->getOrCreateStateSet()));
myViewer.addEventHandler(new osgViewer::StatsHandler);
myViewer.addEventHandler(new osgViewer::WindowSizeHandler);

return myViewer.run();
}
以下所示為OpenCascade提供的幾個(gè)STL文件在OpenSceneGraph中顯示的效果:
Figure 4.1 Shaded Piston
Figure 4.2 Wireframe Piston
Figure 4.3 Shaded Propeller
Figure 4.4 Wireframe Propeller
五、結(jié)論
通過使用OpenCascade的類RWStl來讀取STL格式的文件,理解了STL文件格式;通過將讀取的三角面面片數(shù)據(jù)在OpenSceneGraph中顯示,對(duì)三維物體在計(jì)算機(jī)中的表示有了感性的認(rèn)識(shí)。
六、參考資料
1. OpenCascade中類RWStl.cxx
2. OpenCascade中STL模型數(shù)據(jù)
3. 字節(jié)序、大端、小端:http://www.shnenglu.com/tx7do/archive/2009/01/06/71276.html
Feedback
# re: Open Cascade Data Exchange --- STL 回復(fù) 更多評(píng)論
2018-09-05 20:00 by
博主您好,我如何在opencascade里讀取.sat文件?
# re: Open Cascade Data Exchange --- STL 回復(fù) 更多評(píng)論
2018-09-05 20:09 by
@birds opencascade開源部分沒有這個(gè)功能,要么使用他收費(fèi)的模塊,,要么使用其他SDK。
|