from pathlib import Path import os from fastapi import APIRouter, Depends, HTTPException, Request from services.auth_service import get_current_active_user from services.redis_task_manager import redis_task_manager from models.database import User from utils.logger import get_logger logger = get_logger(__name__) router = APIRouter() _api_routes_cache = {} def _get_cached(key): global _api_routes_cache if key not in _api_routes_cache: try: from api import routes except Exception as e: logger.warning(f"api.routes 模块加载失败: {e}") _api_routes_cache["__error__"] = str(e) return None _api_routes_cache.clear() _api_routes_cache.update({ "tasks": routes.tasks, "cavity_layout_optimizer": routes.cavity_layout_optimizer, "mold_system_designer": routes.mold_system_designer, "side_action_designer": routes.side_action_designer, "mold_cam_designer": routes.mold_cam_designer, "collision_detector": routes.collision_detector, "toolpath_optimizer": routes.toolpath_optimizer, "edm_designer": routes.edm_designer, "machining_simulator": routes.machining_simulator, "cad_exporter": routes.cad_exporter, }) return _api_routes_cache.get(key) async def _get_task_data(task_id: str) -> dict: task = await redis_task_manager.get_task(task_id) if task: return task tasks = _get_cached("tasks") if tasks and task_id in tasks: return tasks[task_id] return None @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 之间") optimizer = _get_cached("cavity_layout_optimizer") if not optimizer: raise HTTPException(503, "服务不可用:核心模块未加载") result = 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") ds = _get_cached("mold_system_designer") if not ds: raise HTTPException(503, "服务不可用:核心模块未加载") result = ds.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), ): body = await request.json() task_id = body.get("task_id") if not task_id: raise HTTPException(404, "缺少 task_id") task_data = await _get_task_data(task_id) if not task_data: 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}) if not task_id: raise HTTPException(404, "缺少 task_id") task_data = await _get_task_data(task_id) if not task_data: raise HTTPException(404, "任务不存在") sd = _get_cached("side_action_designer") if not sd: raise HTTPException(503, "服务不可用:核心模块未加载") result = sd.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), ): 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") cam = _get_cached("mold_cam_designer") if not cam: raise HTTPException(503, "服务不可用:核心模块未加载") result = cam.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") cd = _get_cached("collision_detector") if not cd: raise HTTPException(503, "服务不可用:核心模块未加载") result = cd.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") to = _get_cached("toolpath_optimizer") if not to: raise HTTPException(503, "服务不可用:核心模块未加载") result = to.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), ): 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) ed = _get_cached("edm_designer") if not ed: raise HTTPException(503, "服务不可用:核心模块未加载") result = ed.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) ms = _get_cached("machining_simulator") if not ms: raise HTTPException(503, "服务不可用:核心模块未加载") result = ms.simulate_machining(operations, stock_bbox, resolution) return {"status": "success", "data": result} @router.post("/export-mold") async def export_mold_results( request: Request, current_user: User = Depends(get_current_active_user), ): 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: raise HTTPException(404, "缺少 task_id") task_data = await _get_task_data(task_id) if not task_data: raise HTTPException(404, "任务不存在") cavity_shapes = task_data.get("cavity_shapes") filename = task_data.get("filename", f"mold_{task_id}") if not cavity_shapes: file_path = task_data.get("file_path") if file_path and os.path.exists(str(file_path)): cavity_shapes = await _reparse_stp_for_export(str(file_path), task_data.get("material", "ABS")) if not cavity_shapes: raise HTTPException(400, "该任务尚未完成模具生成或形状数据不可用") exporter = _get_cached("cad_exporter") if not exporter: raise HTTPException(503, "服务不可用:核心模块未加载,请检查 PythonOCC 环境") base_filename = Path(filename).stem result = 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), ): from fastapi.responses import FileResponse exporter = _get_cached("cad_exporter") if not exporter: raise HTTPException(503, "服务不可用") full_path = os.path.join(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(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), ): exporter = _get_cached("cad_exporter") if not exporter: raise HTTPException(503, "服务不可用") result = exporter.get_export_recommendations(target) return {"status": "success", "data": result} async def _reparse_stp_for_export(file_path: str, material: str = "ABS") -> dict: try: from core.stp_parser import STPParser from core.mold_generator import MoldCavityGenerator stp_parser = STPParser() shape = stp_parser.load_step_file(Path(file_path)) mold_gen = MoldCavityGenerator(shrinkage_rate=0.005) mold_gen.set_material(material) cavity_result = mold_gen.generate_mold_cavities(shape) logger.info(f"重新解析 STP 用于导出: {file_path}") return cavity_result except Exception as e: logger.warning(f"重新解析 STP 导出失败: {e}") return None