Open CASCADE 建模:瓶子教程


Open CASCADE Technology(OCCT)是一个面向对象的 C++ 框架,旨在快速生产复杂的 CAD / CAM / CAE 应用程序。它为 C++ 环境中的原始 2D 和 3D 建模提供了无限的可能性。OCCT 为 2D 和 3D 对象的建模、编辑、可视化和数据互操作性提供了足够的构建块。
通过使用 OCCT,用户可以使用原始代码命令创建他们想要的对象(或编辑现有的对象)。


这篇文章来源于 dev.opencascade.org,使用 Microsoft Edge 翻译,原文 Open CASCADE Technology - Modeling: Bottle Tutorial

概述

本教程将教您如何使用 Open CASCADE Technology 服务对 3D 对象进行建模。本教程的目的不是介绍所有 Open CASCADE Technology 课程,而是帮助您开始思考将 Open CASCADE Technology 作为一种工具。

先决条件

本教程假定您具有使用和设置 C++ 的经验。从编程的角度来看,Open CASCADE Technology 旨在通过 3D 建模类、方法和函数增强您的 C++ 工具。所有这些资源的组合将允许您创建大量应用程序。

模型

为了说明如何使用 3D 几何建模工具包中提供的类,您将创建一个瓶子,如下所示:

在这里插入图片描述

在本教程中,我们将逐步创建一个函数,该函数将对瓶子进行建模,如上所示。您将找到本教程的完整源代码,包括 Open CASCADE Technology 发行版中的 MakeBottle 函数。函数体在文件 samples/qt/Tutorial/src/MakeBottle.cxx 中提供。

模型规格

我们首先定义瓶子规格如下:

对象参数参数名称参数值
Bottle heightMyHeight70mm
Bottle widthMyWidth50mm
Bottle thicknessMyThickness30mm

此外,我们决定瓶子的轮廓(底部)将以全局笛卡尔坐标系的原点为中心。

在这里插入图片描述

此建模需要四个步骤:

  • 建立瓶子轮廓
  • 构建瓶子的主体
  • 在瓶口构建螺纹
  • 构建结果复合体

构建瓶子轮廓

定义支撑点

要创建瓶子的轮廓,首先要创建特征点及其坐标,如下图 (XOY) 平面所示。这些点将是定义轮廓几何形状的支座。

在这里插入图片描述

在 Open CASCADE Technology 中,有两个类可以从 X、Y 和 Z 坐标描述 3D 笛卡尔点:

句柄(handle)是一种智能指针,可提供自动内存管理。要为此应用程序选择最佳类,请考虑以下事项:

gp_Pnt 是由值(value)操纵的。像所有同类对象一样,它的生存期有限。
Geom_CartesianPoint 由句柄(handle)操作,可能具有多个引用和较长的生存期。

由于您将定义的所有点仅用于创建轮廓的曲线,因此具有有限生存期的对象就可以了。选择 gp_Pnt 类。要实例化 gp_Pnt 对象,只需指定全局笛卡尔坐标系中点的 X、Y 和 Z 坐标:

gp_Pnt aPnt1(-myWidth / 2., 0, 0);
gp_Pnt aPnt2(-myWidth / 2., -myThickness / 4., 0);
gp_Pnt aPnt3(0, -myThickness / 2., 0);
gp_Pnt aPnt4(myWidth / 2., -myThickness / 4., 0);
gp_Pnt aPnt5(myWidth / 2., 0, 0);

实例化对象后,可以使用类提供的方法访问和修改其数据。例如,要获取点的 X 坐标:

Standard_Real xValue1 = aPnt1.X();

轮廓:定义几何图形

借助先前定义的点,您可以计算瓶子轮廓几何形状的一部分。如下图所示,它将由两条线段和一条弧线组成。

在这里插入图片描述

若要创建此类实体,需要一个特定的数据结构,用于实现 3D 几何对象。这可以在 Open CASCADE Technology 的 Geom 软件包(package)中找到。在 Open CASCADE Technology 中,软件包(package)是一组提供相关功能的类。这些类的名称以它们所属的包(package)的名称开头。例如,Geom_LineGeom_Circle 类属于 Geom 包。Geom 软件包实现了 3D 几何对象:提供了基本曲线和曲面以及更复杂的曲线和曲面(例如 Bezier 和 BSpline)。但是,Geom 包仅提供几何实体的数据结构。您可以直接实例化属于 Geom 的类,但使用 GC 包可以更轻松地计算基本曲线和曲面。这是因为 GC 提供了两个算法类,它们正是我们轮廓所需的:

  • GC_MakeSegment 创建一条线段。它的一个构造函数允许您通过两个端点 P1 和 P2 定义线段。
  • GC_MakeArcOfCircle 创建一条圆弧。一个有用的构造函数从两个端点 P1 和 P3 并通过 P2 创建圆弧。

