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文件结构如下:

说明如下:

下面给出由Open Cascade中导出的一个长方体的STL文件:

长方体的尺寸为长200,宽150,高100,原点在一个角点上。

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中显示效果如下图所示,分别为此长方体的实体渲染模式和线框渲染模式:

Figure 1.2 Shaded and Wireframe box in OpenSceneGraph

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

二进制的STL文件用固定的字节数来给出三角面片的几何信息。文件起始80个字节是文件头,用于存贮零件名;紧接着4个字节的整数来描述模型的三角面片个数;后面逐个给出每个三角面片的几何信息。每个三角面片用固定的50个字节,依次是表示三角面片的法矢量的3个4字节浮点数;表示三角面片三个顶点的3x3个4字节浮点数;最后2个字节用来描述三角面片的属性信息。

三、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_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

 

分别对应二进制文件中相关信息,即文件头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中显示的效果:

Figure 4.1 Shaded Piston

Figure 4.2 Wireframe Piston

Figure 4.3 Shaded Propeller

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

 

PDF Version: Open Cascade DataExchange STL

时间: 2024-09-14 04:38:52

Open Cascade Data Exchange --- STL的相关文章

Model Data Exchange Change Log

Model Data Exchange Change Log eryar@163.com 关键字:AVEVA Plant.PDMS.ModelDataExchange.IGES.STEP.STL.OpenCascade 1. 导出元素不限制为设备(Equipment),可以是任意选中的元素,如SITE.ZONE等等. 2. 可以导出除了管嘴外所有设备模型,包括负实体(Negative Primitives): 3. 可以导出的三维数据文件格式有:IGES.STEP.STL.3DS: 效果图如下所

AVEVA Model Data Exchange

 AVEVA Model Data Exchange Download ModelDataExchange V0.1: the ModelDataExchange Addin 1. Introduction The Model Data Exchange Addin allows the user to extract 3D geometric models from the DESIGN databases and output them in STEP, IGES, STL format.

AVEVA Model Data Exchange Exports Structure Models

AVEVA Model Data Exchange Exports Structure Modelseryar@163.com   Use Model Data Exchange Addin to export structure models for PDMS: Figure 1.1 Structure models in AVEVA PDMS Figure 1.2 Structure models exported by Model Data Exchange Figure 1.3 Put

OpenCASCADE Data Exchange - 3D PDF

OpenCASCADE Data Exchange - 3D PDF eryar@163.com Abstract. Today most 3D engineering model data are save to 3D PDF files. Universal 3D(U3D) along side Product Representation Compact(PRC), U3D is the historical foundation used to embed 3D interactive

AVEVA Model Data Exchange V0.2

Download: AVEVA Model Data Exchange V0.2 AVEVA Model Data Exchange V0.2 增加导出3ds格式功能:

AVEVA Model Data Exchange ChangeLog

配置方法见程序文档说明或:http://www.cppblog.com/eryar/archive/2013/05/01/199875.html   RvmTranslator2.1  [20150818]----------------1. Improve performance for STEP translation;2. Finish parse all the primitive shapes in RVM file; RvmTranslator2.0  [20150504]-----

Model Data Exchange Change Log 2

eryar@163.com 可以导出管嘴模型: 可以导出设备所有模型. Figure 1.1 Equipment models in PDMS Figure 1.2 Exported equipment models include nozzles Figure 2. 1 Pumps and tanks equipments in PDMS Figure 2.2 Pumps and tanks exported by ModelDataExchange

OpenCASCADE Outline

OpenCASCADE Outline eryar@163.com      有网友反映blog中关于OpenCASCADE的文章比较杂乱,不太好找,最好能提供一个大纲,这样方便查找.于是决定将这些学习时写的文章整理下,方便对OpenCASCADE的学习理解.其实在http://www.cnblogs.com/opencascade中,已经将文章按目录重新发表了一遍.可以按OpenCASCADE的模块的顺序来学习,也可以挑选自己感兴趣的部分来学习.      由于本人水平所限,文中的错误不妥之处

Open Cascade DataExchange DXF

Open Cascade DataExchange DXF eryar@163.com 摘要Abstract:对DXF文本格式进行详细介绍,并介绍了如何使用开源库dxflib对DXF文件进行读写操作,并将DXF文件中图形导入到OpenCascade. 关键字Key Words:DXF.Open Cascade.Data Exchange, dxflib 一.引言 Introduction 目前市面上的CAD系统都有自己的数据文件,各个系统之间的数据结构和格式各不相同,这样极大影响了设计和制造部门