Technical note
CAD 特征边提取
记录一次 CAD/CAE 特征边提取功能的工程实现:为什么特征边不能只保存一条 Edge,而要保留来源、角色、Curve、PCurve 和参数区间。
背景:特征边不是把模型里的线复制出来
在 CAD/CAE 模型里,边看起来只是显示在面上的一条线。
但从几何和拓扑角度看,这些线的含义并不一样。
有些边是面的外轮廓,有些边围成孔洞,有些边只是支撑曲面上的一条 U/V 等参数线,还有一些边来自截平面和曲面的交线。
如果只是把这些线显示出来,问题并不复杂。
但这次要解决的不是“画一条线”,而是把它变成后续可以继续编辑、构面和修复的几何对象。
这就要求提取结果至少能够回答几个问题:
这条线从哪张面、哪条原始边产生?
它是外边界、内边界,还是内部引导线?
它对应哪一段三维曲线参数?
它在原始面的 UV 参数域中有没有 PCurve?
后续构面时,它应该裁剪结果,还是只约束曲面形状?
原模型拓扑变化以后,还有没有足够信息重建引用?
所以这次特征边功能的核心,不是批量复制 TopoDS_Edge,而是建立一套带来源、参数和角色的曲线约束表达。
当前所说的特征边包括什么
在当前交互版本里,特征边提取从一张用户选中的 face 开始,结果是一个有限集合:
外轮廓上的 trimming edges;
内轮廓上的 trimming edges;
拾取位置对应的 trimmed U-isoparametric segment;
拾取位置对应的 trimmed V-isoparametric segment。
几何库层面还可以支持平面截线:
PlaneSection = selected face ∩ section plane
另外,库内部也可以从一条 edge 上提取端点、中点、连续性断点、采样曲率极值点和用户指定参数点。
但这里需要区分:
底层几何库具备某种能力;
不等于当前产品交互已经正式开放这种能力。
当前交互版本优先把“曲线提取—预览筛选—确认提交—进入约束构面”这条链路做清楚,没有把点特征和自动脊线识别一起放进来。
这个边界很重要。否则一个“提取特征边”的按钮,很容易被误解成“自动识别模型上所有曲面特征”。
一条可用的特征边应该保存什么
从 OCCT 类型上看,提取结果仍然可以是一条 TopoDS_Edge。
但如果只保存 Edge,后续流程会丢掉很多关键信息。
我更倾向于把一条可用的特征边表达成带语义的 FeatureCurve。它的权威几何对象仍然是 TopoDS_Edge,但同时保留:
Role:
Boundary / Guide / Crease
Kind:
OuterBoundary / InnerBoundary / UIso / VIso /
PlaneSection / UserProjected
Source:
SourceFace / SourceEdge / SourceIndex
Geometry:
Edge / Curve3d / PCurve
Parameter:
FirstParameter / LastParameter
Quality:
Continuity / Confidence / HardConstraint / Message
为什么要保存这么多内容?
因为同一条三维曲线在不同上下文里,含义可能完全不同。
一条外轮廓边进入构面流程时,应该裁剪最终 face。
一条 U 等参数线通常只影响曲面的内部形状,不应该把结果切成两张面。
一条真正的折痕线则意味着单张光顺曲面无法表达,需要进入多 patch 重建。
如果提取阶段只保存一个 Edge,后续构面阶段只能重新猜测它的角色。
如果保留来源和语义,数据可以直接从提取命令传入约束构面命令,而不需要在应用层重新分析一遍。
外边界和内边界怎么提取
面的边界提取以 B-Rep wire 为入口。
流程可以概括为:
读取 source face;
找到外环;
遍历 face 内全部 wire;
在每个 wire 中按拓扑顺序遍历 edge;
判断当前 wire 是 outer wire 还是 inner wire;
为每条 edge 构造 FeatureCurve;
读取 3D Curve、参数区间和 face 上的 PCurve。
外环上的边被标记为:
Role = Boundary
Kind = OuterBoundary
孔洞或其他内环上的边被标记为:
Role = Boundary
Kind = InnerBoundary
这里没有根据显示位置猜测内外关系,而是直接使用 face 的 wire 拓扑和外环识别结果。
对每一条边,还会尝试读取:
BRep_Tool::Curve(edge, first, last);
BRep_Tool::CurveOnSurface(edge, sourceFace, pFirst, pLast);
三维 Curve 用于独立显示、距离计算和后续几何构造。
PCurve 则描述同一条边在 source face 参数域中的二维表示。
如果没有有效三维曲线,当前结果不会把这条边当成可用特征曲线提交。
这也说明:特征边提取不是简单遍历拓扑数量。Edge 存在,不等于它的几何表示已经完整。
等参数线必须经过 trimmed face 裁剪
从支撑曲面上生成 U/V 等参数线本身并不难:
surface->UIso(u);
surface->VIso(v);
真正的问题是,Geom_Surface 表达的是支撑曲面,而用户选中的是经过 wire 裁剪后的 TopoDS_Face。
支撑曲面上的整条 U/V 曲线可能:
超出 face 的外轮廓;
穿过 face 的内孔;
在一个 trimmed face 内形成多个不连续片段;
落在退化或无界参数区间;
根本没有穿过当前 face 的有效内部。
所以不能把 UIso() 或 VIso() 返回的整条曲线直接作为特征边。
当前做法是先读取 face 的 UV bounds,然后沿变化参数方向采样分类。
以固定 U 的等参数线为例:
U = constantParameter
V 从 vMin 走到 vMax
每一个采样点都需要判断它在 trimmed face 中的位置:
IN / ON:
位于有效面域内部或边界,可以保留。
OUT:
位于裁剪区域外,不能生成特征段。
当分类从 OUT 变成 IN,或者从 IN 变成 OUT 时,再细化过渡参数,得到更稳定的区间端点。
随后只对有效区间创建 Edge。
这意味着一条支撑曲面的 U 等参数线,最终可能生成:
0 个特征段:没有穿过有效面域;
1 个特征段:穿过一个连续区域;
多个特征段:被内孔或裁剪边界分割。
这种结果比“每个方向永远返回一条线”更符合真实 B-Rep 面的结构。
为什么只提取拾取位置的一对 U/V 线
一张参数曲面上有无限多条 U/V 等参数线。
如果命令叫“提取全部特征边”,很容易让人误解成系统会自动生成整张曲面的参数网格。
当前版本采用的是一个明确的有限规则:
用户在 face 上拾取一个世界坐标点;
系统把拾取点投影到 face 的 UV 参数域;
得到参数 (u, v);
提取 U = u 和 V = v 穿过 trimmed face 的有效片段。
这样做有几个好处:
结果数量可控;
用户能够理解这两条线为什么出现在这里;
提取位置具有明确交互语义;
后续可以把它们直接作为内部 Guide;
不会因为曲面参数范围过大生成大量无意义对象。
所以当前版本中的“提取特征边”更准确地说是:
提取启用的 trimming edges
+ 拾取点对应的一对有限 U/V 特征段。
它不包含自动 ridge/valley tracing,也不包含曲面上全部可能的等参数线。
平面截线是另一种特征来源
除了 trimming boundary 和 U/V 等参数线,几何库还可以从指定平面与 face 求交。
这类能力可以理解为:
输入:source face + section plane
输出:section edges
平面截线通常更适合表达非参数方向的内部控制线。
它和 U/V 等参数线相比,交互语义更自由,但计算成本和稳定性也更高。截平面不与面相交、近相切、容差不合适或相交结果退化时,都可能得不到有效 Edge。
因此当前交互命令默认聚焦于边界和拾取 U/V 线,平面截线保留在几何库层,由更明确的上层工具按需调用。
这样边界会更清楚:底层能力可以存在,但产品入口不必一次全部暴露。
预览对象不应该提前写入模型
交互命令从选择一张 face 开始。
选中以后,系统会:
提取启用的外边界和内边界;
把拾取点投影到 face UV;
提取拾取位置的 U/V 有效片段;
过滤无效或重复 Edge;
把结果作为临时候选;
生成预览。
预览阶段不应该急着把所有候选 Edge 写入文档数据库。
更合理的流程是:
临时提取
↓
临时预览与筛选
↓
用户确认
↓
创建真正的特征曲线对象
而不是:
先创建大量正式对象
↓
用户再删除不需要的对象
对取消、撤销和预览清理来说,前一种方式更可控。
这也符合前面显示引擎专题里提到的原则:临时交互反馈应该有自己的生命周期,不应该污染基础模型数据。
提交结果为什么要保留来源
用户确认以后,每一条选中的特征边都会作为独立对象提交。
除了输出 Edge,还应该保留:
source owner;
source face index;
source face;
完整 FeatureCurve;
输出名称。
这些来源信息有两个用途。
第一,后续构面时可以直接把 FeatureCurve 转换成带角色的曲线约束。
第二,如果 root shape 发生拓扑替换,上层至少知道这条特征来自哪个 owner 和哪张源面,可以结合 topology history 或新的引用映射尝试恢复关系。
当然,face index 只稳定于当前基线,不是跨任意拓扑修改的永久命名方案。要做到长期稳定引用,还需要更完整的持久命名和历史映射。
所以这里不能夸大 face index 的能力。
它是当前基线上的来源线索,不是永久拓扑命名。
特征边和普通 Edge 的区别
从 OCCT 类型上看,提交结果仍然可以是一条 TopoDS_Edge。
但从应用语义上看,特征边和普通 Edge 不应该完全等价。
普通 Edge 可能只表示模型拓扑的一部分。
特征边还携带:
它为什么被提取;
它在后续构面中的角色;
它来自哪个 face;
它是否应该作为硬约束;
它的三维曲线和 PCurve 是否完整;
它对应哪一段参数区间。
如果应用层只把它当成一个裸 Edge 写入数据库,角色信息会丢失。
之后再进入构面时,所有预选 Edge 只能被兼容性地归为 Boundary。
更推荐的做法是持久化 FeatureCurve 元数据,并在启动构面命令前以结构化约束传入。
这样 OuterBoundary、InnerBoundary 会继续作为 Boundary,U/V 等参数线会继续作为 Guide,而不是被错误地全部解释成外轮廓。
当前能力边界
这次实现有几个需要明确说明的边界。
不做自动脊线和谷线追踪
当前不会在曲面内部自动搜索 ridge、valley 或高曲率网络。
U/V 线来自拾取位置,PlaneSection 来自明确截平面。它们是可解释的交互特征,不是自动曲面特征识别结果。
不保证所有 Edge 都有完整 PCurve
边界 Edge 会尝试从 source face 读取 PCurve,Section 也可以请求计算 PCurve,但异常输入仍可能缺失表示。
后续算法不能因为 Edge 被提取出来,就假设 Curve3d、PCurve、SameRange 和 SameParameter 一定全部可靠。
等参数线提取依赖采样分类
当前使用有限采样和细化识别 face 内区间。
对非常小的孔洞、极窄裁剪区域或复杂参数退化区,采样密度和容差可能影响结果。它是一套工程上可控的裁剪方法,不是对任意病态参数域都完备的解析算法。
特征点尚未进入当前 UI
几何库可以生成点特征,但当前交互版本只开放曲线约束。
提取不等于构面
提取完成只说明得到了一组带语义的曲线。
这些曲线是否能够组成闭合边界、是否存在冲突、能否构成一张光顺面,还要由下一阶段的约束构面流程判断。
一个完整的数据流
当前从选面到后续构面的推荐数据流可以概括为:
选择 source face
↓
提取 outer / inner trimming edges
↓
把 pick point 投影到 UV
↓
提取 trimmed U/V segments
↓
构造带 Role、Kind、Source、Curve、PCurve 的 FeatureCurve
↓
临时预览并允许用户筛选
↓
提交选中的独立特征曲线
↓
持久化 FeatureCurve 元数据
↓
转换为 SurfaceCurveConstraint
↓
进入 Boundary / Guide / Crease 约束构面
这条链路把“看到一条线”和“得到一条可以参与几何重建的约束”区分开了。
小结
如果只从显示结果看,特征边提取好像只是从一张面上多生成了几条 Edge。
但从工程实现看,真正重要的是这些 Edge 后面的语义:
外边界和内边界来自真实 trimming topology;
U/V 线经过 trimmed face 裁剪,只保留有效片段;
截线来自明确的 face-plane section;
每条曲线保留来源、三维几何、PCurve 和参数区间;
临时预览不污染文档数据;
只有用户确认的对象才正式提交;
结构化角色可以直接传入后续构面流程。
这也是我现在对特征提取功能的基本定位:
它不是一个批量画线工具,而是几何重建和区域修复之前的约束采集层。
提取阶段越能保留来源和语义,后续构面就越少依赖猜测。
下一篇可以继续沿着这条链路,讨论这些 Boundary、Guide 和 Crease 曲线为什么不能混在一起,以及系统如何用它们构造一张真正可验收的面。