- 新增XCAF转换器,直接从STP到GLB保留完整装配层级 - 修复HTTP API与CLI参数不一致问题 - 修复静默吞没错误的问题,遵循快速失败原则 - 清理旧文件,整理测试文件到tests目录 - 添加URL下载支持,可直接转换远程STP文件 - 更新文档,准确描述XCAF装配保留功能 技术改进: - 使用STEPCAFControl_Reader读取带装配信息的STEP文件 - 通过RWGltf_CafWriter直接导出GLB,无需STL中间格式 - 支持CPU多线程并行三角化 - HTTP API和CLI使用完全一致的转换参数 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
155 lines
5.0 KiB
Python
155 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
API路由定义
|
||
"""
|
||
|
||
import os
|
||
from fastapi import APIRouter, HTTPException, BackgroundTasks
|
||
from api.schemas import (
|
||
ConvertRequestSchema,
|
||
ConvertResponseSchema,
|
||
TaskStatusSchema,
|
||
HealthResponseSchema
|
||
)
|
||
from services.task_manager import task_manager
|
||
from core.models import ConvertOptions, TaskStatus, PrecisionOptions
|
||
from utils.network_helper import is_url, validate_url
|
||
from utils.helpers import is_stp_file
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
@router.get("/health", response_model=HealthResponseSchema)
|
||
async def health_check():
|
||
"""健康检查接口"""
|
||
return HealthResponseSchema()
|
||
|
||
|
||
@router.post("/api/v1/convert", response_model=ConvertResponseSchema)
|
||
async def convert_file(request: ConvertRequestSchema):
|
||
"""转换文件接口"""
|
||
|
||
# 验证输入(文件或URL)
|
||
if is_url(request.input_path):
|
||
# 验证URL
|
||
is_valid, error_msg = validate_url(request.input_path)
|
||
if not is_valid:
|
||
raise HTTPException(status_code=400, detail=f"URL验证失败: {error_msg}")
|
||
|
||
# 检查URL是否指向STP文件
|
||
if not is_stp_file(request.input_path):
|
||
raise HTTPException(status_code=400, detail="URL必须指向STP或STEP格式的文件")
|
||
else:
|
||
# 验证本地文件
|
||
if not os.path.isfile(request.input_path):
|
||
raise HTTPException(status_code=400, detail=f"输入文件不存在: {request.input_path}")
|
||
|
||
# 验证本地文件扩展名
|
||
if not is_stp_file(request.input_path):
|
||
raise HTTPException(status_code=400, detail="输入文件必须是STP或STEP格式")
|
||
|
||
# 验证输出文件扩展名
|
||
if not request.output_path.lower().endswith('.glb'):
|
||
raise HTTPException(status_code=400, detail="输出文件必须是GLB格式")
|
||
|
||
# 确保输出目录存在
|
||
output_dir = os.path.dirname(request.output_path)
|
||
if output_dir and not os.path.exists(output_dir):
|
||
try:
|
||
os.makedirs(output_dir, exist_ok=True)
|
||
except Exception as e:
|
||
raise HTTPException(status_code=400, detail=f"无法创建输出目录: {str(e)}")
|
||
|
||
# 创建精度选项
|
||
precision = PrecisionOptions(
|
||
linear_deflection=request.options.precision.linear_deflection,
|
||
angular_deflection=request.options.precision.angular_deflection,
|
||
mesh_quality=request.options.precision.mesh_quality
|
||
)
|
||
|
||
# 创建转换选项
|
||
options = ConvertOptions(
|
||
auto_scale=request.options.auto_scale,
|
||
auto_center=request.options.auto_center,
|
||
precision=precision
|
||
)
|
||
|
||
# 创建任务
|
||
task = task_manager.create_task(
|
||
input_path=request.input_path,
|
||
output_path=request.output_path,
|
||
options=options
|
||
)
|
||
|
||
# 启动任务
|
||
success = await task_manager.start_task(task.task_id)
|
||
if not success:
|
||
raise HTTPException(status_code=500, detail="任务启动失败")
|
||
|
||
return ConvertResponseSchema(
|
||
task_id=task.task_id,
|
||
status=task.status.value,
|
||
message="任务已创建并开始处理"
|
||
)
|
||
|
||
|
||
@router.get("/api/v1/status/{task_id}", response_model=TaskStatusSchema)
|
||
async def get_task_status(task_id: str):
|
||
"""获取任务状态接口"""
|
||
|
||
task = task_manager.get_task(task_id)
|
||
if not task:
|
||
raise HTTPException(status_code=404, detail="任务不存在")
|
||
|
||
return TaskStatusSchema(
|
||
task_id=task.task_id,
|
||
status=task.status.value,
|
||
progress=task.progress,
|
||
message=task.message,
|
||
created_at=task.created_at,
|
||
started_at=task.started_at,
|
||
completed_at=task.completed_at,
|
||
error_message=task.error_message
|
||
)
|
||
|
||
|
||
@router.delete("/api/v1/task/{task_id}")
|
||
async def cancel_task(task_id: str):
|
||
"""取消任务接口"""
|
||
|
||
task = task_manager.get_task(task_id)
|
||
if not task:
|
||
raise HTTPException(status_code=404, detail="任务不存在")
|
||
|
||
if task.status in [TaskStatus.COMPLETED, TaskStatus.FAILED]:
|
||
raise HTTPException(status_code=400, detail="任务已完成,无法取消")
|
||
|
||
success = task_manager.cancel_task(task_id)
|
||
if success:
|
||
return {"message": "任务已取消"}
|
||
else:
|
||
return {"message": "任务未在运行中"}
|
||
|
||
|
||
@router.get("/api/v1/tasks")
|
||
async def list_tasks():
|
||
"""获取所有任务列表接口"""
|
||
|
||
tasks = task_manager.get_all_tasks()
|
||
task_list = []
|
||
|
||
for task_id, task in tasks.items():
|
||
task_list.append({
|
||
"task_id": task.task_id,
|
||
"status": task.status.value,
|
||
"progress": task.progress,
|
||
"input_path": task.input_path,
|
||
"output_path": task.output_path,
|
||
"created_at": task.created_at,
|
||
"message": task.message
|
||
})
|
||
|
||
# 按创建时间倒序排列
|
||
task_list.sort(key=lambda x: x["created_at"], reverse=True)
|
||
|
||
return {"tasks": task_list, "total": len(task_list)} |