|
九韶内核 1.0.0.0
|
边界表达(Boundary Representation,简称 BRep)是目前应用最广泛的数据结构,它能够在减轻存储压力的同时,确保数据的完备性与无歧义性。 BRep 仅是一个笼统概念,具体的拓扑结构如何构建仍是一个开放性问题。本文仅讨论九韶内核所采用的 BRep 结构,即 Vertex-Edge-Wire-Face-Shell-Solid-CompSolid-Compound。
我们先对 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 虽在三维空间中没有长度,但在参数域上具备长度,因此属于典型的 Degenerated Edge(退化边)。
接下来,我们将介绍几种较为常见的 Edge。
在封闭的 TopoShape(此处指 Shell、Solid)中,最常见的 Edge 通常是两个不同 Face 的邻接边。普通 Edge 分属两个不同的 Face,且长度不为 0。
一个常见的特例是圆柱的侧边,这条 Edge 仅属于一个 Face,且在该 Face 的 Wire 中出现两次(一次正向,一次反向)。Seam Edge 是合法的。
一个常见的特例是球体的顶边,这条 Edge 长度为 0,退化为一个点。Degenerated Edge(退化边)是合法的。
仅属于 1 个 Face,且在该 Face 的 1 个 Wire 中只出现一次的 Edge,称为自由 Edge。在封闭的 TopoShape 中,自由 Edge 是非法的。
在一个 Shape 或 Solid 中,若某条 Edge 属于两个以上的 Face,则该 Edge 称为非流形 Edge。非流形 Edge 是非法的。
首先,我们来介绍 TopoShape 的树状结构,如下图所示:
对拓扑结构进行遍历是一项十分常见的操作,我们内核提供了两种方式:TopoExplorer 和 WireExplorer。接下来,我们将对这两种方式逐一进行介绍。
TopoExplorer 能够对 TopoShape 中指定类型(Shape、Compound、CompSolid、Solid、Shell、Face、Wire、Edge、Vertex)的元素,按照树状结构进行先序遍历,从而找到该类型的子 shape。例如,遍历某个 TopoShape 中的面可使用如下代码:
需要注意的是,TopoExplorer 不会考虑重复性(IsSame级别的“相等”),也就是说,如果一条 Edge 被两个 Face 共用,那 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 是三维参数曲线,pcurve 是二维参数曲线。pcurve 与 <Edge, Surface, Location> 三者绑定,即 Edge、Surface、Location共同构成一条 pcurve 的索引标识。
一条 Edge 中通常包含一条 curve3d 与两条 pcurve 。从原理上,通过 pcurve 与对应的 Surface 可反向构建出一条三维参数曲线,但由于存在 Tolerance ,这种方式构建的三维参数曲线,与 Edge 中原有的 curve3d 基本不会完全重合。 在几何建模过程中,对 Edge 内包含的 Curve 数量并无强制要求,即使不含任何 Curve 也可正常操作;但除特殊场景(如退化 Edge)外,一条 Edge 应包含 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。
获取 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() 。