bim-twin-viewer/backend/app/services/ifc_converter.py
warnason cf20eb152f
Some checks are pending
CI / backend-lint-and-test (push) Waiting to run
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 14:30:52 +02:00

79 lines
2.2 KiB
Python

"""Service for converting IFC files to glTF/glb for 3D visualization."""
import multiprocessing
import tempfile
from pathlib import Path
import ifcopenshell
import ifcopenshell.geom
def convert_ifc_to_glb(ifc_path: str, glb_path: str) -> None:
"""
Convert an IFC file to glTF Binary (.glb) format.
Uses IfcOpenShell's geometry serializer with element GUIDs preserved
as mesh names, enabling click-to-inspect in the 3D viewer.
"""
ifc_file = ifcopenshell.open(ifc_path)
settings = ifcopenshell.geom.settings()
settings.set(
"dimensionality",
ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS,
)
settings.set("apply-default-materials", True)
serialiser_settings = ifcopenshell.geom.serializer_settings()
serialiser_settings.set("use-element-guids", True)
serialiser = ifcopenshell.geom.serializers.gltf(
glb_path, settings, serialiser_settings
)
serialiser.setFile(ifc_file)
serialiser.setUnitNameAndMagnitude("METER", 1.0)
serialiser.writeHeader()
num_threads = max(1, multiprocessing.cpu_count() - 1)
iterator = ifcopenshell.geom.iterator(
settings,
ifc_file,
num_threads,
exclude=(
"IfcSpace",
"IfcOpeningElement",
),
)
if iterator.initialize():
while True:
serialiser.write(iterator.get())
if not iterator.next():
break
serialiser.finalize()
del serialiser # Ensures temp files are cleaned up
async def convert_ifc_bytes_to_glb(content: bytes, glb_output_path: str) -> None:
"""
Convert IFC file content (bytes) to a glb file.
Writes IFC to a temporary file first, since IfcOpenShell
requires a file path.
"""
import asyncio
with tempfile.NamedTemporaryFile(suffix=".ifc", delete=False) as tmp:
tmp.write(content)
tmp_ifc = tmp.name
try:
# Run CPU-intensive conversion in a thread pool
loop = asyncio.get_event_loop()
await loop.run_in_executor(
None, convert_ifc_to_glb, tmp_ifc, glb_output_path
)
finally:
Path(tmp_ifc).unlink(missing_ok=True)