bim-twin-viewer/backend/app/api/upload.py
warnason fb674ab580 Add server-side IFC to glb conversion for 3D viewing
- New ifc_converter service using IfcOpenShell geometry serializer
- Preserves IFC GlobalIds as mesh names for click-to-inspect
- glb files served via /api/projects/{id}/model.glb endpoint
- New endpoint: lookup element by GlobalId for viewer integration
- Excludes IfcSpace and IfcOpeningElement from 3D output
2026-04-20 19:11:55 +02:00

65 lines
2 KiB
Python

"""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",
)