import sys import ifcopenshell import ifcopenshell.geom as geom from OCC.Core.BRep import BRep_Builder from OCC.Core.TopoDS import TopoDS_Compound from OCC.Core.STEPControl import STEPControl_Writer, STEPControl_AsIs from OCC.Core.IFSelect import IFSelect_RetDone from OCC.Core.BRepTools import breptools # ===== Helper utilities (behavior-preserving refactor) ===== def configure_settings(settings): """Configure geometry settings. Returns True if BREP is enabled.""" # World coords and BREP output enabled_brep = False try: settings.set(settings.USE_WORLD_COORDS, True) for key in ("use-python-opencascade", "USE_PYTHON_OPENCASCADE"): try: settings.set(key if isinstance(key, str) else settings.USE_PYTHON_OPENCASCADE, True) enabled_brep = True break except Exception: settings.set(settings.USE_PYTHON_OPENCASCADE, True) enabled_brep = True break except Exception: pass # Settings for Body, topology, and mesh try: settings.set("context-identifiers", ["Body"]) settings.set("disable-opening-subtractions", True) import ifcopenshell settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.SERIALIZED) settings.set("mesher-linear-deflection", 1e-2) settings.set("mesher-angular-deflection", 0.5) except Exception: pass return enabled_brep def read_serialized_brep_to_shape(brep_txt): """Read serialized BREP text into a TopoDS_Shape, or return None.""" if not brep_txt: return None import tempfile, os from OCC.Core.BRep import BRep_Builder from OCC.Core.TopoDS import TopoDS_Shape from OCC.Core.BRepTools import breptools_Read tmp_path = None try: with tempfile.NamedTemporaryFile(delete=False, suffix=".brep", mode="w", encoding="utf-8") as tf: tf.write(brep_txt) tmp_path = tf.name shp = TopoDS_Shape() ok = breptools_Read(shp, tmp_path, BRep_Builder()) return shp if ok else None except Exception: return None finally: try: if tmp_path: os.remove(tmp_path) except Exception: pass def unify_same_domain_safe(shape): """Unify same-domain faces/edges. Returns the (possibly) improved shape.""" try: from OCC.Core.ShapeUpgrade import ShapeUpgrade_UnifySameDomain usd = ShapeUpgrade_UnifySameDomain(shape, True, True, True) usd.Build() return usd.Shape() except Exception: return shape def configure_step_writer_ap242(): """Set STEP writer to AP242 and 3D curves only (reduce redundancy).""" try: from OCC.Core.Interface import Interface_Static Interface_Static.SetCVal("write.step.schema", "AP242") try: # Only 3D curves (no pcurves) to reduce ents/size Interface_Static.SetIVal("write.surfacecurve.mode", 2) except Exception: pass except Exception: pass def transfer_compound(writer, compound): """Prefer ManifoldSolidBrep transfer, fallback to AsIs.""" try: from OCC.Core.STEPControl import STEPControl_ManifoldSolidBrep writer.Transfer(compound, STEPControl_ManifoldSolidBrep) except Exception: from OCC.Core.STEPControl import STEPControl_AsIs as _AsIs writer.Transfer(compound, _AsIs) def fallback_mesh_build(settings, model, products, builder, compound): """Coarse mesh fallback when no BREP available. Returns number of elements added.""" # Coarser mesh to control size try: settings.set("mesher-linear-deflection", 5e-2) settings.set("mesher-angular-deflection", 1.0) except Exception: pass n_mesh_ok = 0 try: from OCC.Core.BRep import BRep_Builder from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakePolygon, BRepBuilderAPI_MakeFace from OCC.Core.gp import gp_Pnt for prod in products: try: sr = geom.create_shape(settings, prod) if not sr or not hasattr(sr, 'geometry'): continue g = sr.geometry if not (hasattr(g, 'verts') and hasattr(g, 'faces') and g.verts and g.faces): continue verts, faces = g.verts, g.faces points = [gp_Pnt(verts[i], verts[i+1], verts[i+2]) for i in range(0, len(verts), 3)] mesh_faces = 0 for i in range(0, len(faces), 3): try: v1, v2, v3 = faces[i], faces[i+1], faces[i+2] if v1 < len(points) and v2 < len(points) and v3 < len(points): poly = BRepBuilderAPI_MakePolygon() poly.Add(points[v1]); poly.Add(points[v2]); poly.Add(points[v3]); poly.Close() if poly.IsDone(): fm = BRepBuilderAPI_MakeFace(poly.Wire()) if fm.IsDone(): builder.Add(compound, fm.Face()); mesh_faces += 1 except Exception: continue if mesh_faces > 0: n_mesh_ok += 1 except Exception: continue except Exception: # If OCC mesh builders are unavailable, do nothing pass return n_mesh_ok def ifc_to_step(ifc_path, stp_path): # 1) 读 IFC model = ifcopenshell.open(ifc_path) print(f"已加载 IFC 文件: {ifc_path}") # 2) 几何设置:优先导出 BREP,避免在 STEP 中出现大量三角片(CREO 打开更快) settings = geom.settings() settings.set(settings.USE_WORLD_COORDS, True) # IfcOpenShell 0.7.0/0.8.0 兼容处理:尝试字符串键与常量键两种方式 enabled_brep = False for key in ("use-python-opencascade", "USE_PYTHON_OPENCASCADE"): try: settings.set(key if isinstance(key, str) else settings.USE_PYTHON_OPENCASCADE, True) enabled_brep = True break except Exception: try: # 某些版本只支持常量方式 settings.set(settings.USE_PYTHON_OPENCASCADE, True) enabled_brep = True break except Exception: pass if enabled_brep: print("已启用 PythonOCC BREP 输出(TopoDS_Shape)") else: print("警告: 无法开启 BREP 输出,将回退为三角网格几何") # 尽量只处理 Body 表示,避免多余上下文(如 Axis 等) try: settings.set("context-identifiers", ["Body"]) except Exception: pass # 禁用开洞布尔,减少复杂拓扑与面数(可显著降低体积,提升稳定性) try: settings.set("disable-opening-subtractions", True) except Exception: pass # 优先使用 SERIALIZED,将 BREP 以字符串形式返回,便于稳定地读回 TopoDS_Shape try: settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.SERIALIZED) except Exception: pass # 设置网格细化参数(仅在 BREP 不可用、回退为网格时生效);值越大三角越粗,文件越小 # 注意:IfcOpenShell 0.7.0 的 set 接口可能仅接受枚举键,不接受字符串键,因此做兼容处理 try: settings.set("mesher-linear-deflection", 1e-2) # 1cm 线性偏差 settings.set("mesher-angular-deflection", 0.5) # 0.5 弧度角偏差 except Exception: # 老版本无法设置字符串键时忽略,不影响 BREP 导出 pass # 3) 过滤产品:只选择主要的建筑构件类型 included_types = ["IfcWall", "IfcWallStandardCase", "IfcSlab", "IfcBeam", "IfcColumn", "IfcDoor", "IfcWindow", "IfcStair", "IfcRoof", "IfcFoundation"] products = [e for e in model.by_type("IfcProduct") if hasattr(e, "Representation") and e.Representation and e.is_a() in included_types] # 处理所有产品(去除数量限制) print(f"找到 {len(products)} 个主要建筑构件(墙、板、梁、柱等),将全部处理") # 4) 使用几何迭代器处理所有几何体 from OCC.Core.TopoDS import TopoDS_Compound builder = BRep_Builder() compound = TopoDS_Compound() builder.MakeCompound(compound) n_ok = 0 n_failed = 0 # 逐个元素 create_shape,优先通过 SERIALIZED BREP 读回 TopoDS_Shape;不做网格回退 import tempfile, os from OCC.Core.TopoDS import TopoDS_Shape from OCC.Core.BRepTools import breptools_Read for prod in products: try: shape_result = geom.create_shape(settings, prod) if not shape_result or not hasattr(shape_result, 'geometry'): n_failed += 1 if n_failed <= 5: print(f"无法创建几何: {prod.is_a()}") continue geo = shape_result.geometry # 方案 A:优先使用序列化的 BREP 数据读回 TopoDS_Shape brep_txt = getattr(geo, 'brep_data', None) if brep_txt: try: tmp_path = None with tempfile.NamedTemporaryFile(delete=False, suffix=".brep", mode="w", encoding="utf-8") as tf: tf.write(brep_txt) tmp_path = tf.name shp = TopoDS_Shape() ok = breptools_Read(shp, tmp_path, BRep_Builder()) try: os.remove(tmp_path) except Exception: pass if ok: try: from OCC.Core.ShapeUpgrade import ShapeUpgrade_UnifySameDomain usd = ShapeUpgrade_UnifySameDomain(shp, True, True, True) usd.Build() shp = usd.Shape() except Exception: pass builder.Add(compound, shp) n_ok += 1 if n_ok <= 5: print(f"成功处理(BREP-serialized): {prod.is_a()} - {getattr(prod, 'Name', 'N/A')}") continue except Exception: pass # 方案 B:原生 TopoDS_Shape(若 use-python-opencascade 有效) if hasattr(geo, 'Location') or hasattr(geo, 'ShapeType'): # 合并同域面/边并基础修复 try: from OCC.Core.ShapeUpgrade import ShapeUpgrade_UnifySameDomain usd = ShapeUpgrade_UnifySameDomain(geo, True, True, True) usd.Build() geo = usd.Shape() except Exception: pass builder.Add(compound, geo) n_ok += 1 if n_ok <= 5: print(f"成功处理(BREP-native): {prod.is_a()} - {getattr(prod, 'Name', 'N/A')}") else: n_failed += 1 if n_failed <= 5: print(f"跳过(非 BREP): {prod.is_a()}") except Exception as e: n_failed += 1 if n_failed <= 5: print(f"处理失败: {prod.is_a()} - {str(e)}") print(f"处理结果: 成功 {n_ok} 个,失败 {n_failed} 个") if n_ok == 0: print("未获取到任何 BREP,回退为轻量网格 STEP(自动降低三角精度以控制体积)...") # 调整网格精度为更粗,显著减小面数/体积 try: settings.set("mesher-linear-deflection", 5e-2) # 5cm settings.set("mesher-angular-deflection", 1.0) except Exception: pass n_mesh_ok = 0 from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakePolygon, BRepBuilderAPI_MakeFace from OCC.Core.gp import gp_Pnt for prod in products: try: shape_result = geom.create_shape(settings, prod) if not shape_result or not hasattr(shape_result, 'geometry'): continue geometry = shape_result.geometry if not (hasattr(geometry, 'verts') and hasattr(geometry, 'faces') and geometry.verts and geometry.faces): continue verts = geometry.verts faces = geometry.faces # 将顶点打包为 gp_Pnt 列表 points = [] for i in range(0, len(verts), 3): points.append(gp_Pnt(verts[i], verts[i+1], verts[i+2])) mesh_faces = 0 for i in range(0, len(faces), 3): try: v1_idx, v2_idx, v3_idx = faces[i], faces[i+1], faces[i+2] if v1_idx < len(points) and v2_idx < len(points) and v3_idx < len(points): polygon = BRepBuilderAPI_MakePolygon() polygon.Add(points[v1_idx]) polygon.Add(points[v2_idx]) polygon.Add(points[v3_idx]) polygon.Close() if polygon.IsDone(): face_maker = BRepBuilderAPI_MakeFace(polygon.Wire()) if face_maker.IsDone(): builder.Add(compound, face_maker.Face()) mesh_faces += 1 except Exception: continue if mesh_faces > 0: n_mesh_ok += 1 except Exception: continue print(f"网格回退结果: 生成了 {n_mesh_ok} 个构件的三角面(已降面)") if n_mesh_ok == 0: raise RuntimeError("既无法生成 BREP,也没有可用的网格几何。请检查 IFC 或 IfcOpenShell/OCC 安装。") # 5) 写 STEP 文件(AP242 + 仅 3D 曲线;优先实体) try: from OCC.Core.Interface import Interface_Static Interface_Static.SetCVal("write.step.schema", "AP242") Interface_Static.SetIVal("write.surfacecurve.mode", 2) except Exception: pass writer = STEPControl_Writer() try: from OCC.Core.STEPControl import STEPControl_ManifoldSolidBrep writer.Transfer(compound, STEPControl_ManifoldSolidBrep) except Exception: from OCC.Core.STEPControl import STEPControl_AsIs as _AsIs writer.Transfer(compound, _AsIs) status = writer.Write(stp_path) if status != IFSelect_RetDone: raise RuntimeError(f"写 STEP 失败,状态码:{status}") print(f"成功导出: {stp_path},包含 {n_ok} 个构件几何。") if __name__ == "__main__": if len(sys.argv) < 3: print("用法: python main.py input.ifc output.stp") sys.exit(1) ifc_to_step(sys.argv[1], sys.argv[2])