From 31e9b125e3d80c02b43f861fada86bd82384d8ac Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 5 Aug 2025 12:52:26 +0500 Subject: [PATCH] Selection filters - refactor code As most of the code can be just reused --- .../bonsai/bim/module/search/__init__.py | 6 +- .../bonsai/bim/module/search/operator.py | 109 +++++++----------- src/bonsai/bonsai/bim/module/search/prop.py | 66 +++++------ src/bonsai/bonsai/bim/module/search/ui.py | 30 +---- 4 files changed, 74 insertions(+), 137 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/search/__init__.py b/src/bonsai/bonsai/bim/module/search/__init__.py index c89edd454f..e1849c63ae 100644 --- a/src/bonsai/bonsai/bim/module/search/__init__.py +++ b/src/bonsai/bonsai/bim/module/search/__init__.py @@ -42,16 +42,14 @@ classes = ( operator.ShowAllElements, operator.ToggleFilterSelection, prop.BIMColour, - prop.BIMFilterClasses, - prop.BIMFilterBuildingStoreys, + prop.BIMFilterItem, prop.BIMSearchProperties, ui.BIM_PT_search, ui.BIM_PT_filter, ui.BIM_PT_colour_by_property, ui.BIM_PT_select_similar, ui.BIM_UL_colourscheme, - ui.BIM_UL_ifc_class_filter, - ui.BIM_UL_ifc_building_storey_filter, + ui.BIM_UL_ifc_filter, ) diff --git a/src/bonsai/bonsai/bim/module/search/operator.py b/src/bonsai/bonsai/bim/module/search/operator.py index 605fa7857e..56929bbaad 100644 --- a/src/bonsai/bonsai/bim/module/search/operator.py +++ b/src/bonsai/bonsai/bim/module/search/operator.py @@ -636,21 +636,12 @@ class ToggleFilterSelection(Operator): def execute(self, context): props = tool.Search.get_search_props() self.selecting_actionbool = self.action == "SELECT" - if props.filter_type == "CLASSES": - for ifc_class in props.filter_classes: - ifc_class.is_selected = self.selecting_actionbool - elif props.filter_type == "CONTAINER": - for building_storey in props.filter_container: - building_storey.is_selected = self.selecting_actionbool + for item in props.filter_items: + item.is_selected = self.selecting_actionbool return {"FINISHED"} -class ActivateIfcClassFilter(Operator): - """Filter the current selection by IFC class""" - - bl_idname = "bim.activate_ifc_class_filter" - bl_label = "Filter by Class" - +class ActivateFilter(bpy.types.Operator): @classmethod def poll(cls, context): if not context.selected_objects: @@ -658,10 +649,38 @@ class ActivateIfcClassFilter(Operator): return False return True + def execute(self, context): + props = tool.Search.get_search_props() + props.filter_items.clear() + return {"FINISHED"} + + def draw(self, context): + props = tool.Search.get_search_props() + assert self.layout + self.layout.template_list( + "BIM_UL_ifc_filter", + "", + props, + "filter_items", + props, + "filter_items_index", + rows=min(len(props.filter_items), 20), + ) + row = self.layout.row(align=True) + row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT" + row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT" + + +class ActivateIfcClassFilter(ActivateFilter): + """Filter the current selection by IFC class""" + + bl_idname = "bim.activate_ifc_class_filter" + bl_label = "Filter by Class" + def invoke(self, context, event): props = tool.Search.get_search_props() - props.filter_classes.clear() - ifc_types = {} + props.filter_items.clear() + ifc_types: dict[str, int] = {} for obj in context.selected_objects: element = tool.Ifc.get_entity(obj) if not element: @@ -670,55 +689,29 @@ class ActivateIfcClassFilter(Operator): ifc_types[element.is_a()] += 1 for name, total in dict(sorted(ifc_types.items())).items(): - new = props.filter_classes.add() + new = props.filter_items.add() new.name = name new.total = total - props.filter_type = "CLASSES" + props.filter_type = "CLASS" return context.window_manager.invoke_props_dialog(self, width=250) - def execute(self, context): - props = tool.Search.get_search_props() - props.filter_classes.clear() - return {"FINISHED"} - def draw(self, context): - props = tool.Search.get_search_props() - self.layout.template_list( - "BIM_UL_ifc_class_filter", - "", - props, - "filter_classes", - props, - "filter_classes_index", - rows=min(len(props.filter_classes), 20), - ) - row = self.layout.row(align=True) - row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT" - row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT" - - -class ActivateContainerFilter(Operator): +class ActivateContainerFilter(ActivateFilter): """Filter the current selection by Building Storey""" bl_idname = "bim.activate_ifc_container_filter" bl_label = "Filter by Container" - @classmethod - def poll(cls, context): - if not context.selected_objects: - cls.poll_message_set("Select objects to filter.") - return False - return True - def invoke(self, context, event): props = tool.Search.get_search_props() - props.filter_container.clear() + props.filter_items.clear() - containers = {} + containers: dict[str, int] = {} containers.setdefault("None", 0) for obj in context.selected_objects: - container = tool.Spatial.get_container(tool.Ifc.get_entity(obj)) + assert (element := tool.Ifc.get_entity(obj)) + container = tool.Spatial.get_container(element) if not container: containers["None"] += 1 continue @@ -726,7 +719,7 @@ class ActivateContainerFilter(Operator): containers[container.Name] += 1 for name, total in dict(sorted(containers.items())).items(): - new = props.filter_container.add() + new = props.filter_items.add() new.name = name new.total = total @@ -734,26 +727,6 @@ class ActivateContainerFilter(Operator): return context.window_manager.invoke_props_dialog(self, width=250) - def execute(self, context): - props = tool.Search.get_search_props() - props.filter_container.clear() - return {"FINISHED"} - - def draw(self, context): - props = tool.Search.get_search_props() - self.layout.template_list( - "BIM_UL_ifc_building_storey_filter", - "", - props, - "filter_container", - props, - "filter_container_index", - rows=min(len(props.filter_container), 20), - ) - row = self.layout.row(align=True) - row.operator("bim.toggle_filter_selection", text="Select All").action = "SELECT" - row.operator("bim.toggle_filter_selection", text="Deselect All").action = "DESELECT" - class ShowAllElements(Operator): """Show all Physical objects in the 3D View. diff --git a/src/bonsai/bonsai/bim/module/search/prop.py b/src/bonsai/bonsai/bim/module/search/prop.py index 88a5217be3..3cb8a12252 100644 --- a/src/bonsai/bonsai/bim/module/search/prop.py +++ b/src/bonsai/bonsai/bim/module/search/prop.py @@ -33,7 +33,7 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Literal, get_args def get_element_key(self: "BIMSearchProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]: @@ -60,28 +60,26 @@ def get_saved_colourschemes(self: "BIMSearchProperties", context: bpy.types.Cont return ColourByPropertyData.data["saved_colourschemes"] -def update_is_class_selected(self: "BIMFilterClasses", context: bpy.types.Context) -> None: +def update_is_filter_item_selected(self: "BIMFilterItem", context: bpy.types.Context) -> None: if self.is_selected: for obj in self.unselected_objects: + assert obj.obj obj.obj.select_set(True) self.unselected_objects.clear() - else: - for obj in context.selected_objects: - element = tool.Ifc.get_entity(obj) + return + + props = tool.Search.get_search_props() + for obj in context.selected_objects: + element = tool.Ifc.get_entity(obj) + if props.filter_type == "CLASS": if element and element.is_a() == self.name: obj.select_set(False) new = self.unselected_objects.add() new.obj = obj - - -def update_is_container_selected(self: "BIMFilterBuildingStoreys", context: bpy.types.Context) -> None: - if self.is_selected: - for obj in self.unselected_objects: - obj.obj.select_set(True) - self.unselected_objects.clear() - else: - for obj in context.selected_objects: - container = tool.Spatial.get_container(tool.Ifc.get_entity(obj)) + else: + if not element: + continue + container = tool.Spatial.get_container(element) if (container and container.Name == self.name) or (not container and self.name == "None"): obj.select_set(False) new = self.unselected_objects.add() @@ -103,19 +101,8 @@ def update_show_flat_colours(self: "BIMSearchProperties", context: bpy.types.Con space.shading.show_cavity = False -class BIMFilterClasses(PropertyGroup): - is_selected: BoolProperty(name="Is Selected", default=True, update=update_is_class_selected) - total: IntProperty(name="Total") - unselected_objects: CollectionProperty(type=ObjProperty, name="Unfiltered Objects") - - if TYPE_CHECKING: - is_selected: bool - total: int - unselected_objects: bpy.types.bpy_prop_collection_idprop[ObjProperty] - - -class BIMFilterBuildingStoreys(PropertyGroup): - is_selected: BoolProperty(name="Is Level Selected", default=True, update=update_is_container_selected) +class BIMFilterItem(PropertyGroup): + is_selected: BoolProperty(name="Is Selected", default=True, update=update_is_filter_item_selected) total: IntProperty(name="Total") unselected_objects: CollectionProperty(type=ObjProperty, name="Unfiltered Objects") @@ -134,6 +121,9 @@ class BIMColour(PropertyGroup): colour: tuple[float, float, float] +FilterType = Literal["CLASS", "CONTAINER"] + + class BIMSearchProperties(PropertyGroup): element_key: EnumProperty(items=get_element_key, name="Element Key") filter_query: StringProperty(name="Filter Query") @@ -261,11 +251,13 @@ class BIMSearchProperties(PropertyGroup): max_value: FloatProperty(name="Max Value", default=100) colourscheme: CollectionProperty(type=BIMColour) active_colourscheme_index: IntProperty(name="Active Colourscheme Index") - filter_type: StringProperty(name="Filter Type") - filter_classes: CollectionProperty(type=BIMFilterClasses, name="Filter Classes") - filter_classes_index: IntProperty(name="Filter Classes Index") - filter_container: CollectionProperty(type=BIMFilterBuildingStoreys, name="Filter Level") - filter_container_index: IntProperty(name="Filter Level Index") + + # Ideally those should be props on operators, but if we move them to operators, + # then there's no way for suboperator to select/deselect all displayed items. + filter_type: EnumProperty(name="Filter Type", items=[(i, i, "") for i in get_args(FilterType)]) + filter_items: CollectionProperty(type=BIMFilterItem, name="Filter Classes") + filter_items_index: IntProperty(name="Filter Classes Index") + show_flat_colours: BoolProperty( name="Flat Colours", description="Toggle flat shading in the active viewport.", @@ -289,9 +281,7 @@ class BIMSearchProperties(PropertyGroup): max_value: float colourscheme: bpy.types.bpy_prop_collection_idprop[BIMColour] active_colourscheme_index: int - filter_type: str - filter_classes: bpy.types.bpy_prop_collection_idprop[BIMFilterClasses] - filter_classes_index: int - filter_container: bpy.types.bpy_prop_collection_idprop[BIMFilterBuildingStoreys] - filter_container_index: int + filter_type: FilterType + filter_items: bpy.types.bpy_prop_collection_idprop[BIMFilterItem] + filter_items_index: int show_flat_colours: bool diff --git a/src/bonsai/bonsai/bim/module/search/ui.py b/src/bonsai/bonsai/bim/module/search/ui.py index 10ffb9db6f..3481e3709b 100644 --- a/src/bonsai/bonsai/bim/module/search/ui.py +++ b/src/bonsai/bonsai/bim/module/search/ui.py @@ -25,7 +25,7 @@ from bonsai.bim.module.search.data import SearchData, ColourByPropertyData, Sele from typing import TYPE_CHECKING if TYPE_CHECKING: - from bonsai.bim.module.search.prop import BIMSearchProperties, BIMColour, BIMFilterClasses, BIMFilterBuildingStoreys + from bonsai.bim.module.search.prop import BIMSearchProperties, BIMColour, BIMFilterItem class BIM_PT_search(Panel): @@ -164,37 +164,13 @@ class BIM_UL_colourscheme(bpy.types.UIList): split.prop(item, "colour", text="") -class BIM_UL_ifc_class_filter(bpy.types.UIList): +class BIM_UL_ifc_filter(bpy.types.UIList): def draw_item( self, context, layout: bpy.types.UILayout, data: BIMSearchProperties, - item: BIMFilterClasses, - icon, - active_data, - active_propname, - index, - ) -> None: - split = layout - split.use_property_split = True - split.use_property_decorate = False - split.prop( - item, "is_selected", text="", emboss=False, icon="CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT" - ) - split.prop(item, "name", text="", emboss=False, slider=True) - split = split.column() - split.scale_x = 0.5 - split.label(text=str(item.total)) - - -class BIM_UL_ifc_building_storey_filter(bpy.types.UIList): - def draw_item( - self, - context, - layout: bpy.types.UILayout, - data: BIMSearchProperties, - item: BIMFilterBuildingStoreys, + item: BIMFilterItem, icon, active_data, active_propname,