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.youkuaiyun.com/YongsenDY/article/details/115304488
6、aContext->PostProcessModel() 后处理
后处理将三角剖分中的多边形存储到TopoDS_Edge
具体的源码在BRepMesh_ModelHealer.cxx中可以自行查看
三、总结
离散的步骤主要如下:
- 设置离散的参数
- 执行IncrementalMesh算法开始离散
- 设置上下文的参数(包括用什么算法离散、以及初始化六个步骤用到的类的实例)
- 开始离散
4.1. 创建离散化模型
4.2. 对边进行离散
4.3. 修复模型
4.4. 前处理
4.5. 对不同类型的面用不同的算法离散
4.6. 后处理,将数据存储到TopoDS上