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 6b678aed9e..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): @@ -2627,10 +2768,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: diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 8e16be9dc6..ad2b43daf0 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'): @@ -266,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 \ @@ -1875,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 @@ -1903,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 4c59976277..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): @@ -917,6 +918,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 +977,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 3e70d095c7..33312453c0 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 @@ -151,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 @@ -831,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") @@ -851,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() @@ -866,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() @@ -971,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/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/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index fede5e3d88..47cc3b77ca 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): @@ -1437,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:') 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():