OpenCascade源码分析之StlAPI(网格离散化后导出stl文件)

OpenCascade源码分析之StlAPI(网格离散化后导出stl文件)

奈何调库满足不了项目需求,并且网上没有详细资料啊 大家都偷懒的话只能自己来分析了,那就分析一下源码 看它对曲面的离散的步骤是怎么样的吧

一、StlAPI.hxx|StlAPI.cxx

我们在调用将离散网格数据写入stl的时候,一般会用到如下方法:

	//需要离散的拓扑Shape
	TopoDS_Shape cur;
	//保存的文件名
	const char* name = "a.stl";
	StlAPI::Write(cur,name);

那么对于Write方法是如何运作的呢,接下来来到源码部分

//=============================================================================
//function : Write
//purpose  :
//=============================================================================
Standard_Boolean StlAPI::Write (const TopoDS_Shape&    theShape,
                                const Standard_CString theFile,
                                const Standard_Boolean theAsciiMode)
{
  StlAPI_Writer aWriter;
  aWriter.ASCIIMode() = theAsciiMode;
  return aWriter.Write (theShape, theFile);
}

代码中调用了 StlAPI_Writer.hxx 同时设置了写文件是以文本格式还是二进制模式输出

接下来来到StlAPI_Writer部分查看实现

二、StlAPI_Writer.hxx | StlAPI_Writer.cxx

首先补充几个步骤 方便之后的代码阅读
关于Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation (TopoDS::Face (aFace), aLoc);
Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation (TopoDS::Face (aFace), aLoc);

该句代码主要调用了

const Poly_ListOfTriangulation& BRep_Tool::Triangulations (const TopoDS_Face& theFace,
                                                           TopLoc_Location&   theLocation)
{
  theLocation = theFace.Location();
  const BRep_TFace* aTFace = static_cast<const BRep_TFace*>(theFace.TShape().get());
  return aTFace->Triangulations();
}
//而aTFace调用了下面的方法
//=======================================================================
//function : Triangulation
//purpose  :
//=======================================================================
const Handle(Poly_Triangulation)& BRep_TFace::Triangulation (const Poly_MeshPurpose thePurpose) const
{
  //这边thePurpose默认为NONE
  if (thePurpose == Poly_MeshPurpose_NONE)
  {
    return ActiveTriangulation();
  }
  xxxxx //之后的代码 没用到 就不解读了先删除
}
//ActiveTrigulation()返回了成员Handle(Poly_Triangulation) myActiveTriangulation;
const Handle(Poly_Triangulation)& ActiveTriangulation() const { return myActiveTriangulation; }

此文件中只实现了一个方法,就是Write方法
引用的头文件为:

#include <StlAPI_Writer.hxx>

#include <Bnd_Box.hxx>
#include <BRepBndLib.hxx>
#include <Message.hxx>
#include <Message_Messenger.hxx>
#include <OSD_Path.hxx>
#include <OSD_OpenFile.hxx>
#include <RWStl.hxx>
#include <BRep_Tool.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <TopExp_Explorer.hxx>
#include <Poly_Triangulation.hxx>

实现部分:(解读部分写成注释了)

