diff --git a/src/bonsai/bonsai/bim/helper.py b/src/bonsai/bonsai/bim/helper.py index f96839fe01..c7d4bbacc6 100644 --- a/src/bonsai/bonsai/bim/helper.py +++ b/src/bonsai/bonsai/bim/helper.py @@ -17,6 +17,7 @@ # along with Bonsai. If not, see . from __future__ import annotations +import importlib import bpy import json import math @@ -237,36 +238,57 @@ def export_attributes( return attributes +ENUM_ITEMS_DATA = Union[bpy.types.PropertyGroup, bpy.types.ID, bpy.types.Operator, bpy.types.OperatorProperties] + + def prop_with_search( layout: bpy.types.UILayout, - data: Union[bpy.types.PropertyGroup, bpy.types.ID], + data: ENUM_ITEMS_DATA, prop_name: str, should_click_ok_to_validate: bool = False, + original_operator_path: Optional[str] = None, **kwargs: Any, ): # kwargs are layout.prop arguments (text, icon, etc.) row = layout.row(align=True) row.prop(data, prop_name, **kwargs) try: - if len(get_enum_items(data, prop_name)) > 10: + if len(get_enum_items(data, prop_name, original_operator_path=original_operator_path)) > 10: # Magick courtesy of https://blender.stackexchange.com/a/203443/86891 row.context_pointer_set(name="data", data=data) op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM") op.prop_name = prop_name op.should_click_ok_to_validate = should_click_ok_to_validate + op.original_operator_path = original_operator_path or "" except TypeError: # Prop is not iterable pass def get_enum_items( - data: Union[bpy.types.PropertyGroup, bpy.types.ID], prop_name: str, context: Optional[bpy.types.Context] = None + data: ENUM_ITEMS_DATA, + prop_name: str, + context: Optional[bpy.types.Context] = None, + original_operator_path: Optional[str] = None, ) -> Union[ Iterable[Union[tuple[str, str, str], tuple[str, str, str, int], tuple[str, str, str, str, int], None]], None ]: # Retrieve items from a dynamic EnumProperty, which is otherwise not supported # Or throws an error in the console when the items callback returns an empty list # See https://blender.stackexchange.com/q/215781/86891 - prop = data.__annotations__[prop_name] + + # OperatorProperties is missing __annotations__, so need to somehow provide original Operator. + # Couldn't find any way to get Operator from OperatorProperties, so we provide the path explicitly. + # E.g. OpeartorProperties occur when Operator is passed with context_pointer_set. + if isinstance(data, bpy.types.OperatorProperties): + if not original_operator_path: + raise Exception("For OperatorProperties providing the original operator path is required.") + operator_module_path, operator_class = original_operator_path.rsplit(".", 1) + operator_module = importlib.import_module(operator_module_path) + annotations_data = getattr(operator_module, operator_class) + else: + annotations_data = data + + prop = annotations_data.__annotations__[prop_name] items = prop.keywords.get("items") if items is None: return diff --git a/src/bonsai/bonsai/bim/module/style/operator.py b/src/bonsai/bonsai/bim/module/style/operator.py index 5c5d9d82d7..10a7f2ea29 100644 --- a/src/bonsai/bonsai/bim/module/style/operator.py +++ b/src/bonsai/bonsai/bim/module/style/operator.py @@ -317,7 +317,10 @@ class BrowseExternalStyle(bpy.types.Operator): layout.label(text="Data Block Type") layout.prop(self, "data_block_type", text="", icon="GROUP") layout.label(text="Data Block") - layout.prop(self, "data_block", text="") + cls = BrowseExternalStyle + bonsai.bim.helper.prop_with_search( + layout, self, "data_block", text="", original_operator_path=f"{cls.__module__}.{cls.__name__}" + ) if Path(tool.Ifc.get_path()).is_file(): layout.prop(self, "use_relative_path") else: diff --git a/src/bonsai/bonsai/bim/operator.py b/src/bonsai/bonsai/bim/operator.py index f101b06d5a..2d3c1e2643 100644 --- a/src/bonsai/bonsai/bim/operator.py +++ b/src/bonsai/bonsai/bim/operator.py @@ -40,7 +40,7 @@ from mathutils import Vector, Matrix, Euler from math import radians from pathlib import Path from collections import namedtuple -from typing import List +from typing import List, Iterable, Union class SetTab(bpy.types.Operator): @@ -890,11 +890,14 @@ class BIM_OT_enum_property_search(bpy.types.Operator): collection_predefined_types: bpy.props.CollectionProperty(type=StrProperty) prop_name: bpy.props.StringProperty() should_click_ok_to_validate: bpy.props.BoolProperty(default=False) + original_operator_path: bpy.props.StringProperty(name="Original Operator Path", default="", options={"SKIP_SAVE"}) + + identifiers: list[str] def invoke(self, context, event): self.clear_collections() self.data = context.data - items = get_enum_items(self.data, self.prop_name, context) + items = get_enum_items(self.data, self.prop_name, context, original_operator_path=self.original_operator_path) if items is None: return {"FINISHED"} self.add_items_regular(items) @@ -909,7 +912,7 @@ class BIM_OT_enum_property_search(bpy.types.Operator): def execute(self, context): return {"FINISHED"} - def clear_collections(self): + def clear_collections(self) -> None: self.collection_names.clear() self.collection_identifiers.clear() @@ -918,17 +921,21 @@ class BIM_OT_enum_property_search(bpy.types.Operator): self.collection_names.add().name = name self.collection_predefined_types.add().name = predefined_type - def add_items_regular(self, items): + def add_items_regular( + self, + items: Iterable[Union[tuple[str, str, str], tuple[str, str, str, int], tuple[str, str, str, str, int], None]], + ) -> None: self.identifiers = [] + current_value = getattr(self.data, self.prop_name) for item in items: if item is None: # Used as a separator continue self.identifiers.append(item[0]) self.add_item(identifier=item[0], name=item[1]) - if item[0] == getattr(self.data, self.prop_name): + if item[0] == current_value: self.dummy_name = item[1] # We found the current enum name - def add_items_suggestions(self): + def add_items_suggestions(self) -> None: getter_suggestions = getattr(self.data, "getter_enum_suggestions", None) if getter_suggestions is not None: mapping = getter_suggestions.get(self.prop_name)