# STEP BREP 逆向工程方法 本文记录一种面向精确 BREP 的 STEP 逆向工作流。目标不是做出“看起来相似”的模型,而是恢复一个满足以下硬约束的可编辑参数模型: 1. 几何点集一致; 2. BREP 拓扑一致; 3. 解析曲面的参数一致; 4. 边界曲线和关键参数范围一致; 5. 模型能够稳定重建和回放。 当多种特征树都满足这些硬约束时,再用工程师的建模习惯筛选:优先对称、整数或标准尺寸、少量草图、稳定基准、清晰特征依赖和容易修改的参数。 ## 1. 先区分五种“一致” STEP 逆向中最容易犯的错误,是把不同层次的一致混为一谈。 ### 1.1 外观一致 截图、网格或渲染结果相似。它只能用于快速发现大方向错误,不能用于验收。 截图看不出: - 两个相邻面是否已合并; - 一个圆柱面是整圆还是两个半圆; - 隐藏孔、沉孔深度和内部台阶; - 半径的微小差异; - BREP 邻接关系; - 曲面的参数原点和方向。 ### 1.2 数值摘要一致 包围盒、体积、表面积、质心一致。这比外观更强,但仍不充分。不同实体可能碰巧拥有相同的体积和包围盒。 用途: - 快速检查单位和坐标系; - 定位哪个特征导致体积误差; - 验证孔、槽、台阶的总体材料增减; - 在迭代过程中排除明显错误。 ### 1.3 几何点集一致 两个实体占据完全相同的空间。对实体可用双向布尔差集验证: ```text 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/曲线参数范围 | 参数历史和特征顺序 | 工具从哪个基准面开始、何时被后续特征裁剪 | 非唯一坐标架必然相同 | | 模型图回放 | 建模过程能否稳定复现 | 是否依赖交互状态或临时对象 | 回放结果与目标自动一致 | 推荐检查顺序是从便宜到昂贵: ```text 有效性和摘要 -> 解析几何清点 -> 多视图和少量关键截面 -> 面边参数与邻接 -> 候选构建 -> 双向差集 -> 图同构 -> 参数历史 -> 回放 ``` 不要一开始就对所有点做高密度分类,也不要一开始就逐个比较 STEP 文本实体。前者成本高且信息增量有限,后者会被导出器编号和非唯一参数化干扰。 ## 3. 推荐的分阶段工作流 ### 3.1 阶段 A:建立不可变基准 在写建模代码前,先从原始 STEP 导出一份机器可读报告。可以先用文本清点快速了解文件组成: ```bash 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 范围、解析曲面参数和邻接边; - 每条边的类型、参数范围和邻接面。 唯一数量和出现次数必须分开: ```text unique_edges BREP 中不同的边对象数量 edge_occurrences 遍历所有面时边被引用的总次数 ``` 在闭合流形实体中,一条普通边通常被两个面引用,因此出现次数通常接近唯一边数的两倍。 正式实现位于: ```text src/simplecadapi/inverse_engineer/brep/inspect.py ``` Python API: ```python from simplecadapi.inverse_engineer import brep report = brep.inspect_step(path="model.step") report.write_json("model-report.json") ``` CLI: ```bash uv run simplecad-brep inspect model.step -o model-report.json ``` ### 基础属性代码片段 ```python 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 | 放样、填充、自由曲面 | 次数、节点、控制点、连续性 | 例如: ```text 4 个 R1 Sphere 4 个 (R2, R1) Torus 14 个 R1 Cylinder ``` 通常不是手工拼出的 22 个面,而是一次连续局部 R1 圆角的结果。解析面组合比截图更能揭示特征操作。 ### 读取解析曲面参数 ```python 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 度倒角; - 圆角切点; - 草图中的直线方程。 例如一组斜面端点满足: ```text x + |z| = 1.25 ``` 这比“看起来大约 45 度”更强,说明草图很可能使用了对称 45 度约束。 ### 读取边端点和类型 ```python 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:建立直观图像,但只让它承担合适的职责 推荐同时生成: 1. 等轴视图; 2. 三个正交视图; 3. 关键高度或轴向位置的占用截面; 4. 目标/候选叠加图; 5. XOR 差异图。 多视图适合发现: - 坐标方向错误; - 漏掉大特征; - 孔位错误; - 台阶方向错误; - 圆角范围过大; - 草图轮廓错误。 截面图适合发现: - 内孔层级; - 沉孔深度; - 不同高度处的轮廓变化; - 隐藏槽; - 两实体的局部差异。 正式实现: ```text src/simplecadapi/inverse_engineer/brep/render.py src/simplecadapi/inverse_engineer/brep/slices.py ``` 渲染图片需要可选依赖: ```bash uv sync --extra inverse-engineer ``` Python API: ```python 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: ```bash 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 ``` 截面分类示例: ```python 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 的证据解释。例如: ```text R1 来自 14 个 R1 圆柱、4 个 R1 球和 4 个次半径 R1 的圆环 R3.25 来自沉孔圆柱 R1.7 来自贯穿孔圆柱 X=3.25 来自台阶平面 Z=±6 来自对称孔轴 ``` 避免在没有证据时使用: - 随机小偏移; - 大量补偿布尔体; - 任意样条; - 网格拟合; - 按最终面逐面拼壳。 ### 3.7 阶段 G:按特征逐步落地 每一步都打印少量有意义的基准: ```python print( "ground opening fillet", body.get_volume(), len(body.get_faces()), len(body.get_edges()), ) ``` 推荐顺序: 1. 主轮廓; 2. 主拉伸; 3. 大型切除或并集; 4. 台阶; 5. 孔; 6. 圆角和倒角,或根据几何指纹调整到正确历史位置; 7. 输出 STEP; 8. 生成报告和截图; 9. 对比; 10. 只修改造成当前差异的特征。 不要一次添加所有特征。否则拓扑不一致时无法知道是哪一步造成的。 ## 4. 从差异反推建模历史 ### 4.1 面数少但几何相同:检查草图分段 典型情况: ```text 目标:每个孔壁由两个半圆柱面组成 候选:每个孔壁是一个完整圆柱面 ``` 二者几何点集相同,但拓扑不同。原因通常不是布尔算法随机拆面,而是草图边界不同: ```text 完整圆边拉伸 -> 一个圆柱侧面 两条半圆弧闭合拉伸 -> 两个半圆柱侧面 ``` 因此,拓扑差异常常能反推草图实体数量。 ### 4.2 面数多且出现额外平面:检查错误的布尔拆分 把一个圆柱工具拆成左右两个半圆实体分别切除,会得到两个半圆柱面,但也会引入径向分隔平面。 这是一条重要经验: > 想得到分段侧面,应在草图边上分段,而不是把实体工具分成多个带内部平面的实体。 ### 4.3 几何和拓扑相同但 UV 不同:检查特征顺序 典型沉孔: ```text 方案 A:先沉孔,再从肩部开始切小孔 方案 B:先切贯穿小孔,再切大沉孔 ``` 两者最终几何可能相同,但小孔圆柱支撑面的参数原点不同。 如果目标小孔的圆柱原点位于入口平面,UV 还保留了被沉孔切掉的参数区间,这通常说明: ```text 先贯穿孔 再沉孔 ``` 特征顺序不仅影响工程可读性,也会影响 STEP 中保留的曲面参数。 ### 4.4 球面和圆环面组合:检查三维圆角交汇 二维草图圆角后拉伸,常产生圆柱面;对三维实体的多条相交边做圆角,会进一步产生球面和圆环面。 因此: - 只有圆柱圆角面:可能是二维圆角轮廓拉伸; - 同时出现球面和圆环面:更像三维成组选边圆角; - 球面半径和圆环次半径相同:通常直接给出圆角半径。 ## 5. 严格验收方法 ### 5.1 双向布尔差集 ```python 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 几何标记的邻接图同构 正式实现位于: ```text src/simplecadapi/inverse_engineer/brep/compare.py ``` Python API: ```python from simplecadapi.inverse_engineer import brep comparison = brep.compare_steps(target_path="target.step", candidate_path="candidate.step") assert comparison.hard_gate_passed ``` CLI: ```bash uv run simplecad-brep compare target.step candidate.step -o comparison.json ``` 报告应同时满足: ```text same_geometric_point_set = true geometry_labelled_incidence_graph_isomorphic = true ``` ### 规范化非唯一参数 比较几何载体时,不要直接比较平面原点或轴向符号。 平面可规范为: ```text 单位法向 n 有符号距离 d = n dot p ``` 轴线可规范为: ```text 单位方向 d,统一符号 原点到世界原点的最近点 p - d(d dot p) ``` 这样可以识别“同一个平面但原点不同”和“同一根轴但方向相反”。 ### 5.3 参数化表示检查 完成几何和拓扑硬检查后,再检查: - UV 范围; - 圆柱、圆锥、圆环坐标架; - 圆弧参数范围; - B 样条次数、节点、重数、控制点和权重; - 边方向和面方向; - 曲面原点是否反映预期特征顺序。 不要把所有参数差异都当成错误。先问: 1. 这是非唯一坐标架选择吗? 2. 它是否改变几何点集? 3. 它是否改变 BREP 邻接? 4. 它是否揭示不同的草图分段或特征顺序? 5. 用户要求的是精确 BREP,还是 STEP 文本字节一致? STEP 实体编号和文本顺序通常不属于 BREP 本体,不应作为几何逆向的默认验收项。 ### 5.4 回放验收 逆向模型不能只在当前 Python 进程中成功。应记录模型图,重新播放,再对回放结果执行相同检查。 ```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") ``` 需要分别比较: ```text 原始 vs 直接构建 原始 vs 回放构建 直接构建 vs 回放构建 ``` ## 6. 高效迭代策略 ### 6.1 用差异类型决定下一步 | 当前差异 | 优先检查 | |---|---| | 包围盒错误 | 坐标系、单位、基准位置 | | 体积差很大 | 漏掉主切除/并集、特征方向错误 | | 体积接近但面积差大 | 圆角、倒角、孔壁或隐藏台阶 | | 几何相同但面数少 | 草图边分段不足、同域面被合并 | | 几何相同但面数多 | 多余切割平面、重复布尔、错误拆工具 | | Sphere/Torus 数量错误 | 圆角选边范围或圆角时序 | | Cylinder 数量错误 | 圆/圆弧草图分段、孔特征顺序 | | 拓扑计数相同但图不同 | 面邻接、边界方向、错误局部连接 | | UV 原点或范围错误 | 特征创建顺序、工具起始平面 | ### 6.2 先匹配低维输入,再匹配高维结果 工程特征一般遵循: ```text 点/尺寸 -> 草图边 -> 闭合轮廓 -> 面 -> 拉伸/旋转 -> 布尔 -> 圆角 ``` 当最终圆柱面分段不对时,优先检查草图圆弧数量,而不是在最终实体上补切割面。低维输入通常才是拓扑来源。 ### 6.3 用解析面统计做中间目标 完整拓扑还没对齐时,解析面统计是非常有效的阶段目标。例如: ```text 目标:Plane 16, Cylinder 22, Sphere 4, Torus 4 ``` 候选若为: ```text 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. 推荐的自动化产物 每个逆向任务建议固定生成: ```text 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 ``` 最终验收至少包括: ```text BREP valid 双向差集为零 唯一 Face/Edge/Vertex 数量一致 解析面和边类型统计一致 几何标记邻接图同构 关键解析参数一致 模型图可重放 回放结果再次通过全部检查 ``` ## 9. 核心方法总结 最有效的逆向顺序不是“看图后直接建模”,而是: ```text 建立精确基准 -> 恢复坐标语义 -> 清点解析几何 -> 从平面、轴线和边界恢复尺寸关系 -> 用多视图与截面建立直觉 -> 提出最小工程特征树 -> 逐特征构建和输出 -> 用差异类型指导下一轮 -> 双向差集验证几何 -> 几何标记图验证拓扑 -> UV/曲线参数验证特征历史 -> 模型图回放并重复全部验收 ``` 最重要的判断原则是: > 几何和拓扑是硬约束;工程师先验只用于在满足硬约束的多种过程之间做选择。解析几何告诉我们“是什么”,拓扑分段告诉我们“草图如何画”,参数范围和坐标架经常告诉我们“特征按什么顺序做”。