This commit is contained in:
sladro 2025-12-09 17:26:19 +08:00
commit 4527190345
5 changed files with 2642623 additions and 0 deletions

68
.serena/project.yml Normal file
View File

@ -0,0 +1,68 @@
# language of the project (csharp, python, rust, java, typescript, go, cpp, or ruby)
# * For C, use cpp
# * For JavaScript, use typescript
# Special requirements:
# * csharp: Requires the presence of a .sln file in the project folder.
language: python
# whether to use the project's gitignore file to ignore files
# Added on 2025-04-07
ignore_all_files_in_gitignore: true
# list of additional paths to ignore
# same syntax as gitignore, so you can use * and **
# Was previously called `ignored_dirs`, please update your config if you are using that.
# Added (renamed)on 2025-04-07
ignored_paths: []
# whether the project is in read-only mode
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
# Added on 2025-04-18
read_only: false
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
# Below is the complete list of tools for convenience.
# To make sure you have the latest list of tools, and to view their descriptions,
# execute `uv run scripts/print_tool_overview.py`.
#
# * `activate_project`: Activates a project by name.
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
# * `create_text_file`: Creates/overwrites a file in the project directory.
# * `delete_lines`: Deletes a range of lines within a file.
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
# * `execute_shell_command`: Executes a shell command.
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
# * `initial_instructions`: Gets the initial instructions for the current project.
# Should only be used in settings where the system prompt cannot be set,
# e.g. in clients you have no control over, like Claude Desktop.
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
# * `insert_at_line`: Inserts content at a given line in a file.
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
# * `list_memories`: Lists memories in Serena's project-specific memory store.
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
# * `read_file`: Reads a file within the project directory.
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
# * `remove_project`: Removes a project from the Serena configuration.
# * `replace_lines`: Replaces a range of lines within a file with new content.
# * `replace_symbol_body`: Replaces the full definition of a symbol.
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
# * `search_for_pattern`: Performs a search for a pattern in the project.
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
# * `switch_modes`: Activates modes by providing a list of their names
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
excluded_tools: []
# initial prompt for the project. It will always be given to the LLM upon activating the project
# (contrary to the memories, which are loaded on demand).
initial_prompt: ""
project_name: "IfcToStp"

375
main.py Normal file
View 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])

1243263
t.stp Normal file

File diff suppressed because it is too large Load Diff

155654
test.ifc Normal file

File diff suppressed because one or more lines are too long

1243263
test.stp Normal file

File diff suppressed because it is too large Load Diff