From bcbee0659bf47eb83923960224ca12ce421e6c8f Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 8 Jun 2022 12:09:19 +1000 Subject: [PATCH] #2224 Coordination mode now allows interspersed (much faster) object merging during model loading to crunch down large models, with a new by type merge mode --- src/blenderbim/blenderbim/bim/import_ifc.py | 128 +++++++++++++----- .../blenderbim/bim/module/project/prop.py | 18 ++- .../blenderbim/bim/module/project/ui.py | 8 +- 3 files changed, 110 insertions(+), 44 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 119f61a883..ea1fe8e890 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -249,20 +249,14 @@ class IfcImporter: self.profile_code("Create type products") self.place_objects_in_collections() self.profile_code("Place objects in collections") - 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 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")) < 1000: self.clean_mesh() self.profile_code("Mesh cleaning") + if self.ifc_import_settings.should_merge_materials_by_colour or len(self.material_creator.materials) > 300: + self.merge_materials_by_colour() + self.profile_code("Merging by colour") self.set_default_context() self.profile_code("Setting default context") self.update_progress(100) @@ -632,8 +626,14 @@ class IfcImporter: for element in self.native_elements: progress += 1 if progress % 250 == 0: - print("{} / {} elements processed in {:.2f}s ...".format(progress, total, time.time() - checkpoint)) + percent = round(progress / total * 100) + print( + "{} / {} ({}%) elements processed in {:.2f}s ...".format( + progress, total, percent, time.time() - checkpoint + ) + ) checkpoint = time.time() + self.incrementally_merge_objects() native_data = self.native_data[element.GlobalId] representation = native_data["representation"] if not representation: @@ -702,13 +702,17 @@ class IfcImporter: while True: progress += 1 if progress % 250 == 0: + percent_created = round(progress / total * 100) + percent_preprocessed = iterator.progress() + percent_average = (percent_created + percent_preprocessed) / 2 print( - "{} / {} ({}% preprocessed) elements processed in {:.2f}s ...".format( - progress, total, iterator.progress(), time.time() - checkpoint + "{} / {} ({}% created, {}% preprocessed) elements processed in {:.2f}s ...".format( + progress, total, percent_created, percent_preprocessed, time.time() - checkpoint ) ) checkpoint = time.time() - self.update_progress((iterator.progress() / 100 * progress_range) + start_progress) + self.update_progress((percent_average / 100 * progress_range) + start_progress) + self.incrementally_merge_objects() shape = iterator.get() if shape: product = self.file.by_id(shape.guid) @@ -727,6 +731,19 @@ class IfcImporter: print("Done creating geometry") return results + def incrementally_merge_objects(self): + if not self.ifc_import_settings.is_coordinating: + return + if self.ifc_import_settings.merge_mode == "IFC_CLASS": + self.merge_by_class() + self.profile_code("Merging by class") + elif self.ifc_import_settings.merge_mode == "IFC_TYPE": + self.merge_by_type() + self.profile_code("Merging by type") + elif self.ifc_import_settings.merge_mode == "MATERIAL": + self.merge_by_material() + self.profile_code("Merging by material") + def create_structural_items(self): # Create structural collections self.structural_member_collection = bpy.data.collections.new("Members") @@ -1121,33 +1138,74 @@ class IfcImporter: def merge_by_class(self): merge_set = {} - for obj in self.added_data.values(): - if not isinstance(obj, bpy.types.Object): + id_set = {} + for ifc_definition_id, obj in self.added_data.items(): + if not isinstance(obj, bpy.types.Object) or not obj.data: continue - if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name: + element = self.file.by_id(ifc_definition_id) + if not element.is_a("IfcElement"): continue - merge_set.setdefault(obj.name.split("/")[0], []).append(obj) - self.merge_objects(merge_set) + merge_set.setdefault(element.is_a(), []).append(obj) + id_set.setdefault(element.is_a(), []).append(ifc_definition_id) + self.merge_objects(merge_set, id_set) + + def merge_by_type(self): + merge_set = {} + id_set = {} + for ifc_definition_id, obj in self.added_data.items(): + if not isinstance(obj, bpy.types.Object) or not obj.data: + continue + element = self.file.by_id(ifc_definition_id) + if not element.is_a("IfcElement"): + continue + element_type = ifcopenshell.util.element.get_type(element) + if not element_type: + continue + merge_key = str(element_type.id()) + "-" + element_type.Name or "Unnamed" + merge_set.setdefault(merge_key, []).append(obj) + id_set.setdefault(merge_key, []).append(ifc_definition_id) + self.merge_objects(merge_set, id_set) def merge_by_material(self): merge_set = {} - for obj in self.added_data.values(): - if not isinstance(obj, bpy.types.Object): + id_set = {} + for ifc_definition_id, obj in self.added_data.items(): + if not isinstance(obj, bpy.types.Object) or not obj.data: continue - if "/" not in obj.name or "IfcRelAggregates" in obj.users_collection[0].name: + element = self.file.by_id(ifc_definition_id) + if not element.is_a("IfcElement"): continue - if not obj.material_slots: - merge_set.setdefault("no-material", []).append(obj) - else: - merge_set.setdefault(obj.material_slots[0].name, []).append(obj) - self.merge_objects(merge_set) + merge_key = obj.material_slots[0].name if obj.material_slots else "no-material" + merge_set.setdefault(merge_key, []).append(obj) + id_set.setdefault(merge_key, []).append(ifc_definition_id) + self.merge_objects(merge_set, id_set) - def merge_objects(self, merge_set): - for ifc_class, objs in merge_set.items(): - context_override = {} - context_override["object"] = context_override["active_object"] = objs[0] - context_override["selected_objects"] = context_override["selected_editable_objects"] = objs - bpy.ops.object.join(context_override) + def merge_objects(self, merge_set, id_set): + total_objs = sum([len(o) for o in merge_set.values()]) + cumulative_total = 0 + merge_set = {k: v for k, v in sorted(merge_set.items(), key=lambda i: len(i[1]), reverse=True)} + for group_name, objs in merge_set.items(): + total_group_objs = len(objs) + if total_group_objs < 10: + continue + merge_potential = total_objs - cumulative_total + if merge_potential < 250: + print("Merge target achieved") + return + cumulative_total += total_group_objs + print(f"Merging {total_group_objs} objects, {merge_potential} potentially remaining -", group_name) + try: + target = objs[0] + target.data = target.data.copy() + context_override = {} + context_override["object"] = context_override["active_object"] = target + context_override["selected_objects"] = context_override["selected_editable_objects"] = objs + bpy.ops.object.join(context_override) + target.data.name += "-merge" + for ifc_definition_id in id_set[group_name][1:]: + del self.added_data[ifc_definition_id] + except: + print("Merge failed") def merge_materials_by_colour(self): cleaned_materials = {} @@ -1926,8 +1984,7 @@ class IfcImportSettings: self.input_file = None self.diff_file = None self.should_use_cpu_multiprocessing = True - self.should_merge_by_class = False - self.should_merge_by_material = False + self.merge_mode = None self.should_merge_materials_by_colour = False self.should_use_native_meshes = False self.should_clean_mesh = True @@ -1956,8 +2013,7 @@ class IfcImportSettings: settings.diff_file = scene_diff.diff_json_file settings.collection_mode = props.collection_mode settings.should_use_cpu_multiprocessing = props.should_use_cpu_multiprocessing - settings.should_merge_by_class = props.should_merge_by_class - settings.should_merge_by_material = props.should_merge_by_material + settings.merge_mode = props.merge_mode settings.should_merge_materials_by_colour = props.should_merge_materials_by_colour settings.should_use_native_meshes = props.should_use_native_meshes settings.should_clean_mesh = props.should_clean_mesh diff --git a/src/blenderbim/blenderbim/bim/module/project/prop.py b/src/blenderbim/blenderbim/bim/module/project/prop.py index 36cc3b4391..8ef662c481 100644 --- a/src/blenderbim/blenderbim/bim/module/project/prop.py +++ b/src/blenderbim/blenderbim/bim/module/project/prop.py @@ -135,12 +135,20 @@ class BIMProjectProperties(PropertyGroup): active_filter_category_index: IntProperty(name="Active Filter Category Index") filter_query: StringProperty(name="Filter Query") should_filter_spatial_elements: BoolProperty(name="Filter Spatial Elements", default=False) - should_use_cpu_multiprocessing: BoolProperty(name="Import with CPU Multiprocessing", default=True) - should_merge_by_class: BoolProperty(name="Import and Merge by Class", default=False) - should_merge_by_material: BoolProperty(name="Import and Merge by Material", default=False) - should_merge_materials_by_colour: BoolProperty(name="Import and Merge Materials by Colour", default=False) - should_clean_mesh: BoolProperty(name="Import and Clean Mesh", default=True) + should_use_cpu_multiprocessing: BoolProperty(name="CPU Multiprocessing", default=True) + merge_mode: bpy.props.EnumProperty( + items=[ + ("NONE", "None", "No objects are merged"), + ("IFC_CLASS", "IFC Class", "One object per IFC class"), + ("IFC_TYPE", "IFC Type", "One object per IFC construction type"), + ("MATERIAL", "Material", "One object per material"), + ], + name="Merge Mode", + default="NONE", + ) + should_merge_materials_by_colour: BoolProperty(name="Merge Materials by Colour", default=False) should_use_native_meshes: BoolProperty(name="Native Meshes", default=False) + should_clean_mesh: BoolProperty(name="Clean Meshes", default=True) should_cache: BoolProperty(name="Cache", default=False) is_coordinating: BoolProperty(name="For Coordination Only", default=False) deflection_tolerance: FloatProperty(name="Deflection Tolerance", default=0.001) diff --git a/src/blenderbim/blenderbim/bim/module/project/ui.py b/src/blenderbim/blenderbim/bim/module/project/ui.py index 4a3a5182c4..11fbdb13a6 100644 --- a/src/blenderbim/blenderbim/bim/module/project/ui.py +++ b/src/blenderbim/blenderbim/bim/module/project/ui.py @@ -74,16 +74,18 @@ class BIM_PT_project(Panel): row = self.layout.row() row.prop(pprops, "should_use_cpu_multiprocessing") row = self.layout.row() - row.prop(pprops, "should_merge_by_class") + row.prop(pprops, "should_clean_mesh") row = self.layout.row() row.prop(pprops, "should_cache") - row.prop(pprops, "should_merge_by_material") row = self.layout.row() row.prop(pprops, "should_use_native_meshes") + row = self.layout.row() row.prop(pprops, "should_merge_materials_by_colour") row = self.layout.row() row.prop(pprops, "is_coordinating") - row.prop(pprops, "should_clean_mesh") + if pprops.is_coordinating: + row = self.layout.row() + row.prop(pprops, "merge_mode") row = self.layout.row() row.prop(pprops, "deflection_tolerance") row = self.layout.row()