f7aa6c29da
build and deploy Speckle functions / publish-automate-function-version (push) Has been cancelled
321 lines
14 KiB
Python
321 lines
14 KiB
Python
# =============================================================================
|
|
# mapper.py
|
|
# Maps Speckle objects → IFC entity classes.
|
|
#
|
|
# Strategy (priority order):
|
|
# 1. builtInCategory (OST_ enum from properties.builtInCategory) — most reliable
|
|
# 2. speckle_type prefix match — for typed Speckle objects
|
|
# 3. category_name string (traversal context) — display name fallback
|
|
# 4. IfcBuildingElementProxy — last resort
|
|
#
|
|
# builtInCategory values: https://www.revitapidocs.com/2019/ba1c5b30-242f-5fdc-8ea9-ec3b61e6e722.htm
|
|
# =============================================================================
|
|
|
|
|
|
# --- OST_ BuiltInCategory → IFC class (primary lookup) ---
|
|
BUILTIN_CATEGORY_MAP: dict[str, str] = {
|
|
# Architectural - Walls
|
|
"OST_Walls": "IfcWall",
|
|
"OST_CurtainWallPanels": "IfcCurtainWall",
|
|
"OST_CurtainWallMullions": "IfcMember",
|
|
"OST_Fascia": "IfcCovering",
|
|
"OST_Gutters": "IfcPipeSegment",
|
|
|
|
# Architectural - Floors / Roofs / Ceilings
|
|
"OST_Floors": "IfcSlab",
|
|
"OST_Roofs": "IfcRoof",
|
|
"OST_Ceilings": "IfcCovering",
|
|
"OST_RoofSoffit": "IfcCovering",
|
|
|
|
# Architectural - Doors / Windows / Openings
|
|
"OST_Doors": "IfcDoor",
|
|
"OST_Windows": "IfcWindow",
|
|
"OST_CurtainWallFamilies": "IfcCurtainWall",
|
|
"OST_Skylights": "IfcWindow",
|
|
|
|
# Architectural - Stairs / Ramps / Railings
|
|
"OST_Stairs": "IfcStair",
|
|
"OST_StairsRailing": "IfcRailing",
|
|
"OST_RailingTopRail": "IfcRailing",
|
|
"OST_Ramps": "IfcRamp",
|
|
"OST_StairsLandings": "IfcStairFlight",
|
|
"OST_StairsRuns": "IfcStairFlight",
|
|
"OST_StairsSupports": "IfcMember",
|
|
|
|
# Architectural - Rooms / Spaces
|
|
"OST_Rooms": "IfcSpace",
|
|
"OST_Parking": "IfcSpace",
|
|
"OST_Areas": "IfcSpace",
|
|
|
|
# Architectural - Furniture / Casework
|
|
"OST_Furniture": "IfcFurnishingElement",
|
|
"OST_FurnitureSystems": "IfcFurnishingElement",
|
|
"OST_Casework": "IfcFurnishingElement",
|
|
"OST_SpecialtyEquipment": "IfcFurnishingElement",
|
|
"OST_Entourage": "IfcFurnishingElement",
|
|
|
|
# Structural
|
|
"OST_StructuralColumns": "IfcColumn",
|
|
"OST_Columns": "IfcColumn",
|
|
"OST_StructuralFraming": "IfcBeam",
|
|
"OST_StructuralFoundation": "IfcFooting",
|
|
"OST_FoundationSlab": "IfcSlab",
|
|
"OST_StructuralStiffener": "IfcMember",
|
|
"OST_StructuralTruss": "IfcMember",
|
|
"OST_StructuralConnectionModel": "IfcMechanicalFastener",
|
|
"OST_Rebar": "IfcReinforcingBar",
|
|
"OST_FabricAreas": "IfcReinforcingMesh",
|
|
"OST_FabricReinforcement": "IfcReinforcingMesh",
|
|
|
|
# MEP - HVAC
|
|
"OST_DuctCurves": "IfcDuctSegment",
|
|
"OST_DuctFitting": "IfcDuctFitting",
|
|
"OST_DuctAccessory": "IfcDuctSegment",
|
|
"OST_DuctTerminal": "IfcAirTerminal",
|
|
"OST_FlexDuctCurves": "IfcDuctSegment",
|
|
"OST_MechanicalEquipment": "IfcUnitaryEquipment",
|
|
"OST_AirTerminal": "IfcAirTerminal",
|
|
|
|
# MEP - Plumbing
|
|
"OST_PipeCurves": "IfcPipeSegment",
|
|
"OST_PipeFitting": "IfcPipeFitting",
|
|
"OST_PipeAccessory": "IfcPipeSegment",
|
|
"OST_FlexPipeCurves": "IfcPipeSegment",
|
|
"OST_PlumbingFixtures": "IfcSanitaryTerminal",
|
|
"OST_Sprinklers": "IfcFireSuppressionTerminal",
|
|
|
|
# MEP - Electrical
|
|
"OST_ElectricalEquipment": "IfcElectricDistributionBoard",
|
|
"OST_ElectricalFixtures": "IfcElectricAppliance",
|
|
"OST_LightingFixtures": "IfcLightFixture",
|
|
"OST_LightingDevices": "IfcLightFixture",
|
|
"OST_CableTray": "IfcCableCarrierSegment",
|
|
"OST_CableTrayFitting": "IfcCableCarrierFitting",
|
|
"OST_Conduit": "IfcCableCarrierSegment",
|
|
"OST_ConduitFitting": "IfcCableCarrierFitting",
|
|
"OST_CommunicationDevices": "IfcElectricAppliance",
|
|
"OST_DataDevices": "IfcElectricAppliance",
|
|
"OST_FireAlarmDevices": "IfcAlarm",
|
|
"OST_SecurityDevices": "IfcAlarm",
|
|
"OST_NurseCallDevices": "IfcElectricAppliance",
|
|
|
|
# Site / Civil
|
|
"OST_Site": "IfcSite",
|
|
"OST_Topography": "IfcGeographicElement",
|
|
"OST_Toposolid": "IfcGeographicElement",
|
|
"OST_Roads": "IfcRoad",
|
|
"OST_Hardscape": "IfcPavement",
|
|
"OST_Planting": "IfcGeographicElement",
|
|
"OST_SiteSurface": "IfcGeographicElement",
|
|
|
|
# Generic / Annotation (skip or proxy)
|
|
"OST_GenericModel": "IfcBuildingElementProxy",
|
|
"OST_Mass": "IfcBuildingElementProxy",
|
|
"OST_DetailComponents": "IfcAnnotation",
|
|
"OST_Lines": "IfcAnnotation",
|
|
"OST_Grids": "IfcGrid",
|
|
"OST_Levels": "IfcBuildingStorey",
|
|
"OST_Views": "IfcAnnotation",
|
|
}
|
|
|
|
|
|
# --- OST_ BuiltInCategory → PredefinedType (where applicable) ---
|
|
BUILTIN_PREDEFINED_TYPE: dict[str, str] = {
|
|
"OST_RailingTopRail": "HANDRAIL",
|
|
}
|
|
|
|
|
|
# --- speckle_type → IFC class (secondary lookup) ---
|
|
SPECKLE_TYPE_MAP: dict[str, str] = {
|
|
"Objects.BuiltElements.Wall": "IfcWall",
|
|
"Objects.BuiltElements.Floor": "IfcSlab",
|
|
"Objects.BuiltElements.Roof": "IfcRoof",
|
|
"Objects.BuiltElements.Column": "IfcColumn",
|
|
"Objects.BuiltElements.Beam": "IfcBeam",
|
|
"Objects.BuiltElements.Brace": "IfcMember",
|
|
"Objects.BuiltElements.Duct": "IfcDuctSegment",
|
|
"Objects.BuiltElements.Pipe": "IfcPipeSegment",
|
|
"Objects.BuiltElements.Wire": "IfcCableCarrierSegment",
|
|
"Objects.BuiltElements.Opening": "IfcOpeningElement",
|
|
"Objects.BuiltElements.Room": "IfcSpace",
|
|
"Objects.BuiltElements.Ceiling": "IfcCovering",
|
|
"Objects.BuiltElements.Stair": "IfcStair",
|
|
"Objects.BuiltElements.Ramp": "IfcRamp",
|
|
"Objects.BuiltElements.Foundation": "IfcFooting",
|
|
"Objects.BuiltElements.Grid": "IfcGrid",
|
|
"Objects.BuiltElements.Level": "IfcBuildingStorey",
|
|
"Objects.BuiltElements.Revit.RevitWall": "IfcWall",
|
|
"Objects.BuiltElements.Revit.RevitFloor": "IfcSlab",
|
|
"Objects.BuiltElements.Revit.RevitRoof": "IfcRoof",
|
|
"Objects.BuiltElements.Revit.RevitColumn": "IfcColumn",
|
|
"Objects.BuiltElements.Revit.RevitBeam": "IfcBeam",
|
|
"Objects.BuiltElements.Revit.RevitBrace": "IfcMember",
|
|
"Objects.BuiltElements.Revit.RevitDuct": "IfcDuctSegment",
|
|
"Objects.BuiltElements.Revit.RevitPipe": "IfcPipeSegment",
|
|
"Objects.BuiltElements.Revit.RevitRoom": "IfcSpace",
|
|
"Objects.BuiltElements.Revit.RevitStair": "IfcStair",
|
|
"Objects.BuiltElements.Revit.RevitRailing": "IfcRailing",
|
|
"Objects.BuiltElements.Revit.RevitCeiling": "IfcCovering",
|
|
"Objects.BuiltElements.Revit.RevitTopography": "IfcGeographicElement",
|
|
"Objects.BuiltElements.Revit.RevitElementType": "IfcBuildingElementProxy",
|
|
"Objects.Geometry.Mesh": "IfcBuildingElementProxy",
|
|
"Objects.Geometry.Brep": "IfcBuildingElementProxy",
|
|
}
|
|
|
|
# --- Display category name → IFC class (tertiary fallback) ---
|
|
CATEGORY_MAP: dict[str, str] = {
|
|
"Walls": "IfcWall",
|
|
"Floors": "IfcSlab",
|
|
"Roofs": "IfcRoof",
|
|
"Structural Columns": "IfcColumn",
|
|
"Columns": "IfcColumn",
|
|
"Structural Framing": "IfcBeam",
|
|
"Beams": "IfcBeam",
|
|
"Ducts": "IfcDuctSegment",
|
|
"Pipes": "IfcPipeSegment",
|
|
"Conduits": "IfcCableCarrierSegment",
|
|
"Cable Trays": "IfcCableCarrierSegment",
|
|
"Rooms": "IfcSpace",
|
|
"Spaces": "IfcSpace",
|
|
"Ceilings": "IfcCovering",
|
|
"Stairs": "IfcStair",
|
|
"Ramps": "IfcRamp",
|
|
"Railings": "IfcRailing",
|
|
"Top Rails": "IfcRailing",
|
|
"Curtain Panels": "IfcCurtainWall",
|
|
"Curtain Wall Mullions": "IfcMember",
|
|
"Doors": "IfcDoor",
|
|
"Windows": "IfcWindow",
|
|
"Furniture": "IfcFurnishingElement",
|
|
"Furniture Systems": "IfcFurnishingElement",
|
|
"Casework": "IfcFurnishingElement",
|
|
"Plumbing Fixtures": "IfcSanitaryTerminal",
|
|
"Electrical Fixtures": "IfcElectricAppliance",
|
|
"Lighting Fixtures": "IfcLightFixture",
|
|
"Mechanical Equipment": "IfcUnitaryEquipment",
|
|
"Electrical Equipment": "IfcElectricDistributionBoard",
|
|
"Structural Foundations": "IfcFooting",
|
|
"Foundation Slabs": "IfcSlab",
|
|
"Topography": "IfcGeographicElement",
|
|
"Toposolid": "IfcGeographicElement",
|
|
"Planting": "IfcGeographicElement",
|
|
"Site": "IfcSite",
|
|
"Parking": "IfcSpace",
|
|
"Generic Models": "IfcBuildingElementProxy",
|
|
"Mass": "IfcBuildingElementProxy",
|
|
"Specialty Equipment": "IfcFurnishingElement",
|
|
}
|
|
|
|
|
|
def get_predefined_type(obj) -> str | None:
|
|
"""Return the IFC PredefinedType for an object based on its builtInCategory, or None."""
|
|
bic = _get_builtin_category(obj)
|
|
if bic and bic in BUILTIN_PREDEFINED_TYPE:
|
|
return BUILTIN_PREDEFINED_TYPE[bic]
|
|
return None
|
|
|
|
|
|
_bic_cache: dict[int, str | None] = {} # id(obj) → builtInCategory
|
|
|
|
|
|
def _get_builtin_category(obj) -> str | None:
|
|
"""
|
|
Read builtInCategory from obj.properties.builtInCategory.
|
|
Returns the OST_ string or None. Cached per object.
|
|
"""
|
|
oid = id(obj)
|
|
if oid in _bic_cache:
|
|
return _bic_cache[oid]
|
|
result = None
|
|
try:
|
|
props = getattr(obj, "properties", None)
|
|
if props is None:
|
|
try:
|
|
props = obj["properties"]
|
|
except Exception:
|
|
pass
|
|
if props is not None:
|
|
val = getattr(props, "builtInCategory", None)
|
|
if val is None:
|
|
try:
|
|
val = props["builtInCategory"]
|
|
except Exception:
|
|
pass
|
|
if val and isinstance(val, str):
|
|
result = val.strip()
|
|
except Exception:
|
|
pass
|
|
_bic_cache[oid] = result
|
|
return result
|
|
|
|
|
|
# Pre-computed lowercase category map for substring matching
|
|
_CATEGORY_MAP_LOWER: list[tuple[str, str]] = [
|
|
(k.lower(), v) for k, v in CATEGORY_MAP.items()
|
|
]
|
|
|
|
# Classification cache: (obj_id, category_name) → ifc_class
|
|
_classify_cache: dict[tuple, str] = {}
|
|
|
|
|
|
def classify(obj, category_name: str = "") -> str:
|
|
"""
|
|
Determine the IFC class for a Speckle object.
|
|
|
|
Priority:
|
|
1. properties.builtInCategory (OST_ enum) — definitive Revit classification
|
|
2. speckle_type prefix match
|
|
3. category_name from traversal context (display string)
|
|
4. obj.category field
|
|
5. IfcBuildingElementProxy fallback
|
|
"""
|
|
cache_key = (id(obj), category_name)
|
|
if cache_key in _classify_cache:
|
|
return _classify_cache[cache_key]
|
|
|
|
result = _classify_impl(obj, category_name)
|
|
_classify_cache[cache_key] = result
|
|
return result
|
|
|
|
|
|
def _classify_impl(obj, category_name: str) -> str:
|
|
# 1. builtInCategory — most reliable, direct Revit enum
|
|
bic = _get_builtin_category(obj)
|
|
if bic and bic in BUILTIN_CATEGORY_MAP:
|
|
return BUILTIN_CATEGORY_MAP[bic]
|
|
|
|
# 2. speckle_type
|
|
speckle_type = getattr(obj, "speckle_type", "") or ""
|
|
if speckle_type in SPECKLE_TYPE_MAP:
|
|
return SPECKLE_TYPE_MAP[speckle_type]
|
|
for key, ifc_class in SPECKLE_TYPE_MAP.items():
|
|
if speckle_type.startswith(key):
|
|
return ifc_class
|
|
|
|
# 3. category_name from traversal context
|
|
if category_name:
|
|
if category_name in CATEGORY_MAP:
|
|
return CATEGORY_MAP[category_name]
|
|
cat_lower = category_name.lower()
|
|
for key_lower, ifc_class in _CATEGORY_MAP_LOWER:
|
|
if key_lower in cat_lower:
|
|
return ifc_class
|
|
|
|
# 4. obj.category field
|
|
obj_category = getattr(obj, "category", None)
|
|
if obj_category and isinstance(obj_category, str):
|
|
if obj_category in CATEGORY_MAP:
|
|
return CATEGORY_MAP[obj_category]
|
|
obj_cat_lower = obj_category.lower()
|
|
for key_lower, ifc_class in _CATEGORY_MAP_LOWER:
|
|
if key_lower in obj_cat_lower:
|
|
return ifc_class
|
|
|
|
return "IfcBuildingElementProxy"
|
|
|
|
|
|
def reset_caches():
|
|
"""Clear module-level caches (call at start of each export run)."""
|
|
_bic_cache.clear()
|
|
_classify_cache.clear()
|