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