这两个类都返回由句柄操作的 Geom_TrimmedCurve。此实体表示一条基本曲线(在我们的例子中为线段或圆),限制在其两个参数值之间。例如,圆 C 在 0 和 2PI 之间参数化。如果需要创建四分之一的圆,则可以在 C 上创建一个限制在 0 和 M_PI/2 之间的 Geom_TrimmedCurve

Handle(Geom_TrimmedCurve) aArcOfCircle = GC_MakeArcOfCircle(aPnt2,aPnt3,aPnt4);
Handle(Geom_TrimmedCurve) aSegment1    = GC_MakeSegment(aPnt1, aPnt2);
Handle(Geom_TrimmedCurve) aSegment2    = GC_MakeSegment(aPnt4, aPnt5);

所有 GC 类都提供一种强制转换方法,用于通过类似函数的调用自动获取结果。请注意,如果构造失败,此方法将引发异常。若要更显式地处理可能的错误,可以使用 IsDoneValue 方法。例如:

GC_MakeSegment mkSeg (aPnt1, aPnt2);
Handle(Geom_TrimmedCurve) aSegment1;
if(mkSeg.IsDone()){
    aSegment1 = mkSeg.Value();
}
else {
// 处理错误
}

轮廓:定义拓扑结构

您已经创建了轮廓一部分的支座几何体,但这些曲线是独立的,彼此之间没有关系。为了简化建模,将这三条曲线作为一个实体进行操作是正确的。这可以通过使用 TopoDS 软件包中定义的 Open CASCADE Technology 的拓扑数据结构来实现:它定义了几何实体之间的关系,这些实体可以链接在一起以表示复杂的形状。从 TopoDS_Shape 类继承的 TopoDS 包的每个对象都描述了一个拓扑形状,如下所述:

形状Open CASCADE Technology 类描述
VertexTopoDS_Vertex对应于几何图形中某个点的零维形状。
EdgeTopoDS_Edge对应于曲线的一维形状,以每端的顶点为界。
WireTopoDS_Wire由顶点连接的边序列。
FaceTopoDS_Face由闭合线包围的曲面的一部分。
ShellTopoDS_Shell由边连接的一组面。
SolidTopoDS_Solid以 Shell 为边界的 3D 空间的一部分。
CompSolidTopoDS_CompSolid由它们的面连接的一组实体。
CompoundTopoDS_Compound上述任何其他形状的集合。

参考上表,要构建轮廓,您将创建:

  • 先前计算的曲线中的三条边(edge)。
  • 一条由这三条边(edge)连成的线(wire)。

在这里插入图片描述

但是,TopoDS 包仅提供拓扑实体的数据结构。可用于计算标准拓扑对象的算法类可以在 BRepBuilderAPI 包中找到。要创建一条边,请将 BRepBuilderAPI_MakeEdge 类与先前计算的曲线一起使用:

TopoDS_Edge anEdge1 = BRepBuilderAPI_MakeEdge(aSegment1);
TopoDS_Edge anEdge2 = BRepBuilderAPI_MakeEdge(aArcOfCircle);
TopoDS_Edge anEdge3 = BRepBuilderAPI_MakeEdge(aSegment2);

在 Open CASCADE Technology 中,您可以通过多种方式创建边(edge)。一种可能性是直接从两个点创建一条边,在这种情况下,该边的基础几何是一条线,由从两个输入点自动计算的两个顶点为边界。例如,anEdge1 和 anEdge3 可以采用更简单的方式计算:

TopoDS_Edge anEdge1 = BRepBuilderAPI_MakeEdge(aPnt1, aPnt3);
TopoDS_Edge anEdge3 = BRepBuilderAPI_MakeEdge(aPnt4, aPnt5);

要连接边(edge),您需要使用 BRepBuilderAPI_MakeWire 类创建一条线(wire)。有两种方法可以使用这个类创建一条线:

  • 直接从 1 到 4 个边(edge)创建
  • 通过向现有线(wire)添加其他线(wire)或边(edge)(本教程稍后将对此进行说明)

当从少于四条边构建线时,如本例所示,您可以直接使用构造函数,如下所示:

TopoDS_Wire aWire = BRepBuilderAPI_MakeWire(anEdge1, anEdge2, anEdge3);

轮廓:完成轮廓

创建线的第一部分后,您需要计算完整的轮廓。执行此操作的一种简单方法是:

  • 通过反射现有线来计算新的线。
  • 将反射的线添加到初始线中。

在这里插入图片描述

