OpenCascade源码分析之BRepMesh_IncrementalMesh(网格离散化操作)

OpenCascade源码分析之BRepMesh_IncrementalMesh(网格离散化操作)

一、引言

在使用opencascade读取连续曲面模型的时候,一般来说我们都会调用BRepMesh_IncrementalMesh对其进行离散化,然而可能对于离散过程的执行还是没有什么概念,接下来从源码出发来看看它进行了什么操作。

二、源码分析

在调用离散的时候,我们一般会用到下面的代码

TopoDS_Shape cur;	//需要离散的拓扑
IMeshTools_Parameters aMeshParams;	//离散化的参数
BRepMesh_IncrementalMesh::BRepMesh_IncrementalMesh(cur, this->aMeshParams);

BRepMesh_IncrementalMesh.cpp 的代码为

//=======================================================================
//function : Constructor
//purpose  : 
//=======================================================================
BRepMesh_IncrementalMesh::BRepMesh_IncrementalMesh(
  const TopoDS_Shape&          theShape,
  const IMeshTools_Parameters& theParameters,
  const Message_ProgressRange& theRange)
  : myParameters(theParameters)
{
  myShape = theShape;
  Perform(theRange);
}
//=======================================================================
//function : Perform
//purpose  : 
//=======================================================================
void BRepMesh_IncrementalMesh::Perform(const Message_ProgressRange& theRange)
{
  //新建一个上下文
  //myParameters.MeshAlgo返回了参数中的IMeshTools_MeshAlgoType
  //如果没有设置IMeshTools_MeshAlgoType默认为Delaunay三角化Watson方法
  //也可以设置为Delabella方法
  Handle(BRepMesh_Context) aContext = new BRepMesh_Context (myParameters.MeshAlgo);
  //theRange消息进度条,没什么用 不用管
  Perform (aContext, theRange);
}

//=======================================================================
//function : Perform
//purpose  : 
//=======================================================================
void BRepMesh_IncrementalMesh::Perform(const Handle(IMeshTools_Context)& theContext, const Message_ProgressRange& theRange)
{
  //对于我们自己设置的离散参数进行判断,修改一些不合理的值
  //例如当Deflection<Precision::Confusion()时,提醒用户值无效 精度太低
  initParameters();
  //IMeshTools_Context继承至IMeshData_Shape
  //IMeshData_Shape中的SetShape()设置了成员变量TopoDS_Shape myShape
  theContext->SetShape(Shape());
  //设置离散参数
  theContext->ChangeParameters()            = myParameters;
  // 算法结束时不清理临时的数据类型
  theContext->ChangeParameters().CleanModel = Standard_False;

  //进度条的设置
  Message_ProgressScope aPS(theRange, "Perform incmesh", 10);
  //创建一个IMeshTools_MeshBuilder对象,将上下文传入
  IMeshTools_MeshBuilder aIncMesh(theContext);
  //执行操作
  //这边是核心部分!!将会放到后面单独讲解
  //这边是核心部分!!将会放到后面单独讲解
  //这边是核心部分!!将会放到后面单独讲解
  aIncMesh.Perform(aPS.Next(9));
  if (!aPS.More())
  {
    myStatus = IMeshData_UserBreak;
    return;
  }
  myStatus = IMeshData_NoError;

  //返回了上下文中的成员变量Handle (IMeshData_Model)  myModel;
  const Handle(IMeshData_Model)& aModel = theContext->GetModel();
  if (!aModel.IsNull())
  {
    //遍历所有的面
    for (Standard_Integer aFaceIt = 0; aFaceIt < aModel->FacesNb(); ++aFaceIt)
    {
      //IMeshData是一个namespace具体里面包含的东西在下面的链接中
      //https://dev.opencascade.org/doc/refman/html/namespace_i_mesh_data.html
      const IMeshData::IFaceHandle& aDFace = aModel->GetFace(aFaceIt);
      myStatus |= aDFace->GetStatusMask();

      for (Standard_Integer aWireIt = 0; aWireIt < aDFace->WiresNb(); ++aWireIt)
      {
        const IMeshData::IWireHandle& aDWire = aDFace->GetWire(aWireIt);
        myStatus |= aDWire->GetStatusMask();
      }
    }
  }
  aPS.Next(1);
  //设置已经完成标志
  setDone();
}

