diff --git a/src/blenderbim/blenderbim/bim/module/search/operator.py b/src/blenderbim/blenderbim/bim/module/search/operator.py index ef898cc8f6..fc1802baaf 100644 --- a/src/blenderbim/blenderbim/bim/module/search/operator.py +++ b/src/blenderbim/blenderbim/bim/module/search/operator.py @@ -79,12 +79,23 @@ class SelectGlobalId(Operator): def execute(self, context): ifc_file = tool.Ifc.get() - props = context.scene.BIMSearchProperties - global_id = self.global_id or props.global_id - entity = ifc_file.by_guid(global_id) + global_id = self.global_id.strip() + + if not global_id: + self.report({"ERROR"}, "Set Global ID for search.") + return {"CANCELLED"} + + try: + entity = ifc_file.by_guid(global_id) + except RuntimeError: + self.report({"ERROR"}, f"No IFC entity found with guid '{global_id}'.") + return {"CANCELLED"} + obj = tool.Ifc.get_object(entity) if not obj: - self.report({"ERROR"}, "No object found") + self.report({"ERROR"}, f"No Blender object found with guid '{global_id}'.") + return {"CANCELLED"} + obj.select_set(True) bpy.context.view_layer.objects.active = obj return {"FINISHED"} @@ -99,13 +110,29 @@ class SelectIfcClass(Operator): ifc_class: StringProperty() def execute(self, context): + ifc_class = self.ifc_class + + if not ifc_class: + self.report({"ERROR"}, "Set IFC Class for search.") + return {"CANCELLED"} + + sch = tool.Ifc.schema() + try: + sch.declaration_by_name(ifc_class) + except RuntimeError: + self.report({"ERROR"}, f"IFC Class '{ifc_class}' is not found in schema {sch.name()}.") + return {"CANCELLED"} + self.file = IfcStore.get_file() + n_objects = 0 for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id or obj.is_library_indirect: continue element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) - if does_keyword_exist(self.ifc_class, element.is_a(), context): + if does_keyword_exist(ifc_class, element.is_a(), context): obj.select_set(True) + n_objects += 1 + self.report({"INFO"}, f"{n_objects} objects selected.") return {"FINISHED"} @@ -116,11 +143,21 @@ class SelectAttribute(Operator): bl_label = "Select Attribute" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + props = context.scene.BIMSearchProperties + attribute_name = props.search_attribute_name.strip() + if attribute_name: + return True + cls.poll_message_set("Set Attribute Name for search.") + return False + def execute(self, context): self.file = IfcStore.get_file() props = context.scene.BIMSearchProperties - pattern = props.search_attribute_value + attribute_value = props.search_attribute_value attribute_name = props.search_attribute_name + n_objects = 0 for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: continue @@ -130,8 +167,10 @@ class SelectAttribute(Operator): value = next((v for k, v in data.items() if k.lower() == attribute_name.lower()), None) else: value = getattr(element, attribute_name, None) - if does_keyword_exist(pattern, value, context): + if does_keyword_exist(attribute_value, value, context): obj.select_set(True) + n_objects += 1 + self.report({"INFO"}, f"{n_objects} objects selected.") return {"FINISHED"} @@ -142,12 +181,26 @@ class SelectPset(Operator): bl_label = "Select Pset" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + props = context.scene.BIMSearchProperties + pset_name = props.search_pset_name.strip() + prop_name = props.search_prop_name.strip() + if not pset_name: + cls.poll_message_set("Set PSet name for search.") + return False + if not prop_name: + cls.poll_message_set("Set Property name for search.") + return False + return True + def execute(self, context): self.file = IfcStore.get_file() props = context.scene.BIMSearchProperties search_pset_name = props.search_pset_name search_prop_name = props.search_prop_name pattern = props.search_pset_value + n_objects = 0 for obj in context.visible_objects: if not obj.BIMObjectProperties.ifc_definition_id: continue @@ -166,6 +219,8 @@ class SelectPset(Operator): value = props.get(search_prop_name, None) if does_keyword_exist(pattern, value, context): obj.select_set(True) + n_objects += 1 + self.report({"INFO"}, f"{n_objects} objects selected.") return {"FINISHED"} @@ -176,6 +231,10 @@ class ColourByAttribute(Operator): bl_label = "Colour by Attribute" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + return SelectAttribute.poll(context) + def execute(self, context): IfcStore.begin_transaction(self) self.store_state(context) @@ -227,6 +286,10 @@ class ColourByPset(Operator): bl_label = "Colour by Pset" bl_options = {"REGISTER", "UNDO"} + @classmethod + def poll(cls, context): + return SelectPset.poll(context) + def execute(self, context): IfcStore.begin_transaction(self) self.store_state(context) @@ -364,6 +427,13 @@ class ActivateIfcClassFilter(Operator): bl_idname = "bim.activate_ifc_class_filter" bl_label = "Filter by Class" + @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 = bpy.context.scene.BIMSearchProperties props.filter_classes.clear() @@ -410,6 +480,13 @@ class ActivateIfcBuildingStoreyFilter(Operator): bl_idname = "bim.activate_ifc_building_storey_filter" bl_label = "Filter by Building Storey" + @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 = bpy.context.scene.BIMSearchProperties props.filter_building_storeys.clear() @@ -475,14 +552,18 @@ class FilterModelElements(Operator): def execute(self, context): selection_props = context.scene.IfcSelectorProperties - selection = selection_props.selector_query_syntax if selection_props.manual_override else self.add_groups(selection_props) + selection = ( + selection_props.selector_query_syntax + if selection_props.manual_override + else self.add_groups(selection_props) + ) selection_props.selector_query_syntax = selection r = core.search(tool.Search, tool.Spatial, query=selection, action=self.option) if isinstance(r, str): self.report({"WARNING"}, r) return {"FINISHED"} - def add_groups(self, selector:str) -> str: + def add_groups(self, selector: str) -> str: selection = "" for group_index, group in enumerate(selector.groups): if group_index != 0: @@ -517,7 +598,6 @@ class FilterModelElements(Operator): def add_filters(self, selection: str, query: str) -> str: for f_index, f in enumerate(query.filters): - if f_index != 0: selection += " & " if f.and_or == "and" else " | " selection += f".{query.active_option}" @@ -535,12 +615,13 @@ class FilterModelElements(Operator): selection += f'{f.active_option.split(": ")[1]}.{f.active_sub_option.split(": ")[1]} {"!" if f.negation else ""}{f.comparison} {value}' elif f.selector == "Attribute": # we're using the prop_search functionality in blender which returns the index of the option. Sometimes the user can override this and enter a value that doesn't exist in the list. In this case there is no index and we need to handle it. @vulevukusej - pattern = re.compile(r'^[0-9]+:') + pattern = re.compile(r"^[0-9]+:") match = pattern.search(f.active_option) selection += f'{f.active_option.split(": ")[1] if match else f.active_option} {"!" if f.negation else ""}{f.comparison} {value}' selection += "]" return selection + # This needs to be moved into ui code, I know ;) - vulevukusej class IfcSelector(Operator): """Select elements in model with IFC Selector"""