若要对形状(包括线(wire))应用变换,首先需要使用 gp_Trsf 类定义 3D 几何变换的属性。这种转换可以是平移、旋转、缩放、反射或这些转换的组合。在我们的例子中,我们需要定义相对于全局坐标系的 X 轴的反射。用 gp_Ax1 类定义的轴由一个点构建,并具有一个方向(3D 单位向量)。有两种方法可以定义这个轴。第一种方法是从头开始定义它,使用其几何定义:

  • X 轴位于 (0, 0, 0) — 使用 gp_Pnt 类。
  • X 轴方向为 (1, 0, 0) — 使用 gp_Dir 类。gp_Dir 实例是根据其 X、Y 和 Z 坐标创建的。
gp_Pnt aOrigin(0, 0, 0);
gp_Dir xDir(1, 0, 0);
gp_Ax1 xAxis(aOrigin, xDir);

第二种也是最简单的方法是使用 gp 包中定义的几何常数(全局坐标系的原点、主方向和轴)。要获取 X 轴,只需调用 gp::OX 方法:

gp_Ax1 xAxis = gp::OX();

如前所述,3D 几何变换是使用 gp_Trsf 类定义的。有两种不同的方式来使用这个类:

  • 通过所有的它的值定义一个变换矩阵
  • 通过使用与所需转换相对应的适当方法(用于平移的 SetTranslation、用于反射的 SetMirror 等):来自动计算矩阵。

由于最简单的方法总是最好的方法,因此您应该使用以轴为对称中心的 SetMirror 方法。

gp_Trsf aTrsf;
aTrsf.SetMirror(xAxis);

现在,通过指定以下内容,您拥有了使用 BRepBuilderAPI_Transform 类应用转换所需的所有数据:

  • 必须应用变换的形状
  • 几何变换
BRepBuilderAPI_Transform aBRepTrsf(aWire, aTrsf);

BRepBuilderAPI_Transform 不会改变形状的性质:反射线(wire)的结果仍然是线(wire)。但是类似函数的调用或 BRepBuilderAPI_Transform::Shape 方法返回一个 TopoDS_Shape 对象:

TopoDS_Shape aMirroredShape = aBRepTrsf.Shape();

您需要的是一种将生成的反射形状视为线(wire)的方法。TopoDS 全局函数通过将形状转换为其实际类型来提供此类服务。若要转换变换后的线(wire),请使用 TopoDS::Wire 方法。

TopoDS_Wire aMirroredWire = TopoDS::Wire(aMirroredShape);

瓶子的轮廓几乎完成了。您已经创建了两条线:aWireaMirroredWire。您需要将它们连接起来以计算单个形状。为此,请使用 BRepBuilderAPI_MakeWire 类,如下所示:

BRepBuilderAPI_MakeWire mkWire;
mkWire.Add(aWire);
mkWire.Add(aMirroredWire);
TopoDS_Wire myWireProfile = mkWire.Wire();

构建瓶体

拉伸(Prism)轮廓

要计算瓶体,您需要创建一个实体形状。最简单的方法是使用先前创建的轮廓并沿一个方向延伸它。Open CASCADE Technology 的 Prism 功能最适合该任务。它接受一个形状和一个方向作为输入,并根据以下规则生成一个新形状:

ShapeGenerates
VertexEdge
EdgeFace
WireShell
FaceSolid
ShellCompound of Solids

在这里插入图片描述

您当前的轮廓是线(wire)。参考“形状/生成”(Shape/Genesets)表,您需要从其线(wire)中计算面(face)以生成实体(solid)。若要创建面(face),请使用 BRepBuilderAPI_MakeFace 类。如前所述,面(face)是以闭合线(wire)为界的曲面的一部分。通常,BRepBuilderAPI_MakeFace 从曲面和一条或多条线(wire)中计算面(face)。当线(wire)位于平面上时,会自动计算曲面。

TopoDS_Face myFaceProfile = BRepBuilderAPI_MakeFace(myWireProfile);

BRepPrimAPI 包提供了用于创建拓扑基元构造的所有类:长方体、圆锥体、圆柱体、球体等。其中包括 BRepPrimAPI_MakePrism 类。如上所述,Prism 定义如下:

  • 要扫描的基础形状;
  • 有限拉伸的向量或有限和无限拉伸的方向。(a vector for a finite prism or a direction for finite and infinite prisms.)

您希望实体是有限的,沿 Z 轴扫描,并且是 myHeight 高度。向量在其 X、Y 和 Z 坐标上用 gp_Vec 类定义,为:

gp_Vec aPrismVec(0, 0, myHeight);

创建瓶体的所有必要数据现在都可用。只需应用 BRepPrimAPI_MakePrism 类来计算实体(solid):

TopoDS_Shape myBody = BRepPrimAPI_MakePrism(myFaceProfile, aPrismVec);

应用圆角

瓶身的边缘非常锋利。要将它们替换为圆角面,你可以使用 Open CASCADE Technology 的 Fillet 功能。出于我们的目的,我们将指定圆角必须是:

  • 应用于形状的所有边
  • 半径为 myThickness / 12

在这里插入图片描述