总体流程就是如上啦,当然 其中的核心代码还没有讲,接下来讲核心代码

aIncMesh.Perform(aPS.Next(9));	//核心代码!!

Perform中的内容如下

void IMeshTools_MeshBuilder::Perform (const Message_ProgressRange& theRange)
{
  //清除状态位
  ClearStatus ();
  //获得之前设置的上下文
  const Handle (IMeshTools_Context)& aContext = GetContext ();
  if (aContext.IsNull ())
  {
    SetStatus (Message_Fail1);
    return;
  }

  Message_ProgressScope aPS(theRange, "Mesh Perform", 10);
  //开始离散化的六个步骤 这边的if中每个都执行了相应的步骤,然后返回是否成功
  if (aContext->BuildModel ())
  {
    if (aContext->DiscretizeEdges ())
    {
      if (aContext->HealModel ())
      {
        if (aContext->PreProcessModel())
        {
          if (aContext->DiscretizeFaces(aPS.Next(9)))
          {
            if (aContext->PostProcessModel())
            {
              SetStatus(Message_Done1);
            }
            else
            {
              SetStatus(Message_Fail7);
            }
          }
          else
          {
            if (!aPS.More())
            {
              SetStatus(Message_Fail8);
              aContext->Clean();
              return;
            }
            SetStatus(Message_Fail6);
          }
        }
        else
        {
          SetStatus(Message_Fail5);
        }
      }
      else
      {
        SetStatus(Message_Fail4);
      }
    }
    else
    {
      SetStatus (Message_Fail3);
    }
  }
  else
  {
    const Handle (IMeshTools_ModelBuilder)& aModelBuilder =
      aContext->GetModelBuilder ();

    if (aModelBuilder.IsNull ())
    {
      SetStatus (Message_Fail1);
    }
    else
    {
      // Is null shape or another problem?
      SetStatus (aModelBuilder->GetStatus ().IsSet (Message_Fail1) ?
        Message_Warn1 : Message_Fail2);
    }
  }
  aPS.Next(1);
  aContext->Clean ();
}

注:下面的详细步骤 都在BRep_XXXX中实现,IMeshTools_ModelAlgo只是作为基类存在,其中的函数都是virtual的

1、aContext->BuildModel () 创建一个需要离散化的模型

此阶段建立了一个模型

virtual Standard_Boolean BuildModel ()
  {
    if (myModelBuilder.IsNull())
    {
      return Standard_False;
    }
	//创建一个需要离散化的模型的Handle
    myModel = myModelBuilder->Perform(GetShape(), myParameters);

    return !myModel.IsNull();
  }

Peform执行了下面的代码

//! Exceptions protected method to create discrete model for the given shape.
//! Returns nullptr in case of failure.
Handle (IMeshData_Model) Perform (
    const TopoDS_Shape&          theShape,
    const IMeshTools_Parameters& theParameters)
  {
    ClearStatus ();

    try
    {
      OCC_CATCH_SIGNALS
	  //返回模型Handle (IMeshData_Model)
      return performInternal (theShape, theParameters);
    }
    catch (Standard_Failure const&)
    {
      SetStatus (Message_Fail2);
      return NULL;
    }
  }

performInternal

Handle (IMeshData_Model) BRepMesh_ModelBuilder::performInternal (
  const TopoDS_Shape&          theShape,
  const IMeshTools_Parameters& theParameters)
{
  Handle (BRepMeshData_Model) aModel;
  //包围盒
  Bnd_Box aBox;
  //给模型添加包围盒
  BRepBndLib::Add (theShape, aBox, Standard_False);

  if (!aBox.IsVoid ())
  {
    // Build data model for further processing.
    aModel = new BRepMeshData_Model (theShape);
    
    //Relative为是否启用相对计算的边缘公差
    if (theParameters.Relative)
    {
      Standard_Real aMaxSize;
      //获取给定包围盒的最大维度
      BRepMesh_ShapeTool::BoxMaxDimension (aBox, aMaxSize);
      aModel->SetMaxSize(aMaxSize);
    }
    else
    {
      //用于面的挠度将是其边缘和面中的最大挠度
      aModel->SetMaxSize(Max(theParameters.Deflection,
                             theParameters.DeflectionInterior));
    }

	//通过添加面孔和自由边缘来构建形状的离散模型。
	//计算相应形状的挠度,并检查它是否适合现有的多边形表示形式。
    Handle (IMeshTools_ShapeVisitor) aVisitor =
      new BRepMesh_ShapeVisitor (aModel);

    IMeshTools_ShapeExplorer aExplorer (theShape);
    aExplorer.Accept (aVisitor);
    SetStatus (Message_Done1);
  }
  else
  {
    SetStatus (Message_Fail1);
  }

  return aModel;
}

