From 0899a2a87bcf9dd97d97346622a7606afd14b8a7 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 18 Sep 2024 21:08:10 +1000 Subject: [PATCH] Fix bug where switching geometry may not correctly share representations --- .../bonsai/bim/module/geometry/operator.py | 45 +++---- src/bonsai/bonsai/core/geometry.py | 38 +----- src/bonsai/bonsai/tool/geometry.py | 127 ++++++++++++------ 3 files changed, 103 insertions(+), 107 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index 6a7100347f..4210aaceb3 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -295,35 +295,22 @@ class SwitchRepresentation(bpy.types.Operator, tool.Ifc.Operator): return False def _execute(self, context): - target_representation = tool.Ifc.get().by_id(self.ifc_definition_id) - target = target_representation.ContextOfItems - is_subcontext = target.is_a("IfcGeometricRepresentationSubContext") - for obj in set(context.selected_objects + [context.active_object]): - element = tool.Ifc.get_entity(obj) - if not element: - continue - if not obj.mode == "OBJECT": - continue - if obj == context.active_object: - representation = target_representation - else: - if is_subcontext: - representation = ifcopenshell.util.representation.get_representation( - element, target.ContextType, target.ContextIdentifier, target.TargetView - ) - else: - representation = ifcopenshell.util.representation.get_representation(element, target.ContextType) - if not representation: - continue - core.switch_representation( - tool.Ifc, - tool.Geometry, - obj=obj, - representation=representation, - should_reload=self.should_reload, - is_global=self.should_switch_all_meshes, - should_sync_changes_first=True, - ) + context = tool.Ifc.get().by_id(self.ifc_definition_id).ContextOfItems + for obj in tool.Blender.get_selected_objects(): + if ( + (element := tool.Ifc.get_entity(obj)) + and obj.mode == "OBJECT" + and (representation := ifcopenshell.util.representation.get_representation(element, context)) + ): + core.switch_representation( + tool.Ifc, + tool.Geometry, + obj=obj, + representation=representation, + should_reload=self.should_reload, + is_global=self.should_switch_all_meshes, + should_sync_changes_first=True, + ) class RemoveRepresentation(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/bonsai/bonsai/core/geometry.py b/src/bonsai/bonsai/core/geometry.py index 7476c56381..8132d77c88 100644 --- a/src/bonsai/bonsai/core/geometry.py +++ b/src/bonsai/bonsai/core/geometry.py @@ -121,44 +121,10 @@ def switch_representation( if not geometry.does_representation_id_exist(representation_id): return - entity = ifc.get_entity(obj) - assert entity - current_obj_data = geometry.get_object_data(obj) - - if not current_obj_data and geometry.is_text_literal(representation): + if not geometry.get_object_data(obj) and geometry.is_text_literal(representation): return - use_immediate_repr = geometry.should_use_immediate_representation(entity, apply_openings) - if use_immediate_repr: - # if it has openings make sure to switch to element's mapped representation - representation = geometry.unresolve_type_representation(representation, entity) - else: - # doesn't resolve mapped representations in case if it's going to have openings - # otherwise we would also add openings to the type and other occurences mesh data - representation = geometry.resolve_mapped_representation(representation) - - old_repr_data = geometry.get_representation_data(representation) - if should_reload or not old_repr_data: - new_repr_data = geometry.import_representation(obj, representation, apply_openings=apply_openings) - geometry.rename_object(new_repr_data, geometry.get_representation_name(representation)) - geometry.link(representation, new_repr_data) - else: - new_repr_data = old_repr_data - - geometry.change_object_data(obj, new_repr_data, is_global=is_global and not use_immediate_repr) - geometry.record_object_materials(obj) - - # we assume that all the occurences and the type have the same representation context active - # so geometry.delete_data cannot remove the data that's still used by some other object - if should_reload and old_repr_data: - # if current object was using some temporary mesh (like during profile edit mode) instead of `old_repr_data` - # then `change_object_data` won't switch the mesh for all the occurences and we need to do it explicitly - if current_obj_data != old_repr_data and geometry.has_data_users(old_repr_data): - geometry.replace_object_data_globally(old_repr_data, new_repr_data) - geometry.delete_data(old_repr_data) - - geometry.clear_modifiers(obj) - geometry.clear_cache(entity) + geometry.reimport_element_representations(obj, representation, apply_openings=apply_openings) def get_representation_ifc_parameters( diff --git a/src/bonsai/bonsai/tool/geometry.py b/src/bonsai/bonsai/tool/geometry.py index 87a111438b..0c36c6730e 100644 --- a/src/bonsai/bonsai/tool/geometry.py +++ b/src/bonsai/bonsai/tool/geometry.py @@ -603,65 +603,108 @@ class Geometry(bonsai.core.tool.Geometry): return False @classmethod - def import_representation( + def reimport_element_representations( cls, obj: bpy.types.Object, representation: ifcopenshell.entity_instance, apply_openings: bool = True ) -> Union[bpy.types.Mesh, bpy.types.Curve]: + element = tool.Ifc.get_entity(obj) + assert element + + elements = set() + element_types = set() + representation = ifcopenshell.util.representation.resolve_representation(representation) + context = representation.ContextOfItems + for mapped_element in ifcopenshell.util.element.get_elements_by_representation(tool.Ifc.get(), representation): + if mapped_element.is_a("IfcTypeProduct"): + element_types.add(mapped_element) + else: + elements.add(mapped_element) + if element_type := ifcopenshell.util.element.get_type(mapped_element): + element_types.add(element_type) + logger = logging.getLogger("ImportIFC") ifc_import_settings = bonsai.bim.import_ifc.IfcImportSettings.factory(bpy.context, None, logger) - element = tool.Ifc.get_entity(obj) - assert element # Type checker. settings = ifcopenshell.geom.settings() settings.set("weld-vertices", True) settings.set("apply-default-materials", False) settings.set("layerset-first", True) settings.set("keep-bounding-boxes", True) - context = representation.ContextOfItems + settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS) ifc_importer = bonsai.bim.import_ifc.IfcImporter(ifc_import_settings) ifc_importer.file = tool.Ifc.get() - # create_shape doesn't support point cloud representations. - if representation.RepresentationType in ("PointCloud", "Point"): - mesh = tool.Loader.create_point_cloud_mesh(representation) - if mesh is None: - raise Exception(f"Failed to process point cloud representation: {representation}.") - return mesh + # TODO support fallbacks like for point clouds - if element.is_a("IfcTypeProduct"): - # You may only specify a single representation when creating shapes for types - try: - shape = ifcopenshell.geom.create_shape(settings, representation) - except: - settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS) - shape = ifcopenshell.geom.create_shape(settings, representation) - else: - if not apply_openings: - settings.set("disable-opening-subtractions", True) + settings.set("context-ids", [context.id()]) + if not apply_openings: + settings.set("disable-opening-subtractions", True) - if context.ContextIdentifier == "Body" and context.TargetView == "MODEL_VIEW": - try: - shape = ifcopenshell.geom.create_shape(settings, element, representation) - except: - settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS) - shape = ifcopenshell.geom.create_shape(settings, element, representation) - else: - settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS) - shape = ifcopenshell.geom.create_shape(settings, element, representation) + shape = None + iterator = ifcopenshell.geom.iterator(settings, tool.Ifc.get(), multiprocessing.cpu_count(), include=elements) + meshes = {} + if iterator.initialize(): + while True: + shape = iterator.get() + element = tool.Ifc.get().by_id(shape.id) + if obj := tool.Ifc.get_object(element): + mesh_name = tool.Loader.get_mesh_name_from_shape(shape.geometry) + mesh = meshes.get(mesh_name) + if mesh is None: + # Duplicate code + representation = tool.Ifc.get().by_id(int(shape.geometry.id.split("-")[0])) + if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING": + mesh = tool.Loader.create_camera(element, representation, shape) + if element.is_a("IfcAnnotation") and ifc_importer.is_curve_annotation(element): + mesh = ifc_importer.create_curve(element, shape) + elif shape: + mesh = ifc_importer.create_mesh(element, shape) + ifc_importer.material_creator.load_existing_materials() + shape_has_openings = cls.does_shape_has_openings(shape) + ifc_importer.material_creator.create(element, obj, mesh, shape_has_openings) + mesh.BIMMeshProperties.has_openings_applied = apply_openings + if not shape_has_openings: + tool.Loader.load_indexed_colour_map(representation, mesh) + tool.Loader.link_mesh(shape, mesh) + meshes[mesh_name] = mesh - if element.is_a("IfcAnnotation") and element.ObjectType == "DRAWING": - mesh = tool.Loader.create_camera(element, representation, shape) - if element.is_a("IfcAnnotation") and ifc_importer.is_curve_annotation(element): - mesh = ifc_importer.create_curve(element, shape) - elif shape: - mesh = ifc_importer.create_mesh(element, shape) - ifc_importer.material_creator.load_existing_materials() - shape_has_openings = cls.does_shape_has_openings(shape) - ifc_importer.material_creator.create(element, obj, mesh, shape_has_openings) - mesh.BIMMeshProperties.has_openings_applied = apply_openings - if not shape_has_openings: - tool.Loader.load_indexed_colour_map(representation, mesh) + old_mesh = obj.data + cls.change_object_data(obj, mesh, is_global=False) + cls.record_object_materials(obj) + if not cls.has_data_users(old_mesh): + cls.delete_data(old_mesh) + cls.clear_modifiers(obj) + cls.clear_cache(element) - return mesh + if not iterator.next(): + break + + for element in element_types: + if obj := tool.Ifc.get_object(element): + if representation := ifcopenshell.util.representation.get_representation(element, context): + geometry = ifcopenshell.geom.create_shape(settings, representation) + mesh_name = tool.Loader.get_mesh_name_from_shape(geometry) + mesh = meshes.get(mesh_name) + if mesh is None: + # Duplicate code + representation = tool.Ifc.get().by_id(int(geometry.id.split("-")[0])) + if geometry: + mesh = ifc_importer.create_mesh(element, geometry) + ifc_importer.material_creator.load_existing_materials() + shape_has_openings = False + ifc_importer.material_creator.create(element, obj, mesh, shape_has_openings) + mesh.BIMMeshProperties.has_openings_applied = apply_openings + if not shape_has_openings: + tool.Loader.load_indexed_colour_map(representation, mesh) + tool.Loader.link_mesh(geometry, mesh) + meshes[mesh_name] = mesh + + old_mesh = obj.data + cls.change_object_data(obj, mesh, is_global=False) + cls.record_object_materials(obj) + if not cls.has_data_users(old_mesh): + cls.delete_data(old_mesh) + cls.clear_modifiers(obj) + cls.clear_cache(element) @classmethod def does_shape_has_openings(