若要在形状的边缘应用圆角,请使用 BRepFilletAPI_MakeFillet 类。该类通常按如下方式使用:

  • BRepFilletAPI_MakeFillet 构造函数中指定要圆角的形状。
  • 使用 Add 方法添加圆角描述(边和半径)(您可以根据需要添加任意数量的边)。
  • 使用 Shape 方法请求生成的圆角形状。
BRepFilletAPI_MakeFillet mkFillet(myBody);

要添加圆角描述,您需要知道属于您的形状的边缘。最好的解决方案是探索实体以检索其边缘。TopExp_Explorer 类提供了此类功能,该类可浏览 TopoDS_Shape 中描述的数据结构,并提取您特别需要的子形状。通常,通过提供以下信息来创建此探索器:

  • 探索的形状
  • 要查找的子形状的类型。此信息随 TopAbs_ShapeEnum 枚举一起提供。
TopExp_Explorer anEdgeExplorer(myBody, TopAbs_EDGE);

探索器(explorer)通常使用其三种主要方法在循环中应用:

  • More() 用来了解是否有更多子形状可供探索。
  • Current() 知道哪个是当前探索的子形状(仅当 More() 方法返回 true 时才使用)。
  • Next() 移动到下一个要探索的子形状。
while(anEdgeExplorer.More()){
    TopoDS_Edge anEdge = TopoDS::Edge(anEdgeExplorer.Current());
    //Add edge to fillet algorithm
    ...
    anEdgeExplorer.Next();
}

在浏览器(explorer)循环中,您已经找到了瓶子形状的所有边缘。然后,必须使用 Add() 方法将每个实例添加到BRepFilletAPI_MakeFillet 实例中。不要忘记指定圆角的半径。

mkFillet.Add(myThickness / 12., anEdge);

完成此操作后,您可以通过请求圆角形状来执行该过程的最后一步。

myBody = mkFillet.Shape();

添加颈部

要在瓶子上添加颈部,您将创建一个圆柱体并将其融合到瓶身上。圆柱体应放置在瓶体的顶面上,半径为 myThickness / 4,高度为 myHeight / 10

在这里插入图片描述

要定位圆柱体,您需要定义一个坐标系,其中 gp_Ax2 类定义一个来自一个点和两个方向的右手坐标系 - 主(Z)轴方向和 X 方向(Y 方向由这两个方向计算)。要使颈部与顶面的中心对齐,使其处于全局坐标系 (0, 0, myHeight) 中,其法线位于全局 Z 轴上,可以按如下方式定义局部坐标系:

gp_Pnt neckLocation(0, 0, myHeight);
gp_Dir neckAxis = gp::DZ();
gp_Ax2 neckAx2(neckLocation, neckAxis);

若要创建圆柱体,请使用基元构造包中的另一个类:BRepPrimAPI_MakeCylinder 类。您必须提供的信息是:

  • 圆柱体所在的坐标系;
  • 半径和高度。
Standard_Real myNeckRadius = myThickness / 4.;
Standard_Real myNeckHeight = myHeight / 10;
BRepPrimAPI_MakeCylinder MKCylinder(neckAx2, myNeckRadius, myNeckHeight);
TopoDS_Shape myNeck = MKCylinder.Shape();

你现在有两个独立的部分:一个瓶体和一个需要融合在一起的颈部。BRepAlgoAPI 包提供了在形状之间执行布尔运算的服务,特别是:common(布尔交集)、cut(布尔差集)和 fuse(布尔并集)。使用 BRepAlgoAPI_Fuse 融合两种形状:

myBody = BRepAlgoAPI_Fuse(myBody, myNeck);

创建空心实体

由于真正的瓶子用于盛装液体材料,因此您现在应该从瓶子的顶面创建一个空心实体。在 Open CASCADE Technology 中,空心的实体称为 Thick Solid,其内部计算如下:

  • 从初始实体中移除一个或多个面,以获得空心实体的第一个壁 W1。
  • 从 W1 在 D 距离处创建平行壁 W2。如果 D 为正,则 W2 将位于初始实体外部,否则它将位于内部。
  • 从两个壁 W1 和 W2 计算实体。

在这里插入图片描述

若要计算 Thick Solid,请通过提供以下信息来创建 BRepOffsetAPI_MakeThickSolid 类的实例:

  • 形状,必须是空心的。
  • 用于计算的公差(生成形状中重合的公差标准)。
  • 两壁 W1 和 W2 之间的厚度(距离 D)。
  • 要从原始实体中移除以计算第一个壁 W1 的面。

在这个过程中,具有挑战性的部分是找到要从你的形状中去除的面(face) - 颈部的顶面,它:

  • 有一个平面(planar surface)作为基础几何图形;
  • 是瓶子的最高面(在 Z 坐标上)。

要找到具有这些特征的面,您将再次使用一个浏览器(an explorer)遍历瓶子的所有面以找到合适的面(face)。