2、aContext->DiscretizeEdges ()对边进行离散

  virtual Standard_Boolean DiscretizeEdges()
  {
    if (myModel.IsNull() || myEdgeDiscret.IsNull())
    {
      return Standard_False;
    }
    // Discretize edges of a model.
    return myEdgeDiscret->Perform(myModel, myParameters, Message_ProgressRange());
  }

同样的Perform调用了BRepMesh_EdgeDiscret.cxx中的performInternal

Standard_Boolean BRepMesh_EdgeDiscret::performInternal (
  const Handle (IMeshData_Model)& theModel,
  const IMeshTools_Parameters&    theParameters,
  const Message_ProgressRange&    theRange)
{
  (void )theRange;
  myModel      = theModel;
  myParameters = theParameters;

  if (myModel.IsNull())
  {
    return Standard_False;
  }
  //For中的格式为: begin,end,func,isParallel
  //多线程或者单线程跑
  OSD_Parallel::For (0, myModel->EdgesNb (), *this, !myParameters.InParallel);

  myModel.Nullify(); // Do not hold link to model.
  return Standard_True;
}

具体的代码太长了 感兴趣的话 阅读BRepMesh_EdgeDiscret.cxx吧

3、aContext->HealModel () 修复模型

virtual Standard_Boolean HealModel()
  {
    if (myModel.IsNull())
    {
      return Standard_False;
    }

    return myModelHealer.IsNull() ?
      Standard_True :
      myModelHealer->Perform (myModel, myParameters, Message_ProgressRange());
  }

这块的具体实现在BRepMesh_ModelHealer.hxx和BRepMesh_ModelHealer.cxx中,感兴趣的可以自己进行阅读
其中主要的功能有下:

  • connectClosestPoints 将非常近的点进行合并
  • fixFaceBoundaries 修复面的边界(边界可能会出现断连的情况)

4、aContext->PreProcessModel() 前处理

和上面一样的Perform函数 然后调用了performInternal

