- 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
65 lines
2 KiB
Python
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",
|
|
)
|
|
|