九韶内核 1.0.0.0
|
边界表达(Boundary Representation,简称 B-rep)是当前最流行的数据结构。B-rep 可以在减轻存储压力的同时,保持完备性和无歧义。B-rep 使用实体边界上的点、线、面来表示实体,实体的体信息由包含在边界内的三维空间导出。其两个基本元素为:几何信息(Point、Curve、Surface);拓扑信息(Vertex、Edge、Face连接关系)。
我们先给出一个 B-rep 结构的各级拓扑结构的定义:
接下来我们将举一些例子,方便您更好的理解。
不难看出,这个例子有 4 个 Vertex,4 个 Edge,1 个 Wire,1 个 Face。
因为中间重合部分的 Vertex 和 Edge 是共用的,所以这个例子有 6 个 Vertex,7 个 Edge,2 个 Wire,2 个 Face,1 个 Shell。
这个例子有 8 个 Vertex,12 个 Edge,6 个 Wire,6 个 Face,1 个 Shell。
这个例子有 2 个 Vertex,3 个 Edge,3 个 Wire,3 个 Face,1 个 Shell。而 0 号 Edge 在 Wire 中被复用了,所以这是一条经典的 Seam Edge。
![]() | ![]() |
这个例子有 2 个 Vertex,3 个 Edge,1 个 Wire,1 个 Face,1 个 Shell。在这个例子中不仅有 Seam Edge,还有两条没有长度的Edge。事实上,这两条 Edge 尽管在 3D 中没有长度,但是在参数域上是有长度的,所以这是两条经典的 Degenerated Edge(退化边)。
接下来,我们将介绍比较常见的几种 Edge。
在一个封闭的 TopoShape(这里指 Shell、Solid)内,通常来说最常见的 Edge 是两个不同 Face 的邻接 Edge。普通 Edge 属于两个不同的 Face,且长度不为 0。
一个常见的特例是圆柱的侧边,这条 Edge 被一个 Face 所有,且在 Face 的一个 Wire 中出现两次(一次正,一次反)。Seam Edge 是合法的。
一个常见的特例是球的顶边,这条 Edge 长度为 0,退化成了一个点。退化 Edge 是合法的。
只属于 1 个 Face,且在 Face 的 1 个 Wire 中只出现一次的 Edge 称为自由 Edge。自由 Edge 在封闭 TopoShape 中是非法的。
在 1 个 Shape 或 Solid 中,属于超过 2 个 Face 的 Edge 称为非流形 Edge。非流形 Edge 是非法的。
首先我们先来介绍一下 TopoShape 的树状结构,如下图所示:
对一个拓扑结构进行遍历是十分常见的操作,我们内核提供两种方式: AMCAX::TopoExplorer 和 AMCAX::WireExplorer ,接下来我们将对这两种方式进行逐一介绍。
AMCAX::TopoExplorer 可以实现对 TopoShape 中的指定类型( Shape , Compound , CompSolid , Solid , Shell , Face , Wire , Edge , Vertex)按照树状结构进行先序遍历,找到指定类型的子 shape。比如,遍历一个 TopoShape 的面可以用如下代码:
需要注意的是, AMCAX::TopoExplorer 不会考虑重复性(IsSame级别的“相等”),也就是说,如果一条 Edge 被两个 Face 共用,那 AMCAX::TopoExplorer 仍然会遍历这条 Edge 两次。如果不希望遍历两次,则可以使用一个 std::unordered_set 来去重,代码如下:
在上文中,我们提到了重复性,重复性其实隐含了“相等”的定义。接下来我们将介绍关于 TopoShape 三种层次的“相等”,严格程度逐渐增加:
当需要按照拓扑连通顺序遍历 Wire 中的 Edge 时(前一条边的终点 = 下一条边的起点), AMCAX::TopoExplorer 可能无法满足需求。例如,使用三个 Point3 构造三条 Edge 并组合成一个 Wire 时,若添加 Edge 的顺序与它们的实际连接顺序不一致,此时通过 TopoExplorer 遍历得到的 Edge 顺序将与实际拓扑连接顺序不符,而是按构造次序返回的。此时应使用 AMCAX::WireExplorer ,它能确保按照 Edge 的实际连接顺序进行遍历,不过遍历速度会变慢。
由于树状结构和先序遍历的存在,事实上每条 Edge(其他的 Vertex、Wire、Face 也是类似的)都有自己的序号。而 AMCAX::TopoExplorerTool 类提供了一些快捷的遍历操作, AMCAX::TopoExplorerTool::MapShapes 可以获得某类拓扑结构的有序号集合(如 Edge),并且得到的集合是去重的。
事实上,这个序号和在 FreeCAD 中左下角显示的序号是一致的(FreeCAD 从 1 开始,而我们内核从 0 开始)。
AMCAX::TopoExplorerTool::MapShapesAndAncestors 可以获取子 shape 到所属的父 shape 的 Map,而 AMCAX::TopoExplorerTool::MapShapesAndUniqueAncestors 同样可以获取 Map,两者的区别在于当输入 seam 边时,MapShapesAndUniqueAncestors 会返回 1 个所属 face,而 MapShapesAndAncestors 会返回 2 个所属 face。比如 Edge 到所属 Face 的 Map 可以这样获得:
判断一条 Edge 是否是退化的是非常重要的,比如我们不应当在一条退化边上进行采样。我们可以通过 AMCAX::TopoTool::Degenerated 来判断。
可以通过 AMCAX::TopoTool::IsClosed 来判断一条 Edge 是否为 Seam 边。
Vertex 中只有一个 Point,表示三维空间中的点。而从 Vertex 中获取 Point 的方法为 AMCAX::TopoTool::Point :
curve3d 是 3 维参数曲线。pcurve是 2 维参数曲线。pcurve 是与 <Edge, Surface, Location> 绑定的,即 Edge, Surface, Location 是一条 pcurve 的索引。
一个 Edge 中通常有一条 curve3d,两条 pcurve。事实上,通过 pcurve 和 surface 还可以构建一条 3 维参数曲线,但由于 Tolerance 的存在,所以创建出来的两条 3 维参数曲线与 curve3d 基本不会重合。在几何建模的过程中,Edge 中的 Curve 数量没有要求,没有 Curve 也没有问题。但除特殊情况外,最终的几何模型应当包含 1 条 curve3d 和 2 条 pcurve。
接下来我们将介绍几种特殊情况:
如果 Edge 位于一个平面(Geom3Plane)上,则不需要额外的 pcurve。因为平面上的 Edge 可以直接通过 Curve3d 来表示。
Seam Edge 有 1 条 curve3d 和两条 pcurve,这两条 pcurve 都在 1 个 Face 上,两条 pcurve 的区别在于 Edge 的 Orientation:一条 pcurve 对应 Edge 的正向,另一条 pcurve 对应 Edge 的反向。
Degenerated Edge 只有 1 条 pcurve,没有 curve3d。
自由 Edge 有 1 条 curve3d,1 条 pcurve。
需要注意的是,内核不提供为 curve3d 计算 pcurve 的功能。正确的建模方式应当是先构建 pcurve,然后用 pcurve 和 surface 计算 curve3d:
获取 Edge 的 Curve3d 的方法是 AMCAX::TopoTool::Curve 。
有几点需要注意:
1.接口返回的是原始的曲线,或者说,如果一条 Edge 是一个 Geom3Line 和两个 Vertex 构成的线段,那么这个接口获取到的是一条无限长的直线(Geom3Line),fp 和 lp 是两个 Vertex 在曲线上的参数。
2.AMCAX::TopoTool::Curve 有重载函数,一个输入 loc 而另一个没有。输入 loc 的接口得到的一定是指向 edge 中曲线内存的一个指针,而没输入 loc 的接口,在 edge 的 location 为空时,返回指向内存的指针,非空时返回指向一条新构建曲线的指针。
3.如果输入的是一个退化边,会返回空指针。
4.输入 loc 的接口获得的 Curve 和实际的位置差了一个 location,而对 Curve 直接应用 location.Transformation() 是禁止的,这会改变 edge 的内容。正确的做法是,复制一份 Curve 再对 CopyCurve 进行变换:
获取 pcurve 的方法是 AMCAX::TopoTool::CurveOnSurface 。
需要注意的是,如果输入的是 seam 边,需要特殊处理,根据 Edge 的 Orientation 会返回不同的 pcurve:
在上面的示例中,获取了圆柱 seam 边的两条 pcurve ,这两条 pcurve 的朝向始终一致,与 curve3d 的朝向一致,这不需要考虑 edge 的朝向。
一个 Face 中只有一个 Surface。获取 Surface 的方式是 AMCAX::TopoTool::Surface 。
输入 loc 的接口获得的 Surface 和实际的位置差了一个 location,而对 Surface 直接应用 location.Transformation() 是禁止的,这会改变 face 的内容。正确的做法是,复制一份 Surface 再对 CopySurface 进行变换:
Mesh 指的是三角网格。Mesh 在 CAD 中的存在意义是渲染。由于 OpenGL 的绘制基本单元是点、线段、三角形,因此曲线必须离散成多边形进行绘制,曲面必须离散成 Mesh 进行绘制,因此在包含图形用户界面(GUI)的应用程序中,看到的 BRep 模型通常是通过三角网格来可视化的。如下图所示。
显然,离散会带来误差,这意味着在包含 GUI 的应用程序中显示的 BRep 模型并不是模型本身,而是模型的 Mesh,二者之间的误差由离散精度,即网格的疏密程度决定,考虑到 CAD 模型的 Mesh 生成必须非常快,Mesh 的精度可想而知并不高。
由离散带来的问题是非常多的,如通过视觉判断两个事实上相同的圆是否重叠,或两个相同的球是否重叠,就会出现下图所示的情况。或者将光滑曲面渲染成出现褶皱的情况。另一类问题是网格生成失败,这会导致存在破洞。因此看到模型上有洞,需要综合考量 Mesh 的原因和模型本身的原因。
接下来我们将介绍 Mesh 的生成、获取、读写方法。
虽然 Mesh 对于渲染而言是必要的,但不一定要在模型构建后使用内核为模型生成 Mesh ,因为包含 GUI 的软件会自行生成 Mesh,进而用于渲染。渲染中的法向是一个关键信息,如果 Mesh 不提供法向,渲染时会根据 Mesh 自动计算一个近似的法向。如果需要确保三角网格的法向和 CAD 模型的法向是一致的,则可以使用 AMCAX::MakeShapeTool::EnsureNormalConsistency() 。