From a6916e1f59a7fbf085d238ab7db51d34ca413973 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 8 Oct 2019 13:45:48 +1100 Subject: [PATCH] Bug #663 - Implement support for COBie types spreadsheet --- src/ifcblenderexport/cobie.py | 149 +++++++++++++++++++++++++++++++--- 1 file changed, 136 insertions(+), 13 deletions(-) diff --git a/src/ifcblenderexport/cobie.py b/src/ifcblenderexport/cobie.py index 52c30d1220..ab5391b65a 100644 --- a/src/ifcblenderexport/cobie.py +++ b/src/ifcblenderexport/cobie.py @@ -12,12 +12,47 @@ class IfcCobieCsv(): self.floors = {} self.spaces = {} self.zones = {} + self.types = {} + self.type_assets = [ + 'IfcDoorStyle', + 'IfcBuildingElementProxyType', + 'IfcChimneyType', + 'IfcCoveringType', + 'IfcDoorType', + 'IfcFootingType', + 'IfcPileType', + 'IfcRoofType', + 'IfcShadingDeviceType', + 'IfcWindowType', + 'IfcDistributionControlElementType', + 'IfcDistributionChamberElementType', + 'IfcEnergyConversionDeviceType', + 'IfcFlowControllerType', + 'IfcFlowMovingDeviceType', + 'IfcFlowStorageDeviceType', + 'IfcFlowTerminalType', + 'IfcFlowTreatmentDeviceType', + 'IfcElementAssemblyType', + 'IfcBuildingElementPartType', + 'IfcDiscreteAccessoryType', + 'IfcMechanicalFastenerType', + 'IfcReinforcingElementType', + 'IfcVibrationIsolatorType', + 'IfcFurnishingElementType', + 'IfcGeographicElementType', + 'IfcTransportElementType', + 'IfcSpatialZoneType', + 'IfcWindowStyle', + ] self.picklists = { 'Category-Role': [], 'Category-Facility': [], 'FloorType': [], 'Category-Space': [], 'ZoneType': [], + 'Category-Product': [], + 'AssetType': [], + 'DurationUnit': ['day'], # See note about hardcoded day below 'objType': [] } @@ -28,6 +63,7 @@ class IfcCobieCsv(): self.get_floors() self.get_spaces() self.get_zones() + self.get_types() def get_contacts(self): histories = self.file.by_type('IfcOwnerHistory') @@ -115,7 +151,7 @@ class IfcCobieCsv(): 'ExtSystem': self.get_ext_system_from_history(space.OwnerHistory), 'ExtObject': self.get_ext_object(space), 'ExtIdentifier': space.GlobalId, - 'RoomTag': self.get_room_tag_from_space(space), + 'RoomTag': self.get_pset_value_from_object(space, 'COBie_Space', 'RoomTag', 'n/a'), 'UsableHeight': self.get_usable_height_from_space(space), 'GrossArea': self.get_gross_area_from_space(space), 'NetArea': self.get_net_area_from_space(space), @@ -136,6 +172,92 @@ class IfcCobieCsv(): 'Description': self.get_object_attribute(zone, 'Description'), } + def get_types(self): + types = self.file.by_type('IfcTypeObject') + for type in types: + is_a_type_asset = False + for type_asset in self.type_assets: + if type.is_a(type_asset): + is_a_type_asset = True + if not is_a_type_asset: + logging.warning('A type which is not an asset was found for {}'.format(type)) + continue + # The responsibility matrix states to parse IfcMaterial and + # IfcMaterialLayerSet too, but it doesn't make much sense, so I + # don't parse it. + type_name = self.get_object_name(type) + self.types[type_name] = { + 'CreatedBy': self.get_email_from_history(type.OwnerHistory), + 'CreatedOn': self.get_created_on_from_history(type.OwnerHistory), + 'Category': self.get_category_from_object(type, 'Category-Product'), + # The responsibility matrix states two possible fallbacks. I + # choose the 'n/a' option as opposed to repeating the name. + 'Description': self.get_object_attribute(type, 'Description'), + 'AssetType': self.get_pset_value_from_object(type, 'COBie_Asset', 'AssetType', 'n/a', 'AssetType'), + 'Manufacturer': self.get_contact_pset_value_from_object(type, 'Pset_ManufacturerTypeInformation', 'Manufacturer'), + 'ModelNumber': self.get_pset_value_from_object(type, 'Pset_ManufacturerTypeInformation', 'ModelLabel', 'n/a'), + # The responsibility matrix talks about using the Pset_Warranty + # values, but Pset_Warranty only applies to objects, not types, + # and so they are ignored. + 'WarrantyGuarantorParts': self.get_contact_pset_value_from_object(type, 'COBie_Warranty', 'WarrantyGuarantorParts'), + 'WarrantyDurationParts': self.get_pset_value_from_object(type, 'COBie_Warranty', 'WarrantyDurationParts', 0), + 'WarrantyGuarantorLabor': self.get_contact_pset_value_from_object(type, 'COBie_Warranty', 'WarrantyGuarantorLabor'), + 'WarrantyDurationLabor': self.get_pset_value_from_object(type, 'COBie_Warranty', 'WarrantyDurationLabor', 0), + # TODO: this may be derived from the duration values above, but + # until it is clarified, it will be hardcoded as 'day' + 'WarrantyDurationUnit': 'day', + 'ExtSystem': self.get_ext_system_from_history(type.OwnerHistory), + 'ExtObject': self.get_ext_object(type), + 'ExtIdentifier': type.GlobalId, + 'ReplacementCost': self.get_pset_value_from_object(type, 'COBie_EconomicImpactValues', 'ReplacementCost', 'n/a'), + 'ExpectedLife': self.get_expected_life_from_type(type), + # See note about WarrantyDurationUnit above + 'DurationUnit': 'day', + 'NominalLength': self.get_pset_value_from_object(type, 'COBie_Specification', 'NominalLength', 0), + 'NominalWidth': self.get_pset_value_from_object(type, 'COBie_Specification', 'NominalWidth', 0), + 'NominalHeight': self.get_pset_value_from_object(type, 'COBie_Specification', 'NominalHeight', 0), + 'ModelReference': self.get_pset_value_from_object(type, 'Pset_ManufacturerTypeInformation', 'ModelReference', 'n/a'), + 'Shape': self.get_pset_value_from_object(type, 'COBie_Specification', 'Shape', 'n/a'), + 'Size': self.get_pset_value_from_object(type, 'COBie_Specification', 'Size', 'n/a'), + # The responsbility matrix allows the British spelling of + # "colour". I, however, do not. + 'Color': self.get_pset_value_from_object(type, 'COBie_Specification', 'Color', 'n/a'), + 'Finish': self.get_pset_value_from_object(type, 'COBie_Specification', 'Finish', 'n/a'), + 'Grade': self.get_pset_value_from_object(type, 'COBie_Specification', 'Grade', 'n/a'), + 'Material': self.get_pset_value_from_object(type, 'COBie_Specification', 'Material', 'n/a'), + 'Constituents': self.get_pset_value_from_object(type, 'COBie_Specification', 'Constituents', 'n/a'), + 'Features': self.get_pset_value_from_object(type, 'COBie_Specification', 'Features', 'n/a'), + 'AccessibilityPerformance': self.get_pset_value_from_object(type, 'COBie_Specification', 'AccessibilityPerformance', 'n/a'), + 'CodePerformance': self.get_pset_value_from_object(type, 'COBie_Specification', 'CodePerformance', 'n/a'), + 'SustainabilityPerformance': self.get_pset_value_from_object(type, 'COBie_Specification', 'SustainabilityPerformance', 'n/a'), + } + + def get_expected_life_from_type(self, type): + if self.file.schema == 'IFC2X3': + return self.get_pset_value_from_object(type, 'COBie_ServiceLife', 'ServiceLifeDuration', 'n/a') + return self.get_pset_value_from_object(type, 'Pset_ServiceLife', 'ServiceLifeDuration', 'n/a') + + def get_contact_pset_value_from_object(self, object, pset_name, property_name): + result = self.get_pset_value_from_object(object, pset_name, property_name) + if not result: + logging.error('No property {} in {} was found for {}'.format(property_name, pset_name, object)) + if result not in self.contacts: + logging.error('A coresponding {} contact in {} was not found for {}'.format(property_name, pset_name, object)) + return result + + def get_pset_value_from_object(self, object, pset_name, property_name, default=None, picklist=None): + print(object) + print(pset_name) + pset = self.get_pset_from_object(object, pset_name) + if not pset: + if picklist: + self.picklists[picklist].append(default) + return default + property = self.get_property_from_pset(pset, property_name, default) + if picklist: + self.picklists[picklist].append(property) + return property + def get_space_names_from_zone(self, zone): spaces = [] for relationship in zone.IsGroupedBy: @@ -177,24 +299,24 @@ class IfcCobieCsv(): logging.warning('The quantity value {} was not found for {}'.format(name, qto)) return 'n/a' - def get_room_tag_from_space(self, space): - pset = self.get_pset_from_object(space, 'COBie_Space') - if not pset: - return 'n/a' - return self.get_property_from_pset(pset, 'RoomTag') - - def get_property_from_pset(self, pset, name): + def get_property_from_pset(self, pset, name, default=None): for property in pset.HasProperties: if property.Name == name: return property.NominalValue logging.warning('The property {} was not found for {}'.format(name, pset)) - return 'n/a' + return default def get_pset_from_object(self, object, name): - for relationship in object.IsDefinedBy: - if relationship.RelatingPropertyDefinition.is_a('IfcPropertySet') \ - and relationship.RelatingPropertyDefinition.Name == name: - return relationship.RelatingPropertyDefinition + if object.is_a('IfcTypeObject'): + for pset in object.HasPropertySets: + if pset.is_a('IfcPropertySet') \ + and pset.Name == name: + return pset + else: + for relationship in object.IsDefinedBy: + if relationship.RelatingPropertyDefinition.is_a('IfcPropertySet') \ + and relationship.RelatingPropertyDefinition.Name == name: + return relationship.RelatingPropertyDefinition logging.warning('The pset {} was not found for {}'.format(name, object)) def get_height_from_storey(self, storey): @@ -439,4 +561,5 @@ pprint.pprint(ifc_cobie_csv.facilities) pprint.pprint(ifc_cobie_csv.floors) pprint.pprint(ifc_cobie_csv.spaces) pprint.pprint(ifc_cobie_csv.zones) +pprint.pprint(ifc_cobie_csv.types) print('# Finished conversion in {}s'.format(time.time() - start))