bim-twin-viewer/backend/app/api/elements.py
warnason eb2c662d0e
Some checks are pending
CI / backend-lint-and-test (push) Waiting to run
Fix duplicate elements and robust GlobalId lookup
- Remove IfcWallStandardCase from parser (covered by IfcWall as parent type)
- Add seen_global_ids set to prevent duplicate element insertion
- Use scalars().first() instead of scalar_one_or_none() for resilient lookup
- Re-parse required: clears and rebuilds element data without duplicates
2026-04-22 16:43:28 +02:00

89 lines
2.8 KiB
Python

"""API routes for querying building elements."""
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.models.database import get_db
from app.models.element import Element, Project
from app.schemas.element import ElementDetailOut, ElementOut, ProjectOut
router = APIRouter(tags=["elements"])
@router.get("/projects", response_model=list[ProjectOut])
async def list_projects(db: AsyncSession = Depends(get_db)):
"""List all uploaded IFC projects."""
query = select(Project)
result = await db.execute(query)
projects = result.scalars().all()
response = []
for project in projects:
count_query = select(func.count()).where(Element.project_id == project.id)
count_result = await db.execute(count_query)
count = count_result.scalar()
out = ProjectOut.model_validate(project)
out.element_count = count
response.append(out)
return response
@router.get("/projects/{project_id}/elements", response_model=list[ElementOut])
async def list_elements(
project_id: UUID,
ifc_type: str | None = None,
storey: str | None = None,
db: AsyncSession = Depends(get_db),
):
"""List elements for a project, optionally filtered by type or storey."""
query = select(Element).where(Element.project_id == project_id)
if ifc_type:
query = query.where(Element.ifc_type == ifc_type)
if storey:
query = query.where(Element.storey == storey)
result = await db.execute(query)
return result.scalars().all()
@router.get("/projects/{project_id}/elements/by-global-id/{global_id}", response_model=ElementDetailOut)
async def get_element_by_global_id(
project_id: UUID,
global_id: str,
db: AsyncSession = Depends(get_db),
):
"""Look up an element by its IFC GlobalId — used by the 3D viewer on click."""
query = (
select(Element)
.options(selectinload(Element.properties))
.where(Element.project_id == project_id)
.where(Element.global_id == global_id)
)
result = await db.execute(query)
element = result.scalars().first()
if not element:
raise HTTPException(status_code=404, detail="Element not found")
return element
@router.get("/elements/{element_id}", response_model=ElementDetailOut)
async def get_element(element_id: UUID, db: AsyncSession = Depends(get_db)):
"""Get a single element with all its properties."""
query = (
select(Element)
.options(selectinload(Element.properties))
.where(Element.id == element_id)
)
result = await db.execute(query)
element = result.scalar_one_or_none()
if not element:
raise HTTPException(status_code=404, detail="Element not found")
return element