1、1 概述本示例讲述如何使用 Open Cascade 来创建一个 3D 模型,目的并不是介绍所有的 Open Cascade 类,而是对 Open Cascade 有个基本了解;1.1 基本要求要求有使用 C+的经验。从编程角度来说,Open Cascade 提供了高效的模型库、方法和函数,来进行C+开发。利用这些资源,可以开发出稳定的应用程序。1.2 项目在这里利用 3D 几何模型工具提供的类创建一个瓶子,如下图所示:在这里,会将每一步的在一个函数里生成。函数的完整的源代码在程序中,有一个 MakeBottle 函数。这个函数在 Tutorial/src/MakeBottle.cxx 文件中
2、。1.3 项目说明首先,定义瓶子的一些参数:内容 参数名称 参数值瓶高 MyHeight 70mm瓶宽 MyWidth 50mm瓶厚 MyThickness 30mm现在,假定世界笛卡尔直角坐标系的原点位于瓶子底部的中心。创建这个模型需要四步: 创建瓶子的外形; 创建瓶子体; 创建瓶颈上的螺纹; 合并生成瓶子;2 创建瓶子外形2.1 定义关键点为了创建瓶子的外型,第一步是生成特征点,这些点的坐标位于 XOY 平面内,如下图所示,用这些点来生成瓶子的几何外形:在 Open Cascade 中,有两个类来描述 3D 点的 X, Y,Z 坐标。 gp_Pnt 类 通过句柄分配一个临时的Geom_Ca
3、rtesianPoint 类;句柄就是可以自动分配内存的指针类型。那么到底在应用程序中哪种类合适呢?一般的做法是: gp_Pnt 是按值传递,和其它对象一样,有固定的生命周期; Geom_CartesianPoint 是按句柄分配,可以多次引用,有长的生命周期;由于现在定义的这些点只是在生成瓶子的外形时有用,生命周期有限,因此,选择使用 gp_Pnt 类。定义一个 gp_Pnt 对象,只要定义点在世界坐标系的 X,Y ,Z 坐标值就可以了。gp_Pnt aPnt1(-myWidth / 2. , 0 , 0); gp_Pnt aPnt2(-myWidth / 2. , -myThickness
4、 / 4. , 0); gp_Pnt aPnt3(0 , -myThickness / 2. , 0); gp_Pnt aPnt4(myWidth / 2. , -myThickness / 4. , 0); gp_Pnt aPnt5(myWidth / 2. , 0 , 0); 如果使用 Geom_CartesianPoint 类,语法会有点不同,所有对象按句柄分配,需要使用标准 C+的 new 关键字,如下所示:Handle(Geom_CartesianPoint) aPnt1 = new Geom_CartesianPoint(-myWidth / 2. , 0 , 0); 一旦分配了对
5、象,就可以使用它们的方法。再重复一下,语法和 C+一样,比如,为了得到一个点的 X 坐标:gp_Pnt aPnt1(0,0,0); Handle(Geom_CartesianPoint) aPnt2 = new Geom_CartesianPoint(0 , 0 , 0); Standard_Real xValue1 = aPnt1.X(); Standard_Real xValue2 = aPnt2-X(); 2.2 外形轮廓:定义几何体利用前面定义的点,可以计算瓶子几何外形的一部分。如下图所示,它由两个线段和一个圆弧组成。myImageBottle08为了创建这个实体,需要定义一个数据结构
6、,来实现 3D 几何对象,可以在Open Cascade 的 Geom 开发包中找到。一个 Open Cascade 开发包是一组类。这一组类完成的功能相同,或者结构类似,Open Cascade 类的前面的名称和包的名称一样,比如,Geom_Line 类和Geom_Circle 类都属于 Geom 包。Geom 包生成 3D 几何对象:一般的曲线和多个曲线(比如 Bezier 和 BSpline)所组成的平面.Geom 包只提供了几何实体的数据结构。可以直接创建属于 Geom 包的类。因此,使用 GC 包中的计算一般曲线和平面的类更容易一些。这是因为在 GC 包中提供的两个有关计算的类,它对
7、外形计算来说更合适: GC_MakeSegment 类用来创建一个线段。其中一个构造函数是用两个点 P1 和 P2 来定义一个线段。 GC_MakeArcOfCircle 类创建一个园的一部分(圆弧)。一个非常有用的构造函数是用圆弧的两个端点和所通过的一个点来生成;这些类通过一个句柄返回一个Geom_TrimmedCurve。这个实体表示一个基本的曲线。通过两个参数来限制。比如,一个圆 C 的参数是 0 到 2PI,如果需要生成一个四分之一圆,在 C 上应用 Geom_TrimmedCurve方法,将值限制为 0 到 PI/2:Handle(Geom_TrimmedCurve) aArcOfC
8、ircle = GC_MakeArcOfCircle(aPnt2,aPnt3 ,aPnt4); Handle(Geom_TrimmedCurve) aSegment1 = GC_MakeSegment(aPnt1 , aPnt2); Handle(Geom_TrimmedCurve) aSegment2 = GC_MakeSegment(aPnt4 , aPnt5); 所有的 GC 类都提供有重载的方法,可以根据不同的函数类型得到相应的结果,可以调用它的 IsDone 方法,通过返回的值来进行不同处理,这样程序会更安全,比如:GC_MakeSegment mkSeg (aPnt1 , aPnt
9、2); Handle(Geom_TrimmedCurve) aSegment1; if(mkSegment.IsDone() aSegment1 = mkSeg.Value(); . 2.3 外形:定义拓朴结构现在已经创建了外形的一部分,但是这些曲线还是相互独立的,相互间没有任何关系。为了建模方便,需要将这三条曲线生成为一个实体对象。在 TopoDS 包中,有一个 Open Cascade 拓朴数据结构,可以使用它来完成:它可以链接一个几何实体到另一个几何实体上,生成复合图形;这个包中的对象,都是从 TopeDS_Shape 基类继承而来的,下面是各种拓朴图形的描述:图形 Open Casca
10、de 类 描述顶点 TopoDS_Vertex 零维图形,表示几何体上的一个点边 TopoDS_Edge 一维图形,表示一个曲线和一个有边界的向量网格 TopoDS_Wire 由顶点连起来的一系列边面 TopoDS_Face 由闭合的网格组成的边界平面壳 TopoDS_Shell 通过边连接起起来一组面体 TopoDS_Solid 由壳组成的有边界的三维空间复合体 TopoDS_CompSolid 通过面连接的一组体复合对象TopoDS_Compound 由上面各种图形形成的一个集合参考前面的表,可以看出,创建一个外形轮廓,需要生成: 用前面生成的曲线构建出三条边 用这些边形成一个网格myIm
11、ageBottle10TopoDS 包中只提供了拓扑实体的数据结构。在 BRepBuilderAPI 包中,可以找到计算标准拓朴对象的算法类。为了创建一个边,通过前面得出曲线,使用 BRepBuilderAPI_MakeEdge 类来完成:TopoDS_Edge aEdge1 = BRepBuilderAPI_MakeEdge(aSegment1); TopoDS_Edge aEdge2 = BRepBuilderAPI_MakeEdge(aArcOfCircle); TopoDS_Edge aEdge3 = BRepBuilderAPI_MakeEdge(aSegment2); 在 Open
12、 CASCADE 中,有几种创建边的方法。可以直接通过两点来创建一个边,在这种情况下所生成的边是直线。输入的两个顶点是直线的两个端点。比如:TopoDS_Edge aEdge1 = BRepBuilderAPI_MakeEdge(aPnt1 , aPnt3); TopoDS_Edge aEdge2 = BRepBuilderAPI_MakeEdge(aPnt4 , aPnt5); 可以这样简单地生成边 aEdge1 和 aEdge3 。为了将边连接起来,需要用 BRepBuilderAPI_MakeWire 类来生成一个网格。有两种方法: 直接通过一至四个边来生成; 在一个现有的网格上添加其它
13、的网格或边(这点在本示例的后面会解释);用低于四个边来生成网格时,可以直接使用下面的构造函数来直接生成:TopoDS_Wire aWire = BRepBuilderAPI_MakeWire(aEdge1 , aEdge2 , aEdge3);2.4 外形轮廓:完成外形当创建完网格的第一部分后,接下来需要做的是生成整个外形。比较简单的方法是:1. 通过反射现在的网格生成一个新的网格;2. 添加反射的网格到原来的网格中;myImageBottle12为了在图形上应用变换,需要使用 gp_Trsf 类来定义一个 3D 几何变换属性。这种变换可能是平移、旋转、缩放或反射,或者这几种的组合;现在的情况
14、是,需要定义一个反射,它以世界坐标系下的 X 轴做为对称轴。轴,可以用 gp_Ax1 来定义,通过一个点和一个方向( 3D 单位向量)来生成。定义这个轴有两种方法: 第一种方法是根据草图,使用几何方法定义:X 轴位于(0 , 0 , 0):使用 gp_Pnt 类X 辆的方向是(1 , 0 , 0) :使用 gp_Dir 类,通过它的 X,Y ,Z 坐标来生成一个 gp_Dir 实例gp_Pnt aOrigin(0 , 0 , 0); gp_Dir xDir(1 , 0 , 0); gp_Ax1 xAxis(aOrigin , xDir); 第二种也是最简单的方法是直接使用 gp包中的几何常量(
15、原点,常用方向及世界坐标系下的坐标轴),得到 X 轴,可以调用 gp:OX 方法:gp_Ax1 xAxis = gp:OX();前面说过,可以使用 gp_Trsf 类来定义一个 3D 几何变换属性。使用这个类有两种不同的方法: 根据草图定义一个变换矩阵; 利用恰当的方法生成所需的变换(平移用 SetTranslation(),镜像用SetMirror()等):变换矩阵自动计算完成;一般来说,最简单的总是最好的。使用 SetMirror 方法,用轴来做为对称中心;gp_Trsf aTrsf;aTrsf.SetMirror(xAxis); 使用 BRepBuilderAPI_Transform 类
16、来应用所定义的变换。至此,所需的要数据都已准备完成: 要应用变换的图形 几何变换BRepBuilderAPI_Transform aBRepTrsf(aWire , aTrsf);BRepBuilderAPI_Transform 并不修改原来的图形:反射生成的网格仍是一个网格。但是调用类似 BRepBuilderAPI_Transform:Shape 这样的方法会返回一个TopoDS_Shape 对象:TopoDS_Shape aMirroredShape = aBRepTrsf.Shape();假如需要反射后的结果也是一个网格。TopoDS 全局函数提供生成一个图形为它的实际类型的方法。为了
17、生成经过变换的网格,可以使用 TopoDS:Wire 方法;TopoDS_Wire aMirroredWire = TopoDS:Wire(aMirroredShape);瓶子的外形快完成了。已经创建了两个网格:一个网格和一个反射网格。现在要做的是把它们合成为一个单一的图形。可以使用BRepBuilderAPI_MakeWire 类来做这点:a) 创建一个 BRepBuilderAPI_MakeWire 实例对象b) 使用 Add 方法将两个网格的所有边都添加到此对象上。BRepBuilderAPI_MakeWire mkWire; mkWire.Add(aWire); mkWire.Add(
18、aMirroredWire); TopoDS_Wire myWireProfile = mkWire.Wire();创建瓶体3.1 轮廓体为了生成瓶子的瓶体。需要生成一个实体图形,最简单的方法是使用前面创建的外形并沿着一个方向进行推移:Open CASCADE 的实体函数非常适合实现它。它接受一个图形和一个方向为输入参数;然后生成一个图形,生成的规则如下:图形 生成顶点 边边 面网格 壳面 体壳 复合的实体现在的外形是一个网格。参考图形/生成表。利用网格面可以生成体;为了生成面,可以使用 BRepBuilderAPI_MakeFace 类。前面说过,面是由封闭的网格生成的有边界表面。通常,面可
19、以由一个表面和一个或多个网格通过 BRepBuilderAPI_MakeFace来生成。如果网格位于一个平面内,表面会自动生成:TopoDS_Face myFaceProfile = BRepBuilderAPI_MakeFace(myWireProfile); 在 BRepPrimAPI 包中提供了一些类,用来生成具有拓朴结构的图元:长方体、园环、园柱、球等。都位于 BRepPrimAPI_MakePrism 类中,按照上面所说,这个类要由这些来定义: 推移所用的基本图形 有一定长度的向量或有一定长度的方向,或者无限长;现在需要的是有一定长度的实体,沿着 Z 轴推移 myHeight 大小的
20、高度。这个向量,可以由 gp_Vec 类来定义它的 X,Y ,Z 坐标值:gp_Vec aPrismVec(0 , 0 , myHeight); 所有生成瓶体的数据现在都准备好了,下面所做的只是用BRepPrimAPI_MakePrism 类来生成实体:TopoDS_Shape myBody = BRepPrimAPI_MakePrism(myFaceProfile , aPrismVec);3.2 园角生成的瓶体,边角比较锐利,要用圆滑的面打钝它。可以使用在 Open CASCADE 中的园角功能;根据位置的不同,园角实现方法也不同,比如,可以沿着一条直线,或者沿着边缘线来计算。我们的目的是
21、,用简单的方法来生成园角,约定: 在图形的所有边上做园角 园角的半径值为 myThickness/12;为了生成一个边的园角,可以使用 BRepFilletAPI_MakeFillet 类,这个类的一般用法是:1. 在 BRepFilletAPI_MakeFillet 的构造函数中传入需要园角的图形;2. 应用 Add 方法,同时加入园角的参数值(要加入的边和半径),在这里,要加入所有需园角的边;3. 使用 Shape 方法得到园角后的图形;BRepFilletAPI_MakeFillet mkFillet(myBody); 为了添加园角的说明,需要知道图形都有哪些边。最好的解决方法是遍历实体
22、的所有边。可以用 TopExp_Explorer 类来完成。要遍历的数据结构定义是在 TopoDS_Shape 中,可以根据需要扩展子图形;一般来说,要遍历的话,要提供下面的内容: 要遍历的图形 要找的子图形的类型。可以用TopAbs_ShapeEnum 枚举结构体来定义它。TopExp_Explorer aEdgeExplorer(myBody , TopAbs_EDGE); 应用一个遍历循环过程中主要用到三个方法: 是否还有子图形要遍历; 得到当前要遍历的子图形; 得到下一个要遍历的子图形(如果 More()方法返回的结果为真);while(aEdgeExplorer.More() Top
23、oDS_Edge aEdge = TopoDS:Edge(aEdgeExplorer.Current(); /Add edge to fillet algorithm . aEdgeExplorer.Next(); 在遍历循环中,找瓶子图形的所有边。每个边都用 Add 方法添加到BRepFilletAPI_MakeFillet 实例中。记住,要把要园角的半径值也传进去;mkFillet.Add(myThickness / 12. , aEdge); 一旦完成,最后一步是得到园角后的图形:myBody = mkFillet.Shape();3.3 添加瓶颈要添加瓶颈,需生成一个园柱并把它焊到瓶体
24、上;园柱的位置在瓶体的顶面,半径设定 myThickness/4,高度设定 myHeight/10。gp_Pnt neckLocation(0 , 0 , myHeight);gp_Dir neckNormal = gp:DZ();gp_Ax2 neckAx2(neckLocation , neckNormal); 为了定位园柱,要用 gp_Ax2 类来定义一个右手坐标系,这个坐标系可以由一个点和两个方向来定义,这两个方向分别是法线方向和轴方向,轴方向可以由这两个方向计算得出。瓶子体上面的中心,在世界坐标系中的坐标是(0,0,myHeight),法线是轴。所以,局部坐标系可以这们定义:gp_P
25、nt neckLocation(0 , 0 , myHeight);gp_Dir neckNormal = gp:DZ(); gp_Ax2 neckAx2(neckLocation , neckNormal); 为了生成园柱,要用图元包中的另一个类:BRepPrimAPI_MakeCylinder 类。需要提供以下内容: 园柱所在的坐标系; 园柱的半径和高度;Standard_Real myNeckRadius = myThickness / 4.;Standard_Real myNeckHeight = myHeight / 10;TopoDS_Shape myNeck = BRepPrim
26、API_MakeCylinder(neckAx2 , myNeckRadius , myNeckHeight); 现在有了两部分,瓶子体和要焊上去的瓶颈;在 BRepAlgoAPI 包中提供了对两个图形布尔操作的方法:共同(布尔交),切(布尔减),焊(布尔并);使用 BRepAlgoAPI_Fuse 可以把两个图形焊在一起:myBody = BRepAlgoAPI_Fuse(myBody , myNeck);3.4 创建镂空实体实际的瓶子都是用来装液体的,现在要从瓶子顶面生成一个镂空的实体;在 Open CASCADE 中,一个镂空的实体称为一个有厚度的体,生成步骤:1. 从初始的实体中移除一
27、个或多个面得到第一个镂空实体的面;2. 生成一个平行于的面,两个面之间的距离是,如果为正,则在初始实体的外面,否则在里面;3. 通过和生成实体;for(TopExp_Explorer aFaceExplorer(myBody , TopAbs_FACE) ; aFaceExplorer.More() ;aFaceExplorer.Next() TopoDS_Face aFace = TopoDS:Face(aFaceExplorer.Current();TopoDS_Face aFace = TopoDS:Face(aFaceExplorer.Current(); 为了生成一个有厚度的实体,可
28、以生成一个 BRepOffsetAPI_MakeThickSolid类的实例,给它传递以下值: 要镂空的图形 需要计算的公差 两个面和之间的厚度(距离) 从原始的实体中移除面而生成的第一个面;这一步最不好做的是从图形中找到要移除的面:瓶颈园柱体的顶面;这个面: 在几何体上的一个平面; 瓶子上的最高的面(按轴来说);有了这个面的这些特征,可以再次遍历瓶子的所有面并找到这个面:for(TopExp_Explorer aFaceExplorer(myBody , TopAbs_FACE) ; aFaceExplorer.More() ; aFaceExplorer.Next() TopoDS_Fac
29、e aFace = TopoDS:Face(aFaceExplorer.Current(); TopoDS_Face aFace = TopoDS:Face(aFaceExplorer.Current(); 为了检测每个面,需要找到它的表面。有一个工具类可以处理图形的几何属性:BRep_Tool 类,这个类常见的用法有: 对于表面:可以处理表面上的面; 对于曲线:可以处理曲线上的边; 对于向量:可以处理向量上的点;Handle(Geom_Surface) aSurface = BRep_Tool:Surface(aFace); 可以看出,BRep_Tool:Surface 方法返回一个 Geo
30、m_Surface 类的实例,这个实例是通过句柄分配;但是,Geom_Surface 类并不提供 aSurface 对象的实际类型的信息,这个引用的类型可能是 Geom_Plane,Geom_CylindricalSurface 等;类似 Geom_Surface,所有的对象是通过句柄分配,这些对象是从Standard_Transient 派生出来的。在这里与类型相关的两个有用的方法是: DynamicType 方法:可以得出对象的实际类型; IsKind 方法:可以计算一个对象是否是从某种类型派生出来的;DynamicType 方法返回对象的实际类型,但是要用它和已知类型比较来得出aSurf
31、ace 是一个平面,还是一个园柱侧面或其它类型;为了用所给定的类型和要找的类型相比较,可以使用 STANDARD_TYPE 宏。这个宏返回一个类的类型;if(aSurface-DynamicType() = STANDARD_TYPE(Geom_Plane) . 如果比较的结果为真,可以得出 aSurface 的实际类型是 Geom_Plane。接下来,可以在 Standard_Transient 中找到另一个有用的方法,把它从Geom_Surface 转化为 Geom_Plane,这个方法是 DownCast 方法;正如它的名字一样,这个静态方法用来把一种给定的类型转化为另一种类型:Hand
32、le(Geom_Plane) aPlane = Handle(Geom_Plane):DownCast(aSurface); 记住,转化的目的是为了找到瓶子所有表面中最顶部的平面。、现在假设有了这两个全局变量:TopoDS_Face faceToRemove; Standard_Real zMax = -1; 接下来,使用 Geom_Plane:Location 方法就很容易找到在方向上哪个面的原始点最大,比如:gp_Pnt aPnt = aPlane-Location();Standard_Real aZ = aPnt.Z();if(aZ zMax) zMax = aZ; faceToRem
33、ove = aFace; 现在已经找到的瓶颈的顶面。在生成一个镂空的实体前,最后要做的一步是要把这个面放在一个链表中。因为有可能从原始的实体中移除一个或多个面,所以,BRepOffsetAPI_MakeThickSolid 构造函数中传进去的参数是一个面的链表;Open CASCADE 为不同的的对象提供了多种集合,Geom 包中对象的集合类都在 TColGeom 包中;gp 包中的对象的集合类都在 TColgp 包中。图形的集合是在 TopTools 包中。由于 BRepOffsetAPI_MakeThickSolid 需要一个列表,因此,使用 TopTools_ListOfShape 类:TopTools_ListOfShape facesToRemove; facesToRemove.Append(faceToRemove); 现在,所有必须的数据都准备好了,现在可以调用BRepOffsetAPI_MakeThickSolid 构造函数来生成一个镂空的实体了;MyBody = BRepOffsetAPI_MakeThickSolid(myBody , facesToRemove , -myThickness / 50 , 1.e-3);