for(TopExp_Explorer aFaceExplorer(myBody, TopAbs_FACE) ; aFaceExplorer.More() ; aFaceExplorer.Next()){
    TopoDS_Face aFace = TopoDS::Face(aFaceExplorer.Current());
}

对于每个检测到的面,您需要访问形状的几何属性:为此使用 BRep_Tool 类。这个类最常用的方法有:

  • Surface 用于访问面的表面(surface);
  • Curve 用于访问边(edge)的 3D 曲线;
  • Point 用于访问顶点(vertex)的 3D 点。
Handle(Geom_Surface) aSurface = BRep_Tool::Surface(aFace);

如您所见,BRep_Tool::Surface 方法返回由 handle 操作的 Geom_Surface 类的实例。但是,Geom_Surface 类不提供有关对象 aSurface 的实际类型的信息,该对象可能是 Geom_PlaneGeom_CylindricalSurface 等的实例。所有由 handle 操作的对象(如 Geom_Surface)都继承自 Standard_Transient 类,该类提供了两种非常有用的有关类型的方法:

  • DynamicType 用于了解对象的实际类型
  • IsKind 用于了解对象是否继承自一种特定类型

DynamicType 返回对象的实际类型,但您需要将其与现有的已知类型进行比较,以确定 aSurface 是平面、圆柱曲面还是其他类型。若要将给定类型与所查找的类型进行比较,请使用 STANDARD_TYPE 宏,该宏返回类的类型:

if(aSurface->DynamicType() == STANDARD_TYPE(Geom_Plane)){
}

如果这个比较结果为 true,则您知道 aSurface 实际类型为 Geom_Plane。然后,您可以使用继承自 Standard_Transient 的每个类提供的 DownCast() 方法将其从 Geom_Surface 转换为 Geom_Plane。顾名思义,此静态方法用于将对象向下转换为给定类型,语法如下:

Handle(Geom_Plane) aPlane = Handle(Geom_Plane)::DownCast(aSurface);

请记住,所有这些转换的目标是找到位于平面上的瓶子的最高面。假设您有以下两个全局变量:

TopoDS_Face faceToRemove;
Standard_Real zMax = -1;

您可以很容易地找到原点在 Z 中最大的平面,平面的位置是使用 Geom_Plane::Location 方法给出的。例如:

gp_Pnt aPnt = aPlane->Location();
Standard_Real aZ = aPnt.Z();
if(aZ > zMax){
    zMax = aZ;
    faceToRemove = aFace;
}

你现在已经找到了颈部的顶面。在创建空心实体之前的最后一步是将此面放入列表中。由于可以从初始实体中删除多个面,因此 BRepOffsetAPI_MakeThickSolid 构造函数将面(face)列表作为参数。Open CASCADE Technology 为不同类型的对象提供了许多集合:TColGeom 包提供了 Geom 包中对象的集合,TColgp 包提供了 gp 包中对象的集合等。 形状的集合可以在 TopTools 包中找到。由于 BRepOffsetAPI_MakeThickSolid 需要列表,因此请使用 TopTools_ListOfShape 类。

TopTools_ListOfShape facesToRemove;
facesToRemove.Append(faceToRemove);

现在,所有必需的数据都可用,因此可以通过调用 BRepOffsetAPI_MakeThickSolid::MakeThickSolidByJoin 方法来创建空心实体:

BRepOffsetAPI_MakeThickSolid aSolidMaker;
aSolidMaker.MakeThickSolidByJoin(myBody, facesToRemove, -myThickness / 50, 1.e-3);
myBody = aSolidMaker.Shape();

构建瓶口螺纹

创建曲面

到目前为止,您已经学习了如何从 3D 曲线创建边。现在,您将学习如何从 2D 曲线和曲面创建边(edge)。要了解 Open CASCADE Technology 的这一方面,您将在圆柱曲面上根据二维曲线构建螺旋形轮廓。该理论比前面的步骤更复杂,但应用它非常简单。第一步,计算这些圆柱曲面。您已经熟悉 Geom 包的曲线。现在,您可以使用以下方法创建圆柱曲面(Geom_CylindricalSurface):

  • 一个坐标系;
  • 一个半径。

使用用于定位颈部的相同坐标系 neckAx2,你可以创建两个具有以下半径的圆柱曲面 Geom_CylindricalSurface

在这里插入图片描述

请注意,其中一个圆柱形曲面小于颈部。这是有充分理由的:创建螺纹后,您会将其与颈部融合。因此,我们必须确保这两个形状保持接触。

Handle(Geom_CylindricalSurface) aCyl1 = new Geom_CylindricalSurface(neckAx2, myNeckRadius * 0.99);
Handle(Geom_CylindricalSurface) aCyl2 = new Geom_CylindricalSurface(neckAx2, myNeckRadius * 1.05);

