2026-02-11 22:40:35 +08:00
|
|
|
|
# api/routes.py
|
|
|
|
|
|
from fastapi import APIRouter, UploadFile, File, HTTPException, BackgroundTasks, Request, Depends
|
2026-03-04 23:54:32 +08:00
|
|
|
|
from typing import Optional, Dict, Any, List
|
2026-02-11 22:40:35 +08:00
|
|
|
|
import uuid
|
|
|
|
|
|
from datetime import datetime
|
2026-04-19 23:41:35 +08:00
|
|
|
|
import os
|
2026-02-11 22:40:35 +08:00
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
from models.schemas import ProcessingStatus, create_task_info
|
|
|
|
|
|
from core.stp_parser import STPParser
|
|
|
|
|
|
from core.geometry_analyzer import GeometryAnalyzer
|
|
|
|
|
|
from utils.file_handler import FileHandler
|
|
|
|
|
|
from utils.html_generator import HTMLGenerator
|
|
|
|
|
|
from services.storage_integration_rustfs import StorageIntegrationService
|
2026-04-23 00:49:22 +08:00
|
|
|
|
from services.redis_task_manager import redis_task_manager
|
|
|
|
|
|
from services.processing_service import processing_service
|
2026-05-11 11:35:15 +08:00
|
|
|
|
from services.llm_service import llm_service
|
2026-04-23 00:49:22 +08:00
|
|
|
|
from services.task_query_service import TaskQueryService
|
2026-02-11 22:40:35 +08:00
|
|
|
|
from database.database import get_db_session
|
|
|
|
|
|
from utils.logger import get_logger
|
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
from core.mold_generator import MoldCavityGenerator
|
2026-03-14 01:29:40 +08:00
|
|
|
|
from core.aluminum_foam_mold import AluminumFoamMoldGenerator
|
|
|
|
|
|
from core.mold_quality_inspector import AluminumFoamMoldQualityInspector
|
2026-02-16 19:06:41 +08:00
|
|
|
|
from core.mesh_generator import MeshGenerator
|
2026-04-19 23:41:35 +08:00
|
|
|
|
from core.cavity_layout_optimizer import CavityLayoutOptimizer
|
|
|
|
|
|
from core.mold_system_designer import MoldSystemDesigner
|
|
|
|
|
|
from core.side_action_designer import SideActionDesigner
|
|
|
|
|
|
from core.mold_cam import MoldCAMDesigner
|
|
|
|
|
|
from core.mold_machining import CollisionDetector, ToolpathOptimizer, EDMElectrodeDesigner, MachiningSimulator
|
|
|
|
|
|
from core.cad_exporter import CADExporter
|
2026-03-15 13:33:47 +08:00
|
|
|
|
from services.auth_service import get_current_active_user
|
|
|
|
|
|
from models.database import User
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
|
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
|
|
|
|
|
|
stp_parser = STPParser()
|
|
|
|
|
|
geometry_analyzer = GeometryAnalyzer()
|
|
|
|
|
|
file_handler = FileHandler()
|
|
|
|
|
|
html_generator = HTMLGenerator()
|
2026-03-04 00:47:41 +08:00
|
|
|
|
mold_generator = MoldCavityGenerator(shrinkage_rate=0.005)
|
2026-03-14 01:29:40 +08:00
|
|
|
|
# 铝泡沫模具生成器
|
|
|
|
|
|
aluminum_foam_generator = AluminumFoamMoldGenerator(shrinkage_rate=0.015, draft_angle=3.0)
|
|
|
|
|
|
# 铝泡沫模具质量检测器
|
|
|
|
|
|
mold_quality_inspector = AluminumFoamMoldQualityInspector()
|
2026-02-16 19:06:41 +08:00
|
|
|
|
mesh_generator = MeshGenerator(quality="medium")
|
2026-04-19 23:41:35 +08:00
|
|
|
|
cavity_layout_optimizer = CavityLayoutOptimizer()
|
|
|
|
|
|
mold_system_designer = MoldSystemDesigner()
|
|
|
|
|
|
side_action_designer = SideActionDesigner()
|
|
|
|
|
|
mold_cam_designer = MoldCAMDesigner()
|
|
|
|
|
|
collision_detector = CollisionDetector()
|
|
|
|
|
|
toolpath_optimizer = ToolpathOptimizer()
|
|
|
|
|
|
edm_designer = EDMElectrodeDesigner()
|
|
|
|
|
|
machining_simulator = MachiningSimulator()
|
|
|
|
|
|
cad_exporter = CADExporter()
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
|
|
|
|
|
tasks = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/health")
|
2026-02-16 01:32:13 +08:00
|
|
|
|
@router.post("/health")
|
2026-02-11 22:40:35 +08:00
|
|
|
|
async def health():
|
2026-04-23 00:49:22 +08:00
|
|
|
|
task_count = await redis_task_manager.get_task_count()
|
2026-02-11 22:40:35 +08:00
|
|
|
|
return {
|
|
|
|
|
|
"status": "healthy",
|
|
|
|
|
|
"pythonocc": True,
|
2026-04-23 00:49:22 +08:00
|
|
|
|
"total_tasks": task_count,
|
|
|
|
|
|
"redis_connected": redis_task_manager.is_connected,
|
2026-02-11 22:40:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/upload")
|
|
|
|
|
|
async def upload_stp(
|
|
|
|
|
|
background_tasks: BackgroundTasks,
|
|
|
|
|
|
file: UploadFile = File(...),
|
2026-03-14 01:29:40 +08:00
|
|
|
|
material: Optional[str] = "ABS",
|
2026-03-15 13:33:47 +08:00
|
|
|
|
db_session: AsyncSession = Depends(get_db_session),
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user)
|
2026-02-11 22:40:35 +08:00
|
|
|
|
):
|
|
|
|
|
|
"""上传STP文件并存储到数据库"""
|
2026-05-06 10:28:16 +08:00
|
|
|
|
logger.info(
|
|
|
|
|
|
f"[UPLOAD] 用户={current_user.username}(id={current_user.id}) "
|
|
|
|
|
|
f"文件={file.filename} 材料={material}"
|
|
|
|
|
|
)
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
|
|
|
|
|
if not file.filename.lower().endswith(('.stp', '.step')):
|
2026-05-06 10:28:16 +08:00
|
|
|
|
logger.warning(f"[UPLOAD] 拒绝: 不支持的文件类型 - {file.filename}")
|
2026-02-11 22:40:35 +08:00
|
|
|
|
raise HTTPException(400, "只支持STP/STEP文件")
|
|
|
|
|
|
|
|
|
|
|
|
task_id = str(uuid.uuid4())
|
|
|
|
|
|
|
|
|
|
|
|
# 保存文件
|
2026-03-15 13:33:47 +08:00
|
|
|
|
file_path, file_size = await file_handler.save_uploaded_file(file)
|
2026-05-06 10:28:16 +08:00
|
|
|
|
logger.info(f"[UPLOAD] 文件已保存: {file_path} ({file_size} bytes), task_id={task_id}")
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
|
|
|
|
|
# 创建存储集成服务实例
|
|
|
|
|
|
storage_service = StorageIntegrationService()
|
|
|
|
|
|
|
|
|
|
|
|
# 保存STP文件到RustFS + PostgreSQL
|
|
|
|
|
|
stp_file = await storage_service.save_stp_file(
|
|
|
|
|
|
session=db_session,
|
|
|
|
|
|
file_path=file_path,
|
2026-03-15 13:33:47 +08:00
|
|
|
|
original_filename=file.filename,
|
|
|
|
|
|
user_id=current_user.id
|
2026-02-11 22:40:35 +08:00
|
|
|
|
)
|
2026-05-06 10:28:16 +08:00
|
|
|
|
logger.info(f"[UPLOAD] STP文件已存入RustFS+PG: stp_file.id={stp_file.id}")
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
|
|
|
|
|
# 创建处理任务记录
|
|
|
|
|
|
await storage_service.create_processing_task(db_session, task_id, stp_file.id)
|
|
|
|
|
|
|
2026-04-23 23:37:39 +08:00
|
|
|
|
# 创建任务记录(Redis 为主,内存作为兼容回退)
|
2026-04-23 00:49:22 +08:00
|
|
|
|
task_info = create_task_info(
|
2026-02-11 22:40:35 +08:00
|
|
|
|
task_id=task_id,
|
|
|
|
|
|
status=ProcessingStatus.PROCESSING,
|
|
|
|
|
|
filename=file.filename,
|
|
|
|
|
|
file_path=str(file_path),
|
2026-03-15 13:33:47 +08:00
|
|
|
|
file_size=file_size,
|
2026-02-11 22:40:35 +08:00
|
|
|
|
upload_time=str(datetime.now())
|
|
|
|
|
|
)
|
2026-04-23 00:49:22 +08:00
|
|
|
|
await redis_task_manager.set_task(task_id, task_info)
|
|
|
|
|
|
tasks[task_id] = task_info
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
2026-04-23 23:37:39 +08:00
|
|
|
|
# 后台处理走统一编排服务,避免请求会话在后台失效
|
2026-04-23 00:49:22 +08:00
|
|
|
|
background_tasks.add_task(
|
|
|
|
|
|
processing_service.process_file_with_storage,
|
2026-04-23 23:37:39 +08:00
|
|
|
|
task_id,
|
|
|
|
|
|
file_path,
|
|
|
|
|
|
stp_file.id,
|
|
|
|
|
|
material,
|
2026-04-23 00:49:22 +08:00
|
|
|
|
)
|
2026-05-06 10:28:16 +08:00
|
|
|
|
logger.info(f"[UPLOAD] 后台处理已调度: task_id={task_id}")
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"task_id": task_id,
|
|
|
|
|
|
"status": "processing",
|
|
|
|
|
|
"message": "文件上传成功,开始处理并存储到数据库",
|
|
|
|
|
|
"file_info": {
|
|
|
|
|
|
"filename": file.filename,
|
2026-03-15 13:33:47 +08:00
|
|
|
|
"size": file_size,
|
2026-02-11 22:40:35 +08:00
|
|
|
|
"pythonocc_available": True,
|
|
|
|
|
|
"database_file_id": stp_file.id
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/status/{task_id}")
|
2026-02-16 01:32:13 +08:00
|
|
|
|
@router.post("/status/{task_id}")
|
2026-02-17 00:25:18 +08:00
|
|
|
|
async def get_status(task_id: str, db_session: AsyncSession = Depends(get_db_session)):
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取任务状态
|
|
|
|
|
|
|
|
|
|
|
|
优先返回内存中的任务信息;
|
|
|
|
|
|
如果内存中不存在,则从 PostgreSQL + RustFS 组装一个持久化的任务视图,
|
|
|
|
|
|
结构与内存任务保持尽量一致,便于前端集中展示总结性信息。
|
|
|
|
|
|
"""
|
2026-03-08 01:41:06 +08:00
|
|
|
|
try:
|
2026-04-23 00:49:22 +08:00
|
|
|
|
task_view = await TaskQueryService.get_task_view(db_session, task_id)
|
|
|
|
|
|
if task_view is None:
|
|
|
|
|
|
if task_id in tasks:
|
|
|
|
|
|
return tasks[task_id]
|
2026-03-08 01:41:06 +08:00
|
|
|
|
raise HTTPException(404, "任务不存在")
|
|
|
|
|
|
return task_view
|
|
|
|
|
|
except HTTPException:
|
|
|
|
|
|
raise
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"获取任务状态失败: {e}")
|
|
|
|
|
|
raise HTTPException(500, f"获取任务状态失败: {str(e)}")
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/debug/tasks")
|
2026-02-16 01:32:13 +08:00
|
|
|
|
@router.post("/debug/tasks")
|
2026-02-11 22:40:35 +08:00
|
|
|
|
async def debug_tasks():
|
|
|
|
|
|
"""调试接口:查看所有任务"""
|
2026-04-23 00:49:22 +08:00
|
|
|
|
all_tasks = await redis_task_manager.get_all_tasks()
|
2026-02-11 22:40:35 +08:00
|
|
|
|
return {
|
2026-04-23 00:49:22 +08:00
|
|
|
|
"total_tasks": len(all_tasks),
|
|
|
|
|
|
"tasks": all_tasks,
|
|
|
|
|
|
"redis_connected": redis_task_manager.is_connected,
|
|
|
|
|
|
"memory_fallback_tasks": len(tasks),
|
2026-02-11 22:40:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-07 01:01:15 +08:00
|
|
|
|
@router.get("/history")
|
|
|
|
|
|
@router.post("/history")
|
2026-02-16 02:00:10 +08:00
|
|
|
|
async def get_file_history(db_session: AsyncSession = Depends(get_db_session)):
|
2026-03-07 03:04:15 +08:00
|
|
|
|
"""获取按文件名分组的文件历史记录(支持多上传)"""
|
|
|
|
|
|
storage_service = StorageIntegrationService()
|
2026-02-16 00:52:29 +08:00
|
|
|
|
|
2026-03-07 03:04:15 +08:00
|
|
|
|
file_groups = await storage_service.get_all_file_groups(db_session)
|
2026-02-16 00:52:29 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
2026-03-07 03:04:15 +08:00
|
|
|
|
"total_files": len(file_groups),
|
|
|
|
|
|
"files": file_groups
|
2026-02-16 00:52:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-07 01:01:15 +08:00
|
|
|
|
@router.get("/history/{filename}")
|
|
|
|
|
|
@router.post("/history/{filename}")
|
2026-02-16 03:07:02 +08:00
|
|
|
|
async def get_file_records(filename: str, db_session: AsyncSession = Depends(get_db_session)):
|
2026-03-07 03:04:15 +08:00
|
|
|
|
"""获取指定文件名的所有上传记录(支持多上传历史)"""
|
2026-02-16 00:52:29 +08:00
|
|
|
|
import urllib.parse
|
|
|
|
|
|
decoded_filename = urllib.parse.unquote(filename)
|
|
|
|
|
|
|
2026-03-07 03:04:15 +08:00
|
|
|
|
storage_service = StorageIntegrationService()
|
|
|
|
|
|
file_records = await storage_service.get_file_history_by_filename(
|
|
|
|
|
|
db_session,
|
|
|
|
|
|
decoded_filename
|
2026-02-16 02:00:10 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-16 00:52:29 +08:00
|
|
|
|
return file_records
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/result/{task_id}")
|
2026-02-16 01:32:13 +08:00
|
|
|
|
@router.post("/result/{task_id}")
|
2026-02-16 03:12:38 +08:00
|
|
|
|
async def result_page(request: Request, task_id: str, db_session: AsyncSession = Depends(get_db_session)):
|
2026-02-16 00:52:29 +08:00
|
|
|
|
"""结果详情页面"""
|
2026-02-16 03:12:38 +08:00
|
|
|
|
from sqlalchemy import select
|
2026-02-16 18:33:17 +08:00
|
|
|
|
from models.database import ProcessingTask, STPFile, GeometryData, MoldCavityData, HTMLFile
|
|
|
|
|
|
|
|
|
|
|
|
# 从数据库查询任务详情
|
|
|
|
|
|
result = await db_session.execute(
|
2026-02-16 16:25:32 +08:00
|
|
|
|
select(ProcessingTask, STPFile)
|
2026-02-16 03:12:38 +08:00
|
|
|
|
.join(STPFile, ProcessingTask.stp_file_id == STPFile.id)
|
|
|
|
|
|
.where(ProcessingTask.task_id == task_id)
|
2026-02-16 16:25:32 +08:00
|
|
|
|
)
|
2026-02-16 18:33:17 +08:00
|
|
|
|
|
|
|
|
|
|
task_record = result.first()
|
|
|
|
|
|
|
2026-02-16 16:25:32 +08:00
|
|
|
|
if not task_record:
|
|
|
|
|
|
raise HTTPException(404, "任务不存在")
|
2026-02-16 18:33:17 +08:00
|
|
|
|
|
2026-02-16 16:25:32 +08:00
|
|
|
|
task, stp_file = task_record
|
2026-02-16 18:33:17 +08:00
|
|
|
|
|
|
|
|
|
|
# 构建任务详情数据(先只包含基本数据)
|
2026-02-16 16:25:32 +08:00
|
|
|
|
task_data = {
|
|
|
|
|
|
"task_id": task.task_id,
|
|
|
|
|
|
"filename": stp_file.original_filename if stp_file else "",
|
|
|
|
|
|
"file_size": stp_file.file_size if stp_file else 0,
|
|
|
|
|
|
"status": task.status,
|
|
|
|
|
|
"progress": task.progress,
|
|
|
|
|
|
"current_step": task.current_step,
|
2026-02-16 18:33:17 +08:00
|
|
|
|
"created_at": task.created_time.isoformat() if task.created_time else "",
|
|
|
|
|
|
"completed_at": task.completed_time.isoformat() if task.completed_time else "",
|
2026-02-16 16:25:32 +08:00
|
|
|
|
"error": task.error_message if task.error_message else ""
|
|
|
|
|
|
}
|
2026-02-16 18:33:17 +08:00
|
|
|
|
|
2026-02-16 00:52:29 +08:00
|
|
|
|
from fastapi.templating import Jinja2Templates
|
|
|
|
|
|
import os
|
2026-02-16 01:07:44 +08:00
|
|
|
|
# 简化路径配置,直接使用当前工作目录下的templates文件夹
|
|
|
|
|
|
templates_dir = os.path.join(os.getcwd(), "templates")
|
2026-02-16 00:52:29 +08:00
|
|
|
|
templates = Jinja2Templates(directory=templates_dir)
|
|
|
|
|
|
return templates.TemplateResponse("result.html", {
|
|
|
|
|
|
"request": request,
|
2026-02-16 03:12:38 +08:00
|
|
|
|
"task": task_data,
|
2026-02-16 00:52:29 +08:00
|
|
|
|
"pythonocc_available": True,
|
|
|
|
|
|
"version": "3.0.0"
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-11 22:40:35 +08:00
|
|
|
|
async def process_file_with_storage(
|
|
|
|
|
|
task_id: str,
|
|
|
|
|
|
file_path: str,
|
|
|
|
|
|
stp_file_id: int,
|
2026-03-14 01:29:40 +08:00
|
|
|
|
db_session: AsyncSession,
|
|
|
|
|
|
material: str = "ABS"
|
2026-02-11 22:40:35 +08:00
|
|
|
|
):
|
|
|
|
|
|
"""处理文件的后台任务"""
|
|
|
|
|
|
|
|
|
|
|
|
storage_service = StorageIntegrationService()
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
logger.info(f"开始处理文件并生成模具型腔: {file_path}")
|
|
|
|
|
|
|
|
|
|
|
|
# 设置处理超时(5分钟)
|
|
|
|
|
|
import asyncio
|
|
|
|
|
|
timeout_seconds = 300 # 5分钟
|
|
|
|
|
|
|
|
|
|
|
|
async def process_with_timeout():
|
|
|
|
|
|
# 处理逻辑将在下面添加
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# 使用超时保护
|
|
|
|
|
|
try:
|
2026-03-14 01:29:40 +08:00
|
|
|
|
await asyncio.wait_for(process_file_core(storage_service, task_id, file_path, stp_file_id, db_session, material), timeout_seconds)
|
2026-02-11 22:40:35 +08:00
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
|
|
logger.error(f"处理超时: {task_id}")
|
|
|
|
|
|
raise Exception(f"处理超时,超过{timeout_seconds}秒未完成")
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"模具型腔生成失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
await storage_service.update_stp_file_status(db_session, stp_file_id, "failed")
|
|
|
|
|
|
await storage_service.update_task_status(
|
|
|
|
|
|
db_session, task_id, "failed", error_message=str(e)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
tasks[task_id]["status"] = ProcessingStatus.FAILED
|
|
|
|
|
|
tasks[task_id]["error"] = str(e)
|
|
|
|
|
|
tasks[task_id]["completed_at"] = str(datetime.now())
|
2026-03-06 23:37:46 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-04-19 23:41:35 +08:00
|
|
|
|
# ==================== P3 新增 API ====================
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/optimize-layout")
|
|
|
|
|
|
async def optimize_cavity_layout(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""多型腔布局优化"""
|
|
|
|
|
|
body = await request.json()
|
|
|
|
|
|
product_bbox = body.get("product_bbox", {"dimensions": [100, 100, 50]})
|
|
|
|
|
|
cavity_count = body.get("cavity_count", 1)
|
|
|
|
|
|
mold_base_size = body.get("mold_base_size")
|
|
|
|
|
|
layout_type = body.get("layout_type", "auto")
|
|
|
|
|
|
|
|
|
|
|
|
if cavity_count < 1 or cavity_count > 64:
|
|
|
|
|
|
raise HTTPException(400, "型腔数量必须在 1-64 之间")
|
|
|
|
|
|
|
|
|
|
|
|
result = cavity_layout_optimizer.optimize_layout(
|
|
|
|
|
|
product_bbox=product_bbox,
|
|
|
|
|
|
cavity_count=cavity_count,
|
|
|
|
|
|
mold_base_size=mold_base_size,
|
|
|
|
|
|
layout_type=layout_type,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/design-cooling")
|
|
|
|
|
|
async def design_cooling_system(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""冷却系统设计"""
|
|
|
|
|
|
body = await request.json()
|
|
|
|
|
|
mold_size = body.get("mold_size", {"length": 300, "width": 300, "height": 200})
|
|
|
|
|
|
product_bbox = body.get("product_bbox", {"dimensions": [100, 100, 50]})
|
|
|
|
|
|
material = body.get("material", "ABS")
|
|
|
|
|
|
cavity_count = body.get("cavity_count", 1)
|
|
|
|
|
|
cycle_time_target = body.get("cycle_time_target")
|
|
|
|
|
|
|
|
|
|
|
|
from core.mold_system_designer import CoolingSystemDesigner
|
|
|
|
|
|
designer = CoolingSystemDesigner()
|
|
|
|
|
|
result = designer.design_cooling_system(
|
|
|
|
|
|
mold_size=mold_size,
|
|
|
|
|
|
product_bbox=product_bbox,
|
|
|
|
|
|
material=material,
|
|
|
|
|
|
cavity_count=cavity_count,
|
|
|
|
|
|
cycle_time_target=cycle_time_target,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/design-gating")
|
|
|
|
|
|
async def design_gating_system(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""浇注系统设计"""
|
|
|
|
|
|
body = await request.json()
|
|
|
|
|
|
product_bbox = body.get("product_bbox", {"dimensions": [100, 100, 50]})
|
|
|
|
|
|
material = body.get("material", "ABS")
|
|
|
|
|
|
cavity_count = body.get("cavity_count", 1)
|
|
|
|
|
|
gate_type = body.get("gate_type", "auto")
|
|
|
|
|
|
layout_positions = body.get("layout_positions")
|
|
|
|
|
|
|
|
|
|
|
|
from core.mold_system_designer import GatingSystemDesigner
|
|
|
|
|
|
designer = GatingSystemDesigner()
|
|
|
|
|
|
result = designer.design_gating_system(
|
|
|
|
|
|
product_bbox=product_bbox,
|
|
|
|
|
|
material=material,
|
|
|
|
|
|
cavity_count=cavity_count,
|
|
|
|
|
|
gate_type=gate_type,
|
|
|
|
|
|
layout_positions=layout_positions,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/design-mold-system")
|
|
|
|
|
|
async def design_complete_mold_system(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""综合模具系统设计(冷却+浇注)"""
|
|
|
|
|
|
body = await request.json()
|
|
|
|
|
|
mold_size = body.get("mold_size", {"length": 300, "width": 300, "height": 200})
|
|
|
|
|
|
product_bbox = body.get("product_bbox", {"dimensions": [100, 100, 50]})
|
|
|
|
|
|
material = body.get("material", "ABS")
|
|
|
|
|
|
cavity_count = body.get("cavity_count", 1)
|
|
|
|
|
|
gate_type = body.get("gate_type", "auto")
|
|
|
|
|
|
cycle_time_target = body.get("cycle_time_target")
|
|
|
|
|
|
layout_positions = body.get("layout_positions")
|
|
|
|
|
|
|
|
|
|
|
|
result = mold_system_designer.design_complete_system(
|
|
|
|
|
|
mold_size=mold_size,
|
|
|
|
|
|
product_bbox=product_bbox,
|
|
|
|
|
|
material=material,
|
|
|
|
|
|
cavity_count=cavity_count,
|
|
|
|
|
|
gate_type=gate_type,
|
|
|
|
|
|
cycle_time_target=cycle_time_target,
|
|
|
|
|
|
layout_positions=layout_positions,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/ai-parting-detect")
|
|
|
|
|
|
async def ai_parting_surface_detect(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""AI 分型面检测"""
|
|
|
|
|
|
body = await request.json()
|
|
|
|
|
|
task_id = body.get("task_id")
|
|
|
|
|
|
|
|
|
|
|
|
if not task_id or task_id not in tasks:
|
|
|
|
|
|
raise HTTPException(404, "任务不存在")
|
|
|
|
|
|
|
|
|
|
|
|
task_data = tasks[task_id]
|
|
|
|
|
|
geometry_data = task_data.get("geometry_data")
|
|
|
|
|
|
if not geometry_data:
|
|
|
|
|
|
raise HTTPException(400, "该任务尚未完成几何分析")
|
|
|
|
|
|
|
|
|
|
|
|
from core.ai_parting_detector import AIPartingSurfaceDetectorV2
|
|
|
|
|
|
detector = AIPartingSurfaceDetectorV2(use_gnn=True)
|
|
|
|
|
|
|
|
|
|
|
|
result = detector._detect_with_geometry(None, geometry_data)
|
|
|
|
|
|
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/detect-undercuts")
|
|
|
|
|
|
async def detect_undercuts(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""倒扣区域检测与滑块/斜顶机构设计"""
|
|
|
|
|
|
body = await request.json()
|
|
|
|
|
|
task_id = body.get("task_id")
|
|
|
|
|
|
parting_direction = body.get("parting_direction", [0, 0, 1])
|
|
|
|
|
|
mold_size = body.get("mold_size", {"length": 300, "width": 300, "height": 200})
|
|
|
|
|
|
|
|
|
|
|
|
if not task_id or task_id not in tasks:
|
|
|
|
|
|
raise HTTPException(404, "任务不存在")
|
|
|
|
|
|
|
|
|
|
|
|
task_data = tasks[task_id]
|
|
|
|
|
|
geometry_data = task_data.get("geometry_data")
|
|
|
|
|
|
if not geometry_data:
|
|
|
|
|
|
raise HTTPException(400, "该任务尚未完成几何分析")
|
|
|
|
|
|
|
|
|
|
|
|
result = side_action_designer.analyze_and_design(
|
|
|
|
|
|
shape=None, parting_direction=parting_direction, mold_size=mold_size
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/design-cam")
|
|
|
|
|
|
async def design_mold_cam(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""模具CAM刀路设计"""
|
|
|
|
|
|
body = await request.json()
|
|
|
|
|
|
cavity_bbox = body.get("cavity_bbox", {"dimensions": [100, 100, 50], "min": [-50, -50, -25], "max": [50, 50, 25]})
|
|
|
|
|
|
stock_bbox = body.get("stock_bbox", {"dimensions": [150, 150, 100], "min": [-75, -75, -50], "max": [75, 75, 50]})
|
|
|
|
|
|
mold_steel = body.get("mold_steel", "P20")
|
|
|
|
|
|
surface_quality = body.get("surface_quality", "standard")
|
|
|
|
|
|
controller = body.get("controller", "fanuc")
|
|
|
|
|
|
|
|
|
|
|
|
result = mold_cam_designer.design_mold_cam(
|
|
|
|
|
|
cavity_bbox=cavity_bbox,
|
|
|
|
|
|
stock_bbox=stock_bbox,
|
|
|
|
|
|
mold_steel=mold_steel,
|
|
|
|
|
|
surface_quality=surface_quality,
|
|
|
|
|
|
controller=controller,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/check-collision")
|
|
|
|
|
|
async def check_toolpath_collision(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""刀路碰撞检测"""
|
|
|
|
|
|
body = await request.json()
|
|
|
|
|
|
toolpath_points = body.get("toolpath_points", [[0, 0, 50], [10, 10, -5], [20, 20, -10]])
|
|
|
|
|
|
tool = body.get("tool", {"diameter": 10, "flute_length": 30, "shank_diameter": 10})
|
|
|
|
|
|
stock_bbox = body.get("stock_bbox", {"min": [-50, -50, -25], "max": [50, 50, 25]})
|
|
|
|
|
|
clamp_positions = body.get("clamp_positions")
|
|
|
|
|
|
|
|
|
|
|
|
result = collision_detector.check_toolpath_safety(
|
|
|
|
|
|
toolpath_points, tool, stock_bbox, clamp_positions
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/optimize-toolpath")
|
|
|
|
|
|
async def optimize_toolpath(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""刀路优化"""
|
|
|
|
|
|
body = await request.json()
|
|
|
|
|
|
toolpath_points = body.get("toolpath_points", [[0, 0, 50], [10, 10, -5], [20, 20, -10]])
|
|
|
|
|
|
cutting_params = body.get("cutting_params", {"feed_rate_mm_min": 500})
|
|
|
|
|
|
stock_bbox = body.get("stock_bbox")
|
|
|
|
|
|
|
|
|
|
|
|
result = toolpath_optimizer.optimize_toolpath(
|
|
|
|
|
|
toolpath_points, cutting_params, stock_bbox
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/design-electrodes")
|
|
|
|
|
|
async def design_edm_electrodes(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""EDM电极设计"""
|
|
|
|
|
|
body = await request.json()
|
|
|
|
|
|
undercut_regions = body.get("undercut_regions", [{"center": [0, 0, 0], "area": 100, "type": "undercut"}])
|
|
|
|
|
|
cavity_bbox = body.get("cavity_bbox", {"dimensions": [100, 100, 50]})
|
|
|
|
|
|
material = body.get("material", "copper")
|
|
|
|
|
|
spark_gap = body.get("spark_gap", 0.05)
|
|
|
|
|
|
overburn = body.get("overburn", 0.1)
|
|
|
|
|
|
|
|
|
|
|
|
result = edm_designer.design_electrodes(
|
|
|
|
|
|
undercut_regions, cavity_bbox, material, spark_gap, overburn
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/simulate-machining")
|
|
|
|
|
|
async def simulate_machining(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""加工仿真"""
|
|
|
|
|
|
body = await request.json()
|
|
|
|
|
|
operations = body.get("operations", [{"strategy": "z_level_roughing", "levels": [{"z": -5}]}])
|
|
|
|
|
|
stock_bbox = body.get("stock_bbox", {"dimensions": [100, 100, 50], "min": [-50, -50, -25], "max": [50, 50, 25]})
|
|
|
|
|
|
resolution = body.get("resolution", 2.0)
|
|
|
|
|
|
|
|
|
|
|
|
result = machining_simulator.simulate_machining(
|
|
|
|
|
|
operations, stock_bbox, resolution
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== CAD 导出 API ====================
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/export-mold")
|
|
|
|
|
|
async def export_mold_results(
|
|
|
|
|
|
request: Request,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""导出模具设计结果(STEP/IGES/STL/BRep)"""
|
|
|
|
|
|
body = await request.json()
|
|
|
|
|
|
task_id = body.get("task_id")
|
|
|
|
|
|
formats = body.get("formats", ["step", "stl"])
|
|
|
|
|
|
components = body.get("components", ["cavity", "core"])
|
|
|
|
|
|
|
|
|
|
|
|
if not task_id or task_id not in tasks:
|
|
|
|
|
|
raise HTTPException(404, "任务不存在")
|
|
|
|
|
|
|
|
|
|
|
|
task_data = tasks[task_id]
|
|
|
|
|
|
cavity_shapes = task_data.get("cavity_shapes")
|
|
|
|
|
|
if not cavity_shapes:
|
|
|
|
|
|
raise HTTPException(400, "该任务尚未完成模具生成或形状数据不可用")
|
|
|
|
|
|
|
|
|
|
|
|
base_filename = Path(task_data.get("filename", f"mold_{task_id}")).stem
|
|
|
|
|
|
|
|
|
|
|
|
result = cad_exporter.export_mold_results(
|
|
|
|
|
|
cavity_data=cavity_shapes,
|
|
|
|
|
|
base_filename=base_filename,
|
|
|
|
|
|
formats=formats,
|
|
|
|
|
|
components=components,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/export-download/{filepath:path}")
|
|
|
|
|
|
async def download_export_file(
|
|
|
|
|
|
filepath: str,
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""下载导出的CAD文件"""
|
|
|
|
|
|
from fastapi.responses import FileResponse
|
|
|
|
|
|
|
|
|
|
|
|
full_path = os.path.join(cad_exporter.output_dir, filepath)
|
|
|
|
|
|
|
|
|
|
|
|
if not os.path.exists(full_path):
|
|
|
|
|
|
raise HTTPException(404, "文件不存在")
|
|
|
|
|
|
|
|
|
|
|
|
if not os.path.abspath(full_path).startswith(os.path.abspath(cad_exporter.output_dir)):
|
|
|
|
|
|
raise HTTPException(403, "禁止访问")
|
|
|
|
|
|
|
|
|
|
|
|
media_types = {
|
|
|
|
|
|
".step": "application/step",
|
|
|
|
|
|
".stp": "application/step",
|
|
|
|
|
|
".iges": "application/iges",
|
|
|
|
|
|
".igs": "application/iges",
|
|
|
|
|
|
".stl": "model/stl",
|
|
|
|
|
|
".brep": "application/octet-stream",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ext = Path(full_path).suffix.lower()
|
|
|
|
|
|
media_type = media_types.get(ext, "application/octet-stream")
|
|
|
|
|
|
|
|
|
|
|
|
return FileResponse(
|
|
|
|
|
|
full_path,
|
|
|
|
|
|
media_type=media_type,
|
|
|
|
|
|
filename=os.path.basename(full_path),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/export-recommendations")
|
|
|
|
|
|
async def get_export_recommendations(
|
|
|
|
|
|
target: str = "ug",
|
|
|
|
|
|
current_user: User = Depends(get_current_active_user),
|
|
|
|
|
|
):
|
|
|
|
|
|
"""获取导出格式建议(UG/FreeCAD/SolidWorks)"""
|
|
|
|
|
|
result = cad_exporter.get_export_recommendations(target)
|
|
|
|
|
|
return {"status": "success", "data": result}
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-06 23:37:46 +08:00
|
|
|
|
async def _save_analysis_metrics(session, stp_file_id, analysis_result):
|
|
|
|
|
|
"""保存分析指标到数据库"""
|
|
|
|
|
|
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}")
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-03-08 02:53:27 +08:00
|
|
|
|
async def _save_verification_metrics(session, stp_file_id, verification_result):
|
|
|
|
|
|
"""保存验证指标到数据库"""
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
if metrics:
|
|
|
|
|
|
# 更新现有记录
|
|
|
|
|
|
metrics.verification_status = verification_result.get("status", "unknown")
|
|
|
|
|
|
comparison = verification_result.get("comparison", {})
|
|
|
|
|
|
volume_comparison = comparison.get("volume", {})
|
|
|
|
|
|
area_comparison = comparison.get("surface_area", {})
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
# 创建新记录
|
|
|
|
|
|
comparison = verification_result.get("comparison", {})
|
|
|
|
|
|
volume_comparison = comparison.get("volume", {})
|
|
|
|
|
|
area_comparison = comparison.get("surface_area", {})
|
|
|
|
|
|
|
|
|
|
|
|
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}")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-11 22:40:35 +08:00
|
|
|
|
async def process_file_core(
|
|
|
|
|
|
storage_service: StorageIntegrationService,
|
|
|
|
|
|
task_id: str,
|
|
|
|
|
|
file_path: str,
|
|
|
|
|
|
stp_file_id: int,
|
2026-03-14 01:29:40 +08:00
|
|
|
|
db_session: AsyncSession,
|
|
|
|
|
|
material: str = "ABS"
|
2026-02-11 22:40:35 +08:00
|
|
|
|
):
|
|
|
|
|
|
"""核心处理逻辑"""
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
logger.info(f"开始处理文件并生成模具型腔: {file_path}")
|
|
|
|
|
|
|
|
|
|
|
|
# 更新任务状态
|
|
|
|
|
|
await storage_service.update_task_status(
|
|
|
|
|
|
db_session, task_id, "processing", 20, "解析STP文件"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 1. 解析STP文件
|
|
|
|
|
|
await storage_service.update_task_status(
|
|
|
|
|
|
db_session, task_id, "processing", 20, "解析STP文件"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 使用STPParser类进行真实解析
|
|
|
|
|
|
shape = stp_parser.load_step_file(Path(file_path))
|
|
|
|
|
|
geometry_data = stp_parser.analyze_geometry(shape)
|
|
|
|
|
|
|
2026-02-16 19:06:41 +08:00
|
|
|
|
# 2. 生成网格数据并持久化(详细 JSON 存 RustFS,摘要写 PostgreSQL)
|
|
|
|
|
|
await storage_service.update_task_status(
|
|
|
|
|
|
db_session, task_id, "processing", 30, "生成网格数据"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
mesh_result = None
|
|
|
|
|
|
mesh_json = None
|
|
|
|
|
|
try:
|
|
|
|
|
|
mesh_result = mesh_generator.generate_mesh_from_shape(shape)
|
|
|
|
|
|
|
2026-03-07 01:40:37 +08:00
|
|
|
|
# mesh_generator 返回: vertices, faces, points, normals, point_count, vertex_count, face_count
|
|
|
|
|
|
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:
|
2026-02-16 19:06:41 +08:00
|
|
|
|
# 使用已计算的几何边界框,避免重复计算
|
|
|
|
|
|
bbox = geometry_data.get("bounding_box", {})
|
|
|
|
|
|
|
|
|
|
|
|
mesh_json = {
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"file_name": Path(file_path).name,
|
|
|
|
|
|
"generated_at": datetime.now().isoformat(),
|
|
|
|
|
|
"quality": "medium",
|
2026-03-07 01:40:37 +08:00
|
|
|
|
"vertex_count": vertex_count,
|
|
|
|
|
|
"face_count": face_count,
|
|
|
|
|
|
"point_count": point_count,
|
2026-02-16 19:06:41 +08:00
|
|
|
|
},
|
|
|
|
|
|
"mesh": {
|
|
|
|
|
|
"vertices": vertices,
|
|
|
|
|
|
"faces": faces,
|
|
|
|
|
|
},
|
2026-03-07 01:40:37 +08:00
|
|
|
|
"pointcloud": {
|
|
|
|
|
|
"points": points,
|
|
|
|
|
|
"normals": normals,
|
|
|
|
|
|
"count": point_count
|
|
|
|
|
|
},
|
2026-02-16 19:06:41 +08:00
|
|
|
|
"bounding_box": bbox,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await storage_service.save_mesh_data(
|
|
|
|
|
|
db_session,
|
|
|
|
|
|
stp_file_id=stp_file_id,
|
|
|
|
|
|
mesh_json=mesh_json,
|
|
|
|
|
|
quality="medium",
|
|
|
|
|
|
)
|
2026-02-17 00:12:36 +08:00
|
|
|
|
# 将简要网格摘要写入内存任务,便于前端展示汇总信息
|
|
|
|
|
|
tasks[task_id]["mesh_summary"] = {
|
2026-03-07 01:40:37 +08:00
|
|
|
|
"vertex_count": vertex_count,
|
|
|
|
|
|
"face_count": face_count,
|
|
|
|
|
|
"point_count": point_count,
|
2026-02-17 00:12:36 +08:00
|
|
|
|
"quality": "medium",
|
|
|
|
|
|
}
|
2026-02-16 19:06:41 +08:00
|
|
|
|
except Exception as mesh_err:
|
|
|
|
|
|
# 网格失败不影响整体流程,只记录日志
|
|
|
|
|
|
logger.warning(f"网格生成或保存失败,不影响主流程: {mesh_err}")
|
|
|
|
|
|
|
2026-03-10 22:21:54 +08:00
|
|
|
|
# 3. 生成模具型腔(使用真实的 MoldCavityGenerator)
|
2026-02-11 22:40:35 +08:00
|
|
|
|
await storage_service.update_task_status(
|
2026-03-10 22:21:54 +08:00
|
|
|
|
db_session, task_id, "processing", 40, "生成模具型腔"
|
2026-02-11 22:40:35 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-15 12:22:20 +08:00
|
|
|
|
# 材料属性(需在型腔生成前定义)
|
|
|
|
|
|
material_properties = {
|
|
|
|
|
|
"ABS": {"density": 1.05, "shrinkage": 0.005, "name": "ABS"},
|
|
|
|
|
|
"PP": {"density": 0.90, "shrinkage": 0.016, "name": "PP"},
|
|
|
|
|
|
"PE": {"density": 0.95, "shrinkage": 0.020, "name": "PE"},
|
|
|
|
|
|
"PC": {"density": 1.20, "shrinkage": 0.007, "name": "PC"},
|
|
|
|
|
|
"PA": {"density": 1.14, "shrinkage": 0.010, "name": "PA"},
|
|
|
|
|
|
"POM": {"density": 1.41, "shrinkage": 0.020, "name": "POM"},
|
|
|
|
|
|
"PMMA": {"density": 1.18, "shrinkage": 0.005, "name": "PMMA"},
|
|
|
|
|
|
"PBT": {"density": 1.31, "shrinkage": 0.015, "name": "PBT"},
|
|
|
|
|
|
"AlSi10Mg": {"density": 0.45, "shrinkage": 0.015, "name": "AlSi10Mg", "is_foam": True},
|
|
|
|
|
|
"AlSi12": {"density": 0.50, "shrinkage": 0.012, "name": "AlSi12", "is_foam": True},
|
|
|
|
|
|
"Pure Al Foam": {"density": 0.35, "shrinkage": 0.020, "name": "Pure Al Foam", "is_foam": True},
|
|
|
|
|
|
"AlSi7Mg": {"density": 0.40, "shrinkage": 0.018, "name": "AlSi7Mg", "is_foam": True},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
requested_material = material if material in material_properties else "ABS"
|
|
|
|
|
|
selected_material = material_properties.get(requested_material, material_properties["ABS"])
|
|
|
|
|
|
is_foam_material = selected_material.get("is_foam", False)
|
|
|
|
|
|
|
2026-03-10 22:21:54 +08:00
|
|
|
|
# 使用 MoldCavityGenerator 生成型腔数据
|
|
|
|
|
|
cavity_mesh_data = None
|
|
|
|
|
|
try:
|
|
|
|
|
|
if shape:
|
2026-03-14 01:29:40 +08:00
|
|
|
|
if is_foam_material:
|
|
|
|
|
|
aluminum_foam_generator.set_material(selected_material["name"])
|
|
|
|
|
|
cavity_result = aluminum_foam_generator.generate_mold_cavities(shape)
|
|
|
|
|
|
cavity_mesh_data = aluminum_foam_generator.generate_detailed_cavity_json(cavity_result)
|
|
|
|
|
|
logger.info(f"使用铝泡沫模具生成器: {selected_material['name']}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
cavity_result = mold_generator.generate_mold_cavities(shape)
|
|
|
|
|
|
cavity_mesh_data = mold_generator.generate_detailed_cavity_json(cavity_result)
|
2026-03-15 12:22:20 +08:00
|
|
|
|
logger.info(f"使用普通塑料模具生成器: {selected_material['name']}")
|
2026-03-10 22:21:54 +08:00
|
|
|
|
|
2026-03-14 01:29:40 +08:00
|
|
|
|
if cavity_mesh_data:
|
2026-03-14 01:36:53 +08:00
|
|
|
|
logger.info(f"型腔网格数据生成完成: {cavity_mesh_data.get('mold_cavities', {}).get('cavity', {}).get('vertex_count', 0)} 顶点")
|
2026-03-10 22:21:54 +08:00
|
|
|
|
except Exception as cavity_err:
|
|
|
|
|
|
logger.warning(f"型腔生成失败,使用简化数据: {cavity_err}")
|
2026-03-15 12:22:20 +08:00
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
2026-03-10 22:21:54 +08:00
|
|
|
|
cavity_mesh_data = None
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
2026-02-16 19:06:41 +08:00
|
|
|
|
# 4. 生成详细JSON数据(使用计算值)
|
2026-02-11 22:40:35 +08:00
|
|
|
|
await storage_service.update_task_status(
|
2026-02-15 00:42:56 +08:00
|
|
|
|
db_session, task_id, "processing", 60, "生成型腔详细数据"
|
2026-02-11 22:40:35 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-15 00:42:56 +08:00
|
|
|
|
# 使用模具生成器计算各项参数
|
|
|
|
|
|
volume_mm3 = geometry_data.get("volume", 0)
|
|
|
|
|
|
surface_area_mm2 = geometry_data.get("surface_area", 0)
|
|
|
|
|
|
bbox = geometry_data.get("bounding_box", {})
|
|
|
|
|
|
bbox_dims = bbox.get("dimensions", [0, 0, 0])
|
2026-03-10 22:14:56 +08:00
|
|
|
|
|
|
|
|
|
|
material_density = selected_material["density"]
|
|
|
|
|
|
shrinkage_rate = selected_material["shrinkage"]
|
2026-02-15 00:42:56 +08:00
|
|
|
|
|
2026-03-10 22:14:56 +08:00
|
|
|
|
# 计算产品重量
|
2026-02-15 00:42:56 +08:00
|
|
|
|
volume_cm3 = volume_mm3 / 1000
|
2026-03-10 22:14:56 +08:00
|
|
|
|
product_weight_g = volume_cm3 * material_density
|
2026-02-15 00:42:56 +08:00
|
|
|
|
|
|
|
|
|
|
# 计算投影面积(取X、Y方向)
|
|
|
|
|
|
if len(bbox_dims) >= 2:
|
|
|
|
|
|
projected_area_cm2 = (bbox_dims[0] * bbox_dims[1]) / 100
|
|
|
|
|
|
else:
|
|
|
|
|
|
projected_area_cm2 = 0
|
|
|
|
|
|
|
2026-03-10 22:14:56 +08:00
|
|
|
|
# 计算最优型腔数量
|
|
|
|
|
|
# 基于产品重量和投影面积计算
|
|
|
|
|
|
# 小产品(< 100g)可以多型腔,大产品(> 1000g)通常单型腔
|
|
|
|
|
|
if product_weight_g < 50:
|
|
|
|
|
|
cavity_count = 8 # 小产品,多型腔
|
|
|
|
|
|
elif product_weight_g < 100:
|
|
|
|
|
|
cavity_count = 4 # 中小产品
|
|
|
|
|
|
elif product_weight_g < 300:
|
|
|
|
|
|
cavity_count = 2 # 中等产品
|
|
|
|
|
|
elif product_weight_g < 1000:
|
|
|
|
|
|
cavity_count = 1 # 较大产品
|
|
|
|
|
|
else:
|
|
|
|
|
|
cavity_count = 1 # 大产品,单型腔
|
|
|
|
|
|
|
|
|
|
|
|
# 根据投影面积调整型腔数量
|
|
|
|
|
|
# 如果单型腔投影面积超过 400 cm²,减少型腔数量
|
|
|
|
|
|
single_cavity_area = projected_area_cm2
|
|
|
|
|
|
if single_cavity_area > 400:
|
|
|
|
|
|
cavity_count = 1
|
|
|
|
|
|
elif single_cavity_area > 200 and cavity_count > 2:
|
|
|
|
|
|
cavity_count = 2
|
|
|
|
|
|
|
|
|
|
|
|
# 计算总投影面积(包括流道系统)
|
|
|
|
|
|
# 流道系统约占型腔投影面积的 15-25%
|
|
|
|
|
|
runner_ratio = 0.20
|
|
|
|
|
|
total_projected_area = single_cavity_area * cavity_count * (1 + runner_ratio)
|
|
|
|
|
|
|
|
|
|
|
|
# 计算夹紧力(总投影面积 × 注塑压力 / 1000 吨)
|
|
|
|
|
|
# 注塑压力根据材料选择:ABS 约 600-800 kg/cm²
|
|
|
|
|
|
injection_pressure = 700 # kg/cm²
|
|
|
|
|
|
clamping_force_ton = int(total_projected_area * injection_pressure / 1000)
|
|
|
|
|
|
|
|
|
|
|
|
# 确保夹紧力在合理范围内
|
|
|
|
|
|
clamping_force_ton = max(50, min(clamping_force_ton, 3000))
|
2026-02-15 00:42:56 +08:00
|
|
|
|
|
|
|
|
|
|
# 计算壁厚范围
|
|
|
|
|
|
if surface_area_mm2 > 0 and volume_mm3 > 0:
|
|
|
|
|
|
avg_thickness_mm = (volume_mm3 / surface_area_mm2) * 0.6
|
|
|
|
|
|
wall_thickness_min = avg_thickness_mm * 0.7
|
|
|
|
|
|
wall_thickness_max = avg_thickness_mm * 1.3
|
|
|
|
|
|
else:
|
|
|
|
|
|
avg_thickness_mm = 2.5
|
|
|
|
|
|
wall_thickness_min = 2.0
|
|
|
|
|
|
wall_thickness_max = 3.0
|
|
|
|
|
|
|
|
|
|
|
|
# 计算复杂度评分
|
|
|
|
|
|
if surface_area_mm2 > 0 and volume_mm3 > 0:
|
|
|
|
|
|
complexity_score = min((avg_thickness_mm / 5.0), 1.0)
|
|
|
|
|
|
else:
|
|
|
|
|
|
complexity_score = 0.5
|
|
|
|
|
|
|
2026-03-10 22:14:56 +08:00
|
|
|
|
# 计算模具尺寸(基于型腔布局)
|
|
|
|
|
|
# 单型腔:产品尺寸 + 边距
|
|
|
|
|
|
# 多型腔:需要考虑型腔排列
|
|
|
|
|
|
cavity_spacing = 30 # 型腔间距 mm
|
|
|
|
|
|
edge_margin = 50 # 边缘余量 mm
|
|
|
|
|
|
|
|
|
|
|
|
if cavity_count == 1:
|
|
|
|
|
|
mold_length = max(bbox_dims[0] if len(bbox_dims) > 0 else 120, 120) + 2 * edge_margin
|
|
|
|
|
|
mold_width = max(bbox_dims[1] if len(bbox_dims) > 1 else 100, 100) + 2 * edge_margin
|
|
|
|
|
|
elif cavity_count == 2:
|
|
|
|
|
|
# 2型腔:并排排列
|
|
|
|
|
|
mold_length = 2 * max(bbox_dims[0] if len(bbox_dims) > 0 else 120, 120) + cavity_spacing + 2 * edge_margin
|
|
|
|
|
|
mold_width = max(bbox_dims[1] if len(bbox_dims) > 1 else 100, 100) + 2 * edge_margin
|
|
|
|
|
|
elif cavity_count == 4:
|
|
|
|
|
|
# 4型腔:2x2 排列
|
|
|
|
|
|
mold_length = 2 * max(bbox_dims[0] if len(bbox_dims) > 0 else 120, 120) + cavity_spacing + 2 * edge_margin
|
|
|
|
|
|
mold_width = 2 * max(bbox_dims[1] if len(bbox_dims) > 1 else 100, 100) + cavity_spacing + 2 * edge_margin
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 8型腔:2x4 排列
|
|
|
|
|
|
mold_length = 4 * max(bbox_dims[0] if len(bbox_dims) > 0 else 120, 120) + 3 * cavity_spacing + 2 * edge_margin
|
|
|
|
|
|
mold_width = 2 * max(bbox_dims[1] if len(bbox_dims) > 1 else 100, 100) + cavity_spacing + 2 * edge_margin
|
|
|
|
|
|
|
|
|
|
|
|
mold_height = max(bbox_dims[2] if len(bbox_dims) > 2 else 60, 60) + 80 # 包含冷却系统
|
2026-02-15 00:42:56 +08:00
|
|
|
|
|
2026-03-10 22:14:56 +08:00
|
|
|
|
# 计算分型线长度(基于型腔布局)
|
2026-02-15 00:50:22 +08:00
|
|
|
|
if len(bbox_dims) >= 2:
|
2026-03-10 22:14:56 +08:00
|
|
|
|
single_parting_line = 2 * (bbox_dims[0] + bbox_dims[1])
|
|
|
|
|
|
parting_line_length = single_parting_line * cavity_count
|
2026-02-15 00:50:22 +08:00
|
|
|
|
else:
|
|
|
|
|
|
parting_line_length = 0
|
|
|
|
|
|
|
2026-03-10 22:14:56 +08:00
|
|
|
|
# 估算成型周期(基于体积和壁厚)
|
2026-02-15 00:50:22 +08:00
|
|
|
|
# 周期 = 冷却时间 + 注塑时间 + 开合模时间
|
2026-03-10 22:14:56 +08:00
|
|
|
|
cooling_time = (wall_thickness_max ** 2) * 4 # 冷却时间与壁厚平方成正比
|
|
|
|
|
|
injection_time = max(3, volume_cm3 / 100) # 注塑时间
|
|
|
|
|
|
ejection_time = 3 # 顶出时间
|
|
|
|
|
|
cycle_time = cooling_time + injection_time + ejection_time + 5 # 开合模约5秒
|
|
|
|
|
|
|
|
|
|
|
|
# 根据型腔数量调整周期(多型腔需要更长冷却时间)
|
|
|
|
|
|
if cavity_count > 1:
|
|
|
|
|
|
cycle_time = cycle_time * (1 + 0.1 * (cavity_count - 1))
|
2026-02-15 00:50:22 +08:00
|
|
|
|
|
2026-02-11 22:40:35 +08:00
|
|
|
|
detailed_cavity_json = {
|
|
|
|
|
|
"metadata": {
|
|
|
|
|
|
"file_name": Path(file_path).name,
|
|
|
|
|
|
"analysis_date": datetime.now().isoformat(),
|
2026-03-10 22:14:56 +08:00
|
|
|
|
"shrinkage_rate": shrinkage_rate,
|
|
|
|
|
|
"draft_angle": 2.0,
|
|
|
|
|
|
"selected_material": selected_material["name"]
|
2026-02-11 22:40:35 +08:00
|
|
|
|
},
|
|
|
|
|
|
"product_analysis": {
|
2026-02-15 00:42:56 +08:00
|
|
|
|
"volume": volume_mm3,
|
|
|
|
|
|
"surface_area": surface_area_mm2,
|
|
|
|
|
|
"bounding_box": bbox
|
2026-02-11 22:40:35 +08:00
|
|
|
|
},
|
|
|
|
|
|
"manufacturing_info": {
|
2026-03-10 22:14:56 +08:00
|
|
|
|
"recommended_material": selected_material["name"],
|
|
|
|
|
|
"material_density": f"{material_density} g/cm³",
|
2026-02-15 00:42:56 +08:00
|
|
|
|
"estimated_clamping_force": f"{clamping_force_ton} 吨",
|
2026-02-11 22:40:35 +08:00
|
|
|
|
"estimated_mold_size": {
|
2026-02-15 00:42:56 +08:00
|
|
|
|
"length": int(mold_length),
|
|
|
|
|
|
"width": int(mold_width),
|
|
|
|
|
|
"height": int(mold_height)
|
2026-02-15 00:50:22 +08:00
|
|
|
|
},
|
2026-03-10 22:14:56 +08:00
|
|
|
|
"mold_material": "铝合金7075" if clamping_force_ton < 200 else "P20钢材",
|
|
|
|
|
|
"mold_hardness": "HB 150-170" if clamping_force_ton < 200 else "HRC 28-32",
|
2026-02-15 00:50:22 +08:00
|
|
|
|
"surface_finish": "Ra 0.8 μm",
|
|
|
|
|
|
"parting_line_length": f"{parting_line_length:.2f} mm",
|
2026-03-10 22:14:56 +08:00
|
|
|
|
"estimated_cycle_time": f"{int(cycle_time)} 秒",
|
|
|
|
|
|
"injection_pressure": f"{injection_pressure} kg/cm²"
|
2026-02-11 22:40:35 +08:00
|
|
|
|
},
|
|
|
|
|
|
"mold_cavities": {
|
2026-03-10 22:14:56 +08:00
|
|
|
|
"cavity_count": cavity_count,
|
2026-02-11 22:40:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-10 22:21:54 +08:00
|
|
|
|
|
|
|
|
|
|
# 合并型腔网格数据(如果有)
|
|
|
|
|
|
if cavity_mesh_data and "mold_cavities" in cavity_mesh_data:
|
2026-03-15 12:22:20 +08:00
|
|
|
|
mold_cavities = cavity_mesh_data["mold_cavities"]
|
|
|
|
|
|
if "cavity" in mold_cavities:
|
|
|
|
|
|
detailed_cavity_json["mold_cavities"]["cavity"] = mold_cavities["cavity"]
|
|
|
|
|
|
if "core" in mold_cavities:
|
|
|
|
|
|
detailed_cavity_json["mold_cavities"]["core"] = mold_cavities["core"]
|
|
|
|
|
|
if "parting_surface" in mold_cavities:
|
|
|
|
|
|
detailed_cavity_json["mold_cavities"]["parting_surface"] = mold_cavities["parting_surface"]
|
|
|
|
|
|
logger.info(f"型腔网格数据已合并: cavity {mold_cavities.get('cavity', {}).get('vertex_count', 0)} 顶点")
|
|
|
|
|
|
|
|
|
|
|
|
# 添加型腔关键信息
|
|
|
|
|
|
detailed_cavity_json["mold_cavities"]["cavity_key_info"] = {
|
|
|
|
|
|
"geometric_characteristics": {
|
|
|
|
|
|
"product_weight": f"{product_weight_g:.2f} g",
|
|
|
|
|
|
"wall_thickness_range": f"{wall_thickness_min:.2f} - {wall_thickness_max:.2f} mm",
|
|
|
|
|
|
"complexity_score": round(complexity_score, 2),
|
|
|
|
|
|
"product_volume": f"{volume_cm3:.2f} cm³",
|
|
|
|
|
|
"projected_area": f"{projected_area_cm2:.2f} cm²"
|
|
|
|
|
|
},
|
|
|
|
|
|
"quality_considerations": {
|
|
|
|
|
|
"potential_weld_lines": "center" if cavity_count > 1 else "minimal",
|
|
|
|
|
|
"sink_mark_areas": "thick_sections" if wall_thickness_max > 4 else "minimal",
|
|
|
|
|
|
"warpage_risk": "medium" if wall_thickness_max > 5 else "low"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
2026-02-16 19:06:41 +08:00
|
|
|
|
# 5. 生成关键信息(模拟)
|
2026-02-11 22:40:35 +08:00
|
|
|
|
cavity_key_info = detailed_cavity_json["mold_cavities"]["cavity_key_info"]
|
|
|
|
|
|
|
2026-02-16 19:06:41 +08:00
|
|
|
|
# 6. 保存几何数据到数据库
|
2026-02-11 22:40:35 +08:00
|
|
|
|
await storage_service.update_task_status(
|
|
|
|
|
|
db_session, task_id, "processing", 70, "保存几何数据"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
geometry_record = await storage_service.save_geometry_data(
|
|
|
|
|
|
db_session,
|
|
|
|
|
|
stp_file_id,
|
|
|
|
|
|
geometry_data,
|
|
|
|
|
|
geometry_data.get("analysis_method", "mold_cavity")
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-16 19:06:41 +08:00
|
|
|
|
# 7. 保存模具型腔数据
|
2026-02-11 22:40:35 +08:00
|
|
|
|
await storage_service.save_mold_cavity_data(
|
|
|
|
|
|
db_session,
|
|
|
|
|
|
stp_file_id,
|
|
|
|
|
|
detailed_cavity_json
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-07 01:18:25 +08:00
|
|
|
|
# 8. 生成HTML可视化(包含型腔信息和点云数据)
|
2026-02-11 22:40:35 +08:00
|
|
|
|
await storage_service.update_task_status(
|
|
|
|
|
|
db_session, task_id, "processing", 85, "生成可视化报告"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-07 01:40:37 +08:00
|
|
|
|
# 获取点云数据 - mesh_result 直接返回 points 和 normals
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
2026-03-07 01:18:25 +08:00
|
|
|
|
|
2026-02-11 22:40:35 +08:00
|
|
|
|
html_file_path = html_generator.generate_and_save_visualization(
|
|
|
|
|
|
geometry_data,
|
2026-02-15 00:54:58 +08:00
|
|
|
|
Path(file_path).name,
|
2026-03-07 01:18:25 +08:00
|
|
|
|
cavity_data=detailed_cavity_json,
|
|
|
|
|
|
pointcloud_data=pointcloud_data
|
2026-02-11 22:40:35 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 保存HTML文件信息
|
|
|
|
|
|
html_record = await storage_service.save_html_file(
|
|
|
|
|
|
db_session,
|
|
|
|
|
|
stp_file_id,
|
|
|
|
|
|
Path(html_file_path).name,
|
|
|
|
|
|
html_file_path
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-16 19:06:41 +08:00
|
|
|
|
# 9. 分析模具设计
|
2026-04-19 23:41:35 +08:00
|
|
|
|
analysis_result = geometry_analyzer.analyze_mold_design(geometry_data, shape=shape)
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
2026-03-06 23:37:46 +08:00
|
|
|
|
# 9.5 保存完整的分析结果到数据库
|
2026-03-04 23:54:32 +08:00
|
|
|
|
if analysis_result:
|
|
|
|
|
|
await storage_service.save_features_and_recommendations(
|
|
|
|
|
|
db_session,
|
|
|
|
|
|
stp_file_id,
|
|
|
|
|
|
analysis_result.get("detected_features", []),
|
|
|
|
|
|
analysis_result.get("design_recommendations", [])
|
|
|
|
|
|
)
|
2026-03-06 23:37:46 +08:00
|
|
|
|
|
|
|
|
|
|
# 保存质量指标和分析摘要到数据库
|
2026-03-07 00:02:34 +08:00
|
|
|
|
await _save_analysis_metrics(db_session, stp_file_id, analysis_result)
|
2026-03-04 23:54:32 +08:00
|
|
|
|
|
2026-03-07 03:04:15 +08:00
|
|
|
|
# 9.6 更新STP文件的分析摘要字段(用于快速查询)
|
|
|
|
|
|
await storage_service.update_stp_file_analysis_summary(
|
|
|
|
|
|
db_session,
|
|
|
|
|
|
stp_file_id,
|
|
|
|
|
|
volume=volume_mm3,
|
|
|
|
|
|
surface_area=surface_area_mm2,
|
|
|
|
|
|
product_weight=product_weight_g
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-08 22:58:25 +08:00
|
|
|
|
# 9.7 FreeCAD 几何验证(可通过配置禁用)
|
2026-03-08 02:53:27 +08:00
|
|
|
|
verification_result = None
|
2026-03-08 22:58:25 +08:00
|
|
|
|
from config.settings import settings
|
|
|
|
|
|
|
|
|
|
|
|
if settings.ENABLE_FREECAD_VERIFICATION:
|
|
|
|
|
|
await storage_service.update_task_status(
|
|
|
|
|
|
db_session, task_id, "processing", 90, "FreeCAD几何验证"
|
|
|
|
|
|
)
|
2026-03-08 02:53:27 +08:00
|
|
|
|
|
2026-03-08 22:58:25 +08:00
|
|
|
|
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 _save_verification_metrics(
|
|
|
|
|
|
db_session,
|
|
|
|
|
|
stp_file_id,
|
|
|
|
|
|
verification_result
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"FreeCAD验证完成: {verification_result.get('status', 'unknown') if verification_result else 'failed'}")
|
|
|
|
|
|
except Exception as ve:
|
|
|
|
|
|
logger.warning(f"FreeCAD验证失败(不影响主流程): {ve}")
|
|
|
|
|
|
verification_result = {"status": "error", "error": str(ve)}
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.info("FreeCAD验证已禁用(设置 ENABLE_FREECAD_VERIFICATION=true 启用)")
|
|
|
|
|
|
verification_result = {"status": "disabled", "reason": "FreeCAD验证已禁用"}
|
2026-03-08 02:53:27 +08:00
|
|
|
|
|
2026-05-11 15:35:54 +08:00
|
|
|
|
# 9.8 LLM 增强分析
|
2026-05-11 11:35:15 +08:00
|
|
|
|
llm_report = None
|
|
|
|
|
|
if analysis_result:
|
2026-05-18 16:45:17 +08:00
|
|
|
|
side_action_ai = await llm_service.generate_side_action_analysis(
|
|
|
|
|
|
analysis_result, detailed_cavity_json
|
|
|
|
|
|
)
|
|
|
|
|
|
design_report = await llm_service.generate_design_report(
|
|
|
|
|
|
analysis_result, detailed_cavity_json
|
|
|
|
|
|
)
|
|
|
|
|
|
llm_report = llm_service.compose_llm_report(
|
|
|
|
|
|
design_report, side_action_ai
|
|
|
|
|
|
)
|
2026-05-11 11:35:15 +08:00
|
|
|
|
|
2026-02-16 19:06:41 +08:00
|
|
|
|
# 10. 完成处理
|
2026-02-11 22:40:35 +08:00
|
|
|
|
await storage_service.update_stp_file_status(db_session, stp_file_id, "completed")
|
|
|
|
|
|
await storage_service.update_task_status(
|
|
|
|
|
|
db_session, task_id, "completed", 100, "模具型腔生成完成"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 更新内存任务状态
|
|
|
|
|
|
tasks[task_id]["geometry_data"] = geometry_data
|
|
|
|
|
|
tasks[task_id]["analysis_result"] = analysis_result
|
|
|
|
|
|
tasks[task_id]["cavity_data"] = detailed_cavity_json
|
2026-04-19 23:41:35 +08:00
|
|
|
|
tasks[task_id]["cavity_shapes"] = cavity_result
|
2026-02-15 01:09:53 +08:00
|
|
|
|
tasks[task_id]["key_info"] = detailed_cavity_json # 传递完整数据给前端
|
2026-03-07 00:56:51 +08:00
|
|
|
|
tasks[task_id]["html_file"] = f"/html/{Path(html_file_path).name}" # 只使用文件名
|
2026-03-08 03:11:28 +08:00
|
|
|
|
tasks[task_id]["verification"] = verification_result # 添加验证结果
|
2026-05-11 11:35:15 +08:00
|
|
|
|
tasks[task_id]["llm_report"] = llm_report
|
2026-02-11 22:40:35 +08:00
|
|
|
|
tasks[task_id]["status"] = ProcessingStatus.COMPLETED
|
|
|
|
|
|
tasks[task_id]["completed_at"] = str(datetime.now())
|
|
|
|
|
|
|
2026-02-16 00:18:27 +08:00
|
|
|
|
# 调试日志
|
2026-02-11 22:40:35 +08:00
|
|
|
|
logger.info(f"模具型腔生成完成: {task_id}")
|
2026-02-16 00:18:27 +08:00
|
|
|
|
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', {})}")
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"模具型腔生成失败: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
await storage_service.update_stp_file_status(db_session, stp_file_id, "failed")
|
|
|
|
|
|
await storage_service.update_task_status(
|
|
|
|
|
|
db_session, task_id, "failed", error_message=str(e)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
tasks[task_id]["status"] = ProcessingStatus.FAILED
|
|
|
|
|
|
tasks[task_id]["error"] = str(e)
|
2026-03-15 13:33:47 +08:00
|
|
|
|
tasks[task_id]["completed_at"] = str(datetime.now())
|