学习 OCCT 建模时,最容易混淆的一组概念就是:
Geometry 和 Topology 到底是什么关系?
TopoDS_Edge 是不是一条 Geom_Curve?
TopoDS_Face 是不是一个 Geom_Surface?
Edge 上的 3D curve 和 p-curve 又是什么?
Location 和 Orientation 为什么到处都有?
如果这些概念没有想清楚,后面学 Boolean、Fillet、Offset、Sewing、STEP/IGES 导入修复时,很容易陷入 API 细节里。本文根据 Roman Lygin 的 Topology and Geometry in Open CASCADE Part 1–4,以及 OCCT 官方 Modeling Data User Guide,结合 OCCT 的类结构和源码设计,系统整理 Geometry 与 Topology 的关系。
在 OCCT 中,可以先这样理解:
Geometry:描述数学形状
Topology:描述这些数学形状如何组成模型
Geometry 关注的是:
点在哪里?
曲线方程是什么?
曲面参数化是什么?
例如:
gp_Pnt
Geom_Curve
Geom_Surface
Geom2d_Curve
Geom_Line
Geom_Circle
Geom_Plane
Geom_CylindricalSurface
Geom_BSplineCurve
Geom_BSplineSurface
Topology 关注的是:
哪些点构成边?
哪些边构成 wire?
哪些 wire 裁剪出 face?
哪些 face 构成 shell?
哪些 shell 围成 solid?
对应 OCCT 中的拓扑类型:
TopoDS_Vertex
TopoDS_Edge
TopoDS_Wire
TopoDS_Face
TopoDS_Shell
TopoDS_Solid
TopoDS_Compound
因此,千万不要把它们混为一谈:
TopoDS_Vertex 不是 gp_Pnt
TopoDS_Edge 不是 Geom_Curve
TopoDS_Face 不是 Geom_Surface
更准确地说:
TopoDS_Vertex 绑定 / 引用一个 3D 点
TopoDS_Edge 绑定 / 引用一条或多条曲线
TopoDS_Face 绑定 / 引用一个曲面,并用 wire 限定边界
Roman Lygin 在 Part 1 中强调,OCCT 的拓扑结构是一个 oriented one-way graph。
也就是说:
父对象引用子对象
子对象不直接反向引用父对象
例如:
Solid
└── Shell
└── Face
└── Wire
└── Edge
└── Vertex
Face 知道自己包含哪些 Wire / Edge / Vertex,但是 Edge 本身并不知道“我属于哪些 Face”。如果你想知道某条 Edge 被哪些 Face 使用,通常要从更大的 Shape 出发做映射,例如使用:
TopExp::MapShapesAndAncestors(...)
这个设计有一个重要后果:
拓扑对象可以被共享。
例如一个盒子的某条 Edge 同时属于两个 Face。OCCT 不一定复制两份 Edge 的底层数据,而是可以让两个 Face 引用同一个底层 Edge,只是在不同上下文中带有不同的 Orientation。
TopoDS_Shape 的源码结构:TShape + Location + Orientation #理解 OCCT 拓扑结构,必须先理解 TopoDS_Shape。
可以简化成下面这样:
class TopoDS_Shape
{
private:
Handle(TopoDS_TShape) myTShape;
TopLoc_Location myLocation;
TopAbs_Orientation myOrient;
};
也就是说,一个 TopoDS_Shape 并不是完整拥有所有拓扑数据的对象,它更像是一个“带 shape 的引用”:
TopoDS_Shape
├── myTShape // 指向真正的底层拓扑数据
├── myLocation // 这个 shape 在空间中的位置变换
└── myOrient // 这个 shape 在当前上下文中的方向
其中:
TopoDS_TShape:
真正保存拓扑结构和底层几何数据的对象,由 handle 管理,可以共享。
TopoDS_Shape:
按值传递的轻量对象,引用一个 TShape,并附带 Location 和 Orientation。
这就是为什么 OCCT 中经常说:
TopoDS_Shape is manipulated by value.
TopoDS_TShape is manipulated by reference.
可以把它想象成:
TShape 是“定义”
Shape 是“这个定义在某个位置、某个方向下的一次使用”
类图可以简化为:
TopoDS_Shape
│
├── TopoDS_Vertex
├── TopoDS_Edge
├── TopoDS_Wire
├── TopoDS_Face
├── TopoDS_Shell
├── TopoDS_Solid
├── TopoDS_CompSolid
└── TopoDS_Compound
而底层真正的数据结构是:
TopoDS_TShape
│
├── BRep_TVertex
├── BRep_TEdge
├── BRep_TFace
└── ...
需要注意:
TopoDS_Vertex / TopoDS_Edge / TopoDS_Face 是用户日常操作的对象;
BRep_TVertex / BRep_TEdge / BRep_TFace 是底层数据承载者。
在 OCCT 的 BRep 模型中,不是所有拓扑对象都有直接几何表示。
真正直接绑定几何的是:
Vertex
Edge
Face
其他对象主要是组织结构:
Wire = 一组 Edge
Shell = 一组 Face
Solid = 一组 Shell
Compound = 一组 Shape
可以画成:
TopoDS_Vertex
└── BRep_TVertex
├── gp_Pnt
└── tolerance
TopoDS_Edge
└── BRep_TEdge
├── Geom_Curve
├── Geom2d_Curve on surface
├── Poly_Polygon3D
├── Poly_PolygonOnTriangulation
├── tolerance
├── SameRange flag
├── SameParameter flag
└── Degenerated flag
TopoDS_Face
└── BRep_TFace
├── Geom_Surface
├── Wires
├── Poly_Triangulation
├── tolerance
└── location
所以后面分析模型时,要始终记住:
Wire 没有自己的几何曲线;
Shell 没有自己的几何曲面;
Solid 也没有自己的几何体方程。
它们是拓扑组织层。
TopoDS_Vertex 对应一个 0 维拓扑对象,它的主要几何表示是一个 3D 点:
gp_Pnt p = BRep_Tool::Pnt(vertex);
底层可以理解为:
BRep_TVertex
├── gp_Pnt
└── tolerance
Vertex 的 tolerance 表示这个点位置的不确定范围。几何意义上,可以把它想象成以该点为中心、半径为 tolerance 的小球。所有连接到该 Vertex 的 Edge 曲线端点,都应该落在这个小球范围内。
也就是说,Vertex 不一定要求所有边的端点在数学上完全重合,而是在 tolerance 内认为它们连接。
Vertex 也有 Orientation,但它没有直接的几何方向意义。
按照 OCCT 的约定:
TopAbs_FORWARD 的 Vertex 对应 Edge 曲线参数较小的一端
TopAbs_REVERSED 的 Vertex 对应 Edge 曲线参数较大的一端
如果一条 Edge 的 3D curve 参数范围是:
[first, last]
那么:
FORWARD vertex ≈ curve->Value(first)
REVERSED vertex ≈ curve->Value(last)
注意,这个 Orientation 是在 Edge 上下文中的拓扑约定,不是说一个点本身真的有方向。
TopoDS_Edge 是 1 维拓扑对象,通常对应一条受限曲线。它可能是某个 Face 的边界,也可能是一条独立的 floating edge。
Edge 的几何表示比 Vertex 复杂得多。一条 Edge 可能同时包含多种几何表达:
1. 3D 空间曲线:Geom_Curve
2. Face 参数空间中的 2D 曲线:Geom2d_Curve,也叫 p-curve
3. 3D 折线离散表示:Poly_Polygon3D
4. Face 三角网格上的索引折线:Poly_PolygonOnTriangulation
其中最重要的是前两种。
Edge 的主几何表示是一条 3D 曲线:
Standard_Real first, last;
Handle(Geom_Curve) c3d = BRep_Tool::Curve(edge, first, last);
它表示:
C(t) = (x, y, z)
其中:
t ∈ [first, last]
例如:
直线边 -> Geom_Line
圆弧边 -> Geom_Circle
样条边 -> Geom_BSplineCurve
通过参数可以取点:
gp_Pnt p0 = c3d->Value(first);
gp_Pnt p1 = c3d->Value(last);
这里的 first 和 last 不是数组下标,而是曲线参数范围。
当一条 Edge 属于某个 Face 时,它不仅需要有 3D curve,还通常需要有该 Face 上的 2D 参数曲线,也就是 p-curve:
Standard_Real pFirst, pLast;
Handle(Geom2d_Curve) pc =
BRep_Tool::CurveOnSurface(edge, face, pFirst, pLast);
p-curve 表示:
P(t) = (u, v)
它不是 3D 空间中的曲线,而是在 Face 底层 Surface 的参数空间中。
如果 Face 的底层 Surface 是:
S(u, v) = (x, y, z)
那么理论上 Edge 的 3D curve 和 p-curve 应该满足:
C(t) ≈ S(P(t))
展开就是:
C(t) ≈ S(u(t), v(t))
这就是 p-curve 的核心意义。
因为 Face 的边界不仅要在 3D 空间中闭合,也要在 Surface 的 UV 参数空间中闭合。
例如一个圆柱侧面,在 3D 中是闭合曲面,但在 UV 参数空间中可以展开成一个矩形:
v=H +----------------------+
| |
| |
| |
v=0 +----------------------+
u=0 u=2π
在 3D 中:
u = 0 和 u = 2π 是同一条空间线
但在 UV 空间中它们是矩形的左右两条边界。
所以同一个 seam edge 在同一个 Face 的 wire 中可能出现两次,并拥有两条不同的 p-curve:
一条 p-curve 在 u = 0
另一条 p-curve 在 u = 2π
这就是为什么 OCCT 的 Edge 不只是 3D curve,还必须能描述它在 Face 参数空间中的位置。
因为 Edge 可能有多种几何表示,所以 OCCT 需要保证这些表示之间是一致的。
Edge 有两个重要 flag:
BRep_Tool::SameRange(edge)
BRep_Tool::SameParameter(edge)
SameRange = true 表示 Edge 的各种几何表示使用相同的参数范围。
例如:
3D curve:
[first, last] = [0, 10]
p-curve:
[pFirst, pLast] = [0, 10]
这就是 SameRange。
如果 3D curve 是 [0, 10],p-curve 是 [0, 1],那么它们参数范围不一致。
SameParameter = true 更重要。它表示对于同一个参数 t:
C(t) ≈ S(P(t))
也就是说,同一个参数在 3D curve 和 p-curve + surface 上表示 Edge 的同一个位置。
很多 OCCT 建模算法默认 Edge 满足这两个条件。如果自己底层构建 Edge,或者导入外部 CAD 模型后,通常需要检查或修复:
BRepLib::SameParameter(shape, 1.0e-7);
正确做法不是简单地把 flag 强行设为 true,而是让 3D curve、p-curve、surface、vertex parameter 真的一致,再设置对应标识。
Edge 的 Orientation 描述的是:
Edge 的逻辑方向是否和底层曲线参数方向一致
如果底层曲线是:
C(first) -> C(last)
那么:
TopAbs_FORWARD:
Edge 的逻辑方向 = C(first) -> C(last)
TopAbs_REVERSED:
Edge 的逻辑方向 = C(last) -> C(first)
需要注意的是:
Edge Orientation 不会改变底层 Geom_Curve;
它只改变这条 Edge 在当前拓扑上下文中的使用方向。
这对 Wire 和 Face 很重要。因为同一条 Edge 被不同 Face 共享时,在一个 Face 中可能是 FORWARD,在另一个 Face 中可能是 REVERSED。
对于 seam edge,更特殊:它在同一个 Face 的 wire 中可能出现两次,一次 FORWARD,一次 REVERSED。
TopoDS_Face 是 2 维拓扑对象,它表示 3D body 的一个边界单元。
Face 的结构可以理解为:
TopoDS_Face
├── Geom_Surface
├── Outer Wire
│ ├── Edge
│ ├── Edge
│ └── Edge
├── Inner Wires, optional
│ └── holes
├── Poly_Triangulation, optional
└── tolerance
最重要的一句话:
Face = Surface + Wires
Geom_Surface 只是底层数学曲面,它可能是无限的,或者比实际 Face 大得多。
例如盒子的一个矩形面:
底层 surface: Geom_Plane
但 Geom_Plane 是无限平面。真正的矩形区域是通过 wire 裁剪出来的:
无限平面 + 四条边组成的 closed wire = 矩形 Face
再例如圆柱:
圆柱 solid
├── bottom face: Geom_Plane + circular wire
├── top face: Geom_Plane + circular wire
└── side face: Geom_CylindricalSurface + wire
圆柱侧面的 Geom_CylindricalSurface 在 V 方向可以是无限的,实际 Face 通过上下边界和 seam edge 裁剪出来。
Face 的底层几何是 Geom_Surface:
Handle(Geom_Surface) surf = BRep_Tool::Surface(face);
Surface 是一个参数映射:
S(u, v) = (x, y, z)
通过:
gp_Pnt p = surf->Value(u, v);
可以从 UV 参数点得到 3D 空间点。
不同 Surface 的参数空间不同:
Geom_Plane:
U/V 通常都是无界的
Geom_CylindricalSurface:
U 是角度方向,通常周期为 2π
V 是轴向方向
Geom_SphericalSurface:
U 是经度方向
V 是纬度方向
Geom_BSplineSurface:
U/V 有自己的 knot range
这也是为什么 p-curve 是 Edge 在某个 Face 上的局部表达:同一条 3D Edge,在不同 Face 的 Surface 参数空间中,可能有不同的 UV 曲线。
OCCT 对 Face 边界有一个很重要的要求:
Face 的 boundary wire 应该在 3D 空间中闭合;
同时也应该在 Face 的 2D UV 参数空间中闭合。
这正是 p-curve 的意义。
一条 Edge 在 3D 中可以通过 Geom_Curve 表示:
C(t)
但在 Face 上,它还需要通过 p-curve 表示:
P(t) = (u, v)
这样 Face 才能在参数空间中知道自己的边界在哪里。
可以把 Face 看成:
Surface S(u, v)
+
UV 平面上的一个或多个 closed contour
然后通过 Surface 映射成 3D 中的一块面。
Face 也有 Orientation。
Face 的 Orientation 表示:
Face normal 和底层 Surface normal 是否一致
如果:
face.Orientation() == TopAbs_FORWARD
那么:
Face normal = Surface normal
如果:
face.Orientation() == TopAbs_REVERSED
那么:
Face normal = -Surface normal
对于一个正确的 Solid,通常要求所有 Face 的法向朝外。
但是 OCCT 中还有一个更通用的说法:Face normal 指向 material 的反方向。换句话说,实体材料在 Face normal 的背后。
官方 Modeling Data User Guide 对 Orientation 的解释非常关键。
它不是简单的“方向”,而是用于定义边界两侧哪一侧是 interior / material。
OCCT 对不同维度的边界定义了 default region:
对于一条曲线上的 Vertex:
default region 是沿曲线自然方向、参数大于该 vertex 的那一侧。
也就是:
curve parameter > vertex parameter
如果 Vertex 是 FORWARD,则 interior 是 default region。 如果 Vertex 是 REVERSED,则 interior 是 default region 的补集。
对于 Surface 上的一条 Edge:
default region 是沿 Edge 自然方向前进时的左侧。
更精确地说,它由:
surface normal × edge tangent
决定。
在 UV 参数空间中更容易理解:
Edge FORWARD:
material / interior 在左侧
Edge REVERSED:
material / interior 在右侧
这解释了为什么 Face 的 wire 中 Edge 的顺序和 Orientation 非常重要。它们共同决定了 Face 的内部区域在哪里。
对于三维空间中的 Face:
default region 是 surface normal 的负侧。
如果 Face 是 FORWARD:
interior = default region
如果 Face 是 REVERSED:
interior = default region 的补集
这就是为什么 Face Orientation 和实体材料方向相关。
TopAbs_Orientation 不只有 FORWARD 和 REVERSED,还有:
TopAbs_FORWARD
TopAbs_REVERSED
TopAbs_INTERNAL
TopAbs_EXTERNAL
含义可以理解为:
FORWARD:
interior 是 default region
REVERSED:
interior 是 default region 的补集
INTERNAL:
两侧都属于 interior,边界在材料内部
EXTERNAL:
两侧都不属于 interior,边界在材料外部
在普通 solid 建模里,最常见的是 FORWARD 和 REVERSED。 但在更复杂的拓扑关系、内部边界、wire-frame 模型中,INTERNAL 和 EXTERNAL 也会出现。
TopLoc_Location 表示 Shape 的局部坐标变换。
这也是 TopoDS_Shape 的三个核心字段之一:
TopoDS_Shape = TShape + Location + Orientation
Location 的意义是:
同一个底层 TShape,可以在不同位置被复用。
例如一个模型中有多个相同的孔、螺栓、阵列特征,它们可以共享同一个底层几何定义,只是 Location 不同。
简化理解:
TShape:
在定义坐标系中的原始形状
Location:
把原始形状变换到当前使用位置
Orientation:
当前上下文中该 shape 的方向 / interior 选择
所以访问底层几何时要注意 Location。
例如:
TopLoc_Location loc;
Handle(Geom_Surface) surf = BRep_Tool::Surface(face, loc);
gp_Pnt pLocal = surf->Value(u, v);
gp_Pnt pWorld = pLocal.Transformed(loc.Transformation());
如果忽略 Location,就可能得到局部坐标系中的点,而不是模型全局坐标中的点。
可以把 OCCT 中最核心的 BRep 关系总结成这张图:
TopoDS_Face
│
├── Orientation
├── Location
│
├── BRep_TFace
│ ├── Geom_Surface
│ │ └── S(u, v) -> gp_Pnt(x, y, z)
│ │
│ ├── Wires
│ │ └── TopoDS_Wire
│ │ └── TopoDS_Edge
│ │ │
│ │ ├── Orientation
│ │ ├── Location
│ │ │
│ │ ├── BRep_TEdge
│ │ │ ├── 3D Curve: Geom_Curve
│ │ │ │ └── C(t) -> gp_Pnt(x, y, z)
│ │ │ │
│ │ │ ├── p-curve: Geom2d_Curve on Face
│ │ │ │ └── P(t) -> gp_Pnt2d(u, v)
│ │ │ │
│ │ │ ├── SameRange
│ │ │ ├── SameParameter
│ │ │ └── tolerance
│ │ │
│ │ └── Vertices
│ │ └── TopoDS_Vertex
│ │ └── BRep_TVertex
│ │ ├── gp_Pnt
│ │ └── tolerance
│ │
│ └── Poly_Triangulation, optional
其中最核心的几何一致性关系是:
Edge 3D curve:
C(t)
Edge p-curve on Face:
P(t) = (u(t), v(t))
Face surface:
S(u, v)
要求:
C(t) ≈ S(P(t))
这就是 SameParameter 背后的数学含义。
学习和调试 OCCT 模型时,最常用的是 BRep_Tool。
gp_Pnt p = BRep_Tool::Pnt(vertex);
Standard_Real tol = BRep_Tool::Tolerance(vertex);
Standard_Real first, last;
Handle(Geom_Curve) c3d = BRep_Tool::Curve(edge, first, last);
Standard_Real first, last;
Handle(Geom2d_Curve) pc =
BRep_Tool::CurveOnSurface(edge, face, first, last);
Handle(Geom_Surface) surf = BRep_Tool::Surface(face);
bool sameRange = BRep_Tool::SameRange(edge);
bool sameParam = BRep_Tool::SameParameter(edge);
bool degenerated = BRep_Tool::Degenerated(edge);
BRepLib::SameParameter(shape, 1.0e-7);
为了真正理解 Geometry 和 Topology,建议写一个小工具,遍历 Shape 并打印:
Face #i
Orientation
Location
Surface type
Tolerance
Wire #j
Edge #k
Orientation
3D curve type
3D range
p-curve type
p-curve range
SameRange
SameParameter
Degenerated
Vertex #m
Orientation
Point
Tolerance
示例代码片段:
void PrintEdgeInfo(const TopoDS_Edge& edge, const TopoDS_Face& face)
{
std::cout << "Edge orientation: " << edge.Orientation() << std::endl;
Standard_Real f3d, l3d;
Handle(Geom_Curve) c3d = BRep_Tool::Curve(edge, f3d, l3d);
if (!c3d.IsNull()) {
std::cout << "3D Curve: "
<< c3d->DynamicType()->Name()
<< " range [" << f3d << ", " << l3d << "]"
<< std::endl;
}
Standard_Real f2d, l2d;
Handle(Geom2d_Curve) pc =
BRep_Tool::CurveOnSurface(edge, face, f2d, l2d);
if (!pc.IsNull()) {
std::cout << "PCurve: "
<< pc->DynamicType()->Name()
<< " range [" << f2d << ", " << l2d << "]"
<< std::endl;
}
std::cout << "SameRange: "
<< BRep_Tool::SameRange(edge)
<< std::endl;
std::cout << "SameParameter: "
<< BRep_Tool::SameParameter(edge)
<< std::endl;
}
通过 Box、Cylinder、Sphere 三个模型观察,会非常直观:
Box:
Face -> Geom_Plane
Edge -> Geom_Line
Cylinder:
side Face -> Geom_CylindricalSurface
top/bottom Face -> Geom_Plane
circular Edge -> Geom_Circle
seam Edge -> same edge appears twice on lateral face
Sphere:
Face -> Geom_SphericalSurface
seam edge
degenerated edge at poles
OCCT 的 BRep 模型可以理解为:
Topology 组织 Geometry
Geometry 提供数学形状
Location 复用几何定义并放置到空间中
Orientation 决定边界哪一侧是 interior/material
Tolerance 描述局部几何误差
最重要的几组关系是:
Vertex:
TopoDS_Vertex -> BRep_TVertex -> gp_Pnt
Edge:
TopoDS_Edge -> BRep_TEdge -> Geom_Curve
-> Geom2d_Curve on Face
Face:
TopoDS_Face -> BRep_TFace -> Geom_Surface
-> Wires
而整个 OCCT BRep 的核心公式可以写成:
C(t) ≈ S(P(t))
其中:
C(t):
Edge 的 3D 空间曲线
P(t):
Edge 在 Face 参数空间中的 p-curve
S(u, v):
Face 的底层曲面
理解了这件事,就能理解为什么 OCCT 中 Edge 必须同时关注 3D curve 和 p-curve,也能理解为什么 SameParameter、SameRange、Location、Orientation、Tolerance 会成为建模算法稳定性的关键。
如果只记一句话:
TopoDS_* 是拓扑引用;
Geom_* 是数学几何;
BRep_* 把拓扑和几何绑定在一起。
这就是 OpenCASCADE 中 Geometry 与 Topology 的核心。
(完)