定义二维曲线

为了创建瓶子的颈部,您制作了一个基于圆柱形表面的实心圆柱体。您将通过在这样的表面上创建 2D 曲线来创建螺纹轮廓。Geom 包中定义的所有几何图形都已参数化。这意味着 Geom 中的每条曲线或曲面都是使用参数化方程计算的。Geom_CylindricalSurface 曲面由以下参数化方程定义:

P(U, V) = O + R * (cos(U) * xDir + sin(U) * yDir) + V * zDir,其中:

  • P 是由参数 (U, V) 定义的点。
  • OxDiryDirzDir 分别是圆柱曲面局部坐标系的原点、X 方向、Y 方向和 Z 方向。
  • R 是圆柱表面的半径。
  • U 范围为 [0, 2PI]V 为无穷大。

在这里插入图片描述

拥有这种参数化几何的优点是,您可以计算曲面的任何 (U, V) 参数:

  • 3D 点;
  • 在这点的 1、2 到 N 阶的导数向量。

这些参数方程还有另一个优点:可以将曲面视为使用 (U, V) 坐标系定义的二维参数空间。例如,考虑颈部表面的参数范围:

在这里插入图片描述

假设您在此参数化 (U, V) 空间上创建一条 2D 线并计算其 3D 参数曲线。根据线定义,结果如下:

参数值参数方程参数曲线
U = 0P(V) = O + V * zDir平行于 Z 方向的线
V = 0P(U) = O + R * (cos(U) * xDir + sin(U) * yDir)平行于 (O, X, Y) 平面的圆
U != 0 V != 0P(U, V) = O + R * (cos(U) * xDir + sin(U) * yDir) + V * zDir描述圆柱体高度和角度演变的螺旋曲线

螺旋曲线类型正是您所需要的。在颈部表面,这条曲线的演化规律将是:

  • 在 V 参数中:高度描述介于 0myHeighNeck 之间
  • 在 U 参数中:角度描述介于 02PI 之间。但是,由于圆柱曲面是 U 周期曲面,因此您可以决定将此角度演变扩展到 4PI,如下图所示:

在这里插入图片描述

在这个 (U, V) 参数空间中,您将创建一个局部 (X, Y) 坐标系来定位要创建的曲线。此坐标系将定义如下:

  • 位于颈部圆柱体参数空间中间的中心,位于 U、V 坐标中 (2*PI, myNeckHeight / 2)
  • 使用 U、V 坐标中的 (2*PI, myNeckHeight / 4) 向量定义的 X 方向,使曲线占据颈部曲面的一半。

在这里插入图片描述

要使用 Open CASCADE Technology 的 2D 基元几何类型来定义点和坐标系,您将再次实例化 gp 中的类:

  • 若要根据其 X 和 Y 坐标定义 2D 点,请使用 gp_Pnt2d 类。
  • 若要根据其 X 和 Y 坐标定义 2D 方向(单位向量),请使用 gp_Dir2d 类。坐标将自动归一化。
  • 要定义 2D 右手坐标系,请使用 gp_Ax2d 类,该类是根据一个点(坐标系的原点)和一个方向(坐标系的 X 方向)计算得出的。将自动计算 Y 方向。
gp_Pnt2d aPnt(2. * M_PI, myNeckHeight / 2.);
gp_Dir2d aDir(2. * M_PI, myNeckHeight / 4.);
gp_Ax2d anAx2d(aPnt, aDir);

现在,您将定义曲线。如前所述,这些螺纹轮廓是在两个圆柱面上计算的。在下图中,左边的曲线定义了基底(在 aCyl1 表面上),右边的曲线定义了螺纹形状的顶部(在 aCyl2 表面上)。

在这里插入图片描述

您已经使用 Geom 包来定义 3D 几何图元。对于 2D,您将使用 Geom2d 包。对于 Geom,所有几何图形都已参数化。例如,Geom2d_Ellipse 椭圆的定义如下:

  • 以椭圆中心为原点的坐标系;
  • 由坐标系的 X 方向定义的主轴上的大半径;
  • 由坐标系的 Y 方向定义的短轴上的小半径。

假设:

  • 两个椭圆具有相同的主半径,均为 2*PI
  • 第一个椭圆的小半径为 myNeckHeight / 10
  • 第二个椭圆的小半径值是第一个椭圆的四分之一

你的椭圆定义如下:

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);

要描述上面绘制的圆弧的曲线部分,请从创建的椭圆中定义 Geom2d_TrimmedCurve 修剪的曲线和两个参数来限制它们。由于椭圆的参数方程为 P(U) = O + (MajorRadius * cos(U) * XDirection) + (MinorRadius * sin(U) * YDirection),因此椭圆需要限制在 0M_PI 之间。

