对OCCT已经有了多年了解,但时不时还是要翻一翻它的官方文档。今天重读了:Bottle Tutorial
教程概况
这篇教程文档围绕使用Open CASCADE Technology进行3D建模展开,以创建一个瓶子模型为例,逐步介绍建模过程及相关技术要点,主要内容如下:
- 教程概述
- 目的:教会读者使用Open CASCADE Technology服务对3D对象建模,帮助读者将其作为工具来思考和应用,并非介绍所有类。
- 前提条件:假定读者具备C++使用和设置经验,Open CASCADE Technology旨在增强C++的3D建模能力。
- 模型规格:定义了瓶子的关键参数,包括高度(70mm)、宽度(50mm)、厚度(30mm),且瓶子轮廓(底部)以全局笛卡尔坐标系原点为中心。
- 建模步骤
- 构建轮廓:先定义支撑点,选用gp_Pnt类创建点对象;接着利用这些点计算轮廓几何,涉及线段和圆弧;再通过TopoDS和BRepBuilderAPI等相关类构建拓扑结构;最后通过反射现有导线并合并的方式完成轮廓。
- 构建瓶身:使用BRepBuilderAPI_MakeFace和BRepPrimAPI_MakePrism类将轮廓拉伸为实体;利用BRepFilletAPI_MakeFillet类对边缘倒圆角;通过创建圆柱体并与瓶身融合添加瓶颈;借助BRepOffsetAPI_MakeThickSolid类创建空心实体。
- 构建螺纹:计算两个不同半径的圆柱面;在圆柱面的参数化空间定义2D曲线,包括椭圆弧和线段;使用BRepBuilderAPI_MakeEdge和BRepBuilderAPI_MakeWire类构建边缘和导线;调用BRepLib::BuildCurves3d方法计算3D曲线,通过BRepOffsetAPI_ThruSections类创建螺纹实体。
- 构建最终组合体:运用TopoDS_Compound和BRep_Builder类将瓶身和螺纹组合成单一形状,完成瓶子建模。
- 附录:给出了MakeBottle函数的完整定义,展示了整个建模过程在代码层面的实现细节。
关键知识点
几何和拓扑
在OCC中,几何和拓扑经常成对出现。例如:
// Profile : Define the GeometryHandle(Geom_TrimmedCurve) anArcOfCircle = GC_MakeArcOfCircle(aPnt2,aPnt3,aPnt4);Handle(Geom_TrimmedCurve) aSegment1 = GC_MakeSegment(aPnt1, aPnt2);Handle(Geom_TrimmedCurve) aSegment2 = GC_MakeSegment(aPnt4, aPnt5);// Profile : Define the TopologyTopoDS_Edge anEdge1 = BRepBuilderAPI_MakeEdge(aSegment1);TopoDS_Edge anEdge2 = BRepBuilderAPI_MakeEdge(anArcOfCircle);TopoDS_Edge anEdge3 = BRepBuilderAPI_MakeEdge(aSegment2);TopoDS_Wire aWire = BRepBuilderAPI_MakeWire(anEdge1, anEdge2, anEdge3);
OpenCasCade是一个开源的CAD/CAM/CAE几何建模内核,它广泛应用于各种工业设计和工程领域。在OpenCasCade中,几何(Geometry)和拓扑(Topology)是两个核心概念,它们紧密相关且相互协作,共同构成了复杂的三维模型表示。以下从基本概念、相互关系和协同工作机制几个方面介绍它们的关系:
基本概念
- 几何:几何主要关注的是物体的形状和尺寸等数学描述。在OpenCasCade中,几何元素由精确的数学方程定义,用于表示点、线、面、体等基本形状。例如,一个点可以用三维空间中的坐标 ( x , y , z ) (x, y, z) (x,y,z) 来表示;一条直线可以用线性方程描述;一个平面可以用平面方程表示;而像圆柱、圆锥等复杂曲面则由更复杂的数学函数来定义。几何元素提供了模型的精确几何信息,是构建模型的基础。
- 拓扑:拓扑关注的是几何元素之间的连接关系和相对位置,而不考虑具体的形状和尺寸。OpenCasCade中的拓扑元素包括顶点(Vertex)、边(Edge)、面(Face)、壳(Shell)、体(Solid)等。例如,一个边连接两个顶点,一个面由多条边围成,一个体由多个面组成。拓扑结构定义了几何元素之间的邻接关系和层次结构,它描述了模型的整体结构和组织方式。
相互关系
- 拓扑依赖于几何:拓扑元素需要基于几何元素来定义其具体形状。例如,一个拓扑边(TopoDS_Edge)必须依赖于一个几何曲线(如Geom_Line、Geom_Circle等)来确定其在空间中的形状和位置。没有几何曲线的支持,拓扑边就只是一个抽象的连接关系,没有实际的形状。同样,一个拓扑面(TopoDS_Face)需要基于一个几何曲面(如Geom_Plane、Geom_CylindricalSurface等)来定义其表面形状。
- 几何通过拓扑组织:几何元素通过拓扑结构进行组织和管理,形成复杂的三维模型。拓扑结构为几何元素提供了一种结构化的表示方式,使得可以方便地对模型进行操作和分析。例如,通过拓扑结构可以快速找到与某个面相邻的其他面,或者找到组成一个体的所有面。这种组织方式使得模型的构建、修改和查询更加高效和灵活。
协同工作机制
- 模型构建:在创建三维模型时,首先需要定义几何元素,如点、线、面等,然后通过拓扑结构将这些几何元素组合起来。例如,要创建一个立方体,需要先定义六个平面作为几何元素,然后通过拓扑结构将这些平面连接起来,形成一个封闭的体。在OpenCasCade中,可以使用各种API来完成这些操作,如BRepBuilderAPI_MakeEdge用于创建拓扑边,BRepBuilderAPI_MakeFace用于创建拓扑面,BRepPrimAPI_MakeBox用于创建立方体等基本体素。
- 模型操作:在对模型进行操作(如平移、旋转、缩放、布尔运算等)时,几何和拓扑需要协同工作。例如,在进行布尔运算(如并、交、差)时,不仅要对几何形状进行计算,还要更新拓扑结构以反映新的连接关系。OpenCasCade提供了一系列的算法和工具来处理这些操作,确保几何和拓扑的一致性。
- 模型分析:在对模型进行分析(如计算体积、表面积、曲率等)时,需要同时利用几何和拓扑信息。例如,计算一个体的体积需要知道其表面的几何形状和拓扑结构,通过对各个面的积分来得到体积值。OpenCasCade提供了各种分析工具和算法,利用几何和拓扑信息来完成这些计算任务。
示例代码说明
以下是一个简单的OpenCasCade代码示例,展示了如何创建一个简单的拓扑边并关联几何曲线:
#include <gp_Pnt.hxx>
#include <GC_MakeSegment.hxx>
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <TopoDS_Edge.hxx>int main() {// 定义两个点作为几何信息gp_Pnt p1(0, 0, 0);gp_Pnt p2(10, 0, 0);// 创建一个几何线段Handle(Geom_TrimmedCurve) segment = GC_MakeSegment(p1, p2);// 基于几何线段创建一个拓扑边BRepBuilderAPI_MakeEdge edgeBuilder(segment);TopoDS_Edge edge = edgeBuilder.Edge();return 0;
}
在这个示例中,首先定义了两个点 p1
和 p2
,然后使用 GC_MakeSegment
创建了一个几何线段。接着,使用 BRepBuilderAPI_MakeEdge
基于这个几何线段创建了一个拓扑边。这体现了几何和拓扑的紧密结合,几何为拓扑提供了具体的形状信息,而拓扑则将几何元素组织成有意义的结构。
拓扑数据结构的遍历
在 OpenCasCade 这个开源的 CAD/CAM/CAE 几何建模内核中,TopExp_Explorer
类是一个非常重要的工具,主要用于遍历拓扑数据结构中的元素。下面将从其功能、成员函数、工作原理以及使用示例等方面进行详细介绍。
功能概述
TopExp_Explorer
类的核心功能是对 OpenCasCade 中的拓扑数据结构(如 TopoDS_Shape
及其派生类)进行遍历操作。通过该类,可以方便地访问拓扑形状中的各种拓扑元素,例如顶点(TopAbs_VERTEX
)、边(TopAbs_EDGE
)、面(TopAbs_FACE
)等,这在模型分析、修改以及布尔运算等操作中非常有用。
成员函数
- 构造函数
TopExp_Explorer(const TopoDS_Shape& S, const TopAbs_ShapeEnum ToFind, const TopAbs_ShapeEnum ToAvoid = TopAbs_SHAPE)
:初始化一个TopExp_Explorer
对象,用于遍历形状S
中类型为ToFind
的拓扑元素,同时可以选择跳过类型为ToAvoid
的元素。
- 遍历控制函数
Standard_Boolean More()
:检查是否还有更多的元素可供遍历。如果还有未访问的元素,返回Standard_True
;否则返回Standard_False
。void Next()
:将迭代器移动到下一个元素。
- 元素访问函数
const TopoDS_Shape& Current()
:返回当前正在访问的拓扑元素。
工作原理
TopExp_Explorer
类通过维护一个内部的迭代器来实现对拓扑形状的遍历。当创建 TopExp_Explorer
对象时,它会根据指定的形状和要查找的元素类型进行初始化。在调用 More()
函数时,它会检查是否还有未访问的元素;调用 Next()
函数时,迭代器会移动到下一个元素;而 Current()
函数则返回当前迭代器指向的元素。
使用示例
以下是一个简单的示例,展示了如何使用 TopExp_Explorer
类来遍历一个拓扑形状中的所有边:
#include <TopoDS_Shape.hxx>
#include <TopExp_Explorer.hxx>
#include <TopAbs_ShapeEnum.hxx>
#include <BRepPrimAPI_MakeBox.hxx>
#include <iostream>int main() {// 创建一个立方体形状BRepPrimAPI_MakeBox boxMaker(10.0, 10.0, 10.0);TopoDS_Shape box = boxMaker.Shape();// 创建一个 TopExp_Explorer 对象,用于遍历立方体中的所有边TopExp_Explorer edgeExplorer(box, TopAbs_EDGE);// 遍历所有边并输出边的数量int edgeCount = 0;for (; edgeExplorer.More(); edgeExplorer.Next()) {// 获取当前边const TopoDS_Shape& currentEdge = edgeExplorer.Current();edgeCount++;}std::cout << "The number of edges in the box is: " << edgeCount << std::endl;return 0;
}
代码解释
- 创建拓扑形状:使用
BRepPrimAPI_MakeBox
类创建一个立方体形状。 - 初始化
TopExp_Explorer
对象:创建一个TopExp_Explorer
对象edgeExplorer
,指定要遍历的形状为立方体box
,要查找的元素类型为TopAbs_EDGE
(即边)。 - 遍历拓扑元素:使用
for
循环结合More()
和Next()
函数遍历立方体中的所有边。在每次循环中,使用Current()
函数获取当前正在访问的边。 - 输出结果:统计边的数量并输出。
通过这个示例,可以看到 TopExp_Explorer
类提供了一种简单而有效的方式来遍历拓扑形状中的元素,方便进行各种拓扑分析和操作。
创建螺旋线720度
这里的重点是如何创建720度的螺旋线。先看看官方的代码,然后逐行解释,最后是关键点的分析。
myBody = aSolidMaker.Shape();// Threading : Create SurfacesHandle(Geom_CylindricalSurface) aCyl1 = new Geom_CylindricalSurface(neckAx2, myNeckRadius * 0.99);Handle(Geom_CylindricalSurface) aCyl2 = new Geom_CylindricalSurface(neckAx2, myNeckRadius * 1.05);// Threading : Define 2D Curvesgp_Pnt2d aPnt(2. * M_PI, myNeckHeight / 2.);gp_Dir2d aDir(2. * M_PI, myNeckHeight / 4.);gp_Ax2d anAx2d(aPnt, aDir);Standard_Real aMajor = 2. * M_PI;Standard_Real aMinor = myNeckHeight / 10;Handle(Geom2d_Ellipse) anEllipse1 = new Geom2d_Ellipse(anAx2d, aMajor, aMinor);Handle(Geom2d_Ellipse) anEllipse2 = new Geom2d_Ellipse(anAx2d, aMajor, aMinor / 4);Handle(Geom2d_TrimmedCurve) anArc1 = new Geom2d_TrimmedCurve(anEllipse1, 0, M_PI);Handle(Geom2d_TrimmedCurve) anArc2 = new Geom2d_TrimmedCurve(anEllipse2, 0, M_PI);gp_Pnt2d anEllipsePnt1 = anEllipse1->Value(0);gp_Pnt2d anEllipsePnt2 = anEllipse1->Value(M_PI);Handle(Geom2d_TrimmedCurve) aSegment = GCE2d_MakeSegment(anEllipsePnt1, anEllipsePnt2);// Threading : Build Edges and WiresTopoDS_Edge anEdge1OnSurf1 = BRepBuilderAPI_MakeEdge(anArc1, aCyl1);TopoDS_Edge anEdge2OnSurf1 = BRepBuilderAPI_MakeEdge(aSegment, aCyl1);TopoDS_Edge anEdge1OnSurf2 = BRepBuilderAPI_MakeEdge(anArc2, aCyl2);TopoDS_Edge anEdge2OnSurf2 = BRepBuilderAPI_MakeEdge(aSegment, aCyl2);TopoDS_Wire threadingWire1 = BRepBuilderAPI_MakeWire(anEdge1OnSurf1, anEdge2OnSurf1);TopoDS_Wire threadingWire2 = BRepBuilderAPI_MakeWire(anEdge1OnSurf2, anEdge2OnSurf2);BRepLib::BuildCurves3d(threadingWire1);BRepLib::BuildCurves3d(threadingWire2);// Create Threading BRepOffsetAPI_ThruSections aTool(Standard_True);aTool.AddWire(threadingWire1);aTool.AddWire(threadingWire2);aTool.CheckCompatibility(Standard_False);TopoDS_Shape myThreading = aTool.Shape();
代码注释
对上面的代码进行解释。
1. 获取瓶身实体
myBody = aSolidMaker.Shape();
这行代码的作用是从 aSolidMaker
对象中获取最终的瓶身实体形状,并将其赋值给 myBody
变量。aSolidMaker
通常是之前用于构建瓶身的操作对象,比如可能是通过拉伸、布尔运算等操作来创建瓶身的类实例。
2. 创建螺纹所需的圆柱面
Handle(Geom_CylindricalSurface) aCyl1 = new Geom_CylindricalSurface(neckAx2, myNeckRadius * 0.99);
Handle(Geom_CylindricalSurface) aCyl2 = new Geom_CylindricalSurface(neckAx2, myNeckRadius * 1.05);
- 这里创建了两个
Geom_CylindricalSurface
类型的圆柱面对象aCyl1
和aCyl2
。 neckAx2
是一个gp_Ax2
类型的对象,它定义了圆柱面的位置和方向(轴)。myNeckRadius
是瓶颈的半径,aCyl1
的半径为myNeckRadius * 0.99
,aCyl2
的半径为myNeckRadius * 1.05
,这两个不同半径的圆柱面将用于后续定义螺纹的内外边界。
3. 定义二维曲线
gp_Pnt2d aPnt(2. * M_PI, myNeckHeight / 2.);
gp_Dir2d aDir(2. * M_PI, myNeckHeight / 4.);
gp_Ax2d anAx2d(aPnt, aDir);Standard_Real aMajor = 2. * M_PI;
Standard_Real aMinor = myNeckHeight / 10;Handle(Geom2d_Ellipse) anEllipse1 = new Geom2d_Ellipse(anAx2d, aMajor, aMinor);
Handle(Geom2d_Ellipse) anEllipse2 = new Geom2d_Ellipse(anAx2d, aMajor, aMinor / 4);
Handle(Geom2d_TrimmedCurve) anArc1 = new Geom2d_TrimmedCurve(anEllipse1, 0, M_PI);
Handle(Geom2d_TrimmedCurve) anArc2 = new Geom2d_TrimmedCurve(anEllipse2, 0, M_PI);
gp_Pnt2d anEllipsePnt1 = anEllipse1->Value(0);
gp_Pnt2d anEllipsePnt2 = anEllipse1->Value(M_PI);Handle(Geom2d_TrimmedCurve) aSegment = GCE2d_MakeSegment(anEllipsePnt1, anEllipsePnt2);
- 定义局部坐标系:
gp_Pnt2d aPnt(2. * M_PI, myNeckHeight / 2.);
定义了一个二维点aPnt
,作为局部坐标系的原点。gp_Dir2d aDir(2. * M_PI, myNeckHeight / 4.);
定义了一个二维方向aDir
。gp_Ax2d anAx2d(aPnt, aDir);
基于上述点和方向创建了一个二维坐标系anAx2d
。
- 创建椭圆曲线:
aMajor
和aMinor
分别表示椭圆的长半轴和短半轴。anEllipse1
和anEllipse2
是两个不同短半轴的椭圆曲线,它们都基于anAx2d
坐标系。
- 截取椭圆弧:
anArc1
和anArc2
分别是从anEllipse1
和anEllipse2
上截取的 0 到 π \pi π 范围内的椭圆弧。
- 创建线段:
anEllipsePnt1
和anEllipsePnt2
是anEllipse1
上参数为 0 和 π \pi π 的点。aSegment
是连接这两个点的线段。
4. 构建边缘和导线
TopoDS_Edge anEdge1OnSurf1 = BRepBuilderAPI_MakeEdge(anArc1, aCyl1);
TopoDS_Edge anEdge2OnSurf1 = BRepBuilderAPI_MakeEdge(aSegment, aCyl1);
TopoDS_Edge anEdge1OnSurf2 = BRepBuilderAPI_MakeEdge(anArc2, aCyl2);
TopoDS_Edge anEdge2OnSurf2 = BRepBuilderAPI_MakeEdge(aSegment, aCyl2);
TopoDS_Wire threadingWire1 = BRepBuilderAPI_MakeWire(anEdge1OnSurf1, anEdge2OnSurf1);
TopoDS_Wire threadingWire2 = BRepBuilderAPI_MakeWire(anEdge1OnSurf2, anEdge2OnSurf2);
BRepLib::BuildCurves3d(threadingWire1);
BRepLib::BuildCurves3d(threadingWire2);
- 创建边缘:
BRepBuilderAPI_MakeEdge
用于将二维曲线(如椭圆弧和线段)映射到三维圆柱面上,创建对应的拓扑边缘。例如,anEdge1OnSurf1
是将椭圆弧anArc1
映射到圆柱面aCyl1
上得到的边缘。
- 创建导线:
BRepBuilderAPI_MakeWire
用于将多个边缘组合成一个导线。threadingWire1
和threadingWire2
分别是由两个边缘组合而成的导线。
- 计算三维曲线:
BRepLib::BuildCurves3d
用于为导线中的边缘计算三维曲线,确保导线在三维空间中有明确的几何表示。
5. 创建螺纹实体
BRepOffsetAPI_ThruSections aTool(Standard_True);
aTool.AddWire(threadingWire1);
aTool.AddWire(threadingWire2);
aTool.CheckCompatibility(Standard_False);TopoDS_Shape myThreading = aTool.Shape();
- 创建放样工具:
BRepOffsetAPI_ThruSections
是一个用于通过一系列导线创建实体的工具类,Standard_True
表示创建封闭的实体。
- 添加导线:
aTool.AddWire(threadingWire1)
和aTool.AddWire(threadingWire2)
将之前创建的两个导线添加到放样工具中。
- 检查兼容性:
aTool.CheckCompatibility(Standard_False)
表示不进行导线之间的兼容性检查。
- 获取螺纹实体:
aTool.Shape()
调用工具类的Shape
方法,生成最终的螺纹实体,并将其赋值给myThreading
变量。
综上所述,这段代码通过创建圆柱面、定义二维曲线、构建边缘和导线,最后使用放样工具创建了瓶子瓶颈部分的螺纹实体。
720度
圆柱面本身展开是360度的,也就是2PI,但圆柱面本身是无穷的,或者说越过了2PI,就又从0开始了。上面这个二维空间需要一定的想象能力,是把圆柱展开了两次。
在这个二维空间进行绘制的结果如上图,代码如下:
Handle(Geom2d_Ellipse) anEllipse1 = new Geom2d_Ellipse(anAx2d, aMajor, aMinor);Handle(Geom2d_TrimmedCurve) anArc1 = new Geom2d_TrimmedCurve(anEllipse1, 0, M_PI);gp_Pnt2d anEllipsePnt1 = anEllipse1->Value(0);gp_Pnt2d anEllipsePnt2 = anEllipse1->Value(M_PI);Handle(Geom2d_TrimmedCurve) aSegment = GCE2d_MakeSegment(anEllipsePnt1, anEllipsePnt2);
把上述二维的线投到圆柱面的结果如上图所示,代码如下:
// Threading : Build Edges and WiresTopoDS_Edge anEdge1OnSurf1 = BRepBuilderAPI_MakeEdge(anArc1, aCyl1);TopoDS_Edge anEdge2OnSurf1 = BRepBuilderAPI_MakeEdge(aSegment, aCyl1);TopoDS_Wire threadingWire1 = BRepBuilderAPI_MakeWire(anEdge1OnSurf1, anEdge2OnSurf1);BRepLib::BuildCurves3d(threadingWire1);