diff --git a/src/blenderbim/scripts/generate_util_type_json.py b/src/blenderbim/scripts/generate_util_type_json.py index 64c8588969..b4cf7db9f9 100644 --- a/src/blenderbim/scripts/generate_util_type_json.py +++ b/src/blenderbim/scripts/generate_util_type_json.py @@ -1,5 +1,5 @@ # BlenderBIM Add-on - OpenBIM Blender Add-on -# Copyright (C) 2020, 2021 Dion Moult +# Copyright (C) 2020, 2021, 2023 Dion Moult , @Andrej730 # # This file is part of BlenderBIM Add-on. # @@ -22,101 +22,130 @@ import ifcopenshell import ifcopenshell.util.schema -filepath = "IFC4.exp" -schema2x3 = ifcopenshell.ifcopenshell_wrapper.schema_by_name("IFC2X3") -schema4 = ifcopenshell.ifcopenshell_wrapper.schema_by_name("IFC4") +def generate_ifc4_entity_map(filepath, schema_name, manual_corrections={}): + filepath = filepath + schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema_name) -entity_to_type_map = {} + entity_to_type_map = {} -entity = None -is_in_where_rule = False + entity = None + is_in_where_rule = False -with open(filepath) as fp: - line = fp.readline() - while line: - if "ENTITY " in line: - entity = line[len("ENTITY ") : -1] - elif "END_ENTITY" in line: - is_in_where_rule = False - elif entity and ("CorrectTypeAssigned" in line or "CorrectStyleAssigned" in line): - is_in_where_rule = True - elif entity and is_in_where_rule and "IN TYPEOF" in line and "RelatingType" in line: - type_class = line.split("'")[1].split(".")[1] - if type_class == "IFCTRANFORMERTYPE": - # Fix typo in schema - type_class = "IFCTRANSFORMERTYPE" - type_class = schema4.declaration_by_name(type_class).name() - entity_to_type_map.setdefault(entity, []).append(type_class) + with open(filepath) as fp: line = fp.readline() + while line: + if "ENTITY " in line: + entity = line[len("ENTITY ") : -1] + elif "END_ENTITY" in line: + is_in_where_rule = False + elif entity and ("CorrectTypeAssigned" in line or "CorrectStyleAssigned" in line): + is_in_where_rule = True + elif entity and is_in_where_rule and "IN TYPEOF" in line and "RelatingType" in line: + type_class = line.split("'")[1].split(".")[1] + if type_class == "IFCTRANFORMERTYPE": + # Fix typo in schema + type_class = "IFCTRANSFORMERTYPE" + type_class = schema.declaration_by_name(type_class).name() + entity_to_type_map.setdefault(entity, []).append(type_class) + line = fp.readline() -# This type of hacky express parsing doesn't accommodate subtypes, so... + for entity in manual_corrections: + for ifc_type in manual_corrections[entity]: + entity_to_type_map.setdefault(entity, []).append(ifc_type) + # This type of hacky express parsing doesn't accommodate subtypes, so... + def get_inherited_map(declaration): + if not ifcopenshell.util.schema.is_a(declaration, "IfcObject"): + return + if declaration.supertype().name() in entity_to_type_map: + return entity_to_type_map[declaration.supertype().name()] + return get_inherited_map(declaration.supertype()) -def get_inherited_map(declaration): - if not ifcopenshell.util.schema.is_a(declaration, "IfcObject"): - return - if declaration.supertype().name() in entity_to_type_map: - return entity_to_type_map[declaration.supertype().name()] - return get_inherited_map(declaration.supertype()) + for declaration in schema.declarations(): + if not hasattr(declaration, "supertype"): + continue + if not ifcopenshell.util.schema.is_a(declaration, "IfcObject"): + continue + if declaration.is_abstract(): + continue + if declaration.name() in entity_to_type_map: + continue + inherited_map = get_inherited_map(declaration) + if inherited_map: + entity_to_type_map[declaration.name()] = inherited_map + type_to_entity_map = {value: [key] for key in entity_to_type_map for value in entity_to_type_map[key]} -for declaration in schema4.declarations(): - if not hasattr(declaration, "supertype"): - continue - if not ifcopenshell.util.schema.is_a(declaration, "IfcObject"): - continue - if declaration.is_abstract(): - continue - if declaration.name() in entity_to_type_map: - continue - inherited_map = get_inherited_map(declaration) - if inherited_map: - entity_to_type_map[declaration.name()] = inherited_map + schema_version = schema_name.lower().strip("ifc") + with open(f"src/ifcopenshell-python/ifcopenshell/util/entity_to_type_map_{schema_version}.json", "w") as f: + json.dump(entity_to_type_map, f, indent=4) + + return entity_to_type_map -type_to_entity_map = {value: [key] for key in entity_to_type_map for value in entity_to_type_map[key]} # IFC2X3 doesn't seem to define this in EXPRESS, so let's just guess +# TODO: do we need some other way to validate 2x3 mapping? +def generate_ifc2x3_entity_map(): + entity_to_type_map = {} + schema2x3 = ifcopenshell.ifcopenshell_wrapper.schema_by_name("IFC2X3") -entity_to_type_map2x3 = {} + def guess_type_declaration(declaration): + if not ifcopenshell.util.schema.is_a(declaration, "IfcObject"): + return + + try: + type_declaration = schema2x3.declaration_by_name(declaration.name() + "Type") + return type_declaration + except: + pass + + try: + type_declaration = schema2x3.declaration_by_name(declaration.name() + "Style") + return type_declaration + except: + pass + + return guess_type_declaration(declaration.supertype()) + + for declaration in schema2x3.declarations(): + if not hasattr(declaration, "supertype"): + continue + type_declaration = guess_type_declaration(declaration) + if not type_declaration: + continue + if not type_declaration.is_abstract(): + entity_to_type_map.setdefault(declaration.name(), []).append(type_declaration.name()) + for subtype in type_declaration.subtypes(): + if not subtype.is_abstract(): + entity_to_type_map.setdefault(declaration.name(), []).append(subtype.name()) + + type_to_entity_map2x3 = {value: [key] for key in entity_to_type_map for value in entity_to_type_map[key]} + + with open("src/ifcopenshell-python/ifcopenshell/util/entity_to_type_map_2x3.json", "w") as f: + json.dump(entity_to_type_map, f, indent=4) + + return entity_to_type_map -def guess_type_declaration(declaration): - if not ifcopenshell.util.schema.is_a(declaration, "IfcObject"): - return +# specs: https://technical.buildingsmart.org/standards/ifc/ifc-schema-specifications +# ifc4x3 - https://github.com/buildingSMART/IFC4.3-html/releases/tag/sep-13-release (inside .zip) +# ifc4 - https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/EXPRESS/IFC4.exp - try: - type_declaration = schema2x3.declaration_by_name(declaration.name() + "Type") - return type_declaration - except: - pass +# IfcCivilElement correction could be removed in the future releases of IFC +# if https://github.com/buildingSMART/IFC4.3.x-development/issues/583 is solved - try: - type_declaration = schema2x3.declaration_by_name(declaration.name() + "Style") - return type_declaration - except: - pass +ifc4x3_corrections = { + "IfcCivilElement": ["IfcCivilElementType"], +} +entity_to_type_map4x3 = generate_ifc4_entity_map("IFC4X3_TC1.exp", "ifc4x3", ifc4x3_corrections) - return guess_type_declaration(declaration.supertype()) +# some manual IFC4 corrections (more details in entity_to_type_map_4.json commits history) +ifc4_corrections = { + "IfcDoor": ["IfcDoorStyle"], # also will apply change to IfcDoorStandardCase + "IfcWindow": ["IfcWindowStyle"], # also will aply change to IfcWindowStandardCase + "IfcCivilElement": ["IfcCivilElementType"], +} +# +entity_to_type_map4 = generate_ifc4_entity_map("IFC4.exp", "ifc4", ifc4_corrections) - -for declaration in schema2x3.declarations(): - if not hasattr(declaration, "supertype"): - continue - type_declaration = guess_type_declaration(declaration) - if not type_declaration: - continue - if not type_declaration.is_abstract(): - entity_to_type_map2x3.setdefault(declaration.name(), []).append(type_declaration.name()) - for subtype in type_declaration.subtypes(): - if not subtype.is_abstract(): - entity_to_type_map2x3.setdefault(declaration.name(), []).append(subtype.name()) - -type_to_entity_map2x3 = {value: [key] for key in entity_to_type_map2x3 for value in entity_to_type_map2x3[key]} - - -with open("entity_to_type_map_4.json", "w") as f: - json.dump(entity_to_type_map, f, indent=4) - - -with open("entity_to_type_map_2x3.json", "w") as f: - json.dump(entity_to_type_map2x3, f, indent=4) +entity_to_type_map2x3 = generate_ifc2x3_entity_map() diff --git a/src/ifcopenshell-python/ifcopenshell/util/entity_to_type_map_4x3.json b/src/ifcopenshell-python/ifcopenshell/util/entity_to_type_map_4x3.json new file mode 100644 index 0000000000..9b24aefb5d --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/util/entity_to_type_map_4x3.json @@ -0,0 +1,380 @@ +{ + "IfcActuator": [ + "IfcActuatorType" + ], + "IfcAirTerminal": [ + "IfcAirTerminalType" + ], + "IfcAirTerminalBox": [ + "IfcAirTerminalBoxType" + ], + "IfcAirToAirHeatRecovery": [ + "IfcAirToAirHeatRecoveryType" + ], + "IfcAlarm": [ + "IfcAlarmType" + ], + "IfcAudioVisualAppliance": [ + "IfcAudioVisualApplianceType" + ], + "IfcBeam": [ + "IfcBeamType" + ], + "IfcBearing": [ + "IfcBearingType" + ], + "IfcBoiler": [ + "IfcBoilerType" + ], + "IfcBuildingElementPart": [ + "IfcBuildingElementPartType" + ], + "IfcBuildingElementProxy": [ + "IfcBuildingElementProxyType" + ], + "IfcBurner": [ + "IfcBurnerType" + ], + "IfcCableCarrierFitting": [ + "IfcCableCarrierFittingType" + ], + "IfcCableCarrierSegment": [ + "IfcCableCarrierSegmentType" + ], + "IfcCableFitting": [ + "IfcCableFittingType" + ], + "IfcCableSegment": [ + "IfcCableSegmentType" + ], + "IfcCaissonFoundation": [ + "IfcCaissonFoundationType" + ], + "IfcChiller": [ + "IfcChillerType" + ], + "IfcChimney": [ + "IfcChimneyType" + ], + "IfcCoil": [ + "IfcCoilType" + ], + "IfcColumn": [ + "IfcColumnType" + ], + "IfcCommunicationsAppliance": [ + "IfcCommunicationsApplianceType" + ], + "IfcCompressor": [ + "IfcCompressorType" + ], + "IfcCondenser": [ + "IfcCondenserType" + ], + "IfcController": [ + "IfcControllerType" + ], + "IfcConveyorSegment": [ + "IfcConveyorSegmentType" + ], + "IfcCooledBeam": [ + "IfcCooledBeamType" + ], + "IfcCoolingTower": [ + "IfcCoolingTowerType" + ], + "IfcCourse": [ + "IfcCourseType" + ], + "IfcCovering": [ + "IfcCoveringType" + ], + "IfcCurtainWall": [ + "IfcCurtainWallType" + ], + "IfcDamper": [ + "IfcDamperType" + ], + "IfcDeepFoundation": [ + "IfcDeepFoundationType" + ], + "IfcDiscreteAccessory": [ + "IfcDiscreteAccessoryType" + ], + "IfcDistributionBoard": [ + "IfcDistributionBoardType" + ], + "IfcDistributionChamberElement": [ + "IfcDistributionChamberElementType" + ], + "IfcDoor": [ + "IfcDoorType" + ], + "IfcDuctFitting": [ + "IfcDuctFittingType" + ], + "IfcDuctSegment": [ + "IfcDuctSegmentType" + ], + "IfcDuctSilencer": [ + "IfcDuctSilencerType" + ], + "IfcElectricAppliance": [ + "IfcElectricApplianceType" + ], + "IfcElectricDistributionBoard": [ + "IfcElectricDistributionBoardType" + ], + "IfcElectricFlowStorageDevice": [ + "IfcElectricFlowStorageDeviceType" + ], + "IfcElectricFlowTreatmentDevice": [ + "IfcElectricFlowTreatmentDeviceType" + ], + "IfcElectricGenerator": [ + "IfcElectricGeneratorType" + ], + "IfcElectricMotor": [ + "IfcElectricMotorType" + ], + "IfcElectricTimeControl": [ + "IfcElectricTimeControlType" + ], + "IfcElementAssembly": [ + "IfcElementAssemblyType" + ], + "IfcEngine": [ + "IfcEngineType" + ], + "IfcEvaporativeCooler": [ + "IfcEvaporativeCoolerType" + ], + "IfcEvaporator": [ + "IfcEvaporatorType" + ], + "IfcFan": [ + "IfcFanType" + ], + "IfcFastener": [ + "IfcFastenerType" + ], + "IfcFilter": [ + "IfcFilterType" + ], + "IfcFireSuppressionTerminal": [ + "IfcFireSuppressionTerminalType" + ], + "IfcFlowInstrument": [ + "IfcFlowInstrumentType" + ], + "IfcFlowMeter": [ + "IfcFlowMeterType" + ], + "IfcFooting": [ + "IfcFootingType" + ], + "IfcFurniture": [ + "IfcFurnitureType" + ], + "IfcGeographicElement": [ + "IfcGeographicElementType" + ], + "IfcHeatExchanger": [ + "IfcHeatExchangerType" + ], + "IfcHumidifier": [ + "IfcHumidifierType" + ], + "IfcImpactProtectionDevice": [ + "IfcImpactProtectionDeviceType" + ], + "IfcInterceptor": [ + "IfcInterceptorType" + ], + "IfcJunctionBox": [ + "IfcJunctionBoxType" + ], + "IfcKerb": [ + "IfcKerbType" + ], + "IfcLamp": [ + "IfcLampType" + ], + "IfcLightFixture": [ + "IfcLightFixtureType" + ], + "IfcLiquidTerminal": [ + "IfcLiquidTerminalType" + ], + "IfcMechanicalFastener": [ + "IfcMechanicalFastenerType" + ], + "IfcMedicalDevice": [ + "IfcMedicalDeviceType" + ], + "IfcMember": [ + "IfcMemberType" + ], + "IfcMobileTelecommunicationsAppliance": [ + "IfcMobileTelecommunicationsApplianceType" + ], + "IfcMooringDevice": [ + "IfcMooringDeviceType" + ], + "IfcMotorConnection": [ + "IfcMotorConnectionType" + ], + "IfcNavigationElement": [ + "IfcNavigationElementType" + ], + "IfcOutlet": [ + "IfcOutletType" + ], + "IfcPavement": [ + "IfcPavementType" + ], + "IfcPile": [ + "IfcPileType" + ], + "IfcPipeFitting": [ + "IfcPipeFittingType" + ], + "IfcPipeSegment": [ + "IfcPipeSegmentType" + ], + "IfcPlate": [ + "IfcPlateType" + ], + "IfcProtectiveDevice": [ + "IfcProtectiveDeviceType" + ], + "IfcProtectiveDeviceTrippingUnit": [ + "IfcProtectiveDeviceTrippingUnitType" + ], + "IfcPump": [ + "IfcPumpType" + ], + "IfcRail": [ + "IfcRailType" + ], + "IfcRailing": [ + "IfcRailingType" + ], + "IfcRamp": [ + "IfcRampType" + ], + "IfcRampFlight": [ + "IfcRampFlightType" + ], + "IfcReinforcingBar": [ + "IfcReinforcingBarType" + ], + "IfcReinforcingMesh": [ + "IfcReinforcingMeshType" + ], + "IfcRoof": [ + "IfcRoofType" + ], + "IfcSanitaryTerminal": [ + "IfcSanitaryTerminalType" + ], + "IfcSensor": [ + "IfcSensorType" + ], + "IfcShadingDevice": [ + "IfcShadingDeviceType" + ], + "IfcSign": [ + "IfcSignType" + ], + "IfcSignal": [ + "IfcSignalType" + ], + "IfcSlab": [ + "IfcSlabType" + ], + "IfcSolarDevice": [ + "IfcSolarDeviceType" + ], + "IfcSpace": [ + "IfcSpaceType" + ], + "IfcSpaceHeater": [ + "IfcSpaceHeaterType" + ], + "IfcSpatialZone": [ + "IfcSpatialZoneType" + ], + "IfcStackTerminal": [ + "IfcStackTerminalType" + ], + "IfcStair": [ + "IfcStairType" + ], + "IfcStairFlight": [ + "IfcStairFlightType" + ], + "IfcSwitchingDevice": [ + "IfcSwitchingDeviceType" + ], + "IfcSystemFurnitureElement": [ + "IfcSystemFurnitureElementType" + ], + "IfcTank": [ + "IfcTankType" + ], + "IfcTendon": [ + "IfcTendonType" + ], + "IfcTendonAnchor": [ + "IfcTendonAnchorType" + ], + "IfcTendonConduit": [ + "IfcTendonConduitType" + ], + "IfcTrackElement": [ + "IfcTrackElementType" + ], + "IfcTransformer": [ + "IfcTransformerType" + ], + "IfcTransportElement": [ + "IfcTransportElementType" + ], + "IfcTubeBundle": [ + "IfcTubeBundleType" + ], + "IfcUnitaryControlElement": [ + "IfcUnitaryControlElementType" + ], + "IfcUnitaryEquipment": [ + "IfcUnitaryEquipmentType" + ], + "IfcValve": [ + "IfcValveType" + ], + "IfcVehicle": [ + "IfcVehicleType" + ], + "IfcVibrationDamper": [ + "IfcVibrationDamperType" + ], + "IfcVibrationIsolator": [ + "IfcVibrationIsolatorType" + ], + "IfcWall": [ + "IfcWallType" + ], + "IfcWasteTerminal": [ + "IfcWasteTerminalType" + ], + "IfcWindow": [ + "IfcWindowType" + ], + "IfcCivilElement": [ + "IfcCivilElementType" + ], + "IfcWallStandardCase": [ + "IfcWallType" + ] +} \ No newline at end of file diff --git a/src/ifcopenshell-python/ifcopenshell/util/type.py b/src/ifcopenshell-python/ifcopenshell/util/type.py index 64b3f3d421..683848b5b6 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/type.py +++ b/src/ifcopenshell-python/ifcopenshell/util/type.py @@ -1,5 +1,5 @@ # IfcOpenShell - IFC toolkit and geometry engine -# Copyright (C) 2021 Dion Moult +# Copyright (C) 2021, 2023 Dion Moult , @Andrej730 # # This file is part of IfcOpenShell. # @@ -24,28 +24,35 @@ cwd = os.path.dirname(os.path.realpath(__file__)) entity_to_type_map = {} type_to_entity_map = {} -with open(os.path.join(cwd, "entity_to_type_map_2x3.json")) as f: - entity_to_type_map["IFC2X3"] = json.load(f) +mapped_schemas = { + "IFC2X3": "entity_to_type_map_2x3.json", + "IFC4": "entity_to_type_map_4.json", + "IFC4x3": "entity_to_type_map_4x3.json", +} +for schema in mapped_schemas: + # load entity maps from json + schema_path = os.path.join(cwd, mapped_schemas[schema]) + with open(schema_path) as f: + entity_to_type_map[schema] = json.load(f) -with open(os.path.join(cwd, "entity_to_type_map_4.json")) as f: - entity_to_type_map["IFC4"] = json.load(f) - -for schema in ["IFC2X3", "IFC4"]: + # create type_to_entity map type_to_entity_map[schema] = {} for element, element_types in entity_to_type_map[schema].items(): for element_type in element_types: type_to_entity_map[schema].setdefault(element_type, []).append(element) + if schema == "IFC2X3": # There is no official mapping for IFC2X3 but this method gets us something that looks correct for element_type, elements in type_to_entity_map[schema].items(): - guessed_element = element_type[0:-len("Type")] + # need to take both Type (4 symbols) and Style (5 symbols) into account + guessed_element = element_type[:-5] if element_type.endswith("Style") else element_type[:-4] if guessed_element in elements: type_to_entity_map[schema][element_type] = [e for e in elements if guessed_element in e] def get_applicable_types(ifc_class, schema="IFC4"): - return entity_to_type_map[schema].get(ifc_class, []) + return entity_to_type_map[schema.upper()].get(ifc_class, []) def get_applicable_entities(ifc_type_class, schema="IFC4"): - return type_to_entity_map[schema].get(ifc_type_class, []) + return type_to_entity_map[schema.upper()].get(ifc_type_class, [])