Handle(Geom2d_TrimmedCurve) anArc1 = new Geom2d_TrimmedCurve(anEllipse1, 0, M_PI);
Handle(Geom2d_TrimmedCurve) anArc2 = new Geom2d_TrimmedCurve(anEllipse2, 0, M_PI);

最后一步是定义线段,对于两个轮廓来说,线段是相同的:一条线受其中一个圆弧的第一个点和最后一个点的限制。要访问与曲线或曲面的参数相对应的点,请使用 ValueD0 方法(即 0 阶导数),D1 方法用于一阶导数,D2 用于二阶导数。

gp_Pnt2d anEllipsePnt1 = anEllipse1->Value(0);
gp_Pnt2d anEllipsePnt2;
anEllipse1->D0(M_PI, anEllipsePnt2);

在创建瓶子的轮廓时,您使用了 GC 包中的类,提供了创建基本几何图形的算法。在二维几何中,这种算法可以在 GCE2d 软件包中找到。类名和行为与 GC 中的类似。例如,要从两点创建 2D 线段:

Handle(Geom2d_TrimmedCurve) aSegment = GCE2d_MakeSegment(anEllipsePnt1, anEllipsePnt2);

构建边和线

正如您在创建瓶子的基本轮廓时所做的那样,您现在可以:

  • 计算颈部螺纹的边(edge)。
  • 从这些边中计算出两条线(wire)。

在这里插入图片描述

之前,您已经构建了:

  • 螺纹的两个圆柱面
  • 定义螺纹的基本几何形状的三条二维曲线

要计算这些曲线的边,请再次使用 BRepBuilderAPI_MakeEdge 类。它的构造函数之一允许您从曲面的 2D 参数空间中描述的曲线中构建边。

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);

请记住,这些线是由曲面和 2D 曲线构建的。就这些线而言,缺少一个重要的数据项:没有关于 3D 曲线的信息。幸运的是,您不需要自己计算,这可能是一项艰巨的任务,因为数学可能非常复杂。当一个形状包含除 3D 曲线之外的所有必要信息时,Open CASCADE Technology 提供了一种自动构建它们的工具。在 BRepLib 工具包中,你可以使用 BuildCurves3d 方法计算一个形状所有边的 3D 曲线。

BRepLib::BuildCurves3d(threadingWire1);
BRepLib::BuildCurves3d(threadingWire2);

创建螺纹

您已经计算了螺纹的线。螺纹将是一个实体形状,因此您现在必须计算线的面、允许您连接线的面、这些面的壳,然后是实体本身。这可能是一个冗长的操作。定义基本拓扑时,总是有更快的方法来构建实体。您想用两条线创建一个实体。Open CASCADE Technology 提供了一种通过建造阁楼(loft)的快速方法来做到这一点:一个壳(shell)或一个实体(solid)以给定的顺序穿过一组线。loft 函数在 BRepOffsetAPI_ThruSections 类中实现,您可以按如下方式使用该类:

在这里插入图片描述

  • 通过创建类的实例来初始化算法。如果要创建实体,则必须指定此构造函数的第一个参数。默认情况下,BRepOffsetAPI_ThruSections 构建一个 shell。
  • 使用 AddWire 方法添加连续的线。
  • 使用 CheckCompatibility 方法激活(或停用)用于检查线是否具有相同数量的边的选项。在这种情况下,每条线都有两条边,因此您可以停用此选项。
  • 使用 Shape 方法请求生成的放样(loft)形状。
BRepOffsetAPI_ThruSections aTool(Standard_True);
aTool.AddWire(threadingWire1);
aTool.AddWire(threadingWire2);
aTool.CheckCompatibility(Standard_False);
TopoDS_Shape myThreading = aTool.Shape();

构建结果复合体

你几乎完成了瓶子的制作。使用 TopoDS_CompoundBRep_Builder 类从 myBodymyThreading 生成单个形状:

TopoDS_Compound aRes;
BRep_Builder aBuilder;
aBuilder.MakeCompound (aRes);
aBuilder.Add (aRes, myBody);
aBuilder.Add (aRes, myThreading);

恭喜!你的瓶子是完整的。以下是 Tutorial 应用程序的结果快照:

在这里插入图片描述

我们希望本教程能让您感受到 Open CASCADE Technology 的工业实力。如果您想了解更多信息并使用 Open CASCADE Technology 开发重大项目,我们邀请您在我们的网站 https://www.opencascade.com/content/technology-support 上学习我们的培训、支持和咨询服务。我们的专业服务可以最大限度地发挥您的 Open CASCADE Technology 应用程序的功能。

附录

MakeBottle 函数的完整定义(在教程的 samples/qt/tutorial/src/MakeBottle.cxx 文件中定义):

