bdd030ba86
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
115 lines
3.5 KiB
Python
115 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()) |