Files
geMoldInsight/src/moldinsight/api/design_router.py
T
cjw e728dcd226 批次4后续专项完成:D11清偿 + OCC方案B实施 + 部署参数 + D2诚实标注 + CI门禁
① D11 HTML 报告 RustFS 单源化(TECH_DEBT P2 清偿):可视化产物写任务临时目录后
   裸传报告键 html/reports/{filename}(文件名寻址),/html StaticFiles 挂载删除,
   新增 html_report_router 根路径代理(报告键→遗留 JSON 包装→本地卷兜底→404,
   防穿越);URL 形状 /html/{filename} 不变,持久化引用零迁移;celery 摘除
   html_data 卷,镜像不再烤入陈旧报告;顺带删除 get_stp_file_with_data 死数据块
② OCC 方案 B(D10 清偿):run_occ(op_name, payload) 契约 + 常驻工作进程池
   (occ_process_pool + occ_worker 操作注册表),超时/崩溃 terminate 换新补位、
   任务级超时 recover 整体重建,残留线程泄漏根治;TopoDS 不跨进程(generate_cavity
   分模 + 方案 STEP 持久化全在子进程内,返回 export_manifest);删除内存形状缓存链、
   CADExporter.export_mold_results、shape_loader(→ stp_materializer)
③ OCC 方案 A 部署参数:CELERY_CONCURRENCY / CELERY_MAX_TASKS_PER_CHILD 进
   Dockerfile.celery + compose + .env.example
④ D2 诚实标注:铝价响应带 source: "simulated",前端按来源渲染标注(原硬编码
   "上海期货交易所"属虚假声明),死代码 getAluminumPrice 删除
⑤ CI 门禁:.gitea/workflows/ci.yml 三 job(pytest / 前端构建含 vue-tsc /
   openapi 漂移检测)

接口变更三件套随批完成(openapi 76→77 paths + gen:api + 前端构建通过;方案 B
接口面零变化)。测试基线 143 passed, 0 skipped(新增 16 项)。文档六处同步。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-18 17:22:01 +08:00

