23 KiB
STEP BREP 逆向工程方法
本文记录一种面向精确 BREP 的 STEP 逆向工作流。目标不是做出“看起来相似”的模型,而是恢复一个满足以下硬约束的可编辑参数模型:
- 几何点集一致;
- BREP 拓扑一致;
- 解析曲面的参数一致;
- 边界曲线和关键参数范围一致;
- 模型能够稳定重建和回放。
当多种特征树都满足这些硬约束时,再用工程师的建模习惯筛选:优先对称、整数或标准尺寸、少量草图、稳定基准、清晰特征依赖和容易修改的参数。
1. 先区分五种“一致”
STEP 逆向中最容易犯的错误,是把不同层次的一致混为一谈。
1.1 外观一致
截图、网格或渲染结果相似。它只能用于快速发现大方向错误,不能用于验收。
截图看不出:
- 两个相邻面是否已合并;
- 一个圆柱面是整圆还是两个半圆;
- 隐藏孔、沉孔深度和内部台阶;
- 半径的微小差异;
- BREP 邻接关系;
- 曲面的参数原点和方向。
1.2 数值摘要一致
包围盒、体积、表面积、质心一致。这比外观更强,但仍不充分。不同实体可能碰巧拥有相同的体积和包围盒。
用途:
- 快速检查单位和坐标系;
- 定位哪个特征导致体积误差;
- 验证孔、槽、台阶的总体材料增减;
- 在迭代过程中排除明显错误。
1.3 几何点集一致
两个实体占据完全相同的空间。对实体可用双向布尔差集验证:
A - B 的体积 = 0
B - A 的体积 = 0
这是几何一致的硬检查,但它不保证拓扑一致。例如一个完整圆柱面和两个半圆柱面可以表示相同点集。
1.4 BREP 拓扑一致
面、边、顶点数量相同,并且面—边—顶点邻接图同构。
仅比较数量还不够。两个模型都可能有 20 个面,但连接方式不同。可靠做法是给图节点添加几何标签,再检查图同构:
- 面标签:类型、面积、几何中心、半径、轴线、球心等;
- 边标签:类型、长度、几何中心、半径、轴线等;
- 顶点标签:三维坐标;
- 图边:Face—Edge 和 Edge—Vertex 关联。
1.5 参数化表示一致
同一个几何载体仍可能具有不同参数表示:
- 圆柱轴方向相反;
- 平面原点不同;
- 圆弧起始角不同;
- UV 范围平移或反向;
- 一条整圆边与两条半圆边;
- 相同孔通过不同特征顺序产生不同的曲面参数原点。
有些参数差异只是非唯一表示,不应误判为几何差异;有些则是建模历史的有效证据。必须结合设计语义判断。
2. 检查手段速查表
| 检查手段 | 最适合回答的问题 | 能提供的直觉 | 不能单独证明什么 |
|---|---|---|---|
| STEP 实体类型计数 | 文件主要由什么几何组成 | 是机械解析体、自由曲面还是混合模型 | 导入后的唯一拓扑数量 |
| BREP 有效性 | 文件能否作为可靠实体继续运算 | 是否存在开壳、坏边、容差问题 | 几何是否符合设计意图 |
| 唯一 Face/Edge/Vertex 数量 | 拓扑复杂度和分段程度 | 圆是否被拆弧、面是否被切分 | 邻接关系是否一致 |
| 拓扑出现次数 | 面如何重复引用边和顶点 | 是否像闭合流形、是否有非流形连接 | 两模型是否同构 |
| 包围盒 | 单位、整体尺寸和主方向 | 哪个方向可能是厚度或长度 | 内部孔槽和局部形状 |
| 体积/面积/质心 | 材料增减是否正确 | 漏掉的是大特征还是表面细节 | 几何点集和拓扑一致 |
| 解析面类型统计 | 特征家族是否正确 | 孔、倒角、圆角、自由曲面来源 | 每个面的具体位置和连接 |
| 面参数与包围盒 | 单个特征的尺寸和位置 | 半径、轴线、台阶、角度、深度 | 建模历史唯一解 |
| 边类型与端点 | 草图分段和切点 | 整圆/半圆、45 度线、圆角切线 | 隐藏面的完整形状 |
| 面-边-顶点邻接图 | BREP 连接结构是否一致 | 哪个局部特征造成分段差异 | 几何点集必然一致 |
| 双向布尔差集 | 两实体是否占据相同空间 | 几何已经完成,可转向拓扑检查 | 面分段和参数化一致 |
| 多视图截图 | 大方向和明显特征是否正确 | 最快发现轴向、孔位、台阶错误 | 精确尺寸、内部结构和拓扑 |
| 物理截面 | 隐藏结构和不同深度轮廓 | 沉孔、槽、壁厚、局部层级 | 未采样位置必然一致 |
| 截面 XOR | 候选差异出现在哪个区域 | 直接定位下一步应修改的特征 | 连续空间上的严格零差异 |
| UV/曲线参数范围 | 参数历史和特征顺序 | 工具从哪个基准面开始、何时被后续特征裁剪 | 非唯一坐标架必然相同 |
| 模型图回放 | 建模过程能否稳定复现 | 是否依赖交互状态或临时对象 | 回放结果与目标自动一致 |
推荐检查顺序是从便宜到昂贵:
有效性和摘要
-> 解析几何清点
-> 多视图和少量关键截面
-> 面边参数与邻接
-> 候选构建
-> 双向差集
-> 图同构
-> 参数历史
-> 回放
不要一开始就对所有点做高密度分类,也不要一开始就逐个比较 STEP 文本实体。前者成本高且信息增量有限,后者会被导出器编号和非唯一参数化干扰。
3. 推荐的分阶段工作流
3.1 阶段 A:建立不可变基准
在写建模代码前,先从原始 STEP 导出一份机器可读报告。可以先用文本清点快速了解文件组成:
rg -o '^[[:space:]]*#[0-9]+[[:space:]]*=[[:space:]]*[A-Z0-9_]+' model.step \
| sed -E 's/.*=[[:space:]]*//' \
| sort \
| uniq -c \
| sort -nr
这能快速发现 ADVANCED_FACE、PLANE、CYLINDRICAL_SURFACE、B_SPLINE_SURFACE_WITH_KNOTS 等实体,但它只是 STEP 表示层清点。EDGE_CURVE 数量不一定等于内核导入后的唯一 Edge 数量;退化边、缝边和表示方式都会造成差异。最终基准必须来自导入后的 BREP。
机器可读报告至少包括:
- STEP 是否能读取;
- 转移根数量;
- BREP 是否有效;
- Solid、Shell、Face、Edge、Vertex 数量;
- 唯一拓扑数量和拓扑出现次数;
- 包围盒、体积、表面积、质心;
- 面类型统计;
- 边类型统计;
- 每个面的面积、UV 范围、解析曲面参数和邻接边;
- 每条边的类型、参数范围和邻接面。
唯一数量和出现次数必须分开:
unique_edges BREP 中不同的边对象数量
edge_occurrences 遍历所有面时边被引用的总次数
在闭合流形实体中,一条普通边通常被两个面引用,因此出现次数通常接近唯一边数的两倍。
正式实现位于:
src/simplecadapi/inverse_engineer/brep/inspect.py
Python API:
from simplecadapi.inverse_engineer import brep
report = brep.inspect_step(path="model.step")
report.write_json("model-report.json")
CLI:
uv run simplecad-brep inspect model.step -o model-report.json
基础属性代码片段
from OCP.BRepCheck import BRepCheck_Analyzer
from OCP.BRepGProp import BRepGProp
from OCP.BRepBndLib import BRepBndLib
from OCP.Bnd import Bnd_Box
from OCP.GProp import GProp_GProps
assert BRepCheck_Analyzer(shape).IsValid()
bbox = Bnd_Box()
BRepBndLib.AddOptimal_s(shape, bbox)
print("bbox", bbox.Get())
volume_props = GProp_GProps()
BRepGProp.VolumeProperties_s(shape, volume_props)
print("volume", volume_props.Mass())
print("center", volume_props.CentreOfMass().Coord())
area_props = GProp_GProps()
BRepGProp.SurfaceProperties_s(shape, area_props)
print("area", area_props.Mass())
3.2 阶段 B:恢复坐标语义
不要根据 X/Y/Z 名称直接假设左右、前后和高度。
应综合使用:
- 包围盒三个跨度;
- 主成分分析;
- 平面和圆柱轴线分布;
- 对称平面拟合;
- 正交视图和典型截面;
- 机械功能,例如孔轴通常对应装配或加工方向。
坐标语义判断错误会导致整个特征解释反转。比如一个方向上的 U 形截面可能是左右对称结构,也可能被错误解释为前后凹槽。
3.3 阶段 C:做解析几何清点
解析面类型是最有价值的“建模指纹”之一。
| 几何类型 | 常见来源 | 可恢复的信息 |
|---|---|---|
| Plane | 拉伸端面、切割平面、倒角 | 基准方向、台阶位置、厚度、角度 |
| Cylinder | 孔、轴、拉伸圆弧、直边圆角 | 半径、轴线、深度、圆弧分段 |
| Sphere | 三条圆角边交汇、球切割 | 圆角半径、特征交汇方式 |
| Torus | 圆弧轮廓的边圆角、环形过渡 | 主半径、次半径、圆角中心 |
| Cone | 锥孔、沉头、拔模 | 锥角、轴线、顶点 |
| BSpline | 放样、填充、自由曲面 | 次数、节点、控制点、连续性 |
例如:
4 个 R1 Sphere
4 个 (R2, R1) Torus
14 个 R1 Cylinder
通常不是手工拼出的 22 个面,而是一次连续局部 R1 圆角的结果。解析面组合比截图更能揭示特征操作。
读取解析曲面参数
from OCP.BRepAdaptor import BRepAdaptor_Surface
surface = BRepAdaptor_Surface(face, True)
kind = surface.GetType()
cylinder = surface.Cylinder()
print("radius", cylinder.Radius())
print("axis", cylinder.Axis().Direction().Coord())
print("origin", cylinder.Location().Coord())
sphere = surface.Sphere()
print("radius", sphere.Radius())
print("center", sphere.Location().Coord())
torus = surface.Torus()
print("major", torus.MajorRadius())
print("minor", torus.MinorRadius())
3.4 阶段 D:从平面和边恢复尺寸关系
不要只读包围盒。每个解析面的平面方程、轴线和边界端点能够恢复:
- 台阶高度;
- 壁厚;
- 孔深;
- 孔距;
- 对称位置;
- 45 度倒角;
- 圆角切点;
- 草图中的直线方程。
例如一组斜面端点满足:
x + |z| = 1.25
这比“看起来大约 45 度”更强,说明草图很可能使用了对称 45 度约束。
读取边端点和类型
from OCP.BRepAdaptor import BRepAdaptor_Curve
curve = BRepAdaptor_Curve(edge)
first = curve.FirstParameter()
last = curve.LastParameter()
p0 = curve.Value(first)
p1 = curve.Value(last)
print("type", curve.GetType())
print("start", p0.Coord())
print("end", p1.Coord())
if "Circle" in str(curve.GetType()):
circle = curve.Circle()
print("radius", circle.Radius())
print("center", circle.Location().Coord())
3.5 阶段 E:建立直观图像,但只让它承担合适的职责
推荐同时生成:
- 等轴视图;
- 三个正交视图;
- 关键高度或轴向位置的占用截面;
- 目标/候选叠加图;
- XOR 差异图。
多视图适合发现:
- 坐标方向错误;
- 漏掉大特征;
- 孔位错误;
- 台阶方向错误;
- 圆角范围过大;
- 草图轮廓错误。
截面图适合发现:
- 内孔层级;
- 沉孔深度;
- 不同高度处的轮廓变化;
- 隐藏槽;
- 两实体的局部差异。
正式实现:
src/simplecadapi/inverse_engineer/brep/render.py
src/simplecadapi/inverse_engineer/brep/slices.py
渲染图片需要可选依赖:
uv sync --extra inverse-engineer
Python API:
from simplecadapi.inverse_engineer import brep
brep.render_step_views(step_path="candidate.step", output_path="candidate-views.png")
comparison = brep.compare_step_slices(
target_path="target.step",
candidate_path="candidate.step",
output_path="slice-overlay.png",
)
CLI:
uv run simplecad-brep render candidate.step candidate-views.png
uv run simplecad-brep slices target.step candidate.step slice-overlay.png
# 默认自动检查联合包围盒的 X/Y/Z 中心截面;特征边界应显式追加:
uv run simplecad-brep slices target.step candidate.step feature-slices.png \
--slice xz:-1.6 --slice xz:4.0 --slice xy:0
截面分类示例:
from OCP.BRepClass3d import BRepClass3d_SolidClassifier
from OCP.TopAbs import TopAbs_IN, TopAbs_ON
from OCP.gp import gp_Pnt
classifier = BRepClass3d_SolidClassifier()
classifier.Load(shape)
classifier.Perform(gp_Pnt(x, y, z), 1e-8)
inside = classifier.State() in (TopAbs_IN, TopAbs_ON)
默认截面只用于快速建立直觉。逆向时应进一步选择穿过特征边界、台阶高度、沉孔肩部、对称面和疑似差异区域的物理截面。
注意:逐点 BREP 分类较慢。截面图是诊断工具,应控制分辨率;几何硬验收应使用布尔差集,而不是靠提高图片分辨率。
3.6 阶段 F:提出最小特征假设
先写出一个人类可读的候选特征树,不要马上编码大量细节。
好的候选通常包含:
- 一个主草图和一次拉伸;
- 对称约束或镜像;
- 少量孔、槽、台阶;
- 局部圆角和倒角;
- 清晰的加工顺序。
每个参数都应能由原始 BREP 的证据解释。例如:
R1 来自 14 个 R1 圆柱、4 个 R1 球和 4 个次半径 R1 的圆环
R3.25 来自沉孔圆柱
R1.7 来自贯穿孔圆柱
X=3.25 来自台阶平面
Z=±6 来自对称孔轴
避免在没有证据时使用:
- 随机小偏移;
- 大量补偿布尔体;
- 任意样条;
- 网格拟合;
- 按最终面逐面拼壳。
3.7 阶段 G:按特征逐步落地
每一步都打印少量有意义的基准:
print(
"ground opening fillet",
body.get_volume(),
len(body.get_faces()),
len(body.get_edges()),
)
推荐顺序:
- 主轮廓;
- 主拉伸;
- 大型切除或并集;
- 台阶;
- 孔;
- 圆角和倒角,或根据几何指纹调整到正确历史位置;
- 输出 STEP;
- 生成报告和截图;
- 对比;
- 只修改造成当前差异的特征。
不要一次添加所有特征。否则拓扑不一致时无法知道是哪一步造成的。
4. 从差异反推建模历史
4.1 面数少但几何相同:检查草图分段
典型情况:
目标:每个孔壁由两个半圆柱面组成
候选:每个孔壁是一个完整圆柱面
二者几何点集相同,但拓扑不同。原因通常不是布尔算法随机拆面,而是草图边界不同:
完整圆边拉伸 -> 一个圆柱侧面
两条半圆弧闭合拉伸 -> 两个半圆柱侧面
因此,拓扑差异常常能反推草图实体数量。
4.2 面数多且出现额外平面:检查错误的布尔拆分
把一个圆柱工具拆成左右两个半圆实体分别切除,会得到两个半圆柱面,但也会引入径向分隔平面。
这是一条重要经验:
想得到分段侧面,应在草图边上分段,而不是把实体工具分成多个带内部平面的实体。
4.3 几何和拓扑相同但 UV 不同:检查特征顺序
典型沉孔:
方案 A:先沉孔,再从肩部开始切小孔
方案 B:先切贯穿小孔,再切大沉孔
两者最终几何可能相同,但小孔圆柱支撑面的参数原点不同。
如果目标小孔的圆柱原点位于入口平面,UV 还保留了被沉孔切掉的参数区间,这通常说明:
先贯穿孔
再沉孔
特征顺序不仅影响工程可读性,也会影响 STEP 中保留的曲面参数。
4.4 球面和圆环面组合:检查三维圆角交汇
二维草图圆角后拉伸,常产生圆柱面;对三维实体的多条相交边做圆角,会进一步产生球面和圆环面。
因此:
- 只有圆柱圆角面:可能是二维圆角轮廓拉伸;
- 同时出现球面和圆环面:更像三维成组选边圆角;
- 球面半径和圆环次半径相同:通常直接给出圆角半径。
5. 严格验收方法
5.1 双向布尔差集
from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut
from OCP.BRepGProp import BRepGProp
from OCP.GProp import GProp_GProps
def cut_volume(a, b):
operation = BRepAlgoAPI_Cut(a, b)
operation.SetFuzzyValue(1e-9)
operation.Build()
props = GProp_GProps()
BRepGProp.VolumeProperties_s(operation.Shape(), props)
return props.Mass()
assert cut_volume(target, candidate) < 1e-9
assert cut_volume(candidate, target) < 1e-9
这证明实体点集相同,但仍需拓扑检查。
5.2 几何标记的邻接图同构
正式实现位于:
src/simplecadapi/inverse_engineer/brep/compare.py
Python API:
from simplecadapi.inverse_engineer import brep
comparison = brep.compare_steps(target_path="target.step", candidate_path="candidate.step")
assert comparison.hard_gate_passed
CLI:
uv run simplecad-brep compare target.step candidate.step -o comparison.json
报告应同时满足:
same_geometric_point_set = true
geometry_labelled_incidence_graph_isomorphic = true
规范化非唯一参数
比较几何载体时,不要直接比较平面原点或轴向符号。
平面可规范为:
单位法向 n
有符号距离 d = n dot p
轴线可规范为:
单位方向 d,统一符号
原点到世界原点的最近点 p - d(d dot p)
这样可以识别“同一个平面但原点不同”和“同一根轴但方向相反”。
5.3 参数化表示检查
完成几何和拓扑硬检查后,再检查:
- UV 范围;
- 圆柱、圆锥、圆环坐标架;
- 圆弧参数范围;
- B 样条次数、节点、重数、控制点和权重;
- 边方向和面方向;
- 曲面原点是否反映预期特征顺序。
不要把所有参数差异都当成错误。先问:
- 这是非唯一坐标架选择吗?
- 它是否改变几何点集?
- 它是否改变 BREP 邻接?
- 它是否揭示不同的草图分段或特征顺序?
- 用户要求的是精确 BREP,还是 STEP 文本字节一致?
STEP 实体编号和文本顺序通常不属于 BREP 本体,不应作为几何逆向的默认验收项。
5.4 回放验收
逆向模型不能只在当前 Python 进程中成功。应记录模型图,重新播放,再对回放结果执行相同检查。
import simplecadapi as scad
with scad.GraphSession(graph_id="reverse_part") as session:
body = build_body()
scad.capture_result(value=body)
payload = scad.export_model_json(session)
rebuilt = scad.replay_model_json(payload)[0]
scad.export_step(rebuilt, "replayed.step")
需要分别比较:
原始 vs 直接构建
原始 vs 回放构建
直接构建 vs 回放构建
6. 高效迭代策略
6.1 用差异类型决定下一步
| 当前差异 | 优先检查 |
|---|---|
| 包围盒错误 | 坐标系、单位、基准位置 |
| 体积差很大 | 漏掉主切除/并集、特征方向错误 |
| 体积接近但面积差大 | 圆角、倒角、孔壁或隐藏台阶 |
| 几何相同但面数少 | 草图边分段不足、同域面被合并 |
| 几何相同但面数多 | 多余切割平面、重复布尔、错误拆工具 |
| Sphere/Torus 数量错误 | 圆角选边范围或圆角时序 |
| Cylinder 数量错误 | 圆/圆弧草图分段、孔特征顺序 |
| 拓扑计数相同但图不同 | 面邻接、边界方向、错误局部连接 |
| UV 原点或范围错误 | 特征创建顺序、工具起始平面 |
6.2 先匹配低维输入,再匹配高维结果
工程特征一般遵循:
点/尺寸 -> 草图边 -> 闭合轮廓 -> 面 -> 拉伸/旋转 -> 布尔 -> 圆角
当最终圆柱面分段不对时,优先检查草图圆弧数量,而不是在最终实体上补切割面。低维输入通常才是拓扑来源。
6.3 用解析面统计做中间目标
完整拓扑还没对齐时,解析面统计是非常有效的阶段目标。例如:
目标:Plane 16, Cylinder 22, Sphere 4, Torus 4
候选若为:
Plane 16, Cylinder 18, Sphere 4, Torus 4
说明主体、台阶和圆角网络很可能已正确,剩余问题集中在圆柱类特征,不应重做整个主体。
6.4 保存候选,但不要让试验污染最终入口
建议保留:
- 每轮 STEP;
- 每轮报告;
- 每轮截图;
- 差异日志;
- 被否定的假设及原因。
最终应收敛为一个独立、可读、可回放的入口,不能依赖多个临时试验脚本互相导入。
7. 本次实践中走过的弯路
7.1 只根据截图猜主特征
问题:截图遮挡内部结构,容易把缺口、孔和圆角顺序解释错。
改进:截图只用于提出假设;立即用解析曲面、面边界和截面验证。
7.2 对所有边做圆角
问题:fillet(all_edges) 产生了过多球面,且破坏目标的平面和圆环统计。
改进:根据设计语义分组选边。先识别“左侧开口轮廓”这一语义组,再验证它生成的解析面组合。
7.3 用两个半圆实体切出分段孔
问题:虽然圆柱面数量增加到目标值,却额外产生了两个径向平面。
改进:用两条半圆弧组成一个闭合草图,再做一次拉伸切除。
7.4 把体积和面积一致当作完成
问题:完整圆柱孔和两个半圆柱孔具有相同几何、体积和面积,但拓扑不同。
改进:加入唯一拓扑数量、解析面统计和邻接图同构。
7.5 直接比较平面原点和轴向
问题:同一个平面可选择任意平面内原点,同一根轴可反向,导致大量假差异。
改进:比较规范化平面方程和规范化轴线,不比较任意坐标架选择。
7.6 截面采样过密
问题:逐点 BREP 分类成本高,六张高分辨率截面会超时。
改进:截面图保持适中分辨率用于直观诊断;精确几何一致由双向布尔差集负责。
7.7 先做沉孔再从肩部做小孔
问题:最终几何和拓扑正确,但小孔圆柱参数原点与目标不同。
改进:先从入口平面做贯穿孔,再做沉孔。这个顺序既符合常见加工意图,也复现目标 UV 历史。
8. 推荐的自动化产物
每个逆向任务建议固定生成:
reference_report.json
candidate_report.json
candidate.step
candidate_replayed.step
candidate.model.json
candidate_views.png
candidate_slice_overlay.png
candidate_brep_compare.json
最终验收至少包括:
BREP valid
双向差集为零
唯一 Face/Edge/Vertex 数量一致
解析面和边类型统计一致
几何标记邻接图同构
关键解析参数一致
模型图可重放
回放结果再次通过全部检查
9. 核心方法总结
最有效的逆向顺序不是“看图后直接建模”,而是:
建立精确基准
-> 恢复坐标语义
-> 清点解析几何
-> 从平面、轴线和边界恢复尺寸关系
-> 用多视图与截面建立直觉
-> 提出最小工程特征树
-> 逐特征构建和输出
-> 用差异类型指导下一轮
-> 双向差集验证几何
-> 几何标记图验证拓扑
-> UV/曲线参数验证特征历史
-> 模型图回放并重复全部验收
最重要的判断原则是:
几何和拓扑是硬约束;工程师先验只用于在满足硬约束的多种过程之间做选择。解析几何告诉我们“是什么”,拓扑分段告诉我们“草图如何画”,参数范围和坐标架经常告诉我们“特征按什么顺序做”。