"""API route for IFC file upload and parsing.""" import os from pathlib import Path from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, UploadFile from fastapi.responses import FileResponse from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings from app.models.database import get_db from app.schemas.element import ProjectOut from app.services.ifc_parser import parse_ifc_file from app.services.ifc_converter import convert_ifc_bytes_to_glb router = APIRouter(tags=["upload"]) GLB_DIR = Path(settings.upload_dir) / "glb" @router.post("/upload", response_model=ProjectOut) async def upload_ifc(file: UploadFile, db: AsyncSession = Depends(get_db)): """Upload an IFC file, parse it, and store elements in the database.""" if not file.filename.lower().endswith(".ifc"): raise HTTPException(status_code=400, detail="Only .ifc files are accepted") contents = await file.read() # Parse semantic data into database try: project = await parse_ifc_file( filename=file.filename, content=contents, db=db, ) except Exception as e: raise HTTPException(status_code=422, detail=f"Failed to parse IFC file: {e}") # Convert geometry to glb try: GLB_DIR.mkdir(parents=True, exist_ok=True) glb_path = str(GLB_DIR / f"{project.id}.glb") await convert_ifc_bytes_to_glb(contents, glb_path) except Exception as e: # Log but don't fail — semantic data is still usable without 3D print(f"WARNING: glb conversion failed: {e}") return project @router.get("/projects/{project_id}/model.glb") async def get_model_glb(project_id: UUID): """Serve the pre-converted glb file for 3D viewing.""" glb_path = GLB_DIR / f"{project_id}.glb" if not glb_path.exists(): raise HTTPException(status_code=404, detail="3D model not found") return FileResponse( path=str(glb_path), media_type="model/gltf-binary", filename=f"{project_id}.glb", )