186 lines
6.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# api/design_router.py
"""模具结构设计类接口(批次 3 自 advanced_router 拆分,D1)。
- 请求体一律 Pydantic 模型(原 request.json() 手动解析退役,校验失败统一 422)
- 纯 Python 设计计算统一经 asyncio.to_thread 投放线程池,不阻塞事件循环;
OCC 相关的倒扣检测经 processing_service.run_occ 的常驻 OCC 进程池
(PythonOCC 非线程安全,进程内串行;见 OCC_THROUGHPUT.md 方案 B)
"""
import asyncio
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession
from shared.services.auth_service import get_current_active_user
from shared.database.database import get_db_session
from shared.models.identity import User
from moldinsight.services.processing_service import processing_service
from moldinsight.services.task_query_service import TaskQueryService
from moldinsight.api.core_modules import get_core_module
router = APIRouter()
# ---- 请求模型 ----
class BBox3D(BaseModel):
dimensions: List[float] = Field(default_factory=lambda: [100.0, 100.0, 50.0])
min: Optional[List[float]] = None
max: Optional[List[float]] = None
class MoldSize(BaseModel):
length: float = 300.0
width: float = 300.0
height: float = 200.0
class OptimizeLayoutRequest(BaseModel):
product_bbox: BBox3D = Field(default_factory=BBox3D)
cavity_count: int = Field(default=1, ge=1, le=64)
mold_base_size: Optional[BBox3D] = None
layout_type: str = "auto"
class CoolingDesignRequest(BaseModel):
mold_size: MoldSize = Field(default_factory=MoldSize)
product_bbox: BBox3D = Field(default_factory=BBox3D)
material: str = "ABS"
cavity_count: int = Field(default=1, ge=1, le=64)
cycle_time_target: Optional[float] = None
class GatingDesignRequest(BaseModel):
product_bbox: BBox3D = Field(default_factory=BBox3D)
material: str = "ABS"
cavity_count: int = Field(default=1, ge=1, le=64)
gate_type: str = "auto"
layout_positions: Optional[List[List[float]]] = None
class MoldSystemDesignRequest(BaseModel):
mold_size: MoldSize = Field(default_factory=MoldSize)
product_bbox: BBox3D = Field(default_factory=BBox3D)
material: str = "ABS"
cavity_count: int = Field(default=1, ge=1, le=64)
gate_type: str = "auto"
cycle_time_target: Optional[float] = None
layout_positions: Optional[List[List[float]]] = None
class UndercutDetectRequest(BaseModel):
task_id: str
parting_direction: List[float] = Field(default_factory=lambda: [0.0, 0.0, 1.0])
mold_size: MoldSize = Field(default_factory=MoldSize)
@router.post("/optimize-layout")
async def optimize_cavity_layout(
body: OptimizeLayoutRequest,
current_user: User = Depends(get_current_active_user),
):
optimizer = get_core_module("cavity_layout_optimizer")
if not optimizer:
raise HTTPException(503, "服务不可用:核心模块未加载")
# 纯 Python 布局优化,投放线程池避免阻塞事件循环
result = await asyncio.to_thread(
optimizer.optimize_layout,
product_bbox=body.product_bbox.model_dump(exclude_none=True),
cavity_count=body.cavity_count,
mold_base_size=body.mold_base_size.model_dump(exclude_none=True) if body.mold_base_size else None,
layout_type=body.layout_type,
)
return {"status": "success", "data": result}
@router.post("/design-cooling")
async def design_cooling_system(
body: CoolingDesignRequest,
current_user: User = Depends(get_current_active_user),
):
from moldinsight.core.mold_system_designer import CoolingSystemDesigner
designer = CoolingSystemDesigner()
result = await asyncio.to_thread(
designer.design_cooling_system,
mold_size=body.mold_size.model_dump(),
product_bbox=body.product_bbox.model_dump(exclude_none=True),
material=body.material,
cavity_count=body.cavity_count,
cycle_time_target=body.cycle_time_target,
)
return {"status": "success", "data": result}
@router.post("/design-gating")
async def design_gating_system(
body: GatingDesignRequest,
current_user: User = Depends(get_current_active_user),
):
from moldinsight.core.mold_system_designer import GatingSystemDesigner
designer = GatingSystemDesigner()
result = await asyncio.to_thread(
designer.design_gating_system,
product_bbox=body.product_bbox.model_dump(exclude_none=True),
material=body.material,
cavity_count=body.cavity_count,
gate_type=body.gate_type,
layout_positions=body.layout_positions,
)
return {"status": "success", "data": result}
@router.post("/design-mold-system")
async def design_complete_mold_system(
body: MoldSystemDesignRequest,
current_user: User = Depends(get_current_active_user),
):
ds = get_core_module("mold_system_designer")
if not ds:
raise HTTPException(503, "服务不可用:核心模块未加载")
result = await asyncio.to_thread(
ds.design_complete_system,
mold_size=body.mold_size.model_dump(),
product_bbox=body.product_bbox.model_dump(exclude_none=True),
material=body.material,
cavity_count=body.cavity_count,
gate_type=body.gate_type,
cycle_time_target=body.cycle_time_target,
layout_positions=body.layout_positions,
)
return {"status": "success", "data": result}
@router.post("/detect-undercuts")
async def detect_undercuts(
body: UndercutDetectRequest,
current_user: User = Depends(get_current_active_user),
db_session: AsyncSession = Depends(get_db_session),
):
# 归属校验统一走 TaskQueryService(与 /api/status 共用,含 404/403 语义)
await TaskQueryService.ensure_task_access(db_session, body.task_id, current_user.id)
sd = get_core_module("side_action_designer")
if not sd:
raise HTTPException(503, "服务不可用:核心模块未加载")
# 方案 B:从持久化 STP 原件落盘,倒扣分析在常驻 OCC 子进程内完成(形状不跨进程)
from moldinsight.services.stp_materializer import get_stp_materializer
stp_path = await get_stp_materializer().materialize_stp_for_task(db_session, body.task_id)
if stp_path is None:
raise HTTPException(410, "任务几何不可用:无法从存储重建 STP 形状,请重新上传分析")
try:
result = await processing_service.run_occ(
"detect_undercuts",
{
"stp_path": str(stp_path),
"parting_direction": body.parting_direction,
"mold_size": body.mold_size.model_dump(),
},
)
finally:
stp_path.unlink(missing_ok=True)
return {"status": "success", "data": result}