prop_with_search for external styles enum #5569

Ping @Gorgious56 as perhaps you might be interested in this weird case - it seems passing Operator with context_pointer_set makes it OperatorProperties which doesn't have original __annotations__.
This commit is contained in:
Andrej730
2024-10-14 12:22:41 +05:00
parent 5ffef568f7
commit 9e2189fea4
3 changed files with 43 additions and 11 deletions
+26 -4
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations from __future__ import annotations
import importlib
import bpy import bpy
import json import json
import math import math
@@ -237,36 +238,57 @@ def export_attributes(
return attributes return attributes
ENUM_ITEMS_DATA = Union[bpy.types.PropertyGroup, bpy.types.ID, bpy.types.Operator, bpy.types.OperatorProperties]
def prop_with_search( def prop_with_search(
layout: bpy.types.UILayout, layout: bpy.types.UILayout,
data: Union[bpy.types.PropertyGroup, bpy.types.ID], data: ENUM_ITEMS_DATA,
prop_name: str, prop_name: str,
should_click_ok_to_validate: bool = False, should_click_ok_to_validate: bool = False,
original_operator_path: Optional[str] = None,
**kwargs: Any, **kwargs: Any,
): ):
# kwargs are layout.prop arguments (text, icon, etc.) # kwargs are layout.prop arguments (text, icon, etc.)
row = layout.row(align=True) row = layout.row(align=True)
row.prop(data, prop_name, **kwargs) row.prop(data, prop_name, **kwargs)
try: 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 # Magick courtesy of https://blender.stackexchange.com/a/203443/86891
row.context_pointer_set(name="data", data=data) row.context_pointer_set(name="data", data=data)
op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM") op = row.operator("bim.enum_property_search", text="", icon="VIEWZOOM")
op.prop_name = prop_name op.prop_name = prop_name
op.should_click_ok_to_validate = should_click_ok_to_validate 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 except TypeError: # Prop is not iterable
pass pass
def get_enum_items( 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[ ) -> Union[
Iterable[Union[tuple[str, str, str], tuple[str, str, str, int], tuple[str, str, str, str, int], None]], None 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 # 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 # Or throws an error in the console when the items callback returns an empty list
# See https://blender.stackexchange.com/q/215781/86891 # 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") items = prop.keywords.get("items")
if items is None: if items is None:
return return
@@ -317,7 +317,10 @@ class BrowseExternalStyle(bpy.types.Operator):
layout.label(text="Data Block Type") layout.label(text="Data Block Type")
layout.prop(self, "data_block_type", text="", icon="GROUP") layout.prop(self, "data_block_type", text="", icon="GROUP")
layout.label(text="Data Block") 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(): if Path(tool.Ifc.get_path()).is_file():
layout.prop(self, "use_relative_path") layout.prop(self, "use_relative_path")
else: else:
+13 -6
View File
@@ -40,7 +40,7 @@ from mathutils import Vector, Matrix, Euler
from math import radians from math import radians
from pathlib import Path from pathlib import Path
from collections import namedtuple from collections import namedtuple
from typing import List from typing import List, Iterable, Union
class SetTab(bpy.types.Operator): 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) collection_predefined_types: bpy.props.CollectionProperty(type=StrProperty)
prop_name: bpy.props.StringProperty() prop_name: bpy.props.StringProperty()
should_click_ok_to_validate: bpy.props.BoolProperty(default=False) 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): def invoke(self, context, event):
self.clear_collections() self.clear_collections()
self.data = context.data 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: if items is None:
return {"FINISHED"} return {"FINISHED"}
self.add_items_regular(items) self.add_items_regular(items)
@@ -909,7 +912,7 @@ class BIM_OT_enum_property_search(bpy.types.Operator):
def execute(self, context): def execute(self, context):
return {"FINISHED"} return {"FINISHED"}
def clear_collections(self): def clear_collections(self) -> None:
self.collection_names.clear() self.collection_names.clear()
self.collection_identifiers.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_names.add().name = name
self.collection_predefined_types.add().name = predefined_type 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 = [] self.identifiers = []
current_value = getattr(self.data, self.prop_name)
for item in items: for item in items:
if item is None: # Used as a separator if item is None: # Used as a separator
continue continue
self.identifiers.append(item[0]) self.identifiers.append(item[0])
self.add_item(identifier=item[0], name=item[1]) 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 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) getter_suggestions = getattr(self.data, "getter_enum_suggestions", None)
if getter_suggestions is not None: if getter_suggestions is not None:
mapping = getter_suggestions.get(self.prop_name) mapping = getter_suggestions.get(self.prop_name)