diff --git a/src/bonsai/bonsai/bim/module/misc/__init__.py b/src/bonsai/bonsai/bim/module/misc/__init__.py index 167cb65d6f..dbef8387a3 100644 --- a/src/bonsai/bonsai/bim/module/misc/__init__.py +++ b/src/bonsai/bonsai/bim/module/misc/__init__.py @@ -25,7 +25,7 @@ classes = ( operator.RemoveQuickFavoritesItem, operator.MoveQuickFavoritesItem, operator.AddQuickFavoritesItem, - operator.EnableQuickFavoriteSearch, + operator.ConfirmQuickFavoriteOperator, operator.DrawSystemArrows, operator.GetConnectedSystemElements, operator.IfcSverchokUseBonsaiFile, diff --git a/src/bonsai/bonsai/bim/module/misc/data.py b/src/bonsai/bonsai/bim/module/misc/data.py new file mode 100644 index 0000000000..eb697d0bff --- /dev/null +++ b/src/bonsai/bonsai/bim/module/misc/data.py @@ -0,0 +1,48 @@ +# Bonsai - OpenBIM Blender Add-on +# Copyright (C) 2021 Dion Moult +# +# This file is part of Bonsai. +# +# Bonsai is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Bonsai is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Bonsai. If not, see . + +from typing import Any + +import bpy + + +def refresh() -> None: + QuickFavoritesData.is_loaded = False + + +class QuickFavoritesData: + data: dict[str, Any] = {} + is_loaded = False + + @classmethod + def load(cls) -> None: + cls.data = { + "operators": cls.operators(), + } + cls.is_loaded = True + + @classmethod + def operators(cls) -> list[str]: + items: list[str] = [] + for module_name in dir(bpy.ops): + module = getattr(bpy.ops, module_name) + for op_name in dir(module): + op = getattr(module, op_name) + bl_label = op.get_rna_type().name + items.append(f"{module_name}.{op_name} - {bl_label}") + return items diff --git a/src/bonsai/bonsai/bim/module/misc/operator.py b/src/bonsai/bonsai/bim/module/misc/operator.py index 994a3a3829..f96d82963d 100644 --- a/src/bonsai/bonsai/bim/module/misc/operator.py +++ b/src/bonsai/bonsai/bim/module/misc/operator.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . -from typing import TYPE_CHECKING, Literal, assert_never, cast, get_args +from typing import TYPE_CHECKING, Literal, assert_never, get_args import bpy import ifcopenshell.util.geolocation @@ -355,9 +355,9 @@ class DrawSystemArrows(bpy.types.Operator, tool.Ifc.Operator): return matrix -class EnableQuickFavoriteSearch(bpy.types.Operator): - bl_idname = "bim.enable_quick_favorite_search" - bl_label = "Enable Search" +class ConfirmQuickFavoriteOperator(bpy.types.Operator): + bl_idname = "bim.confirm_quick_favorite_operator" + bl_label = "Confirm Operator" bl_options = {"REGISTER", "UNDO"} index: bpy.props.IntProperty() # pyright: ignore[reportRedeclaration] @@ -367,18 +367,13 @@ class EnableQuickFavoriteSearch(bpy.types.Operator): def execute(self, context) -> set["rna_enums.OperatorReturnItems"]: props = tool.Misc.get_misc_props() fav = props.quick_favorites[self.index] - name = fav.search.strip() + rna = fav.get_searched_operator() - # TODO: don't use try / except. - try: - module, func = name.split(".", 1) - op = getattr(getattr(bpy.ops, module), func) - rna = cast(bpy.types.Struct, op.get_rna_type()) - except (ValueError, AttributeError): - self.report({"ERROR"}, f"Operator '{name}' not found.") + if rna is None: + self.report({"INFO"}, "No operator entered for search.") return {"CANCELLED"} - fav.operator_id = name + fav.operator_id = tool.Blender.operator_idname_to_py(rna.identifier) fav.label = rna.name fav.properties.clear() has_skipped = False @@ -429,7 +424,7 @@ class ImportQuickFavorites(bpy.types.Operator): fav = props.quick_favorites.add() fav.label = qf.ui_name fav.search = qf.op_idname_py - bpy.ops.bim.enable_quick_favorite_search(index=i) + bpy.ops.bim.confirm_quick_favorite_operator(index=i) fav.label = qf.ui_name or fav.label for prop in fav.properties: diff --git a/src/bonsai/bonsai/bim/module/misc/prop.py b/src/bonsai/bonsai/bim/module/misc/prop.py index 4ff1eb53f3..0fa3812f0b 100644 --- a/src/bonsai/bonsai/bim/module/misc/prop.py +++ b/src/bonsai/bonsai/bim/module/misc/prop.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . -from typing import TYPE_CHECKING, Literal, get_args +from typing import TYPE_CHECKING, Literal, cast, get_args import bpy from bpy.props import ( @@ -30,6 +30,8 @@ from bpy.props import ( ) from bpy.types import PropertyGroup +from bonsai.bim.module.misc.data import QuickFavoritesData + QuickFavoriteValueType = Literal["float_value", "bool_value", "int_value", "string_value"] @@ -61,20 +63,46 @@ class QuickFavoriteProperty(PropertyGroup): is_active: bool +def get_operator_suggestions(self: "QuickFavoritesItem", context: bpy.types.Context, edit_text: str) -> list[str]: + if not QuickFavoritesData.is_loaded: + QuickFavoritesData.load() + return QuickFavoritesData.data["operators"] + + class QuickFavoritesItem(PropertyGroup): - is_expanded: BoolProperty(name="Is Expanded", default=True) # pyright: ignore[reportRedeclaration] - search: StringProperty(name="Search", default="") # pyright: ignore[reportRedeclaration] + is_expanded: BoolProperty(name="Is Expanded", default=False) # pyright: ignore[reportRedeclaration] + search: StringProperty( # pyright: ignore[reportRedeclaration] + name="Search", + default="", + search=get_operator_suggestions, + # Resetting `search_options`, allowing users only to use suggestions. + search_options=set(), + ) properties: CollectionProperty(type=QuickFavoriteProperty) # pyright: ignore[reportRedeclaration] - operator_id: StringProperty(name="Operator ID", default="") # pyright: ignore[reportRedeclaration] + operator_id: StringProperty( # pyright: ignore[reportRedeclaration] + name="Operator ID", + default="", + ) label: StringProperty( # pyright: ignore[reportRedeclaration] name="Label", description="Label that will be used in Quick Favorites for this operator", default="", ) + def get_searched_operator(self) -> bpy.types.Struct | None: + if not self.search: + return None + search_label = self.search + name = search_label.split(" - ", 1)[0] + module, func = name.split(".", 1) + op = getattr(getattr(bpy.ops, module), func) + rna = cast(bpy.types.Struct, op.get_rna_type()) + return rna + if TYPE_CHECKING: is_expanded: bool search: str + """Internal property set when confirming results of the search field""" properties: bpy.types.bpy_prop_collection_idprop[QuickFavoriteProperty] operator_id: str label: str diff --git a/src/bonsai/bonsai/bim/module/misc/ui.py b/src/bonsai/bonsai/bim/module/misc/ui.py index ca79281849..3e4a952bc3 100644 --- a/src/bonsai/bonsai/bim/module/misc/ui.py +++ b/src/bonsai/bonsai/bim/module/misc/ui.py @@ -77,8 +77,8 @@ class BIM_PT_quick_favorites_manager(bpy.types.Panel): row = layout.row(align=True) row.label(text="Quick Favorites:") - row.operator("bim.import_quick_favorites", text="", icon="BLENDER") row.operator("bim.add_quick_favorites_item", text="", icon="ADD") + row.operator("bim.import_quick_favorites", text="", icon="BLENDER") op = row.operator("bim.show_description", text="", icon="INFO") op.attr_name = "Quick Favorites Manager" op.description = ( @@ -115,13 +115,16 @@ class BIM_PT_quick_favorites_manager(bpy.types.Panel): continue row = box.row(align=True) row.prop(fav, "search", text="") - row.operator("bim.enable_quick_favorite_search", text="", icon="VIEWZOOM").index = i + row.operator("bim.confirm_quick_favorite_operator", text="", icon="VIEWZOOM").index = i if not fav.operator_id: continue layout.separator() - box.label(text="Properties:") - prop_box = box.box() - for item in fav.properties: - row = prop_box.row(align=True) - row.prop(item, item.value_prop, text=item.display_name) - row.prop(item, "is_active", text="", icon="RADIOBUT_ON" if item.is_active else "RADIOBUT_OFF") + if fav.properties: + box.label(text="Properties:") + prop_box = box.box() + for item in fav.properties: + row = prop_box.row(align=True) + row.prop(item, item.value_prop, text=item.display_name) + row.prop(item, "is_active", text="", icon="RADIOBUT_ON" if item.is_active else "RADIOBUT_OFF") + else: + box.label(text="No Properties.")