375 lines
15 KiB
Python
375 lines
15 KiB
Python
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]) |