CadHubManage/app/core/ifc2stp_converter.py

250 lines
8.3 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.

"""
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,
}