From 6b07aeb70303bda88f6e84ffaee066096edd29a3 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 12 Jul 2020 10:19:22 +1000 Subject: [PATCH 1/5] Allow export of positioning elements (only IfcGrid for now) See #911 --- src/ifcblenderexport/blenderbim/bim/prop.py | 1 + src/ifcblenderexport/blenderbim/bim/schema.py | 1 + .../schema/IfcPositioningElement_IFC4.json | 77 +++++++++++++++++++ src/ifcblenderexport/getIfcElements.py | 3 +- 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/ifcblenderexport/blenderbim/bim/schema/IfcPositioningElement_IFC4.json diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 3e70d095c7..940225bbc8 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -130,6 +130,7 @@ def getIfcProducts(self, context): 'IfcSpatialElement', 'IfcGroup', 'IfcStructural', + 'IfcPositioningElement', 'IfcContext', 'IfcAnnotation']]) return products_enum diff --git a/src/ifcblenderexport/blenderbim/bim/schema.py b/src/ifcblenderexport/blenderbim/bim/schema.py index 4e40b0488d..4298b81042 100644 --- a/src/ifcblenderexport/blenderbim/bim/schema.py +++ b/src/ifcblenderexport/blenderbim/bim/schema.py @@ -17,6 +17,7 @@ class IfcSchema(): 'IfcSpatialElement', 'IfcGroup', 'IfcStructural', + 'IfcPositioningElement', 'IfcMaterialDefinition', 'IfcParameterizedProfileDef', 'IfcBoundaryCondition', diff --git a/src/ifcblenderexport/blenderbim/bim/schema/IfcPositioningElement_IFC4.json b/src/ifcblenderexport/blenderbim/bim/schema/IfcPositioningElement_IFC4.json new file mode 100644 index 0000000000..7702b8ffff --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/schema/IfcPositioningElement_IFC4.json @@ -0,0 +1,77 @@ +{ + "IfcGrid": { + "is_abstract": false, + "parent": "IfcProduct", + "attributes": [ + { + "name": "GlobalId", + "type": "IfcGloballyUniqueId", + "is_enum": false, + "enum_values": [] + }, + { + "name": "Name", + "type": "IfcLabel", + "is_enum": false, + "enum_values": [] + }, + { + "name": "Description", + "type": "IfcText", + "is_enum": false, + "enum_values": [] + }, + { + "name": "ObjectType", + "type": "IfcLabel", + "is_enum": false, + "enum_values": [] + }, + { + "name": "PredefinedType", + "type": "IfcGridTypeEnum", + "is_enum": true, + "enum_values": [ + "RECTANGULAR", + "RADIAL", + "TRIANGULAR", + "IRREGULAR", + "USERDEFINED", + "NOTDEFINED" + ] + } + ], + "complex_attributes": [ + { + "name": "OwnerHistory", + "type": "IfcOwnerHistory", + "is_select": false, + "select_types": [] + }, + { + "name": "IsDeclaredBy", + "type": "IfcRelDefinesByObject", + "is_select": false, + "select_types": [] + }, + { + "name": "IsTypedBy", + "type": "IfcRelDefinesByType", + "is_select": false, + "select_types": [] + }, + { + "name": "ObjectPlacement", + "type": "IfcObjectPlacement", + "is_select": false, + "select_types": [] + }, + { + "name": "Representation", + "type": "IfcProductRepresentation", + "is_select": false, + "select_types": [] + } + ] + } +} \ No newline at end of file diff --git a/src/ifcblenderexport/getIfcElements.py b/src/ifcblenderexport/getIfcElements.py index 775d1ced5a..9f29c89c02 100644 --- a/src/ifcblenderexport/getIfcElements.py +++ b/src/ifcblenderexport/getIfcElements.py @@ -157,7 +157,8 @@ filename_filters = { 'IfcParameterizedProfileDef_IFC4.json': ['IfcParameterizedProfileDef'], 'IfcBoundaryCondition_IFC4.json': ['IfcBoundaryCondition'], 'IfcElementType_IFC4.json': ['IfcElementType', 'IfcSpatialElementType'], - 'IfcAnnotation_IFC4.json': ['IfcAnnotation'] + 'IfcAnnotation_IFC4.json': ['IfcAnnotation'], + 'IfcPositioningElement_IFC4.json': ['IfcGrid'] # IfcPositioningElement in the future } for filename, filters in filename_filters.items(): From bdbe988dc782eb78f1190b1087194e812d1cf250 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 12 Jul 2020 11:06:14 +1000 Subject: [PATCH 2/5] Don't bother creation relationships to invalid psets. An invalid pset can occur when the pset doesn't contain any properties. This can happen when all properties are not part of the pset definition template. This may lead to unwanted data loss, but strictly enforces buildingSMART pset definitions. --- src/ifcblenderexport/blenderbim/bim/export_ifc.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index 6b678aed9e..29f10a8eee 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -2627,10 +2627,11 @@ class IfcExporter(): def relate_objects_to_psets(self): for relating_property_key, related_objects in self.ifc_parser.rel_defines_by_pset.items(): - self.file.createIfcRelDefinesByProperties( - ifcopenshell.guid.new(), self.owner_history, None, None, - [o['ifc'] for o in related_objects], - self.ifc_parser.psets[relating_property_key]['ifc']) + if self.ifc_parser.psets[relating_property_key]['ifc']: + self.file.createIfcRelDefinesByProperties( + ifcopenshell.guid.new(), self.owner_history, None, None, + [o['ifc'] for o in related_objects], + self.ifc_parser.psets[relating_property_key]['ifc']) def relate_objects_to_materials(self): if not self.ifc_export_settings.has_representations: From fa1926fa8479e2336e1d8aced088d2e88746ca61 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 13 Jul 2020 15:29:06 +1000 Subject: [PATCH 3/5] Exporting now uses custom people and organisations defined from the UI. See #875. --- .../blenderbim/bim/__init__.py | 5 + .../bim/data/owner/organisation.json | 30 --- .../blenderbim/bim/data/owner/person.json | 35 --- .../blenderbim/bim/export_ifc.py | 225 ++++++++++++++---- .../blenderbim/bim/operator.py | 38 +++ src/ifcblenderexport/blenderbim/bim/prop.py | 60 +++-- src/ifcblenderexport/blenderbim/bim/ui.py | 52 ++-- 7 files changed, 309 insertions(+), 136 deletions(-) delete mode 100644 src/ifcblenderexport/blenderbim/bim/data/owner/organisation.json delete mode 100644 src/ifcblenderexport/blenderbim/bim/data/owner/person.json diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 2009d6f05c..11beb5030d 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -65,10 +65,14 @@ if bpy is not None: operator.RemoveObjectConstraint, operator.AddPerson, operator.RemovePerson, + operator.AddPersonRole, + operator.RemovePersonRole, operator.AddPersonAddress, operator.RemovePersonAddress, operator.AddOrganisation, operator.RemoveOrganisation, + operator.AddOrganisationRole, + operator.RemoveOrganisationRole, operator.AddOrganisationAddress, operator.RemoveOrganisationAddress, operator.AddDocumentInformation, @@ -158,6 +162,7 @@ if bpy is not None: operator.ConvertLocalToGlobal, prop.StrProperty, prop.Variable, + prop.Role, prop.Address, prop.Person, prop.Organisation, diff --git a/src/ifcblenderexport/blenderbim/bim/data/owner/organisation.json b/src/ifcblenderexport/blenderbim/bim/data/owner/organisation.json deleted file mode 100644 index 75f9d2d261..0000000000 --- a/src/ifcblenderexport/blenderbim/bim/data/owner/organisation.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "Name": "IfcOpenShell", - "Description": "IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.", - "Roles": [{ - "Role": "USERDEFINED", - "UserDefinedRole": "CONTRIBUTOR" - }], - "Addresses": [ - { - "Purpose": "USERDEFINED", - "UserDefinedPurpose": "WEBPAGE", - "Description": "The main webpage of the software collection.", - "WWWHomePageURL": "https://ifcopenshell.org" - }, - { - "Purpose": "USERDEFINED", - "UserDefinedPurpose": "WEBPAGE", - "Description": "The BlenderBIM webpage of the software collection.", - "WWWHomePageURL": "https://blenderbim.org" - }, - { - "Purpose": "USERDEFINED", - "UserDefinedPurpose": "REPOSITORY", - "Description": "The source code repository of the software collection.", - "WWWHomePageURL": "https://github.com/IfcOpenShell/IfcOpenShell.git" - } - ] - } -] diff --git a/src/ifcblenderexport/blenderbim/bim/data/owner/person.json b/src/ifcblenderexport/blenderbim/bim/data/owner/person.json deleted file mode 100644 index caa8276916..0000000000 --- a/src/ifcblenderexport/blenderbim/bim/data/owner/person.json +++ /dev/null @@ -1,35 +0,0 @@ -[ - { - "Identification": "Moult", - "FamilyName": "Moult", - "GivenName": "Dion", - "MiddleNames": ["Sebastian", "Isan", "Tan"], - "PrefixTitles": ["Mr"], - "SuffixTitles": ["UE"], - "Roles": [{ - "Role": "ARCHITECT", - "UserDefinedRole": null, - "Description": "Draws the pretty pictures" - }], - "Addresses": [{ - "Purpose": "OFFICE", - "Description": "Headquarters", - "UserDefinedPurpose": null, - "InternalLocation": "Cupboard under the stairs", - "AddressLines": [ "221B Baker Street" ], - "PostalBox": null, - "Town": "MyTown", - "Region": "Middle-Earth", - "PostalCode": "42", - "Country": "Narnia" - }, { - "Purpose": "OFFICE", - "Description": "Headquarters", - "UserDefinedPurpose": null, - "TelephoneNumbers": ["0123456789"], - "ElectronicMailAddresses": ["dion@thinkmoult.com"], - "WWWHomePageURL": "https://thinkmoult.com", - "MessagingIDs": ["irc://irc.freenode.net##architect"] - }] - } -] diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index 29f10a8eee..ed01cd3b30 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -697,12 +697,120 @@ class IfcParser(): return results def get_people(self): - with open(self.data_dir + 'owner/person.json') as file: - return [{'raw': p} for p in json.load(file)] + data_map = { + 'name': 'Identification', + 'family_name': 'FamilyName', + 'given_name': 'GivenName', + } + list_data_map = { + 'middle_names': 'MiddleNames', + 'prefix_titles': 'PrefixTitles', + 'suffix_titles': 'SuffixTitles', + } + results = [] + for person in bpy.context.scene.BIMProperties.people: + attributes = {} + for key, value in data_map.items(): + if getattr(person, key): + attributes[value] = getattr(person, key) + for key, value in list_data_map.items(): + if getattr(person, key): + attributes[value] = getattr(person, key).split(',') + results.append({ + 'ifc': None, + 'raw': person, + 'attributes': attributes, + 'roles': self.get_roles(person.roles), + 'addresses': self.get_addresses(person.addresses) + }) + return results def get_organisations(self): - with open(self.data_dir + 'owner/organisation.json') as file: - return [{'raw': o} for o in json.load(file)] + data_map = { + 'name': 'Name', + 'description': 'Description', + } + results = [] + for organisation in bpy.context.scene.BIMProperties.organisations: + attributes = {} + for key, value in data_map.items(): + if getattr(organisation, key): + attributes[value] = getattr(organisation, key) + results.append({ + 'ifc': None, + 'raw': organisation, + 'attributes': attributes, + 'roles': self.get_roles(organisation.roles), + 'addresses': self.get_addresses(organisation.addresses) + }) + return results + + def get_roles(self, roles): + data_map = { + 'name': 'Role', + 'user_defined_role': 'UserDefinedRole', + 'description': 'Description', + } + results = [] + for role in roles: + attributes = {} + for key, value in data_map.items(): + if getattr(role, key): + attributes[value] = getattr(role, key) + results.append({ + 'ifc': None, + 'raw': role, + 'attributes': attributes + }) + return results + + def get_addresses(self, addresses): + results = [] + address_data_map = { + 'purpose': 'Purpose', + 'description': 'Description', + 'user_defined_purpose': 'UserDefinedPurpose', + } + postal_data_map = { + 'internal_location': 'InternalLocation', + 'postal_box': 'PostalBox', + 'town': 'Town', + 'region': 'Region', + 'postal_code': 'PostalCode', + 'country': 'Country', + } + telecom_data_map = { + 'pager_number': 'PagerNumber', + 'www_home_page_url': 'WWWHomePageURL', + } + telecom_list_data_map = { + 'telephone_numbers': 'TelephoneNumbers', + 'fascimile_numbers': 'FascimileNumbers', + 'electronic_mail_addresses': 'ElectronicMailAddresses', + 'messaging_ids': 'MessagingIDs', + } + for address in addresses: + attributes = {} + if 'IfcPostalAddress' in address.name: + merged_data_map = {**address_data_map, **postal_data_map} + if address.address_lines: + attributes['AddressLines'] = address.address_lines.split('/') + elif 'IfcTelecomAddress' in address.name: + merged_data_map = {**address_data_map, **telecom_data_map} + for key, value in telecom_list_data_map.items(): + if getattr(address, key): + attributes[value] = getattr(address, key).split(',') + for key, value in merged_data_map.items(): + if getattr(address, key): + attributes[value] = getattr(address, key) + results.append({ + 'ifc': None, + 'raw': address, + 'is_postal': 'IfcPostalAddress' in address.name, + 'is_telecom': 'IfcTelecomAddress' in address.name, + 'attributes': attributes + }) + return results def get_document_references(self): results = {} @@ -1221,15 +1329,55 @@ class IfcExporter(): self.file.wrapped_data.header.file_name.time_stamp = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat() self.file.wrapped_data.header.file_name.preprocessor_version = 'IfcOpenShell {}'.format(ifcopenshell.version) self.file.wrapped_data.header.file_name.originating_system = '{} {}'.format( - self.owner_history.OwningApplication.ApplicationFullName, - self.owner_history.OwningApplication.Version, - ) - if self.schema == 'IFC2X3': - self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Id + self.get_application_name(), self.get_application_version()) + if self.owner_history: + if self.schema == 'IFC2X3': + self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Id + else: + self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Identification else: - self.file.wrapped_data.header.file_name.authorization = self.owner_history.OwningUser.ThePerson.Identification + self.file.wrapped_data.header.file_name.authorization = 'Nobody' + + def get_application_name(self): + return 'BlenderBIM' + + def get_application_version(self): + return '.'.join([str(x) for x in [addon.bl_info.get('version', (-1,-1,-1)) for addon in addon_utils.modules() if addon.bl_info['name'] == 'BlenderBIM'][0]]) + + def get_application_organisation(self): + self.application_organisation = self.file.create_entity('IfcOrganization', **{ + "Name": "IfcOpenShell", + "Description": "IfcOpenShell is an open source (LGPL) software library that helps users and software developers to work with the IFC file format.", + "Roles": [self.file.create_entity('IfcActorRole', **{ + "Role": "USERDEFINED", + "UserDefinedRole": "CONTRIBUTOR" + })], + "Addresses": [ + self.file.create_entity('IfcTelecomAddress', **{ + "Purpose": "USERDEFINED", + "UserDefinedPurpose": "WEBPAGE", + "Description": "The main webpage of the software collection.", + "WWWHomePageURL": "https://ifcopenshell.org" + }), + self.file.create_entity('IfcTelecomAddress', **{ + "Purpose": "USERDEFINED", + "UserDefinedPurpose": "WEBPAGE", + "Description": "The BlenderBIM Add-on webpage of the software collection.", + "WWWHomePageURL": "https://blenderbim.org" + }), + self.file.create_entity('IfcTelecomAddress', **{ + "Purpose": "USERDEFINED", + "UserDefinedPurpose": "REPOSITORY", + "Description": "The source code repository of the software collection.", + "WWWHomePageURL": "https://github.com/IfcOpenShell/IfcOpenShell.git" + }) + ] + }) + return self.application_organisation def create_owner_history(self): + person = None + organisation = None for person in self.ifc_parser.people: if self.schema == 'IFC2X3' and person['ifc'].Id == bpy.context.scene.BIMProperties.person: break @@ -1238,18 +1386,20 @@ class IfcExporter(): for organisation in self.ifc_parser.organisations: if organisation['ifc'].Name == bpy.context.scene.BIMProperties.organisation: break + if not person or not organisation: + self.owner_history = None + return person_and_organisation = self.file.create_entity('IfcPersonAndOrganization', **{ 'ThePerson': person['ifc'], 'TheOrganization': organisation['ifc'], 'Roles': None # TODO }) - version = '.'.join([str(x) for x in [addon.bl_info.get('version', (-1,-1,-1)) for addon in addon_utils.modules() if addon.bl_info['name'] == 'BlenderBIM'][0]]) - developer_organisation = [o for o in self.ifc_parser.organisations if o['ifc'].Name == 'IfcOpenShell'][0]['ifc'] + developer_organisation = self.get_application_organisation() application = self.file.create_entity('IfcApplication', **{ 'ApplicationDeveloper': developer_organisation, - 'Version': version, - 'ApplicationFullName': 'BlenderBIM', - 'ApplicationIdentifier': 'BlenderBIM' + 'Version': self.get_application_version(), + 'ApplicationFullName': self.get_application_name(), + 'ApplicationIdentifier': self.get_application_name() }) self.owner_history = self.file.create_entity('IfcOwnerHistory', **{ 'OwningUser': person_and_organisation, @@ -1317,45 +1467,36 @@ class IfcExporter(): def create_people(self): for person in self.ifc_parser.people: - data = person['raw'].copy() - if data['Roles']: - data['Roles'] = self.create_roles(data['Roles']) - if data['Addresses']: - data['Addresses'] = self.create_addresses(data['Addresses']) - if self.schema == 'IFC2X3' and 'Identification' in data: - data['Id'] = data['Identification'] - del data['Identification'] - person['ifc'] = self.file.create_entity('IfcPerson', **data) + if person['roles']: + person['attributes']['Roles'] = self.create_roles(person['roles']) + if person['addresses']: + person['attributes']['Addresses'] = self.create_addresses(person['addresses']) + if self.schema == 'IFC2X3' and 'Identification' in person['attributes']: + person['attributes']['Id'] = person['attributes']['Identification'] + del person['attributes']['Identification'] + person['ifc'] = self.file.create_entity('IfcPerson', **person['attributes']) def create_organisations(self): for organisation in self.ifc_parser.organisations: - data = organisation['raw'].copy() - if data['Roles']: - data['Roles'] = self.create_roles(data['Roles']) - if data['Addresses']: - data['Addresses'] = self.create_addresses(data['Addresses']) - organisation['ifc'] = self.file.create_entity('IfcOrganization', **data) + if organisation['roles']: + organisation['attributes']['Roles'] = self.create_roles(organisation['roles']) + if organisation['addresses']: + organisation['attributes']['Addresses'] = self.create_addresses(organisation['addresses']) + organisation['ifc'] = self.file.create_entity('IfcOrganization', **organisation['attributes']) def create_roles(self, roles): results = [] for role in roles: - results.append(self.file.create_entity('IfcActorRole', **role)) + results.append(self.file.create_entity('IfcActorRole', **role['attributes'])) return results def create_addresses(self, addresses): results = [] for address in addresses: - is_postal_address = False - for key in ['InternalLocation', 'AddressLines', 'PostalBox', 'Town', - 'Region', 'PostalCode', 'Country']: - if key in address: - is_postal_address = True - if is_postal_address: - results.append(self.file.create_entity('IfcPostalAddress', **address)) - else: - if self.schema == 'IFC2X3' and 'MessagingIDs' in address: - del address['MessagingIDs'] - results.append(self.file.create_entity('IfcTelecomAddress', **address)) + if self.schema == 'IFC2X3' and 'MessagingIDs' in address['attributes']: + del address['attributes']['MessagingIDs'] + results.append(self.file.create_entity('IfcPostalAddress' if + address['is_postal'] else 'IfcTelecomAddress', **address['attributes'])) return results def create_library_information(self): diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 4c59976277..1210bce1ab 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -917,6 +917,25 @@ class RemovePersonAddress(bpy.types.Operator): return {'FINISHED'} +class AddPersonRole(bpy.types.Operator): + bl_idname = 'bim.add_person_role' + bl_label = 'Add Person Role' + + def execute(self, context): + new = bpy.context.scene.BIMProperties.people[bpy.context.scene.BIMProperties.active_person_index].roles.add() + return {'FINISHED'} + + +class RemovePersonRole(bpy.types.Operator): + bl_idname = 'bim.remove_person_role' + bl_label = 'Remove Person Role' + index: bpy.props.IntProperty() + + def execute(self, context): + bpy.context.scene.BIMProperties.people[bpy.context.scene.BIMProperties.active_person_index].roles.remove(self.index) + return {'FINISHED'} + + class AddOrganisation(bpy.types.Operator): bl_idname = 'bim.add_organisation' bl_label = 'Add Organisation' @@ -957,6 +976,25 @@ class RemoveOrganisationAddress(bpy.types.Operator): return {'FINISHED'} +class AddOrganisationRole(bpy.types.Operator): + bl_idname = 'bim.add_organisation_role' + bl_label = 'Add Organisation Role' + + def execute(self, context): + new = bpy.context.scene.BIMProperties.organisations[bpy.context.scene.BIMProperties.active_organisation_index].roles.add() + return {'FINISHED'} + + +class RemoveOrganisationRole(bpy.types.Operator): + bl_idname = 'bim.remove_organisation_role' + bl_label = 'Remove Organisation Role' + index: bpy.props.IntProperty() + + def execute(self, context): + bpy.context.scene.BIMProperties.organisations[bpy.context.scene.BIMProperties.active_organisation_index].roles.remove(self.index) + return {'FINISHED'} + + class AddDocumentInformation(bpy.types.Operator): bl_idname = 'bim.add_document_information' bl_label = 'Add Document Information' diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 940225bbc8..f6f9a305ec 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -152,21 +152,15 @@ def getProfileDef(self, context): def getPersons(self, context): global persons_enum - if len(persons_enum) < 1: - persons_enum.clear() - with open(os.path.join(self.data_dir, 'owner', 'person.json'), 'r') as f: - persons = json.load(f) - persons_enum.extend([(p['Identification'], p['Identification'], '') for p in persons]) + persons_enum.clear() + persons_enum.extend([(p.name, p.name, '') for p in bpy.context.scene.BIMProperties.people]) return persons_enum def getOrganisations(self, context): global organisations_enum - if len(organisations_enum) < 1: - organisations_enum.clear() - with open(os.path.join(self.data_dir, 'owner', 'organisation.json'), 'r') as f: - organisations = json.load(f) - organisations_enum.extend([(o['Name'], o['Name'], '') for o in organisations]) + organisations_enum.clear() + organisations_enum.extend([(o.name, o.name, '') for o in bpy.context.scene.BIMProperties.organisations]) return organisations_enum @@ -832,13 +826,19 @@ class PropertyTemplate(PropertyGroup): class Address(PropertyGroup): name: StringProperty(name="Name") # Stores IfcPostalAddress or IfcTelecomAddress - purpose: StringProperty(name="Purpose") + purpose: EnumProperty(items=[ + ('OFFICE', 'OFFICE', 'An office address.'), + ('SITE', 'SITE', 'A site address.'), + ('HOME', 'HOME', 'A home address.'), + ('DISTRIBUTIONPOINT', 'DISTRIBUTIONPOINT', 'A postal distribution point address.'), + ('USERDEFINED', 'USERDEFINED', 'A user defined address type to be provided.'), + ], name='Purpose') description: StringProperty(name="Description") user_defined_purpose: StringProperty(name="Custom Purpose") internal_location: StringProperty(name="Internal Location") address_lines: StringProperty(name="Address") - postal_box: StringProperty(name="PO Box") + postal_box: StringProperty(name="Postal Box") town: StringProperty(name="Town") region: StringProperty(name="Region") postal_code: StringProperty(name="Postal Code") @@ -852,10 +852,41 @@ class Address(PropertyGroup): messaging_ids: StringProperty(name="IMs") +class Role(PropertyGroup): + name: EnumProperty(items=[ + ('SUPPLIER', 'SUPPLIER', ''), + ('MANUFACTURER', 'MANUFACTURER', ''), + ('CONTRACTOR', 'CONTRACTOR', ''), + ('SUBCONTRACTOR', 'SUBCONTRACTOR', ''), + ('ARCHITECT', 'ARCHITECT', ''), + ('STRUCTURALENGINEER', 'STRUCTURALENGINEER', ''), + ('COSTENGINEER', 'COSTENGINEER', ''), + ('CLIENT', 'CLIENT', ''), + ('BUILDINGOWNER', 'BUILDINGOWNER', ''), + ('BUILDINGOPERATOR', 'BUILDINGOPERATOR', ''), + ('MECHANICALENGINEER', 'MECHANICALENGINEER', ''), + ('ELECTRICALENGINEER', 'ELECTRICALENGINEER', ''), + ('PROJECTMANAGER', 'PROJECTMANAGER', ''), + ('FACILITIESMANAGER', 'FACILITIESMANAGER', ''), + ('CIVILENGINEER', 'CIVILENGINEER', ''), + ('COMMISSIONINGENGINEER', 'COMMISSIONINGENGINEER', ''), + ('ENGINEER', 'ENGINEER', ''), + ('OWNER', 'OWNER', ''), + ('CONSULTANT', 'CONSULTANT', ''), + ('CONSTRUCTIONMANAGER', 'CONSTRUCTIONMANAGER', ''), + ('FIELDCONSTRUCTIONMANAGER', 'FIELDCONSTRUCTIONMANAGER', ''), + ('RESELLER', 'RESELLER', ''), + ('USERDEFINED', 'USERDEFINED', ''), + ], name='Name') + user_defined_role: StringProperty(name="Custom Role") + description: StringProperty(name="Description") + + class Organisation(PropertyGroup): name: StringProperty(name="Name") description: StringProperty(name="Description") - roles: StringProperty(name="Roles") + roles: CollectionProperty(name="Roles", type=Role) + active_role_index: bpy.props.IntProperty() addresses: CollectionProperty(name="Addresses", type=Address) active_address_index: bpy.props.IntProperty() @@ -867,7 +898,8 @@ class Person(PropertyGroup): middle_names: StringProperty(name="Middle Names") prefix_titles: StringProperty(name="Prefixes") suffix_titles: StringProperty(name="Suffixes") - roles: StringProperty(name="Roles") + roles: CollectionProperty(name="Roles", type=Role) + active_role_index: bpy.props.IntProperty() addresses: CollectionProperty(name="Addresses", type=Address) active_address_index: bpy.props.IntProperty() diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index fede5e3d88..3ef31d7cde 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -854,11 +854,17 @@ class BIM_PT_owner(Panel): scene = context.scene props = scene.BIMProperties - row = layout.row() - row.prop(props, 'person') + if not props.person: + layout.label(text="No people found.") + else: + row = layout.row() + row.prop(props, 'person') - row = layout.row() - row.prop(props, 'organisation') + if not props.organisation: + layout.label(text="No organisations found.") + else: + row = layout.row() + row.prop(props, 'organisation') class BIM_PT_people(Panel): @@ -879,7 +885,7 @@ class BIM_PT_people(Panel): row.operator('bim.add_person') if props.people: - layout.template_list('BIM_UL_topics', '', + layout.template_list('BIM_UL_generic', '', props, 'people', props, 'active_person_index') if props.active_person_index < len(props.people): @@ -897,11 +903,9 @@ class BIM_PT_people(Panel): row.prop(person, 'prefix_titles') row = layout.row() row.prop(person, 'suffix_titles') - row = layout.row() - row.prop(person, 'roles') - + layout.label(text="Roles:") + draw_roles_ui(layout, person, 'person') layout.label(text="Addresses:") - draw_addresses_ui(layout, person, 'person') @@ -923,7 +927,7 @@ class BIM_PT_organisations(Panel): row.operator('bim.add_organisation') if props.organisations: - layout.template_list('BIM_UL_topics', '', + layout.template_list('BIM_UL_generic', '', props, 'organisations', props, 'active_organisation_index') if props.active_organisation_index < len(props.organisations): @@ -933,20 +937,38 @@ class BIM_PT_organisations(Panel): row.operator('bim.remove_organisation', icon='X', text='').index = props.active_organisation_index row = layout.row() row.prop(organisation, 'description') - row = layout.row() - row.prop(organisation, 'roles') - + layout.label(text="Roles:") + draw_roles_ui(layout, organisation, 'organisation') layout.label(text="Addresses:") - draw_addresses_ui(layout, organisation, 'organisation') +def draw_roles_ui(layout, parent, parent_type): + row = layout.row() + row.operator(f'bim.add_{parent_type}_role') + + if parent.roles: + layout.template_list('BIM_UL_generic', '', + parent, 'roles', parent, 'active_role_index') + + if parent.active_role_index < len(parent.roles): + role = parent.roles[parent.active_role_index] + row = layout.row() + row.prop(role, 'name') + row.operator(f'bim.remove_{parent_type}_role', icon='X', text='').index = parent.active_role_index + if role.name == 'USERDEFINED': + row = layout.row() + row.prop(role, 'user_defined_role') + row = layout.row() + row.prop(role, 'description') + + def draw_addresses_ui(layout, parent, parent_type): row = layout.row() row.operator(f'bim.add_{parent_type}_address') if parent.addresses: - layout.template_list('BIM_UL_topics', '', + layout.template_list('BIM_UL_generic', '', parent, 'addresses', parent, 'active_address_index') if parent.active_address_index < len(parent.addresses): From 88331d16a2df8b94c8a877d5ff6e0aa44033e267 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 13 Jul 2020 16:12:34 +1000 Subject: [PATCH 4/5] Fix bug where imported materials that duplicated a name could be incorrectly assigned --- src/ifcblenderexport/blenderbim/bim/import_ifc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 8e16be9dc6..96ab37cedb 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -5,6 +5,7 @@ import ifcopenshell.util.selector import bpy import bmesh import os +import re import shutil import threading import json @@ -105,7 +106,7 @@ class MaterialCreator(): return if len(obj.material_slots) == 1: return - slots = [s.name for s in obj.material_slots] + slots = [self.canonicalise_material_name(s.name) for s in obj.material_slots] material_to_slot = {} for i, material in enumerate(mesh['ios_materials']): if material == 'NULLMAT': @@ -119,6 +120,9 @@ class MaterialCreator(): else 0) for mat_id in mesh['ios_material_ids']] mesh.polygons.foreach_set('material_index', material_index) + def canonicalise_material_name(self, name): + return re.sub(r'\.[0-9]{3}', '', name) + def parse_material(self, element): for association in element.HasAssociations: if association.is_a('IfcRelAssociatesMaterial'): From 42f597a7af6108bfb2d469caa9d8d872e4bb4bbb Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 13 Jul 2020 16:15:23 +1000 Subject: [PATCH 5/5] New experimental mode to allow users to profile import progress --- .../blenderbim/bim/import_ifc.py | 46 +++++++++++++++++++ .../blenderbim/bim/operator.py | 1 + src/ifcblenderexport/blenderbim/bim/prop.py | 1 + src/ifcblenderexport/blenderbim/bim/ui.py | 2 + 4 files changed, 50 insertions(+) diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 96ab37cedb..ad2b43daf0 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -270,57 +270,101 @@ class IfcImporter(): self.material_creator = MaterialCreator(ifc_import_settings) + def profile_code(self, message): + if not self.ifc_import_settings.should_import_with_profiling: + return + if not self.time: + self.time = time.time() + print('{} :: {:.2f}'.format(message, time.time() - self.time)) + self.time = time.time() + def execute(self): + self.profile_code('Starting import process') self.load_existing_rooted_elements() + self.profile_code('Load existing rooted elements') self.load_diff() + self.profile_code('Load diff') self.cache_file() + self.profile_code('Caching file') self.load_file() + self.profile_code('Loading file') self.set_ifc_file() + self.profile_code('Setting file') if self.ifc_import_settings.should_auto_set_workarounds: self.auto_set_workarounds() + self.profile_code('Set vendor worksarounds') self.calculate_unit_scale() + self.profile_code('Calculate unit scale') self.set_units() + self.profile_code('Set units') self.create_geometric_representation_contexts() + self.profile_code('Create contexts') self.create_project() + self.profile_code('Create project') self.create_classifications() + self.profile_code('Create classifications') self.create_document_information() + self.profile_code('Create doc info') self.create_document_references() + self.profile_code('Create doc refs') self.create_spatial_hierarchy() + self.profile_code('Create spatial hierarchy') self.purge_diff() + self.profile_code('Purge diffs') self.create_type_products() + self.profile_code('Create type products') if self.ifc_import_settings.should_import_aggregates: self.create_aggregates() + self.profile_code('Create aggregates') self.create_openings_collection() + self.profile_code('Create opening collection') self.process_element_filter() + self.profile_code('Process element filter') if self.ifc_import_settings.should_import_native: self.parse_native_elements() + self.profile_code('Parsing native elements') self.filter_ifc() + self.profile_code('Filtering ifc') self.patch_ifc() + self.profile_code('Patching ifc') self.create_georeferencing() + self.profile_code('Georeferencing ifc') self.create_groups() + self.profile_code('Creating groups') self.create_grids() + self.profile_code('Creating grids') if self.ifc_import_settings.should_import_native: self.create_native_products() + self.profile_code('Creating native products') # TODO: Deprecate after bug #682 is fixed and the new importer is stable if self.ifc_import_settings.should_use_legacy: self.create_products_legacy() else: self.create_products() + self.profile_code('Creating meshified products') self.relate_openings() + self.profile_code('Relating openings') self.place_objects_in_spatial_tree() + self.profile_code('Placing objects in spatial tree') if self.ifc_import_settings.should_merge_aggregates: self.merge_aggregates() + self.profile_code('Merging aggregates') if self.ifc_import_settings.should_merge_by_class: self.merge_by_class() + self.profile_code('Merging by class') elif self.ifc_import_settings.should_merge_by_material: self.merge_by_material() + self.profile_code('Merging by material') if self.ifc_import_settings.should_merge_materials_by_colour \ or (self.ifc_import_settings.should_auto_set_workarounds \ and len(self.material_creator.materials) > 300): self.merge_materials_by_colour() + self.profile_code('Merging by colour') self.add_project_to_scene() + self.profile_code('Add project to scene') if self.ifc_import_settings.should_clean_mesh and len(self.file.by_type('IfcElement')) < 10000: self.clean_mesh() + self.profile_code('Mesh cleaning') def auto_set_workarounds(self): if 'DDS-CAD' in self.file.wrapped_data.header.file_name.originating_system \ @@ -1879,6 +1923,7 @@ class IfcImportSettings: self.should_import_spaces = False self.should_treat_styled_item_as_material = False self.should_use_cpu_multiprocessing = False + self.should_import_with_profiling = False self.should_import_native = False self.should_use_legacy = False self.should_merge_aggregates = False @@ -1907,6 +1952,7 @@ class IfcImportSettings: settings.should_auto_set_workarounds = scene_bim.import_should_auto_set_workarounds settings.should_treat_styled_item_as_material = scene_bim.import_should_treat_styled_item_as_material settings.should_use_cpu_multiprocessing = scene_bim.import_should_use_cpu_multiprocessing + settings.should_import_with_profiling = scene_bim.import_should_import_with_profiling settings.should_import_native = scene_bim.import_should_import_native settings.should_use_legacy = scene_bim.import_should_use_legacy settings.should_import_aggregates = scene_bim.import_should_import_aggregates diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 1210bce1ab..6767ca2b72 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -87,6 +87,7 @@ class ImportIFC(bpy.types.Operator, ImportHelper): ifc_importer = import_ifc.IfcImporter(ifc_import_settings) ifc_importer.execute() ifc_import_settings.logger.info('Import finished in {:.2f} seconds'.format(time.time() - start)) + print('Import finished in {:.2f} seconds'.format(time.time() - start)) return {'FINISHED'} class SelectGlobalId(bpy.types.Operator): diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index f6f9a305ec..33312453c0 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -1004,6 +1004,7 @@ class BIMProperties(PropertyGroup): import_should_use_legacy: BoolProperty(name="Import with Legacy Importer", default=False) import_should_import_native: BoolProperty(name="Import Native Representations", default=False) import_should_use_cpu_multiprocessing: BoolProperty(name="Import with CPU Multiprocessing", default=False) + import_should_import_with_profiling: BoolProperty(name="Import with Profiling", default=True) import_should_import_aggregates: BoolProperty(name="Import Aggregates", default=True) import_should_merge_aggregates: BoolProperty(name="Import and Merge Aggregates", default=False) import_should_merge_by_class: BoolProperty(name="Import and Merge by Class", default=False) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 3ef31d7cde..47cc3b77ca 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -1459,6 +1459,8 @@ class BIM_PT_mvd(Panel): row.prop(bim_properties, 'import_should_import_native') row = layout.row() row.prop(bim_properties, 'import_should_use_cpu_multiprocessing') + row = layout.row() + row.prop(bim_properties, 'import_should_import_with_profiling') layout.label(text='Simplifications:')