250 lines
8.3 KiB
Python
250 lines
8.3 KiB
Python
"""
|
||
IFC to STP converter.
|
||
Copied from root-level ifc2stp.py and wrapped in a class for app integration.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import tempfile
|
||
import time
|
||
from pathlib import Path
|
||
|
||
import ifcopenshell
|
||
import ifcopenshell.geom as geom
|
||
from OCC.Core.BRep import BRep_Builder
|
||
from OCC.Core.BRepTools import breptools
|
||
from OCC.Core.IFSelect import IFSelect_RetDone
|
||
from OCC.Core.STEPControl import STEPControl_Writer
|
||
from OCC.Core.TopoDS import TopoDS_Compound
|
||
|
||
|
||
def configure_settings(settings):
|
||
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
|
||
|
||
try:
|
||
settings.set("context-identifiers", ["Body"])
|
||
settings.set("disable-opening-subtractions", True)
|
||
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):
|
||
if not brep_txt:
|
||
return None
|
||
from OCC.Core.BRep import BRep_Builder
|
||
from OCC.Core.BRepTools import breptools_Read
|
||
from OCC.Core.TopoDS import TopoDS_Shape
|
||
|
||
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):
|
||
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():
|
||
try:
|
||
from OCC.Core.Interface import Interface_Static
|
||
|
||
Interface_Static.SetCVal("write.step.schema", "AP242")
|
||
try:
|
||
Interface_Static.SetIVal("write.surfacecurve.mode", 2)
|
||
except Exception:
|
||
pass
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
def transfer_compound(writer, compound):
|
||
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, products, builder, compound):
|
||
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.BRepBuilderAPI import BRepBuilderAPI_MakeFace, BRepBuilderAPI_MakePolygon
|
||
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:
|
||
pass
|
||
return n_mesh_ok
|
||
|
||
|
||
class Ifc2StpConverter:
|
||
def convert(self, ifc_path: str, stp_path: str) -> dict:
|
||
started = time.perf_counter()
|
||
|
||
ifc_path_obj = Path(ifc_path)
|
||
stp_path_obj = Path(stp_path)
|
||
if not ifc_path_obj.is_absolute() or not stp_path_obj.is_absolute():
|
||
raise ValueError("ifc_path 和 stp_path 必须是绝对路径")
|
||
if ifc_path_obj.suffix.lower() != ".ifc":
|
||
raise ValueError("ifc_path 必须是 .ifc 文件")
|
||
if stp_path_obj.suffix.lower() not in {".stp", ".step"}:
|
||
raise ValueError("stp_path 必须是 .stp 或 .step 文件")
|
||
if not ifc_path_obj.exists() or not ifc_path_obj.is_file():
|
||
raise FileNotFoundError(f"IFC 文件不存在: {ifc_path}")
|
||
if not stp_path_obj.parent.exists():
|
||
raise FileNotFoundError(f"输出目录不存在: {stp_path_obj.parent}")
|
||
|
||
model = ifcopenshell.open(str(ifc_path_obj))
|
||
settings = geom.settings()
|
||
configure_settings(settings)
|
||
|
||
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
|
||
]
|
||
|
||
builder = BRep_Builder()
|
||
compound = TopoDS_Compound()
|
||
builder.MakeCompound(compound)
|
||
|
||
n_ok = 0
|
||
n_failed = 0
|
||
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
|
||
continue
|
||
geo = shape_result.geometry
|
||
|
||
brep_txt = getattr(geo, "brep_data", None)
|
||
if brep_txt:
|
||
shp = read_serialized_brep_to_shape(brep_txt)
|
||
if shp:
|
||
shp = unify_same_domain_safe(shp)
|
||
builder.Add(compound, shp)
|
||
n_ok += 1
|
||
continue
|
||
|
||
if hasattr(geo, "Location") or hasattr(geo, "ShapeType"):
|
||
geo = unify_same_domain_safe(geo)
|
||
builder.Add(compound, geo)
|
||
n_ok += 1
|
||
else:
|
||
n_failed += 1
|
||
except Exception:
|
||
n_failed += 1
|
||
|
||
if n_ok == 0:
|
||
n_mesh_ok = fallback_mesh_build(settings, products, builder, compound)
|
||
if n_mesh_ok == 0:
|
||
raise RuntimeError("既无法生成 BREP,也没有可用网格几何")
|
||
|
||
configure_step_writer_ap242()
|
||
writer = STEPControl_Writer()
|
||
transfer_compound(writer, compound)
|
||
status = writer.Write(str(stp_path_obj))
|
||
if status != IFSelect_RetDone:
|
||
raise RuntimeError(f"写 STEP 失败,状态码: {status}")
|
||
|
||
duration_ms = int((time.perf_counter() - started) * 1000)
|
||
return {
|
||
"ifc_path": str(ifc_path_obj),
|
||
"stp_path": str(stp_path_obj),
|
||
"duration_ms": duration_ms,
|
||
"products_total": len(products),
|
||
"products_success": n_ok,
|
||
"products_failed": n_failed,
|
||
}
|
||
|