Standard_Boolean StlAPI_Writer::Write (const TopoDS_Shape&    theShape,
                                       const Standard_CString theFileName,
                                       const Message_ProgressRange& theProgress)
{
  Standard_Integer aNbNodes = 0;
  Standard_Integer aNbTriangles = 0;

  // 计算离散之后点的总数目和面的总数目
  //例如:一个圆柱体由三个TopoDS_Face组成,顶面、底面、侧面,对每个面进行离散化后
  //     每个面上都会产生三角面和顶点,计算总数即可。
  for (TopExp_Explorer anExpSF (theShape, TopAbs_FACE); anExpSF.More(); anExpSF.Next())
  {
    TopLoc_Location aLoc;
    Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation (TopoDS::Face (anExpSF.Current()), aLoc);
    if (! aTriangulation.IsNull())
    {
      aNbNodes += aTriangulation->NbNodes ();
      aNbTriangles += aTriangulation->NbTriangles ();
    }
  }

  //这里如果没有执行过BRepMesh_IncrementalMesh的离散的话
  //TopoDS_Face是没有三角信息的
  if (aNbTriangles == 0)
  {
    // No triangulation on the shape
    // 这个Shape上没有面
    return Standard_False;
  }

  // 建立临时的Poly_Triangulation
  //在OpenCascade中曲面的三角剖分的网格数据都保存在类Poly_Triangulation中
  //TopoDS_Shape是不存储实际数据的
  //实际上是初始化了如下成员变量
  //Poly_ArrayOfNodes         myNodes; //存放点
  //Poly_Array1OfTriangle     myTriangles; //存放面
  Handle(Poly_Triangulation) aMesh = new Poly_Triangulation (aNbNodes, aNbTriangles, Standard_False);

  // 记录没有进行离散的TopoDS_Face数量
  Standard_Integer aNbFacesNoTri = 0;
  // 为填充数据做准备
  Standard_Integer aNodeOffset = 0;
  Standard_Integer aTriangleOffet = 0;
  //遍历Shape上所有的面
  for (TopExp_Explorer anExpSF (theShape, TopAbs_FACE); anExpSF.More(); anExpSF.Next())
  {
    const TopoDS_Shape& aFace = anExpSF.Current();
    TopLoc_Location aLoc;
    //获取了一个用于实际存储数据的句柄进行操作
    Handle(Poly_Triangulation) aTriangulation = BRep_Tool::Triangulation (TopoDS::Face (aFace), aLoc);
    //如果句柄为NULL说明该面没有被离散
    if (aTriangulation.IsNull())
    {
      //记录没有离散面的数量
      ++aNbFacesNoTri;
      continue;
    }
	//如果面被离散过了 并且有了三角形数据则进行下面的操作
    // 复制顶点
    //gp_Trsf是用于将2D点到3D的转换
    gp_Trsf aTrsf = aLoc.Transformation();
    //遍历所有的点,下标从1开始
    for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter)
    {
      //gp_Pnt用于存储点的实体数据
      gp_Pnt aPnt = aTriangulation->Node (aNodeIter);
      //坐标转换
      aPnt.Transform (aTrsf);
      //SetNode(theIndex,thePnt)调用了myNodes.SetValue (theIndex - 1, thePnt);
      //将aMesh中存放点的数组增加点进去
      //aNodeOffset是防止点覆盖到相同的下标上去 因为每个面的aNodeIter都是从1开始计算的
      aMesh->SetNode (aNodeIter + aNodeOffset, aPnt);
    }

    // 存储三角网格的数据 和上面差不多
    // 获得三角形的朝向,对这个不理解的可以去阅读一下TopoDS的文档,这是拓扑定义
    const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
    for (Standard_Integer aTriIter = 1; aTriIter <= aTriangulation->NbTriangles(); ++aTriIter)
    {
      Poly_Triangle aTri = aTriangulation->Triangle (aTriIter);
	  //获得顶点的下标 在离散化后会自动生成index->pnt 一个下标对应一个点
	  //Standard_Integer就是int类型 只不过被typedef了 写成int也可以
      Standard_Integer anId[3];
      aTri.Get (anId[0], anId[1], anId[2]);
      //如果面翻转了,则翻回来
      if (anOrientation == TopAbs_REVERSED)
      {
        // Swap 1, 2.
        Standard_Integer aTmpIdx = anId[1];
        anId[1] = anId[2];
        anId[2] = aTmpIdx;
      }

      // 防止遍历不同面的时候产生相同的id,加上偏移量重新生成三角形
      anId[0] += aNodeOffset;
      anId[1] += aNodeOffset;
      anId[2] += aNodeOffset;
	  // 重新设置面上点的id值
      aTri.Set (anId[0], anId[1], anId[2]);
      aMesh->SetTriangle (aTriIter + aTriangleOffet, aTri);
    }
	//偏移量增加
    aNodeOffset += aTriangulation->NbNodes();
    aTriangleOffet += aTriangulation->NbTriangles();
  }

  OSD_Path aPath (theFileName);
  Standard_Boolean isDone = (myASCIIMode
    ? RWStl::WriteAscii(aMesh, aPath, theProgress)
    : RWStl::WriteBinary(aMesh, aPath, theProgress));
  return isDone;
}
OK 现在我们已经理解了如何设置Poly_Triangulation来保存一个被离散过的TopoDS_Shape的实体数据
接下来就是RWStl::WriteAscii(aMesh, aPath, theProgress); 让我们来看看它是如何通过Poly_Triangulation实现写入操作

