# -*- coding: utf-8 -*- """ step-to-stl.py —— 真实 STEP 批量转 STL 网格 (桌面软件窗口内 3D 预览用) 内核: OpenCASCADE 的 Python 绑定 (cadquery/OCP, 开源 LGPL)。 用途: 把 catalog\\step\\ 里的真实数模 (体积 > 20KB 的 .step/.stp, 占位几何 ~7KB 自动跳过) 转成 catalog\\mesh\\ 下同名 .stl 网格。桌面 3D 标签页发现有网格就在窗口内直渲。 断点续跑: 同名 STL 已存在且不比 STEP 旧 → 跳过。 以后新增真实 STEP 后重跑本脚本即可 (或用 NX journal 的 exportStl 自动导出)。 用法: python step-to-stl.py """ import os import sys BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) STEP_DIR = os.path.join(BASE, 'catalog', 'step') MESH_DIR = os.path.join(BASE, 'catalog', 'mesh') REAL_MIN_BYTES = 20 * 1024 # 20KB 以上算真实数模 (占位几何 ~7KB) LIN_DEFLECTION = 1.5 # 网格线性偏差 (mm): 越小越精细、文件越大 (1.5mm 屏幕预览足够, 保证流畅旋转) def convert(step_path, stl_path): from cadquery.occ_impl.shapes import Shape from OCP.STEPControl import STEPControl_Reader from OCP.StlAPI import StlAPI_Writer from OCP.BRepMesh import BRepMesh_IncrementalMesh from OCP.IFSelect import IFSelect_RetDone reader = STEPControl_Reader() if reader.ReadFile(str(step_path)) != IFSelect_RetDone: raise RuntimeError('STEP 读取失败: %s' % step_path) reader.TransferRoots() shape = Shape(reader.OneShape()) BRepMesh_IncrementalMesh(shape.wrapped, LIN_DEFLECTION, False, 0.5, True).Perform() writer = StlAPI_Writer() writer.SetASCIIMode(False) # 二进制 STL (比 ASCII 小 ~3 倍) if not writer.Write(shape.wrapped, str(stl_path)): raise RuntimeError('STL 写出失败: %s' % stl_path) def main(): if not os.path.isdir(MESH_DIR): os.makedirs(MESH_DIR) step_files = [f for f in os.listdir(STEP_DIR) if f.lower().endswith(('.step', '.stp'))] done = skip = 0 errors = [] for name in sorted(step_files): step_path = os.path.join(STEP_DIR, name) if os.path.getsize(step_path) < REAL_MIN_BYTES: continue # 占位几何, 不转 stl_path = os.path.join(MESH_DIR, os.path.splitext(name)[0] + '.stl') # 跳过条件: STL 存在、比 STEP 新、且不是占位网格 (<50KB 视为占位, 强制重转) if (os.path.exists(stl_path) and os.path.getsize(stl_path) > 50 * 1024 and os.path.getmtime(stl_path) >= os.path.getmtime(step_path)): skip += 1 continue try: convert(step_path, stl_path) done += 1 print(' OK %-24s -> %s (%.1f KB)' % (name, os.path.basename(stl_path), os.path.getsize(stl_path) / 1024.0)) except Exception as e: errors.append('%s: %s' % (name, e)) print('FAIL %s: %s' % (name, e)) print('完成: 转换 %d, 跳过 %d, 失败 %d → %s' % (done, skip, len(errors), MESH_DIR)) if errors: sys.exit(1) if __name__ == '__main__': main()