日期:2026年3月15日标签:Computer Graphics

OpenCASCADE 中的 Geometry 与 Topology:从 Vertex、Edge、Face 到 Location / Orientation #

学习 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 的关系。


1. 一句话区分 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 限定边界

2. OCCT 的 Topology 是一个有向的一路图 #

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。


3. 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 是底层数据承载者。

4. 只有 Vertex、Edge、Face 直接绑定 Geometry #

在 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 也没有自己的几何体方程。

它们是拓扑组织层。


5. Vertex:拓扑点,不只是 gp_Pnt #

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 #

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 上下文中的拓扑约定,不是说一个点本身真的有方向。


6. Edge:拓扑边,不等于 Geom_Curve #

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

其中最重要的是前两种。


7. Edge 的 3D Curve:空间曲线 C(t) #

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

这里的 firstlast 不是数组下标,而是曲线参数范围。


8. Edge 的 p-curve:Face 参数空间中的 UV 曲线 #

当一条 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 的核心意义。

为什么需要 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 参数空间中的位置。


9. Edge 的 SameRange 和 SameParameter #

因为 Edge 可能有多种几何表示,所以 OCCT 需要保证这些表示之间是一致的。

Edge 有两个重要 flag:

BRep_Tool::SameRange(edge)
BRep_Tool::SameParameter(edge)

SameRange #

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 #

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 真的一致,再设置对应标识。


10. Edge Orientation:拓扑方向和曲线方向的关系 #

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。


11. Face:拓扑面,不等于 Geom_Surface #

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 裁剪出来。


12. Surface:参数曲面 S(u, v) #

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 曲线。


13. Face 的 Wire:3D 闭合 + 2D 闭合 #

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 中的一块面。


14. Face Orientation:Face normal 和 Surface normal #

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 的背后。


15. Orientation 与 Default Region #

官方 Modeling Data User Guide 对 Orientation 的解释非常关键。

它不是简单的“方向”,而是用于定义边界两侧哪一侧是 interior / material。

OCCT 对不同维度的边界定义了 default region:

Vertex 限制 Curve #

对于一条曲线上的 Vertex:

default region 是沿曲线自然方向、参数大于该 vertex 的那一侧。

也就是:

curve parameter > vertex parameter

如果 Vertex 是 FORWARD,则 interior 是 default region。 如果 Vertex 是 REVERSED,则 interior 是 default region 的补集。

Edge 限制 Surface #

对于 Surface 上的一条 Edge:

default region 是沿 Edge 自然方向前进时的左侧。

更精确地说,它由:

surface normal × edge tangent

决定。

在 UV 参数空间中更容易理解:

Edge FORWARD:
    material / interior 在左侧

Edge REVERSED:
    material / interior 在右侧

这解释了为什么 Face 的 wire 中 Edge 的顺序和 Orientation 非常重要。它们共同决定了 Face 的内部区域在哪里。

Face 限制 Space #

对于三维空间中的 Face:

default region 是 surface normal 的负侧。

如果 Face 是 FORWARD:

interior = default region

如果 Face 是 REVERSED:

interior = default region 的补集

这就是为什么 Face Orientation 和实体材料方向相关。


16. 四种 Orientation:FORWARD / REVERSED / INTERNAL / EXTERNAL #

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 也会出现。


17. Location:几何定义坐标系与使用坐标系 #

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,就可能得到局部坐标系中的点,而不是模型全局坐标中的点。


18. Vertex / Edge / Face 的关系总图 #

可以把 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 背后的数学含义。


19. 常用 API:从 Topology 取 Geometry #

学习和调试 OCCT 模型时,最常用的是 BRep_Tool

取 Vertex 的点 #

gp_Pnt p = BRep_Tool::Pnt(vertex);
Standard_Real tol = BRep_Tool::Tolerance(vertex);

取 Edge 的 3D 曲线 #

Standard_Real first, last;
Handle(Geom_Curve) c3d = BRep_Tool::Curve(edge, first, last);

取 Edge 在 Face 上的 p-curve #

Standard_Real first, last;
Handle(Geom2d_Curve) pc =
    BRep_Tool::CurveOnSurface(edge, face, first, last);

取 Face 的 Surface #

Handle(Geom_Surface) surf = BRep_Tool::Surface(face);

检查 Edge 几何一致性 #

bool sameRange = BRep_Tool::SameRange(edge);
bool sameParam = BRep_Tool::SameParameter(edge);
bool degenerated = BRep_Tool::Degenerated(edge);

修复 SameParameter #

BRepLib::SameParameter(shape, 1.0e-7);

20. 一个调试工具的思路:Geometry Inspector #

为了真正理解 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

21. 最终总结 #

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 的核心。

(完)

目录