diff --git a/src/bonsai/bonsai/bim/module/aggregate/operator.py b/src/bonsai/bonsai/bim/module/aggregate/operator.py index fccb246143..a68975027d 100644 --- a/src/bonsai/bonsai/bim/module/aggregate/operator.py +++ b/src/bonsai/bonsai/bim/module/aggregate/operator.py @@ -268,23 +268,26 @@ class BIM_OT_select_aggregate(bpy.types.Operator): ) should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) + filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) @classmethod def description(cls, context, properties): if properties.select_parts: - return "Select Aggregate and Parts.\n\nCtrl+click to select only one level deep\nSHIFT+Click to remove from selection set\nALT+Click to also unhide hidden objects (viewport and local hide)" + return "Select Aggregate and Parts.\n\nSHIFT+Click to remove from selection set\nCTRL+Click to filter selection to matching objects only\nCTRL+SHIFT+Click to select only one level deep\nALT+Click to also unhide hidden objects (viewport and local hide)" else: - return "Select Aggregate\n\nSHIFT+Click to remove from selection set\nALT+Click to also unhide hidden objects (viewport and local hide)" + return "Select Aggregate\n\nSHIFT+Click to remove from selection set\nCTRL+Click to filter selection to matching objects only\nALT+Click to also unhide hidden objects (viewport and local hide)" def invoke(self, context, event): - if event.type == "LEFTMOUSE" and event.ctrl: + if event.type == "LEFTMOUSE" and event.ctrl and event.shift: self.one_level_deep = True self.should_unhide = event.alt - self.remove_from_selection = event.shift + self.remove_from_selection = event.shift and not event.ctrl + self.filter_selection = event.ctrl and not event.shift return self.execute(context) def execute(self, context): - if self.remove_from_selection: + keep_current_selection = self.remove_from_selection or self.filter_selection + if keep_current_selection: objects = [context.active_object] if context.active_object else [] else: objects = context.selected_objects @@ -295,11 +298,11 @@ class BIM_OT_select_aggregate(bpy.types.Operator): aggregate = ifcopenshell.util.element.get_aggregate(element) if aggregate: all_parts.append(aggregate) - if not self.remove_from_selection: + if not keep_current_selection: obj.select_set(False) else: pass - if not element and not self.remove_from_selection: + if not element and not keep_current_selection: obj.select_set(False) if self.select_parts: @@ -323,24 +326,36 @@ class BIM_OT_select_aggregate(bpy.types.Operator): add_descendants(subpart) - for element in set(selected_parts + all_parts): - obj = tool.Ifc.get_object(element) - if obj: - if self.should_unhide: - obj.hide_viewport = False - obj.hide_set(False) - obj.select_set(not self.remove_from_selection) + if self.filter_selection: + matched_objs = {tool.Ifc.get_object(element) for element in set(selected_parts + all_parts)} + for obj in context.selected_objects: + if obj not in matched_objs: + obj.select_set(False) + else: + for element in set(selected_parts + all_parts): + obj = tool.Ifc.get_object(element) + if obj: + if self.should_unhide: + obj.hide_viewport = False + obj.hide_set(False) + obj.select_set(not self.remove_from_selection) else: - for aggregate_element in all_parts: - aggregate_obj = tool.Ifc.get_object(aggregate_element) - if aggregate_obj: - if self.should_unhide: - aggregate_obj.hide_viewport = False - aggregate_obj.hide_set(False) - aggregate_obj.select_set(not self.remove_from_selection) - if not self.remove_from_selection: - bpy.context.view_layer.objects.active = aggregate_obj + if self.filter_selection: + matched_objs = {tool.Ifc.get_object(element) for element in all_parts} + for obj in context.selected_objects: + if obj not in matched_objs: + obj.select_set(False) + else: + for aggregate_element in all_parts: + aggregate_obj = tool.Ifc.get_object(aggregate_element) + if aggregate_obj: + if self.should_unhide: + aggregate_obj.hide_viewport = False + aggregate_obj.hide_set(False) + aggregate_obj.select_set(not self.remove_from_selection) + if not self.remove_from_selection: + bpy.context.view_layer.objects.active = aggregate_obj # copy selection query to clipboard result = "" @@ -425,26 +440,30 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator): select_parts: bpy.props.BoolProperty(default=False) should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) + filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) @classmethod def description(cls, context, properties): if properties.select_parts: - return "Select all aggregates, subaggregates and all their parts\n\nSHIFT+Click to remove from selection set\nALT+Click to also unhide hidden objects (viewport and local hide)" + return "Select all aggregates, subaggregates and all their parts\n\nSHIFT+Click to remove from selection set\nCTRL+Click to filter selection to matching objects only\nALT+Click to also unhide hidden objects (viewport and local hide)" else: - return "Select all aggregates\n\nSHIFT+Click to remove from selection set\nALT+Click to also unhide hidden objects (viewport and local hide)" + return "Select all aggregates\n\nSHIFT+Click to remove from selection set\nCTRL+Click to filter selection to matching objects only\nALT+Click to also unhide hidden objects (viewport and local hide)" def invoke(self, context, event): self.should_unhide = event.alt - self.remove_from_selection = event.shift + self.remove_from_selection = event.shift and not event.ctrl + self.filter_selection = event.ctrl and not event.shift return self.execute(context) def execute(self, context): - if self.remove_from_selection: + keep_current_selection = self.remove_from_selection or self.filter_selection + if keep_current_selection: objects = [context.active_object] if context.active_object else [] else: objects = context.selected_objects + matched_objs = set() for obj in objects: - if not self.remove_from_selection: + if not keep_current_selection: obj.select_set(False) element = tool.Ifc.get_entity(obj) aggregate = ifcopenshell.util.element.get_aggregate(element) @@ -474,6 +493,9 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator): for element in parts_objs: obj = tool.Ifc.get_object(element) if obj: + if self.filter_selection: + matched_objs.add(obj) + continue if self.should_unhide: obj.hide_viewport = False obj.hide_set(False) @@ -482,11 +504,19 @@ class BIM_OT_select_linked_aggregates(bpy.types.Operator): for element in parts: obj = tool.Ifc.get_object(element) if obj: + if self.filter_selection: + matched_objs.add(obj) + continue if self.should_unhide: obj.hide_viewport = False obj.hide_set(False) obj.select_set(not self.remove_from_selection) + if self.filter_selection: + for obj in context.selected_objects: + if obj not in matched_objs: + obj.select_set(False) + return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/group/operator.py b/src/bonsai/bonsai/bim/module/group/operator.py index e871f07e8d..ed7d0d0499 100644 --- a/src/bonsai/bonsai/bim/module/group/operator.py +++ b/src/bonsai/bonsai/bim/module/group/operator.py @@ -198,18 +198,21 @@ class SelectGroupElements(bpy.types.Operator): bl_description = ( "Select objects assigned to the selected group and all nested groups" "\nSHIFT + CLICK to remove from selection set" - "\nCTRL + CLICK to exclude children" + "\nCTRL + CLICK to filter selection to matching objects only" + "\nCTRL + SHIFT + CLICK to exclude children" "\nALT + CLICK to also unhide hidden objects (viewport and local hide)" ) group: bpy.props.IntProperty() is_recursive: bpy.props.BoolProperty(name="Is Recursive", default=True, options={"SKIP_SAVE"}) should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) + filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) def invoke(self, context, event): - self.is_recursive = not event.ctrl + self.is_recursive = not (event.ctrl and event.shift) self.should_unhide = event.alt - self.remove_from_selection = event.shift + self.remove_from_selection = event.shift and not event.ctrl + self.filter_selection = event.ctrl and not event.shift return self.execute(context) def execute(self, context): @@ -217,6 +220,7 @@ class SelectGroupElements(bpy.types.Operator): ifcopenshell.util.element.get_grouped_by(tool.Ifc.get().by_id(self.group), is_recursive=self.is_recursive), unhide=self.should_unhide, remove=self.remove_from_selection, + filter_selection=self.filter_selection, ) return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/material/operator.py b/src/bonsai/bonsai/bim/module/material/operator.py index 201aa77f46..26d6b3a2e4 100644 --- a/src/bonsai/bonsai/bim/module/material/operator.py +++ b/src/bonsai/bonsai/bim/module/material/operator.py @@ -61,15 +61,17 @@ class DisableEditingMaterials(bpy.types.Operator): class SelectByMaterial(bpy.types.Operator): bl_idname = "bim.select_by_material" bl_label = "Select By Material" - bl_description = "Select objects using the provided material\n\nSHIFT+Click to remove from selection set\nALT+Click to also unhide hidden objects (viewport and local hide)" + bl_description = "Select objects using the provided material\n\nSHIFT+Click to remove from selection set\nCTRL+Click to filter selection to matching objects only\nALT+Click to also unhide hidden objects (viewport and local hide)" bl_options = {"REGISTER", "UNDO"} material: bpy.props.IntProperty() should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) + filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) def invoke(self, context, event): self.should_unhide = event.alt - self.remove_from_selection = event.shift + self.remove_from_selection = event.shift and not event.ctrl + self.filter_selection = event.ctrl and not event.shift return self.execute(context) def execute(self, context): @@ -80,6 +82,7 @@ class SelectByMaterial(bpy.types.Operator): material=material, should_unhide=self.should_unhide, remove_from_selection=self.remove_from_selection, + filter_selection=self.filter_selection, ) # copy selection query to clipboard diff --git a/src/bonsai/bonsai/bim/module/search/operator.py b/src/bonsai/bonsai/bim/module/search/operator.py index 8f22219c09..d0faf9dad6 100644 --- a/src/bonsai/bonsai/bim/module/search/operator.py +++ b/src/bonsai/bonsai/bim/module/search/operator.py @@ -1264,7 +1264,7 @@ class SelectGlobalId(Operator): class SelectIfcClass(Operator): - """Click to select all objects that match with the given IFC class\nSHIFT + Click to remove from selection set\nALT + Click to also unhide hidden objects (viewport and local hide)""" + """Click to select all objects that match with the given IFC class\nSHIFT + Click to remove from selection set\nCTRL + Click to filter selection to matching objects only\nALT + Click to also unhide hidden objects (viewport and local hide)""" bl_idname = "bim.select_ifc_class" bl_label = "Select IFC Class" @@ -1272,14 +1272,16 @@ class SelectIfcClass(Operator): should_filter_predefined_type: BoolProperty(default=False) should_unhide: BoolProperty(default=False) remove_from_selection: BoolProperty(default=False, options={"SKIP_SAVE"}) + filter_selection: BoolProperty(default=False, options={"SKIP_SAVE"}) def invoke(self, context, event): - self.remove_from_selection = event.shift + self.remove_from_selection = event.shift and not event.ctrl + self.filter_selection = event.ctrl and not event.shift self.should_unhide = event.alt return self.execute(context) def execute(self, context): - if self.remove_from_selection: + if self.remove_from_selection or self.filter_selection: objects = [context.active_object] if context.active_object else [] else: objects = context.selected_objects @@ -1289,6 +1291,12 @@ class SelectIfcClass(Operator): if element := tool.Ifc.get_entity(obj): classes.add(element.is_a()) predefined_types.add(ifcopenshell.util.element.get_predefined_type(element)) + if self.filter_selection: + for obj in context.selected_objects: + element = tool.Ifc.get_entity(obj) + if not element or not any(element.is_a(cls) for cls in classes): + obj.select_set(False) + return {"FINISHED"} result = "" for cls in classes: for element in tool.Ifc.get().by_type(cls): @@ -1464,10 +1472,11 @@ class SelectSimilar(Operator): calculated_sum: bpy.props.FloatProperty(name="Calculated Sum", default=0.0) remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) + filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) @classmethod def description(cls, context, properties): - base = "Select objects with a similar value\n\nSHIFT+CLICK remove from selection set.\nALT+CLICK also unhide hidden objects (viewport and local hide)." + base = "Select objects with a similar value\n\nSHIFT+CLICK remove from selection set.\nCTRL+CLICK filter selection to matching objects only.\nALT+CLICK also unhide hidden objects (viewport and local hide)." key = getattr(properties, "key", None) active = context.active_object @@ -1480,7 +1489,7 @@ class SelectSimilar(Operator): value = ifcopenshell.util.selector.get_element_value(element, key) if isinstance(value, (int, float)): - return base + ("\nCTRL+CLICK display the sum of all selected objects") + return base + ("\nCTRL+SHIFT+CLICK display the sum of all selected objects") else: return base @@ -1492,8 +1501,9 @@ class SelectSimilar(Operator): return False def invoke(self, context, event): - self.calculate_sum = event.ctrl and event.type == "LEFTMOUSE" - self.remove_from_selection = event.shift and event.type == "LEFTMOUSE" + self.calculate_sum = event.ctrl and event.shift and event.type == "LEFTMOUSE" + self.remove_from_selection = event.shift and not event.ctrl and event.type == "LEFTMOUSE" + self.filter_selection = event.ctrl and not event.shift and event.type == "LEFTMOUSE" self.should_unhide = event.alt return self.execute(context) @@ -1513,7 +1523,12 @@ class SelectSimilar(Operator): return {"CANCELLED"} matched_count = self._select_objects(context, key, reference_values, tolerance) - verb = "Deselected" if self.remove_from_selection else "Selected" + if self.filter_selection: + verb = "Filtered selection to" + elif self.remove_from_selection: + verb = "Deselected" + else: + verb = "Selected" if all(isinstance(v, (int, float)) for v in reference_values): self.report( @@ -1539,7 +1554,7 @@ class SelectSimilar(Operator): def _get_reference_values(self, context, key): objects = ( [context.active_object] - if self.remove_from_selection + if self.remove_from_selection or self.filter_selection else (context.selected_objects or [context.active_object]) ) values = [self._get_value(obj, key) for obj in objects] @@ -1552,6 +1567,17 @@ class SelectSimilar(Operator): def _select_objects(self, context, key, reference_values, tolerance): count = 0 + if self.filter_selection: + # Keep only the already selected objects that match, select nothing new. + for obj in context.selected_objects: + obj_value = self._get_value(obj, key) + if obj_value is not None and any( + self._compare_values(obj_value, ref_value, tolerance) for ref_value in reference_values + ): + count += 1 + else: + obj.select_set(False) + return count objects = context.scene.objects if self.should_unhide else context.visible_objects for obj in objects: obj_value = self._get_value(obj, key) diff --git a/src/bonsai/bonsai/bim/module/spatial/operator.py b/src/bonsai/bonsai/bim/module/spatial/operator.py index 967059cda4..c37779d7d1 100644 --- a/src/bonsai/bonsai/bim/module/spatial/operator.py +++ b/src/bonsai/bonsai/bim/module/spatial/operator.py @@ -284,19 +284,21 @@ class SelectContainer(bpy.types.Operator): class SelectSimilarContainer(bpy.types.Operator): bl_idname = "bim.select_similar_container" bl_label = "Select Similar Container" - bl_description = "Recursively selects all objects in the container.\n\nCtrl+click to select only one level deep\nShift+click to remove from selection set\nAlt+click to also unhide hidden objects (viewport and local hide)" + bl_description = "Recursively selects all objects in the container.\n\nShift+click to remove from selection set\nCtrl+click to filter selection to matching objects only\nCtrl+Shift+click to select only one level deep\nAlt+click to also unhide hidden objects (viewport and local hide)" bl_options = {"REGISTER", "UNDO"} container: bpy.props.IntProperty(default=0) is_recursive: bpy.props.BoolProperty(default=True) should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) + filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) def invoke(self, context, event): - if event.type == "LEFTMOUSE" and event.ctrl: + if event.type == "LEFTMOUSE" and event.ctrl and event.shift: self.is_recursive = False self.should_unhide = event.alt - self.remove_from_selection = event.shift + self.remove_from_selection = event.shift and not event.ctrl + self.filter_selection = event.ctrl and not event.shift return self.execute(context) def execute(self, context): @@ -314,6 +316,7 @@ class SelectSimilarContainer(bpy.types.Operator): is_recursive=self.is_recursive, should_unhide=self.should_unhide, remove_from_selection=self.remove_from_selection, + filter_selection=self.filter_selection, ) self.is_recursive = True # <-- forcibly reset return {"FINISHED"} @@ -441,21 +444,24 @@ class SelectDecomposedElements(bpy.types.Operator): is_recursive: bpy.props.BoolProperty(default=True, options={"SKIP_SAVE"}) should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) + filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) @classmethod def description(cls, context, operator): return ( "Select the active item" - + "\nSHIFT+CLICK to remove from selection set.\nCTRL+CLICK to select only one level deep" + + "\nSHIFT+CLICK to remove from selection set.\nCTRL+CLICK to filter selection to matching objects only" + + "\nCTRL+SHIFT+CLICK to select only one level deep" + "\nALT+CLICK to also unhide hidden objects (viewport and local hide)" ) def invoke(self, context, event): if event.type == "LEFTMOUSE": - if event.ctrl: + if event.ctrl and event.shift: self.is_recursive = False self.should_unhide = event.alt - self.remove_from_selection = event.shift + self.remove_from_selection = event.shift and not event.ctrl + self.filter_selection = event.ctrl and not event.shift return self.execute(context) def execute(self, context): @@ -463,6 +469,7 @@ class SelectDecomposedElements(bpy.types.Operator): tool.Spatial.get_filtered_elements(self.should_filter, self.is_recursive), unhide=self.should_unhide, remove=self.remove_from_selection, + filter_selection=self.filter_selection, ) # Make selected active element in list, the active object @@ -474,7 +481,8 @@ class SelectDecomposedElements(bpy.types.Operator): obj = tool.Ifc.get_object(ifc_entity) if obj: context.view_layer.objects.active = obj - obj.select_set(not self.remove_from_selection) + if not self.filter_selection: + obj.select_set(not self.remove_from_selection) return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/type/operator.py b/src/bonsai/bonsai/bim/module/type/operator.py index 183c34b320..aac3c146bd 100644 --- a/src/bonsai/bonsai/bim/module/type/operator.py +++ b/src/bonsai/bonsai/bim/module/type/operator.py @@ -221,7 +221,7 @@ class SelectType(bpy.types.Operator): class SelectSimilarType(bpy.types.Operator): - """Select Similar Type\nSHIFT+Click to remove from selection set\nALT+Click to also unhide hidden objects (viewport and local hide)""" + """Select Similar Type\nSHIFT+Click to remove from selection set\nCTRL+Click to filter selection to matching objects only\nALT+Click to also unhide hidden objects (viewport and local hide)""" bl_idname = "bim.select_similar_type" bl_label = "Select Similar Type" @@ -229,15 +229,17 @@ class SelectSimilarType(bpy.types.Operator): related_object: bpy.props.StringProperty() should_unhide: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) remove_from_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) + filter_selection: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) def invoke(self, context, event): self.should_unhide = event.alt - self.remove_from_selection = event.shift + self.remove_from_selection = event.shift and not event.ctrl + self.filter_selection = event.ctrl and not event.shift return self.execute(context) def execute(self, context): self.file = tool.Ifc.get() - if self.remove_from_selection: + if self.remove_from_selection or self.filter_selection: objects = [context.active_object] if context.active_object else [] else: objects = bpy.context.selected_objects @@ -252,6 +254,13 @@ class SelectSimilarType(bpy.types.Operator): continue relating_types.add(relating_type) + if self.filter_selection: + for obj in context.selected_objects: + element = tool.Ifc.get_entity(obj) + if not element or ifcopenshell.util.element.get_type(element) not in relating_types: + obj.select_set(False) + return {"FINISHED"} + result = "" for relating_type in relating_types: related_objects = ifcopenshell.util.element.get_types(relating_type) diff --git a/src/bonsai/bonsai/core/material.py b/src/bonsai/bonsai/core/material.py index ab88ffbcd4..2a49b5be39 100644 --- a/src/bonsai/bonsai/core/material.py +++ b/src/bonsai/bonsai/core/material.py @@ -87,9 +87,13 @@ def select_by_material( material: ifcopenshell.entity_instance, should_unhide: bool = False, remove_from_selection: bool = False, + filter_selection: bool = False, ) -> None: spatial.select_products( - material_tool.get_elements_by_material(material), unhide=should_unhide, remove=remove_from_selection + material_tool.get_elements_by_material(material), + unhide=should_unhide, + remove=remove_from_selection, + filter_selection=filter_selection, ) diff --git a/src/bonsai/bonsai/core/spatial.py b/src/bonsai/bonsai/core/spatial.py index 3bb170edca..1539e22121 100644 --- a/src/bonsai/bonsai/core/spatial.py +++ b/src/bonsai/bonsai/core/spatial.py @@ -125,9 +125,13 @@ def select_similar_container( is_recursive: bool = True, should_unhide: bool = False, remove_from_selection: bool = False, + filter_selection: bool = False, ) -> None: spatial.select_products( - spatial.get_decomposed_elements(container, is_recursive), unhide=should_unhide, remove=remove_from_selection + spatial.get_decomposed_elements(container, is_recursive), + unhide=should_unhide, + remove=remove_from_selection, + filter_selection=filter_selection, ) diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index 85216fc358..ce5ec47094 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -975,7 +975,7 @@ class Spatial: def run_spatial_assign_container(cls, container, objs): pass def run_spatial_import_spatial_decomposition(cls): pass def select_object(cls, obj): pass - def select_products(cls, products, unhide=False, remove=False): pass + def select_products(cls, products, unhide=False, remove=False, filter_selection=False): pass def set_active_object(cls, obj, selection_mode=None): pass def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): pass def set_target_container_as_default(cls): pass diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py index 2e087996d6..1498073854 100644 --- a/src/bonsai/bonsai/tool/spatial.py +++ b/src/bonsai/bonsai/tool/spatial.py @@ -193,11 +193,26 @@ class Spatial(bonsai.core.tool.Spatial): @classmethod def select_products( - cls, products: Iterable[ifcopenshell.entity_instance], unhide: bool = False, remove: bool = False + cls, + products: Iterable[ifcopenshell.entity_instance], + unhide: bool = False, + remove: bool = False, + filter_selection: bool = False, ) -> None: assert (view_layer := bpy.context.view_layer) # Update view layer, otherwise `objects` might be missing just created objects. view_layer.update() + if filter_selection: + # Keep only the already selected objects that match, select nothing new. + matched_objs = set() + for product in products: + obj = tool.Ifc.get_object(product) + if obj and view_layer.objects.get(obj.name): + matched_objs.add(obj) + for obj in bpy.context.selected_objects: + if obj not in matched_objs: + obj.select_set(False) + return for product in products: obj = tool.Ifc.get_object(product) if obj and view_layer.objects.get(obj.name):