289 lines
11 KiB
Python
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
|
|
} |