Files
IFC-Exporter/utils/writer.py
T
2026-03-03 10:06:20 +01:00

116 lines
3.5 KiB
Python

# =============================================================================
# writer.py
# Creates and manages the IFC file structure:
# IfcProject → IfcSite → IfcBuilding → IfcBuildingStorey (one per level)
#
# Also provides StoreyManager which lazily creates storeys on demand
# as the traversal encounters new level names.
# =============================================================================
import ifcopenshell
import ifcopenshell.api
import utils.config as config
def create_ifc_scaffold() -> tuple:
"""
Create the IFC file with the required project/site/building hierarchy.
Returns:
(ifc_file, building, body_context)
- ifc_file: The ifcopenshell file object
- building: The IfcBuilding entity (storeys are assigned under this)
- body_context: The Body geometry subcontext for shape representations
"""
ifc = ifcopenshell.file(schema="IFC4X3")
# Project
project = ifcopenshell.api.run(
"root.create_entity", ifc,
ifc_class="IfcProject",
name=config.IFC_PROJECT_NAME,
)
# Units — millimetres (matching Revit/Speckle source data)
# This avoids any mm→m conversion errors and keeps coordinates at full precision
ifcopenshell.api.run(
"unit.assign_unit", ifc,
length={"is_metric": True, "raw": "MILLIMETRES"},
)
# Geometry contexts
model_ctx = ifcopenshell.api.run(
"context.add_context", ifc,
context_type="Model",
)
body_ctx = ifcopenshell.api.run(
"context.add_context", ifc,
context_type="Model",
context_identifier="Body",
target_view="MODEL_VIEW",
parent=model_ctx,
)
# Spatial hierarchy
site = ifcopenshell.api.run(
"root.create_entity", ifc,
ifc_class="IfcSite",
name=config.IFC_SITE_NAME,
)
building = ifcopenshell.api.run(
"root.create_entity", ifc,
ifc_class="IfcBuilding",
name=config.IFC_BUILDING_NAME,
)
ifcopenshell.api.run(
"aggregate.assign_object", ifc,
relating_object=project,
products=[site],
)
ifcopenshell.api.run(
"aggregate.assign_object", ifc,
relating_object=site,
products=[building],
)
return ifc, building, body_ctx
class StoreyManager:
"""
Lazily creates IfcBuildingStorey entities as new level names are encountered.
Keeps storeys in insertion order so the IFC file is logically ordered.
"""
def __init__(self, ifc: ifcopenshell.file, building):
self.ifc = ifc
self.building = building
self._storeys: dict[str, object] = {} # level_name → IfcBuildingStorey
def get_or_create(self, level_name: str):
"""Return existing storey or create a new one for this level name."""
if level_name not in self._storeys:
storey = ifcopenshell.api.run(
"root.create_entity", self.ifc,
ifc_class="IfcBuildingStorey",
name=level_name,
)
ifcopenshell.api.run(
"aggregate.assign_object", self.ifc,
relating_object=self.building,
products=[storey],
)
self._storeys[level_name] = storey
print(f" 🏢 Created storey: {level_name}")
return self._storeys[level_name]
@property
def count(self) -> int:
return len(self._storeys)
@property
def names(self) -> list[str]:
return list(self._storeys.keys())