重构路由
This commit is contained in:
@@ -0,0 +1,437 @@
|
||||
# services/processing_service.py
|
||||
"""STP 文件处理流程编排器 — 协调解析、网格生成、型腔生成、保存、验证"""
|
||||
|
||||
import asyncio
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from core.stp_parser import STPParser
|
||||
from core.geometry_analyzer import GeometryAnalyzer
|
||||
from core.mold_generator import MoldCavityGenerator
|
||||
from core.aluminum_foam_mold import AluminumFoamMoldGenerator
|
||||
from core.mold_quality_inspector import AluminumFoamMoldQualityInspector
|
||||
from core.mesh_generator import MeshGenerator
|
||||
from services.storage_integration_rustfs import StorageIntegrationService
|
||||
from services.redis_task_manager import redis_task_manager
|
||||
from services.material_service import MaterialService
|
||||
from services.calculation_service import CalculationService
|
||||
from models.schemas import ProcessingStatus
|
||||
from database.database import db_manager
|
||||
from utils.html_generator import HTMLGenerator
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class ProcessingService:
|
||||
"""核心处理流程编排 — 协调 STP 解析、网格、型腔、计算、保存、验证"""
|
||||
|
||||
def __init__(self):
|
||||
self.stp_parser = STPParser()
|
||||
self.geometry_analyzer = GeometryAnalyzer()
|
||||
self.mold_generator = MoldCavityGenerator(shrinkage_rate=0.005)
|
||||
self.aluminum_foam_generator = AluminumFoamMoldGenerator(shrinkage_rate=0.015, draft_angle=3.0)
|
||||
self.mold_quality_inspector = AluminumFoamMoldQualityInspector()
|
||||
self.mesh_generator = MeshGenerator(quality="medium")
|
||||
self.html_generator = HTMLGenerator()
|
||||
self.storage_service = StorageIntegrationService()
|
||||
|
||||
# ─── 对外入口 ───
|
||||
|
||||
async def process_file_with_storage(
|
||||
self,
|
||||
task_id: str,
|
||||
file_path: str,
|
||||
stp_file_id: int,
|
||||
material: str = "ABS",
|
||||
):
|
||||
"""处理文件的后台任务 — 使用独立数据库会话"""
|
||||
|
||||
# 创建独立的数据库会话,避免请求范围会话关闭
|
||||
async with db_manager.session() as db_session:
|
||||
try:
|
||||
logger.info(f"开始处理文件并生成模具型腔: {file_path}")
|
||||
|
||||
# 设置处理超时(5分钟)
|
||||
timeout_seconds = 300
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(
|
||||
self.process_file_core(
|
||||
task_id, file_path, stp_file_id, db_session, material
|
||||
),
|
||||
timeout_seconds,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"处理超时: {task_id}")
|
||||
raise Exception(f"处理超时,超过{timeout_seconds}秒未完成")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"模具型腔生成失败: {e}")
|
||||
|
||||
await self.storage_service.update_stp_file_status(db_session, stp_file_id, "failed")
|
||||
await self.storage_service.update_task_status(
|
||||
db_session, task_id, "failed", error_message=str(e)
|
||||
)
|
||||
|
||||
# 安全更新 Redis 任务状态
|
||||
task = await redis_task_manager.get_task(task_id)
|
||||
if task:
|
||||
await redis_task_manager.update_task(task_id, {
|
||||
"status": ProcessingStatus.FAILED,
|
||||
"error": str(e),
|
||||
"completed_at": str(datetime.now()),
|
||||
})
|
||||
|
||||
async def process_file_core(
|
||||
self,
|
||||
task_id: str,
|
||||
file_path: str,
|
||||
stp_file_id: int,
|
||||
db_session: AsyncSession,
|
||||
material: str = "ABS",
|
||||
):
|
||||
"""核心处理逻辑"""
|
||||
|
||||
try:
|
||||
logger.info(f"开始处理文件并生成模具型腔: {file_path}")
|
||||
|
||||
# 1. 解析STP文件
|
||||
await self.storage_service.update_task_status(
|
||||
db_session, task_id, "processing", 20, "解析STP文件"
|
||||
)
|
||||
|
||||
shape = self.stp_parser.load_step_file(Path(file_path))
|
||||
geometry_data = self.stp_parser.analyze_geometry(shape)
|
||||
|
||||
# 2. 生成网格数据并持久化
|
||||
await self.storage_service.update_task_status(
|
||||
db_session, task_id, "processing", 30, "生成网格数据"
|
||||
)
|
||||
|
||||
mesh_result = await self._step_generate_mesh(
|
||||
shape, geometry_data, file_path, db_session, stp_file_id, task_id
|
||||
)
|
||||
|
||||
# 3. 生成模具型腔
|
||||
await self.storage_service.update_task_status(
|
||||
db_session, task_id, "processing", 40, "生成模具型腔"
|
||||
)
|
||||
|
||||
# 材料属性 — 通过 MaterialService 集中管理
|
||||
requested_material = MaterialService.resolve_material(material)
|
||||
selected_material = MaterialService.get_material(requested_material)
|
||||
is_foam_material = MaterialService.is_foam_material(requested_material)
|
||||
|
||||
cavity_mesh_data = await self._step_generate_cavity(
|
||||
shape, selected_material, is_foam_material
|
||||
)
|
||||
|
||||
# 4. 生成详细JSON数据 — 委托 CalculationService
|
||||
await self.storage_service.update_task_status(
|
||||
db_session, task_id, "processing", 60, "生成型腔详细数据"
|
||||
)
|
||||
|
||||
detailed_cavity_json = CalculationService.build_detailed_cavity_json(
|
||||
geometry_data=geometry_data,
|
||||
material=selected_material,
|
||||
file_path=str(file_path),
|
||||
cavity_mesh_data=cavity_mesh_data,
|
||||
)
|
||||
|
||||
if cavity_mesh_data and "mold_cavities" in cavity_mesh_data:
|
||||
mold_cavities = cavity_mesh_data["mold_cavities"]
|
||||
logger.info(f"型腔网格数据已合并: cavity {mold_cavities.get('cavity', {}).get('vertex_count', 0)} 顶点")
|
||||
|
||||
# 5. 生成关键信息
|
||||
cavity_key_info = detailed_cavity_json["mold_cavities"]["cavity_key_info"]
|
||||
|
||||
# 6. 保存几何数据到数据库
|
||||
await self.storage_service.update_task_status(
|
||||
db_session, task_id, "processing", 70, "保存几何数据"
|
||||
)
|
||||
|
||||
await self.storage_service.save_geometry_data(
|
||||
db_session,
|
||||
stp_file_id,
|
||||
geometry_data,
|
||||
geometry_data.get("analysis_method", "mold_cavity"),
|
||||
)
|
||||
|
||||
# 7. 保存模具型腔数据
|
||||
await self.storage_service.save_mold_cavity_data(
|
||||
db_session, stp_file_id, detailed_cavity_json
|
||||
)
|
||||
|
||||
# 8. 生成HTML可视化
|
||||
await self.storage_service.update_task_status(
|
||||
db_session, task_id, "processing", 85, "生成可视化报告"
|
||||
)
|
||||
|
||||
pointcloud_data = None
|
||||
if mesh_result:
|
||||
pointcloud_data = {
|
||||
"points": mesh_result.get("points", []),
|
||||
"normals": mesh_result.get("normals", []),
|
||||
"point_count": mesh_result.get("point_count", 0),
|
||||
}
|
||||
|
||||
html_file_path = self.html_generator.generate_and_save_visualization(
|
||||
geometry_data,
|
||||
Path(file_path).name,
|
||||
cavity_data=detailed_cavity_json,
|
||||
pointcloud_data=pointcloud_data,
|
||||
)
|
||||
|
||||
await self.storage_service.save_html_file(
|
||||
db_session,
|
||||
stp_file_id,
|
||||
Path(html_file_path).name,
|
||||
html_file_path,
|
||||
)
|
||||
|
||||
# 9. 分析模具设计
|
||||
analysis_result = self.geometry_analyzer.analyze_mold_design(geometry_data)
|
||||
|
||||
if analysis_result:
|
||||
await self.storage_service.save_features_and_recommendations(
|
||||
db_session,
|
||||
stp_file_id,
|
||||
analysis_result.get("detected_features", []),
|
||||
analysis_result.get("design_recommendations", []),
|
||||
)
|
||||
|
||||
await self._save_analysis_metrics(db_session, stp_file_id, analysis_result)
|
||||
|
||||
# 9.6 更新STP文件的分析摘要字段
|
||||
await self.storage_service.update_stp_file_analysis_summary(
|
||||
db_session,
|
||||
stp_file_id,
|
||||
volume=geometry_data.get("volume", 0),
|
||||
surface_area=geometry_data.get("surface_area", 0),
|
||||
product_weight=CalculationService.calculate_product_weight(
|
||||
geometry_data.get("volume", 0), selected_material["density"]
|
||||
),
|
||||
)
|
||||
|
||||
# 9.7 FreeCAD 几何验证
|
||||
verification_result = await self._step_verify(
|
||||
file_path, db_session, task_id, stp_file_id, analysis_result
|
||||
)
|
||||
|
||||
# 10. 完成处理
|
||||
await self.storage_service.update_stp_file_status(db_session, stp_file_id, "completed")
|
||||
await self.storage_service.update_task_status(
|
||||
db_session, task_id, "completed", 100, "模具型腔生成完成"
|
||||
)
|
||||
|
||||
# 更新任务缓存状态
|
||||
await redis_task_manager.update_task(task_id, {
|
||||
"geometry_data": geometry_data,
|
||||
"analysis_result": analysis_result,
|
||||
"cavity_data": detailed_cavity_json,
|
||||
"key_info": detailed_cavity_json,
|
||||
"html_file": f"/html/{Path(html_file_path).name}",
|
||||
"verification": verification_result,
|
||||
"status": ProcessingStatus.COMPLETED,
|
||||
"completed_at": str(datetime.now()),
|
||||
})
|
||||
|
||||
logger.info(f"模具型腔生成完成: {task_id}")
|
||||
logger.info(f"key_info metadata: {detailed_cavity_json.get('metadata', {})}")
|
||||
logger.info(f"key_info manufacturing_info: {detailed_cavity_json.get('manufacturing_info', {})}")
|
||||
logger.info(f"key_info geometric_characteristics: {detailed_cavity_json.get('mold_cavities', {}).get('cavity_key_info', {}).get('geometric_characteristics', {})}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"模具型腔生成失败: {e}")
|
||||
|
||||
await self.storage_service.update_stp_file_status(db_session, stp_file_id, "failed")
|
||||
await self.storage_service.update_task_status(
|
||||
db_session, task_id, "failed", error_message=str(e)
|
||||
)
|
||||
|
||||
task = await redis_task_manager.get_task(task_id)
|
||||
if task:
|
||||
await redis_task_manager.update_task(task_id, {
|
||||
"status": ProcessingStatus.FAILED,
|
||||
"error": str(e),
|
||||
"completed_at": str(datetime.now()),
|
||||
})
|
||||
|
||||
# ─── 内部步骤 ───
|
||||
|
||||
async def _step_generate_mesh(
|
||||
self, shape, geometry_data: dict, file_path: str,
|
||||
db_session: AsyncSession, stp_file_id: int, task_id: str,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""生成网格数据并持久化,失败不影响主流程"""
|
||||
mesh_result = None
|
||||
try:
|
||||
mesh_result = self.mesh_generator.generate_mesh_from_shape(shape)
|
||||
|
||||
vertices = mesh_result.get("vertices", [])
|
||||
faces = mesh_result.get("faces", [])
|
||||
points = mesh_result.get("points", [])
|
||||
normals = mesh_result.get("normals", [])
|
||||
point_count = mesh_result.get("point_count", 0)
|
||||
vertex_count = mesh_result.get("vertex_count", 0)
|
||||
face_count = mesh_result.get("face_count", 0)
|
||||
|
||||
if vertices and faces:
|
||||
bbox = geometry_data.get("bounding_box", {})
|
||||
|
||||
mesh_json = {
|
||||
"metadata": {
|
||||
"file_name": Path(file_path).name,
|
||||
"generated_at": datetime.now().isoformat(),
|
||||
"quality": "medium",
|
||||
"vertex_count": vertex_count,
|
||||
"face_count": face_count,
|
||||
"point_count": point_count,
|
||||
},
|
||||
"mesh": {
|
||||
"vertices": vertices,
|
||||
"faces": faces,
|
||||
},
|
||||
"pointcloud": {
|
||||
"points": points,
|
||||
"normals": normals,
|
||||
"count": point_count,
|
||||
},
|
||||
"bounding_box": bbox,
|
||||
}
|
||||
|
||||
await self.storage_service.save_mesh_data(
|
||||
db_session,
|
||||
stp_file_id=stp_file_id,
|
||||
mesh_json=mesh_json,
|
||||
quality="medium",
|
||||
)
|
||||
await redis_task_manager.update_task(task_id, {
|
||||
"mesh_summary": {
|
||||
"vertex_count": vertex_count,
|
||||
"face_count": face_count,
|
||||
"point_count": point_count,
|
||||
"quality": "medium",
|
||||
}
|
||||
})
|
||||
except Exception as mesh_err:
|
||||
logger.warning(f"网格生成或保存失败,不影响主流程: {mesh_err}")
|
||||
|
||||
return mesh_result
|
||||
|
||||
async def _step_generate_cavity(
|
||||
self, shape, selected_material: dict, is_foam_material: bool,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""生成模具型腔数据"""
|
||||
cavity_mesh_data = None
|
||||
try:
|
||||
if shape:
|
||||
if is_foam_material:
|
||||
self.aluminum_foam_generator.set_material(selected_material["name"])
|
||||
cavity_result = self.aluminum_foam_generator.generate_mold_cavities(shape)
|
||||
cavity_mesh_data = self.aluminum_foam_generator.generate_detailed_cavity_json(cavity_result)
|
||||
logger.info(f"使用铝泡沫模具生成器: {selected_material['name']}")
|
||||
else:
|
||||
cavity_result = self.mold_generator.generate_mold_cavities(shape)
|
||||
cavity_mesh_data = self.mold_generator.generate_detailed_cavity_json(cavity_result)
|
||||
logger.info(f"使用普通塑料模具生成器: {selected_material['name']}")
|
||||
|
||||
if cavity_mesh_data:
|
||||
logger.info(f"型腔网格数据生成完成: {cavity_mesh_data.get('mold_cavities', {}).get('cavity', {}).get('vertex_count', 0)} 顶点")
|
||||
except Exception as cavity_err:
|
||||
logger.warning(f"型腔生成失败,使用简化数据: {cavity_err}")
|
||||
traceback.print_exc()
|
||||
cavity_mesh_data = None
|
||||
|
||||
return cavity_mesh_data
|
||||
|
||||
async def _step_verify(
|
||||
self, file_path: str, db_session: AsyncSession,
|
||||
task_id: str, stp_file_id: int, analysis_result: Optional[dict],
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""FreeCAD 几何验证(可通过配置禁用)"""
|
||||
from config.settings import settings
|
||||
|
||||
if not settings.ENABLE_FREECAD_VERIFICATION:
|
||||
logger.info("FreeCAD验证已禁用(设置 ENABLE_FREECAD_VERIFICATION=true 启用)")
|
||||
return {"status": "disabled", "reason": "FreeCAD验证已禁用"}
|
||||
|
||||
await self.storage_service.update_task_status(
|
||||
db_session, task_id, "processing", 90, "FreeCAD几何验证"
|
||||
)
|
||||
|
||||
try:
|
||||
from services.verification_service import GeometryVerificationService
|
||||
verification_svc = GeometryVerificationService(timeout=settings.FREECAD_VERIFICATION_TIMEOUT)
|
||||
verification_result = await verification_svc.verify_stp_file(file_path)
|
||||
|
||||
if verification_result and analysis_result:
|
||||
await self._save_verification_metrics(db_session, stp_file_id, verification_result)
|
||||
|
||||
logger.info(f"FreeCAD验证完成: {verification_result.get('status', 'unknown') if verification_result else 'failed'}")
|
||||
return verification_result
|
||||
except Exception as ve:
|
||||
logger.warning(f"FreeCAD验证失败(不影响主流程): {ve}")
|
||||
return {"status": "error", "error": str(ve)}
|
||||
|
||||
# ─── 指标持久化 ───
|
||||
|
||||
async def _save_analysis_metrics(self, session: AsyncSession, stp_file_id: int, analysis_result: dict):
|
||||
"""保存分析指标到数据库"""
|
||||
from models.database import AnalysisMetrics
|
||||
|
||||
quality_metrics = analysis_result.get("quality_metrics", {})
|
||||
analysis_summary = analysis_result.get("analysis_summary", "")
|
||||
|
||||
metrics = AnalysisMetrics(
|
||||
stp_file_id=stp_file_id,
|
||||
volume_utilization=quality_metrics.get("volume_utilization", 0),
|
||||
topology_complexity=quality_metrics.get("topology_complexity", 0),
|
||||
wall_uniformity=quality_metrics.get("wall_uniformity", 0),
|
||||
analysis_summary=analysis_summary,
|
||||
)
|
||||
|
||||
session.add(metrics)
|
||||
await session.commit()
|
||||
logger.info(f"分析指标保存成功: {metrics.id}")
|
||||
|
||||
async def _save_verification_metrics(self, session: AsyncSession, stp_file_id: int, verification_result: dict):
|
||||
"""保存验证指标到数据库"""
|
||||
from models.database import AnalysisMetrics
|
||||
from sqlalchemy import select
|
||||
|
||||
result = await session.execute(
|
||||
select(AnalysisMetrics).where(AnalysisMetrics.stp_file_id == stp_file_id)
|
||||
)
|
||||
metrics = result.scalar_one_or_none()
|
||||
|
||||
comparison = verification_result.get("comparison", {})
|
||||
volume_comparison = comparison.get("volume", {})
|
||||
area_comparison = comparison.get("surface_area", {})
|
||||
|
||||
if metrics:
|
||||
metrics.verification_status = verification_result.get("status", "unknown")
|
||||
metrics.verification_volume_diff = volume_comparison.get("difference_percent", 0)
|
||||
metrics.verification_area_diff = area_comparison.get("difference_percent", 0)
|
||||
metrics.verification_details = verification_result
|
||||
else:
|
||||
metrics = AnalysisMetrics(
|
||||
stp_file_id=stp_file_id,
|
||||
verification_status=verification_result.get("status", "unknown"),
|
||||
verification_volume_diff=volume_comparison.get("difference_percent", 0),
|
||||
verification_area_diff=area_comparison.get("difference_percent", 0),
|
||||
verification_details=verification_result,
|
||||
)
|
||||
session.add(metrics)
|
||||
|
||||
await session.commit()
|
||||
logger.info(f"验证指标保存成功: stp_file_id={stp_file_id}")
|
||||
|
||||
|
||||
# 模块级单例,供路由层直接使用
|
||||
processing_service = ProcessingService()
|
||||
Reference in New Issue
Block a user