2026-02-11 22:40:35 +08:00
|
|
|
|
# api/routes.py
|
2026-05-28 17:59:39 +08:00
|
|
|
|
from fastapi import APIRouter, UploadFile, File, HTTPException, 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 utils.file_handler import FileHandler
|
|
|
|
|
|
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.task_query_service import TaskQueryService
|
2026-02-11 22:40:35 +08:00
|
|
|
|
from database.database import get_db_session
|
2026-05-29 09:47:21 +08:00
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
from celery_tasks import process_stp_task
|
|
|
|
|
|
_use_celery = True
|
|
|
|
|
|
except ImportError:
|
|
|
|
|
|
process_stp_task = None
|
|
|
|
|
|
_use_celery = False
|
2026-02-11 22:40:35 +08:00
|
|
|
|
from utils.logger import get_logger
|
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
file_handler = FileHandler()
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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(
|
|
|
|
|
|
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)
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
2026-04-23 23:37:39 +08:00
|
|
|
|
# 后台处理走统一编排服务,避免请求会话在后台失效
|
2026-05-25 18:35:33 +08:00
|
|
|
|
process_params = {"material": material}
|
2026-05-29 09:47:21 +08:00
|
|
|
|
if _use_celery:
|
|
|
|
|
|
process_stp_task.delay(task_id, str(file_path), stp_file.id, process_params)
|
|
|
|
|
|
logger.info(f"[UPLOAD] Celery 任务已调度: task_id={task_id}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
import asyncio
|
|
|
|
|
|
from services.processing_service import processing_service
|
|
|
|
|
|
asyncio.create_task(processing_service.process_file_with_storage(
|
|
|
|
|
|
task_id, str(file_path), stp_file.id, process_params
|
|
|
|
|
|
))
|
|
|
|
|
|
logger.info(f"[UPLOAD] 直接后台处理: task_id={task_id} (celery 未安装)")
|
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:
|
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,
|
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-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")
|
|
|
|
|
|
|
2026-05-25 18:35:33 +08:00
|
|
|
|
if not task_id:
|
|
|
|
|
|
raise HTTPException(404, "缺少 task_id")
|
|
|
|
|
|
|
|
|
|
|
|
task_data = await redis_task_manager.get_task(task_id)
|
|
|
|
|
|
if not task_data:
|
2026-04-19 23:41:35 +08:00
|
|
|
|
raise HTTPException(404, "任务不存在")
|
|
|
|
|
|
|
|
|
|
|
|
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})
|
|
|
|
|
|
|
2026-05-25 18:35:33 +08:00
|
|
|
|
if not task_id:
|
|
|
|
|
|
raise HTTPException(404, "缺少 task_id")
|
|
|
|
|
|
|
|
|
|
|
|
task_data = await redis_task_manager.get_task(task_id)
|
|
|
|
|
|
if not task_data:
|
2026-04-19 23:41:35 +08:00
|
|
|
|
raise HTTPException(404, "任务不存在")
|
|
|
|
|
|
|
|
|
|
|
|
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"])
|
2026-05-25 18:35:33 +08:00
|
|
|
|
scheme_id = body.get("scheme_id")
|
2026-04-19 23:41:35 +08:00
|
|
|
|
|
2026-05-25 18:35:33 +08:00
|
|
|
|
if not task_id:
|
|
|
|
|
|
raise HTTPException(404, "缺少 task_id")
|
2026-04-19 23:41:35 +08:00
|
|
|
|
|
2026-05-25 18:35:33 +08:00
|
|
|
|
cavity_shapes = processing_service.get_export_shapes(task_id, scheme_id=scheme_id)
|
2026-04-19 23:41:35 +08:00
|
|
|
|
if not cavity_shapes:
|
2026-05-25 18:35:33 +08:00
|
|
|
|
task_data = await redis_task_manager.get_task(task_id)
|
|
|
|
|
|
if not task_data:
|
|
|
|
|
|
raise HTTPException(404, "任务不存在")
|
|
|
|
|
|
filename = task_data.get("filename", f"mold_{task_id}")
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
|
400,
|
|
|
|
|
|
f"该任务的 OCC 形状数据已过期(仅保留 STEP 导出文件),"
|
|
|
|
|
|
f"请通过历史页面的下载链接获取已导出的 STEP 文件",
|
|
|
|
|
|
)
|
2026-04-19 23:41:35 +08:00
|
|
|
|
|
2026-05-25 18:35:33 +08:00
|
|
|
|
task_data = await redis_task_manager.get_task(task_id)
|
|
|
|
|
|
base_filename = (
|
|
|
|
|
|
Path(task_data.get("filename", f"mold_{task_id}")).stem
|
|
|
|
|
|
if task_data
|
|
|
|
|
|
else f"mold_{task_id}"
|
|
|
|
|
|
)
|
2026-04-19 23:41:35 +08:00
|
|
|
|
|
|
|
|
|
|
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-02-11 22:40:35 +08:00
|
|
|
|
|