CadHubManage/ifc2stp.py

375 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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])