Files
geMoldInsight/scripts/verify_stp.py
T
2026-03-07 03:04:15 +08:00

440 lines
16 KiB
Python

#!/usr/bin/env python3
"""
STP文件几何验证脚本
使用FreeCAD命令行验证PythonOCC提取的几何数据是否正确
使用方法:
freecad -c verify_stp.py <stp_file_path>
或直接运行:
python verify_stp.py <stp_file_path>
"""
import sys
import os
import json
from datetime import datetime
from pathlib import Path
# 添加项目路径
PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT / "src"))
# 全局结果字典
verification_result = {
"file": "",
"timestamp": "",
"freecad": {},
"pythonocc": {},
"comparison": {},
"status": "pending"
}
def verify_with_freecad(stp_path):
"""使用FreeCAD验证STP文件"""
try:
import FreeCAD
import Part
import Mesh
import MeshPart
print(f"\n{'='*70}")
print(f"FreeCAD 验证")
print(f"{'='*70}\n")
# 创建新文档
doc = FreeCAD.newDocument("Verification")
# 导入STP文件
Part.insert(stp_path, "Verification")
# 获取所有形状对象
shapes = []
for obj in doc.Objects:
if hasattr(obj, 'Shape') and not obj.Shape.isNull():
shapes.append(obj.Shape)
if not shapes:
print("❌ 未找到有效的几何形状")
return None
# 合并所有形状
compound = shapes[0]
for s in shapes[1:]:
compound = compound.fuse(s)
# 提取几何数据
bbox = compound.BoundBox
# 计算体积和表面积
volume_mm3 = compound.Volume
surface_area_mm2 = compound.Area
# 获取质心
com = compound.CenterOfMass
# 拓扑信息
topology = {
"faces": len(compound.Faces),
"edges": len(compound.Edges),
"vertices": len(compound.Vertexes),
"wires": len(compound.Wires),
"shells": len(compound.Shells),
"solids": len(compound.Solids)
}
# 生成网格用于点云验证
mesh = MeshPart.meshFromShape(compound, LinearDeflection=0.1, AngularDeflection=0.5)
result = {
"volume_mm3": float(volume_mm3),
"volume_cm3": float(volume_mm3 / 1000),
"surface_area_mm2": float(surface_area_mm2),
"surface_area_cm2": float(surface_area_mm2 / 100),
"bounding_box": {
"x_min": float(bbox.XMin),
"x_max": float(bbox.XMax),
"y_min": float(bbox.YMin),
"y_max": float(bbox.YMax),
"z_min": float(bbox.ZMin),
"z_max": float(bbox.ZMax),
"x_length": float(bbox.XLength),
"y_length": float(bbox.YLength),
"z_length": float(bbox.ZLength),
"center": [float(bbox.Center.x), float(bbox.Center.y), float(bbox.Center.z)]
},
"center_of_mass": [float(com.x), float(com.y), float(com.z)],
"topology": topology,
"mesh": {
"points": mesh.CountPoints,
"facets": mesh.CountFacets
}
}
# 打印结果
print(f"体积: {result['volume_cm3']:.4f} cm³ ({result['volume_mm3']:.2f} mm³)")
print(f"表面积: {result['surface_area_cm2']:.4f} cm² ({result['surface_area_mm2']:.2f} mm²)")
print(f"\n边界框:")
print(f" X: {result['bounding_box']['x_length']:.4f} mm ({result['bounding_box']['x_min']:.2f} ~ {result['bounding_box']['x_max']:.2f})")
print(f" Y: {result['bounding_box']['y_length']:.4f} mm ({result['bounding_box']['y_min']:.2f} ~ {result['bounding_box']['y_max']:.2f})")
print(f" Z: {result['bounding_box']['z_length']:.4f} mm ({result['bounding_box']['z_min']:.2f} ~ {result['bounding_box']['z_max']:.2f})")
print(f" 中心: ({result['bounding_box']['center'][0]:.2f}, {result['bounding_box']['center'][1]:.2f}, {result['bounding_box']['center'][2]:.2f})")
print(f"\n质心: ({result['center_of_mass'][0]:.4f}, {result['center_of_mass'][1]:.4f}, {result['center_of_mass'][2]:.4f})")
print(f"\n拓扑信息:")
print(f" 面: {topology['faces']}")
print(f" 边: {topology['edges']}")
print(f" 顶点: {topology['vertices']}")
print(f" 线框: {topology['wires']}")
print(f" 壳体: {topology['shells']}")
print(f" 实体: {topology['solids']}")
print(f"\n网格:")
print(f" 点数: {result['mesh']['points']}")
print(f" 面数: {result['mesh']['facets']}")
# 关闭文档
FreeCAD.closeDocument("Verification")
return result
except Exception as e:
print(f"❌ FreeCAD验证失败: {e}")
import traceback
traceback.print_exc()
return None
def verify_with_pythonocc(stp_path):
"""使用PythonOCC验证STP文件"""
try:
from OCC.Core.STEPControl import STEPControl_Reader
from OCC.Core.IFSelect import IFSelect_RetDone
from OCC.Core.BRepTools import breptools_Read
from OCC.Core.TopExp import TopExp_Explorer
from OCC.Core.TopAbs import TopAbs_SOLID, TopAbs_FACE, TopAbs_EDGE, TopAbs_VERTEX
from OCC.Core.BRepGProp import brepgprop_VolumeProperties, brepgprop_SurfaceProperties
from OCC.Core.GProp import GProp_GProps
from OCC.Core.Bnd import Bnd_Box
from OCC.Core.BRepBndLib import brepbndlib_Add
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
from OCC.Core.TopLoc import TopLoc_Location
from OCC.Core.BRep import BRep_Tool
print(f"\n{'='*70}")
print(f"PythonOCC 验证")
print(f"{'='*70}\n")
# 读取STP文件
reader = STEPControl_Reader()
status = reader.ReadFile(stp_path)
if status != IFSelect_RetDone:
print("❌ 无法读取STP文件")
return None
reader.TransferRoots()
shape = reader.OneShape()
# 生成网格
mesh_gen = BRepMesh_IncrementalMesh(shape, 0.1)
mesh_gen.Perform()
# 计算体积
vol_props = GProp_GProps()
brepgprop_VolumeProperties(shape, vol_props)
volume_mm3 = vol_props.Mass()
com = vol_props.CentreOfMass()
# 计算表面积
surf_props = GProp_GProps()
brepgprop_SurfaceProperties(shape, surf_props)
surface_area_mm2 = surf_props.Mass()
# 计算边界框
bbox = Bnd_Box()
brepbndlib_Add(shape, bbox)
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
# 拓扑统计
def count_topology(shape, top_type):
explorer = TopExp_Explorer(shape, top_type)
count = 0
while explorer.More():
count += 1
explorer.Next()
return count
topology = {
"faces": count_topology(shape, TopAbs_FACE),
"edges": count_topology(shape, TopAbs_EDGE),
"vertices": count_topology(shape, TopAbs_VERTEX),
"solids": count_topology(shape, TopAbs_SOLID)
}
# 统计网格顶点和三角形
mesh_points = 0
mesh_facets = 0
explorer = TopExp_Explorer(shape, TopAbs_FACE)
while explorer.More():
face = explorer.Current()
location = TopLoc_Location()
triangulation = BRep_Tool.Triangulation(face, location)
if triangulation:
mesh_points += triangulation.NbNodes()
mesh_facets += triangulation.NbTriangles()
explorer.Next()
result = {
"volume_mm3": float(volume_mm3),
"volume_cm3": float(volume_mm3 / 1000),
"surface_area_mm2": float(surface_area_mm2),
"surface_area_cm2": float(surface_area_mm2 / 100),
"bounding_box": {
"x_min": float(xmin),
"x_max": float(xmax),
"y_min": float(ymin),
"y_max": float(ymax),
"z_min": float(zmin),
"z_max": float(zmax),
"x_length": float(xmax - xmin),
"y_length": float(ymax - ymin),
"z_length": float(zmax - zmin),
"center": [float((xmin + xmax) / 2), float((ymin + ymax) / 2), float((zmin + zmax) / 2)]
},
"center_of_mass": [float(com.X()), float(com.Y()), float(com.Z())],
"topology": topology,
"mesh": {
"points": mesh_points,
"facets": mesh_facets
}
}
# 打印结果
print(f"体积: {result['volume_cm3']:.4f} cm³ ({result['volume_mm3']:.2f} mm³)")
print(f"表面积: {result['surface_area_cm2']:.4f} cm² ({result['surface_area_mm2']:.2f} mm²)")
print(f"\n边界框:")
print(f" X: {result['bounding_box']['x_length']:.4f} mm ({result['bounding_box']['x_min']:.2f} ~ {result['bounding_box']['x_max']:.2f})")
print(f" Y: {result['bounding_box']['y_length']:.4f} mm ({result['bounding_box']['y_min']:.2f} ~ {result['bounding_box']['y_max']:.2f})")
print(f" Z: {result['bounding_box']['z_length']:.4f} mm ({result['bounding_box']['z_min']:.2f} ~ {result['bounding_box']['z_max']:.2f})")
print(f" 中心: ({result['bounding_box']['center'][0]:.2f}, {result['bounding_box']['center'][1]:.2f}, {result['bounding_box']['center'][2]:.2f})")
print(f"\n质心: ({result['center_of_mass'][0]:.4f}, {result['center_of_mass'][1]:.4f}, {result['center_of_mass'][2]:.4f})")
print(f"\n拓扑信息:")
print(f" 面: {topology['faces']}")
print(f" 边: {topology['edges']}")
print(f" 顶点: {topology['vertices']}")
print(f" 实体: {topology['solids']}")
print(f"\n网格:")
print(f" 点数: {result['mesh']['points']}")
print(f" 面数: {result['mesh']['facets']}")
return result
except Exception as e:
print(f"❌ PythonOCC验证失败: {e}")
import traceback
traceback.print_exc()
return None
def compare_results(freecad_result, pythonocc_result):
"""对比FreeCAD和PythonOCC的结果"""
print(f"\n{'='*70}")
print(f"对比结果")
print(f"{'='*70}\n")
if not freecad_result or not pythonocc_result:
print("❌ 无法对比,缺少数据")
return None
comparison = {}
# 体积对比
vol_diff = abs(freecad_result['volume_mm3'] - pythonocc_result['volume_mm3'])
vol_diff_percent = (vol_diff / freecad_result['volume_mm3']) * 100 if freecad_result['volume_mm3'] > 0 else 0
comparison['volume'] = {
"freecad": freecad_result['volume_cm3'],
"pythonocc": pythonocc_result['volume_cm3'],
"difference_mm3": vol_diff,
"difference_percent": vol_diff_percent
}
print(f"体积对比:")
print(f" FreeCAD: {freecad_result['volume_cm3']:.4f} cm³")
print(f" PythonOCC: {pythonocc_result['volume_cm3']:.4f} cm³")
print(f" 差异: {vol_diff:.4f} mm³ ({vol_diff_percent:.4f}%)")
print(f" 状态: {'✅ 通过' if vol_diff_percent < 1 else '❌ 超差'}")
# 表面积对比
area_diff = abs(freecad_result['surface_area_mm2'] - pythonocc_result['surface_area_mm2'])
area_diff_percent = (area_diff / freecad_result['surface_area_mm2']) * 100 if freecad_result['surface_area_mm2'] > 0 else 0
comparison['surface_area'] = {
"freecad": freecad_result['surface_area_cm2'],
"pythonocc": pythonocc_result['surface_area_cm2'],
"difference_mm2": area_diff,
"difference_percent": area_diff_percent
}
print(f"\n表面积对比:")
print(f" FreeCAD: {freecad_result['surface_area_cm2']:.4f} cm²")
print(f" PythonOCC: {pythonocc_result['surface_area_cm2']:.4f} cm²")
print(f" 差异: {area_diff:.4f} mm² ({area_diff_percent:.4f}%)")
print(f" 状态: {'✅ 通过' if area_diff_percent < 2 else '❌ 超差'}")
# 边界框对比
bbox_fc = freecad_result['bounding_box']
bbox_occ = pythonocc_result['bounding_box']
x_diff = abs(bbox_fc['x_length'] - bbox_occ['x_length'])
y_diff = abs(bbox_fc['y_length'] - bbox_occ['y_length'])
z_diff = abs(bbox_fc['z_length'] - bbox_occ['z_length'])
comparison['bounding_box'] = {
"x": {"freecad": bbox_fc['x_length'], "pythonocc": bbox_occ['x_length'], "difference": x_diff},
"y": {"freecad": bbox_fc['y_length'], "pythonocc": bbox_occ['y_length'], "difference": y_diff},
"z": {"freecad": bbox_fc['z_length'], "pythonocc": bbox_occ['z_length'], "difference": z_diff}
}
print(f"\n边界框对比:")
print(f" X轴: FreeCAD={bbox_fc['x_length']:.4f}, PythonOCC={bbox_occ['x_length']:.4f}, 差异={x_diff:.4f} mm")
print(f" Y轴: FreeCAD={bbox_fc['y_length']:.4f}, PythonOCC={bbox_occ['y_length']:.4f}, 差异={y_diff:.4f} mm")
print(f" Z轴: FreeCAD={bbox_fc['z_length']:.4f}, PythonOCC={bbox_occ['z_length']:.4f}, 差异={z_diff:.4f} mm")
# 质心对比
com_fc = freecad_result['center_of_mass']
com_occ = pythonocc_result['center_of_mass']
com_diff = [
abs(com_fc[0] - com_occ[0]),
abs(com_fc[1] - com_occ[1]),
abs(com_fc[2] - com_occ[2])
]
comparison['center_of_mass'] = {
"freecad": com_fc,
"pythonocc": com_occ,
"difference": com_diff
}
print(f"\n质心对比:")
print(f" FreeCAD: ({com_fc[0]:.4f}, {com_fc[1]:.4f}, {com_fc[2]:.4f})")
print(f" PythonOCC: ({com_occ[0]:.4f}, {com_occ[1]:.4f}, {com_occ[2]:.4f})")
print(f" 差异: ({com_diff[0]:.4f}, {com_diff[1]:.4f}, {com_diff[2]:.4f}) mm")
# 拓扑对比
topo_fc = freecad_result['topology']
topo_occ = pythonocc_result['topology']
comparison['topology'] = {
"faces": {"freecad": topo_fc['faces'], "pythonocc": topo_occ['faces']},
"edges": {"freecad": topo_fc['edges'], "pythonocc": topo_occ['edges']},
"vertices": {"freecad": topo_fc['vertices'], "pythonocc": topo_occ['vertices']}
}
print(f"\n拓扑对比:")
print(f" 面: FreeCAD={topo_fc['faces']}, PythonOCC={topo_occ['faces']}")
print(f" 边: FreeCAD={topo_fc['edges']}, PythonOCC={topo_occ['edges']}")
print(f" 顶点: FreeCAD={topo_fc['vertices']}, PythonOCC={topo_occ['vertices']}")
# 总体评估
passed = vol_diff_percent < 1 and area_diff_percent < 2
print(f"\n{'='*70}")
print(f"验证结果: {'✅ 通过' if passed else '❌ 失败'}")
print(f"{'='*70}\n")
comparison['overall_status'] = "passed" if passed else "failed"
return comparison
def main():
"""主函数"""
if len(sys.argv) < 2:
print("用法: python verify_stp.py <stp_file_path>")
print(" 或: freecad -c verify_stp.py <stp_file_path>")
sys.exit(1)
stp_path = sys.argv[1]
if not os.path.exists(stp_path):
print(f"❌ 文件不存在: {stp_path}")
sys.exit(1)
# 初始化结果
verification_result["file"] = stp_path
verification_result["timestamp"] = datetime.now().isoformat()
print(f"\n{'#'*70}")
print(f"# STP文件几何验证报告")
print(f"# 文件: {stp_path}")
print(f"# 时间: {verification_result['timestamp']}")
print(f"{'#'*70}")
# FreeCAD验证
freecad_result = verify_with_freecad(stp_path)
verification_result["freecad"] = freecad_result or {}
# PythonOCC验证
pythonocc_result = verify_with_pythonocc(stp_path)
verification_result["pythonocc"] = pythonocc_result or {}
# 对比结果
comparison = compare_results(freecad_result, pythonocc_result)
verification_result["comparison"] = comparison or {}
verification_result["status"] = comparison.get('overall_status', 'failed') if comparison else 'failed'
# 保存JSON报告
report_path = Path(stp_path).stem + "_verification_report.json"
with open(report_path, 'w', encoding='utf-8') as f:
json.dump(verification_result, f, indent=2, ensure_ascii=False)
print(f"\n验证报告已保存: {report_path}")
return verification_result
if __name__ == "__main__":
result = main()
sys.exit(0 if result['status'] == 'passed' else 1)