Standard_Boolean BRepMesh_ModelPreProcessor::performInternal(
  const Handle(IMeshData_Model)& theModel,
  const IMeshTools_Parameters&   theParameters,
  const Message_ProgressRange&   theRange)
{
  (void )theRange;
  if (theModel.IsNull())
  {
    return Standard_False;
  }

  const Standard_Integer aFacesNb    = theModel->FacesNb();
  const Standard_Boolean isOneThread = !theParameters.InParallel;
  //并行运行或者单线程运行
  //对锥面进行分割 对所有3d的曲线以及参数曲线进行分割和缝合
  OSD_Parallel::For(0, aFacesNb, SeamEdgeAmplifier        (theModel, theParameters),                      isOneThread);
  //判断三角化的一致性 判断是否有过期的三角数据
  OSD_Parallel::For(0, aFacesNb, TriangulationConsistency (theModel, theParameters.AllowQualityDecrease), isOneThread);

  // 从过时的多边形中清理边缘和面.
  Handle(NCollection_IncAllocator) aTmpAlloc(new NCollection_IncAllocator(IMeshData::MEMORY_BLOCK_SIZE_HUGE));
  NCollection_Map<IMeshData_Face*> aUsedFaces(1, aTmpAlloc);
  for (Standard_Integer aEdgeIt = 0; aEdgeIt < theModel->EdgesNb(); ++aEdgeIt)
  {
    const IMeshData::IEdgeHandle& aDEdge = theModel->GetEdge(aEdgeIt);
    if (aDEdge->IsFree())
    {
      if (aDEdge->IsSet(IMeshData_Outdated))
      {
        TopLoc_Location aLoc;
        BRep_Tool::Polygon3D(aDEdge->GetEdge(), aLoc);
        BRepMesh_ShapeTool::NullifyEdge(aDEdge->GetEdge(), aLoc);
      }

      continue;
    }
    
    for (Standard_Integer aPCurveIt = 0; aPCurveIt < aDEdge->PCurvesNb(); ++aPCurveIt)
    {
      // Find adjacent outdated face.
      const IMeshData::IFaceHandle aDFace = aDEdge->GetPCurve(aPCurveIt)->GetFace();
      if (!aUsedFaces.Contains(aDFace.get()))
      {
        aUsedFaces.Add(aDFace.get());
        if (aDFace->IsSet(IMeshData_Outdated))
        {
          TopLoc_Location aLoc;
          const Handle(Poly_Triangulation)& aTriangulation =
            BRep_Tool::Triangulation(aDFace->GetFace(), aLoc);

          // Clean all edges of oudated face.
          for (Standard_Integer aWireIt = 0; aWireIt < aDFace->WiresNb(); ++aWireIt)
          {
            const IMeshData::IWireHandle& aDWire = aDFace->GetWire(aWireIt);
            for (Standard_Integer aWireEdgeIt = 0; aWireEdgeIt < aDWire->EdgesNb(); ++aWireEdgeIt)
            {
              const IMeshData::IEdgeHandle aTmpDEdge = aDWire->GetEdge(aWireEdgeIt);
              BRepMesh_ShapeTool::NullifyEdge(aTmpDEdge->GetEdge(), aTriangulation, aLoc);
            }
          }

          BRepMesh_ShapeTool::NullifyFace(aDFace->GetFace());
        }
      }
    }
  }

  return Standard_True;
}

总结一下 前处理基本上是对一些过期的三角数据和边数据进行清理

5、aContext->DiscretizeFaces(aPS.Next(9)) 面的离散

这里的算法比较庞大,基本上是根据平面不同的类型来执行不同的算法

switch (theSurfaceType)
  {
  case GeomAbs_Plane:
    return theParameters.InternalVerticesMode ?
      new NodeInsertionMeshAlgo<BRepMesh_DefaultRangeSplitter>::Type :
      new BaseMeshAlgo::Type;
    break;

  case GeomAbs_Sphere:
    return new NodeInsertionMeshAlgo<BRepMesh_SphereRangeSplitter>::Type;
    break;

  case GeomAbs_Cylinder:
    return theParameters.InternalVerticesMode ?
      new NodeInsertionMeshAlgo<BRepMesh_CylinderRangeSplitter>::Type :
      new BaseMeshAlgo::Type;
    break;

  case GeomAbs_Cone:
    return new NodeInsertionMeshAlgo<BRepMesh_ConeRangeSplitter>::Type;
    break;

  case GeomAbs_Torus:
    return new NodeInsertionMeshAlgo<BRepMesh_TorusRangeSplitter>::Type;
    break;

  case GeomAbs_SurfaceOfRevolution:
    return new DeflectionControlMeshAlgo<BRepMesh_BoundaryParamsRangeSplitter>::Type;
    break;

  default:
    return new DeflectionControlMeshAlgo<BRepMesh_NURBSRangeSplitter>::Type;
  }

这里可以读一下这个大佬的文章,总结的还不错
https://blog.csdn.net/YongsenDY/article/details/115304488

6、aContext->PostProcessModel() 后处理

后处理将三角剖分中的多边形存储到TopoDS_Edge
具体的源码在BRepMesh_ModelHealer.cxx中可以自行查看

三、总结

离散的步骤主要如下:

  1. 设置离散的参数
  2. 执行IncrementalMesh算法开始离散
  3. 设置上下文的参数(包括用什么算法离散、以及初始化六个步骤用到的类的实例)
  4. 开始离散
    4.1. 创建离散化模型
    4.2. 对边进行离散
    4.3. 修复模型
    4.4. 前处理
    4.5. 对不同类型的面用不同的算法离散
    4.6. 后处理,将数据存储到TopoDS上
  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值