Files
geMoldInsight/src/moldinsight/core/stp_parser.py
T
2026-05-29 18:10:08 +08:00

289 lines
11 KiB
Python

# core/stp_parser.py
from pathlib import Path
from typing import Dict, Any, Optional, List
import numpy as np
import json
from shared.utils.logger import get_logger
from OCC.Core.GProp import GProp_GProps
from OCC.Core.BRepGProp import brepgprop
from OCC.Core.TopoDS import TopoDS_Shape
logger = get_logger(__name__)
class STPParser:
"""STP文件解析器"""
def __init__(self):
# 强制要求PythonOCC必须可用
self._verify_occ_availability()
def _verify_occ_availability(self):
"""验证PythonOCC是否可用,不可用则抛出异常"""
try:
from OCC.Core.STEPControl import STEPControl_Reader
from OCC.Core.IFSelect import IFSelect_RetDone
logger.info("PythonOCC验证通过")
except ImportError as e:
logger.error("PythonOCC不可用,服务无法运行")
raise RuntimeError("PythonOCC未安装,请安装PythonOCC后再运行服务") from e
def load_step_file(self, file_path: Path) -> TopoDS_Shape:
"""加载STP文件"""
try:
from OCC.Core.STEPControl import STEPControl_Reader
from OCC.Core.IFSelect import IFSelect_RetDone
logger.info(f"加载STP文件: {file_path}")
reader = STEPControl_Reader()
status = reader.ReadFile(str(file_path))
if status == IFSelect_RetDone:
reader.TransferRoots()
shape = reader.OneShape()
logger.info("STP文件加载成功")
return shape
else:
raise ValueError(f"STP文件读取失败,状态码: {status}")
except Exception as e:
logger.error(f"STP解析失败: {e}")
raise
def analyze_geometry(self, shape: TopoDS_Shape) -> Dict[str, Any]:
"""分析几何属性"""
try:
from OCC.Core.GProp import GProp_GProps
from OCC.Core.BRepGProp import brepgprop
from OCC.Core.Bnd import Bnd_Box
from OCC.Core.BRepBndLib import brepbndlib
from OCC.Core.TopExp import TopExp_Explorer
from OCC.Core.TopAbs import TopAbs_FACE, TopAbs_EDGE, TopAbs_VERTEX
logger.info("开始几何分析...")
# 计算边界框
bbox = self._compute_bounding_box(shape)
# 计算体积和表面积
volume = self._compute_volume(shape)
area = self._compute_surface_area(shape)
# 分析拓扑
topology = self._analyze_topology(shape)
# 计算质心
center_of_mass = self._compute_center_of_mass(shape)
# 计算惯性属性
inertia_properties = self._compute_inertia_properties(shape)
result = {
"bounding_box": bbox,
"volume": float(volume),
"surface_area": float(area),
"topology": topology,
"center_of_mass": center_of_mass,
"inertia_properties": inertia_properties,
"analysis_method": "pythonocc"
}
logger.info("几何分析完成")
return result
except Exception as e:
logger.error(f"几何分析失败: {e}")
raise
def _compute_bounding_box(self, shape: TopoDS_Shape) -> Dict[str, Any]:
"""计算边界框"""
try:
from OCC.Core.Bnd import Bnd_Box
from OCC.Core.BRepBndLib import brepbndlib
bbox = Bnd_Box()
brepbndlib.Add(shape, bbox)
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
return {
"min": [float(xmin), float(ymin), float(zmin)],
"max": [float(xmax), float(ymax), float(zmax)],
"dimensions": [
float(xmax - xmin),
float(ymax - ymin),
float(zmax - zmin)
],
"center": [
float((xmin + xmax) / 2),
float((ymin + ymax) / 2),
float((zmin + zmax) / 2)
]
}
except Exception as e:
logger.error(f"边界框计算失败: {e}")
return self._default_bounding_box()
def _compute_volume(self, shape: TopoDS_Shape) -> float:
"""计算体积"""
try:
from OCC.Core.GProp import GProp_GProps
from OCC.Core.BRepGProp import brepgprop
props = GProp_GProps()
brepgprop.VolumeProperties(shape, props)
volume = props.Mass()
if volume <= 0:
raise ValueError("计算得到的体积为0或负数,形状可能无效")
return volume
except Exception as e:
logger.error(f"体积计算失败: {e}")
raise RuntimeError(f"体积计算失败: {e}") from e
def _compute_surface_area(self, shape: TopoDS_Shape) -> float:
"""计算表面积"""
try:
from OCC.Core.GProp import GProp_GProps
from OCC.Core.BRepGProp import brepgprop
props = GProp_GProps()
brepgprop.SurfaceProperties(shape, props)
area = props.Mass()
# 如果计算结果为0,使用备选估算方法
if area <= 0:
logger.warning("表面积计算结果为0,使用边界框估算")
raise ValueError("Surface area is zero")
return area
except ValueError:
# 基于边界框估算表面积
try:
bbox = self._compute_bounding_box(shape)
dims = bbox.get("dimensions", [0, 0, 0])
if any(d <= 0 for d in dims):
raise RuntimeError("边界框尺寸无效,无法估算表面积")
# 简化的估算公式:2*(lw + lh + wh)
estimated_area = 2 * (dims[0]*dims[1] + dims[0]*dims[2] + dims[1]*dims[2])
logger.warning(f"使用边界框估算表面积: {estimated_area:.2f} mm²")
return estimated_area
except Exception as e:
logger.error(f"表面积估算失败: {e}")
raise RuntimeError(f"表面积计算失败: {e}") from e
except Exception as e:
logger.error(f"表面积计算失败: {e}")
raise RuntimeError(f"表面积计算失败: {e}") from e
def _compute_center_of_mass(self, shape: TopoDS_Shape) -> List[float]:
"""计算质心"""
try:
from OCC.Core.GProp import GProp_GProps
from OCC.Core.BRepGProp import brepgprop
props = GProp_GProps()
brepgprop.VolumeProperties(shape, props)
center = props.CentreOfMass()
return [float(center.X()), float(center.Y()), float(center.Z())]
except Exception as e:
logger.error(f"质心计算失败: {e}")
# 回退到边界框中心
try:
bbox = self._compute_bounding_box(shape)
return bbox.get("center", [0.0, 0.0, 0.0])
except Exception:
raise RuntimeError(f"质心计算失败且边界框回退也失败: {e}") from e
def _compute_inertia_properties(self, shape: TopoDS_Shape) -> Dict[str, Any]:
"""计算惯性属性"""
try:
from OCC.Core.GProp import GProp_GProps
from OCC.Core.BRepGProp import brepgprop
props = GProp_GProps()
brepgprop.VolumeProperties(shape, props)
inertia = props.MatrixOfInertia()
return {
"mass": float(props.Mass()),
"moment_of_inertia": [
[float(inertia.Value(1, 1)), float(inertia.Value(1, 2)), float(inertia.Value(1, 3))],
[float(inertia.Value(2, 1)), float(inertia.Value(2, 2)), float(inertia.Value(2, 3))],
[float(inertia.Value(3, 1)), float(inertia.Value(3, 2)), float(inertia.Value(3, 3))]
]
}
except Exception as e:
logger.error(f"惯性属性计算失败: {e}")
return {}
def _analyze_topology(self, shape: TopoDS_Shape) -> Dict[str, int]:
"""分析拓扑"""
try:
from OCC.Core.TopExp import TopExp_Explorer
from OCC.Core.TopAbs import TopAbs_FACE, TopAbs_EDGE, TopAbs_VERTEX
def count_elements(element_type):
explorer = TopExp_Explorer(shape, element_type)
count = 0
while explorer.More():
count += 1
explorer.Next()
return count
return {
"faces": count_elements(TopAbs_FACE),
"edges": count_elements(TopAbs_EDGE),
"vertices": count_elements(TopAbs_VERTEX)
}
except Exception as e:
logger.error(f"拓扑分析失败: {e}")
raise
def _default_bounding_box(self) -> Dict[str, Any]:
"""默认边界框(边界框计算失败时的回退值,标注为估算)"""
return {
"min": [0.0, 0.0, 0.0],
"max": [0.0, 0.0, 0.0],
"dimensions": [0.0, 0.0, 0.0],
"center": [0.0, 0.0, 0.0],
"estimated": True
}
def export_to_json(self, geometry_data: Dict[str, Any], output_path: Path) -> str:
"""将几何数据导出为JSON文件"""
try:
# 确保输出目录存在
output_path.parent.mkdir(parents=True, exist_ok=True)
# 添加元数据
json_data = {
"metadata": {
"export_time": str(np.datetime64('now')),
"analysis_method": geometry_data.get("analysis_method", "unknown"),
"version": "1.0.0"
},
"geometry_data": geometry_data
}
# 保存JSON文件
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(json_data, f, indent=2, ensure_ascii=False)
logger.info(f"几何数据已导出到: {output_path}")
return str(output_path)
except Exception as e:
logger.error(f"JSON导出失败: {e}")
raise
def get_json_data(self, geometry_data: Dict[str, Any]) -> Dict[str, Any]:
"""获取JSON格式的几何数据"""
return {
"metadata": {
"export_time": str(np.datetime64('now')),
"analysis_method": geometry_data.get("analysis_method", "unknown"),
"version": "1.0.0"
},
"geometry_data": geometry_data
}