TopoDS_Shape MakeBottle(const Standard_Real myWidth, const Standard_Real myHeight,
                        const Standard_Real myThickness)
{
    // Profile : Define Support Points
    gp_Pnt aPnt1(-myWidth / 2., 0, 0);        
    gp_Pnt aPnt2(-myWidth / 2., -myThickness / 4., 0);
    gp_Pnt aPnt3(0, -myThickness / 2., 0);
    gp_Pnt aPnt4(myWidth / 2., -myThickness / 4., 0);
    gp_Pnt aPnt5(myWidth / 2., 0, 0);
    // Profile : Define the Geometry
    Handle(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 Topology
    TopoDS_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);
    // Complete Profile
    gp_Ax1 xAxis = gp::OX();
    gp_Trsf aTrsf;
    aTrsf.SetMirror(xAxis);
    BRepBuilderAPI_Transform aBRepTrsf(aWire, aTrsf);
    TopoDS_Shape aMirroredShape = aBRepTrsf.Shape();
    TopoDS_Wire aMirroredWire = TopoDS::Wire(aMirroredShape);
    BRepBuilderAPI_MakeWire mkWire;
    mkWire.Add(aWire);
    mkWire.Add(aMirroredWire);
    TopoDS_Wire myWireProfile = mkWire.Wire();
    // Body : Prism the Profile
    TopoDS_Face myFaceProfile = BRepBuilderAPI_MakeFace(myWireProfile);
    gp_Vec aPrismVec(0, 0, myHeight);
    TopoDS_Shape myBody = BRepPrimAPI_MakePrism(myFaceProfile, aPrismVec);
    // Body : Apply Fillets
    BRepFilletAPI_MakeFillet mkFillet(myBody);
    TopExp_Explorer anEdgeExplorer(myBody, TopAbs_EDGE);
    while(anEdgeExplorer.More()){
        TopoDS_Edge anEdge = TopoDS::Edge(anEdgeExplorer.Current());
        //Add edge to fillet algorithm
        mkFillet.Add(myThickness / 12., anEdge);
        anEdgeExplorer.Next();
    }
    myBody = mkFillet.Shape();
    // Body : Add the Neck
    gp_Pnt neckLocation(0, 0, myHeight);
    gp_Dir neckAxis = gp::DZ();
    gp_Ax2 neckAx2(neckLocation, neckAxis);
    Standard_Real myNeckRadius = myThickness / 4.;
    Standard_Real myNeckHeight = myHeight / 10.;
    BRepPrimAPI_MakeCylinder MKCylinder(neckAx2, myNeckRadius, myNeckHeight);
    TopoDS_Shape myNeck = MKCylinder.Shape();
    myBody = BRepAlgoAPI_Fuse(myBody, myNeck);
    // Body : Create a Hollowed Solid
    TopoDS_Face   faceToRemove;
    Standard_Real zMax = -1;
    for(TopExp_Explorer aFaceExplorer(myBody, TopAbs_FACE); aFaceExplorer.More(); aFaceExplorer.Next()){
        TopoDS_Face aFace = TopoDS::Face(aFaceExplorer.Current());
        // Check if <aFace> is the top face of the bottle's neck 
        Handle(Geom_Surface) aSurface = BRep_Tool::Surface(aFace);
        if(aSurface->DynamicType() == STANDARD_TYPE(Geom_Plane)){
            Handle(Geom_Plane) aPlane = Handle(Geom_Plane)::DownCast(aSurface);
            gp_Pnt aPnt = aPlane->Location();
            Standard_Real aZ   = aPnt.Z();
            if(aZ > zMax){
                zMax = aZ;
                faceToRemove = aFace;
            }
        }
    }
    TopTools_ListOfShape facesToRemove;
    facesToRemove.Append(faceToRemove);
    BRepOffsetAPI_MakeThickSolid aSolidMaker;
    aSolidMaker.MakeThickSolidByJoin(myBody, facesToRemove, -myThickness / 50, 1.e-3);
    myBody = aSolidMaker.Shape();
    // Threading : Create Surfaces
    Handle(Geom_CylindricalSurface) aCyl1 = new Geom_CylindricalSurface(neckAx2, myNeckRadius * 0.99);
    Handle(Geom_CylindricalSurface) aCyl2 = new Geom_CylindricalSurface(neckAx2, myNeckRadius * 1.05);
    // Threading : Define 2D Curves
    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);
    // Threading : Build Edges and Wires
    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);
    // Create Threading 
    BRepOffsetAPI_ThruSections aTool(Standard_True);
    aTool.AddWire(threadingWire1);
    aTool.AddWire(threadingWire2);
    aTool.CheckCompatibility(Standard_False);
    TopoDS_Shape myThreading = aTool.Shape();
    // Building the Resulting Compound 
    TopoDS_Compound aRes;
    BRep_Builder aBuilder;
    aBuilder.MakeCompound (aRes);
    aBuilder.Add (aRes, myBody);
    aBuilder.Add (aRes, myThreading);
    return aRes;
}
  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值