三、写入操作 RWStl.hxx | RWStl.cxx

话不多说,直接上源码
stl文件的写入还是简单的
//=============================================================================
//function : writeASCII
//purpose  :
//=============================================================================
Standard_Boolean RWStl::writeASCII (const Handle(Poly_Triangulation)& theMesh,
                                    FILE* theFile,
                                    const Message_ProgressRange& theProgress)
{
  // note that space after 'solid' is necessary for many systems
  // 写入文件中
  if (fwrite ("solid \n", 1, 7, theFile) != 7)
  {
    return Standard_False;
  }
  //这边都是基础知识 应该很好理解,也可以改写为iostream的方式读写
  char aBuffer[512];
  memset (aBuffer, 0, sizeof(aBuffer));
  //获取三角面的个数
  const Standard_Integer NBTriangles = theMesh->NbTriangles();
  //这玩意是个进度条,没什么用 不用管,除非有进度可视化需求
  Message_ProgressScope aPS (theProgress, "Triangles", NBTriangles);
  //创建了一个保存顶点id的数组
  Standard_Integer anElem[3] = {0, 0, 0};
  for (Standard_Integer aTriIter = 1; aTriIter <= NBTriangles; ++aTriIter)
  {
    const Poly_Triangle aTriangle = theMesh->Triangle (aTriIter);
    //获取面上的顶点id
    aTriangle.Get (anElem[0], anElem[1], anElem[2]);

    const gp_Pnt aP1 = theMesh->Node (anElem[0]);
    const gp_Pnt aP2 = theMesh->Node (anElem[1]);
    const gp_Pnt aP3 = theMesh->Node (anElem[2]);
	//两条边用来计算法向量
    const gp_Vec aVec1 (aP1, aP2);
    const gp_Vec aVec2 (aP1, aP3);
    //得到法向量
    gp_Vec aVNorm = aVec1.Crossed (aVec2);
    if (aVNorm.SquareMagnitude() > gp::Resolution())
    {
      aVNorm.Normalize();
    }
    else
    {
      aVNorm.SetCoord (0.0, 0.0, 0.0);
    }
    //写入面的数据,stl的格式可以自行搜索一下 stl的格式非常的简单
    Sprintf (aBuffer,
          " 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",
          aVNorm.X(), aVNorm.Y(), aVNorm.Z(),
          aP1.X(), aP1.Y(), aP1.Z(),
          aP2.X(), aP2.Y(), aP2.Z(),
          aP3.X(), aP3.Y(), aP3.Z());

    if (fprintf (theFile, "%s", aBuffer) < 0)
    {
      return Standard_False;
    }

    //每当处理了1000个三角面的时候 更新一次进度条
    if ((aTriIter % IND_THRESHOLD) == 0)
    {
      if (!aPS.More())
        return Standard_False;
      aPS.Next(IND_THRESHOLD);
    }
  }
  //stl文件的结束符
  if (fwrite ("endsolid\n", 1, 9, theFile) != 9)
  {
    return Standard_False;
  }

  return Standard_True;
}

好啦,至此整体的源码就已经读完了,如果还有不懂的可以评论区评论,或者自己读读源码咯。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值