feat: Add IFC to STP conversion functionality and update configuration paths
This commit is contained in:
parent
16242e63d8
commit
37ed4aa446
@ -37,6 +37,7 @@ class WSMessageType:
|
||||
SET_BASE_PATH = "set_base_path"
|
||||
SET_FILE_EXTENSIONS = "set_file_extensions"
|
||||
GET_FILE_CONFIG = "get_file_config"
|
||||
CONVERT_IFC_TO_STP = "convert_ifc_to_stp"
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@ -881,10 +882,47 @@ async def handle_client_message(message: dict, client_id: str, user_id: str):
|
||||
"timestamp": websocket_manager._get_timestamp()
|
||||
}, client_id)
|
||||
|
||||
elif message_type == WSMessageType.CONVERT_IFC_TO_STP:
|
||||
ifc_path = message.get("ifc_path")
|
||||
stp_path = message.get("stp_path")
|
||||
|
||||
if not ifc_path or not stp_path:
|
||||
await websocket_manager.send_personal_message({
|
||||
"type": MessageType.ERROR,
|
||||
"message": "缂哄皯鍙傛暟: ifc_path 鎴? stp_path",
|
||||
"data": {
|
||||
"ifc_path": ifc_path,
|
||||
"stp_path": stp_path
|
||||
},
|
||||
"timestamp": websocket_manager._get_timestamp()
|
||||
}, client_id)
|
||||
return
|
||||
|
||||
try:
|
||||
from app.core.ifc2stp_converter import Ifc2StpConverter
|
||||
|
||||
result = Ifc2StpConverter().convert(ifc_path=ifc_path, stp_path=stp_path)
|
||||
await websocket_manager.send_personal_message({
|
||||
"type": MessageType.INFO,
|
||||
"message": "IFC杞琒TP鎴愬姛",
|
||||
"data": result,
|
||||
"timestamp": websocket_manager._get_timestamp()
|
||||
}, client_id)
|
||||
except Exception as e:
|
||||
await websocket_manager.send_personal_message({
|
||||
"type": MessageType.ERROR,
|
||||
"message": f"IFC杞琒TP澶辫触: {str(e)}",
|
||||
"data": {
|
||||
"ifc_path": ifc_path,
|
||||
"stp_path": stp_path
|
||||
},
|
||||
"timestamp": websocket_manager._get_timestamp()
|
||||
}, client_id)
|
||||
|
||||
else:
|
||||
# 未知消息类型
|
||||
await websocket_manager.send_personal_message({
|
||||
"type": MessageType.ERROR,
|
||||
"message": f"未知的消息类型: {message_type}",
|
||||
"timestamp": websocket_manager._get_timestamp()
|
||||
}, client_id)
|
||||
}, client_id)
|
||||
|
||||
249
app/core/ifc2stp_converter.py
Normal file
249
app/core/ifc2stp_converter.py
Normal file
@ -0,0 +1,249 @@
|
||||
"""
|
||||
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,
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
file_storage:
|
||||
cad_files_path: D:\App\vue\Github\serena
|
||||
cad_files_path: C:\Users\sladr\Documents\陀螺泵PROE设计\
|
||||
file_extensions:
|
||||
MD:
|
||||
- .md
|
||||
Creo:
|
||||
- .prt
|
||||
- .asm
|
||||
software:
|
||||
creo:
|
||||
check_process_name:
|
||||
|
||||
375
ifc2stp.py
Normal file
375
ifc2stp.py
Normal file
@ -0,0 +1,375 @@
|
||||
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])
|
||||
Loading…
Reference in New Issue
Block a user