zoukankan      html  css  js  c++  java
  • 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
    ");
      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
    "
              "   outer loop
    "
              "     vertex % 12e % 12e % 12e
    "
              "     vertex % 12e % 12e % 12e
    "
              "     vertex % 12e % 12e % 12e
    "
              "   endloop
    "
              " endfacet
    ",
              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
    ";
      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
    " : "binary
    ");
    #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) == '
    ')
            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) != '
    ');
    #ifdef DEB
      cout << "start mesh
    ";
    #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
    ", 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
    ", 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
    ", 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
    ", 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
    ";
    #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

  • 相关阅读:
    【codeforces 785B】Anton and Classes
    【codeforces 785C】Anton and Fairy Tale
    【t003】string
    【BZOJ 1028】[JSOI2007]麻将
    【t011】最小覆盖子串
    【BZOJ 1029】[JSOI2007]建筑抢修
    cgb2008-京淘day02
    抽象类与接口概念及代码实例
    【hihocoder 1296】数论三·约瑟夫问题
    【hihocoder 1295】Eular质数筛法
  • 原文地址:https://www.cnblogs.com/opencascade/p/3507828.html
Copyright © 2011-2022 走看看