Open Cascade Data Exchange STL


Open Cascade Data Exchange STL

eryar@163.com

摘要Abstract:介紹了三維數據交換格式STL的組成,以及Open Cascade中對STL的讀寫。並將Open Cascade讀進來的STL的三角面片在OpenSceneGraph中顯示。 

關鍵字Key Words:STL, Open Cascade, OpenSceneGraph, Data Exchange 

STL(the Stereo Lithograpy)是快速原型系統所應用的標准文件類型。它的目的是將幾何數據發送到可以讀取和解釋這些數據的機器,這種機器可將模型轉換成塑料的物理模型。STL是用三角網格來表示三維模型的。STL文件格式簡單,只能描述三維物體的幾何信息,不支持顏色、材質等信息,是三維打印機支持的最常見的文件格式。由於STL文件的網格表示方法只能表示封閉的形狀,所以要轉換的形狀必須是實體,或封閉的面和體。STL文件有兩種:一種是明碼(ASCII)格式,一種是二進制(Binary)格式。 

一、STL的明碼(ASCII)格式

ASCII格式的STL文件逐行給出三角面片的幾何信息,每行以1個或2個關鍵字開頭。STL文件中的三角面片的信息單元facet是一個帶法向方向的三角面片,STL三維模型就是由這一系列的三角面片構成。整個STL文件的首行給出了文件路徑及文件名。在一個STL文件中,每個facet由7行數據組成:facet normal是三角面片指向實體外部的單位法矢量;outer loop說明隨后的3行數據分別是三角面片的3個頂點坐標,3頂點沿指向實體外部的法矢量方向逆時針排列。 

ASCII格式的STL文件結構如下: 

wps_clip_image-30396

說明如下: 

wps_clip_image-7699

下面給出由Open Cascade中導出的一個長方體的STL文件: 

長方體的尺寸為長200,寬150,高100,原點在一個角點上。 

wps_clip_image-14545

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明碼文件可知,上述數據將一個長方體的6個面用12個三角形來表示。在OpenSceneGraph中顯示效果如下圖所示,分別為此長方體的實體渲染模式和線框渲染模式: 

wps_clip_image-30014 wps_clip_image-7473

Figure 1.2 Shaded and Wireframe box in OpenSceneGraph 

二、STL的二進制(Binary)格式

二進制的STL文件用固定的字節數來給出三角面片的幾何信息。文件起始80個字節是文件頭,用於存貯零件名;緊接着4個字節的整數來描述模型的三角面片個數;后面逐個給出每個三角面片的幾何信息。每個三角面片用固定的50個字節,依次是表示三角面片的法矢量的3個4字節浮點數;表示三角面片三個頂點的3x3個4字節浮點數;最后2個字節用來描述三角面片的屬性信息。 

wps_clip_image-17429

三、OCC中STL文件的讀寫Read/Write STL in Open Cascade

在Open Cascade中STL文件的讀寫分別使用類:StlAPI_Reader/StlAPI_Writer來實現。查看源程序可知,寫STL文件的步驟如下: 

l 遍歷一個TopoDS_Shape所有的面Face; 

l 使用工具BRep_Tool::Triangulation將每個面Face三角面片化; 

l 計算每個三角面片的法矢量; 

l 將結果寫入文件。 

類RWStl對STL的讀定也是有兩種格式,即ASCII格式和Binary格式: 

n RWStl::WriteBinary 

n RWStl::WriteAscii 

n RWStl::ReadBinary 

n RWStl::ReadAscii 

程序的具體實現可以查看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_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();)
  {
    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"
    fscanf(file,"%*s %*s");

    // 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 = ReadMesh->AddOnlyNewVertex (aV1.X(), aV1.Y(), aV1.Z());
    i2 = ReadMesh->AddOnlyNewVertex (aV2.X(), aV2.Y(), aV2.Z());
    i3 = ReadMesh->AddOnlyNewVertex (aV3.X(), aV3.Y(), aV3.Z());
    ReadMesh->AddTriangle (i1, i2, i3, aN.X(), aN.Y(), aN.Z());

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

分別對應二進制文件中相關信息,即文件頭84個字節,每個三角面片50個字節,STL文件最小為284字節。ASCII的STL中每個三角面有7行。 

在數據的讀寫過程中,對數據進行了小端轉換。將double數據轉換成小端表示的代碼如下所示: 

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

使用聯合體(union)來處理顯得很優雅。關於大端、小端的相關信息請參考: 

http://www.cppblog.com/tx7do/archive/2009/01/06/71276.html 

四、在OpenSceneGraph中顯示STL

結合OpenCascade中對STL文件讀寫的功能和OpenSceneGraph的顯示功能,將STL讀取所得數據進行顯示。源程序如下所示: 

// 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提供的幾個STL文件在OpenSceneGraph中顯示的效果: 

wps_clip_image-4060

Figure 4.1 Shaded Piston 

wps_clip_image-21794

Figure 4.2 Wireframe Piston 

wps_clip_image-3504

Figure 4.3 Shaded Propeller 

wps_clip_image-1112

Figure 4.4 Wireframe Propeller 

五、結論 

通過使用OpenCascade的類RWStl來讀取STL格式的文件,理解了STL文件格式;通過將讀取的三角面面片數據在OpenSceneGraph中顯示,對三維物體在計算機中的表示有了感性的認識。 

六、參考資料 

1. OpenCascade中類RWStl.cxx 

2. OpenCascade中STL模型數據 

3. 字節序、大端、小端:http://www.cppblog.com/tx7do/archive/2009/01/06/71276.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM