From 77b15a879787dc6876f3acd10c5c92dcc97b63a7 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Sat, 16 Dec 2023 22:27:31 +0800 Subject: [PATCH] refactor EnumPropHelper to improve BME again. --- bbp_ng/OP_ADDS_bme.py | 26 ++++---- bbp_ng/OP_ADDS_component.py | 2 + bbp_ng/PROP_virtools_group.py | 2 + bbp_ng/PROP_virtools_material.py | 2 + bbp_ng/UTIL_bme.py | 61 ++++++++++++++++++- bbp_ng/UTIL_functions.py | 101 ++++++++++++++----------------- bbp_ng/UTIL_virtools_types.py | 10 ++- bbp_ng/json/trafos.json | 3 + 8 files changed, 137 insertions(+), 70 deletions(-) diff --git a/bbp_ng/OP_ADDS_bme.py b/bbp_ng/OP_ADDS_bme.py index e28466b..e49f79c 100644 --- a/bbp_ng/OP_ADDS_bme.py +++ b/bbp_ng/OP_ADDS_bme.py @@ -1,10 +1,12 @@ import bpy import typing from . import PROP_preferences, PROP_virtools_mesh, PROP_virtools_group, PROP_bme_material -from . import UTIL_functions, UTIL_icons_manager +from . import UTIL_functions, UTIL_icons_manager, UTIL_bme #region BME Adder +_g_EnumHelper_BmeStructType: UTIL_bme.EnumPropHelper = UTIL_bme.EnumPropHelper() + class BBP_PG_bme_adder_params(bpy.types.PropertyGroup): prop_int: bpy.props.IntProperty( name = 'Single Int', description = 'Single Int', @@ -59,15 +61,7 @@ class BBP_OT_add_bme_struct(bpy.types.Operator): bme_struct_type: bpy.props.EnumProperty( name = "Type", description = "BME struct type", - items = ( - ('TEST1', 'test1', 'test desc1', UTIL_icons_manager.get_empty_icon(), 1), - ('TEST2', 'test2', 'test desc2', UTIL_icons_manager.get_empty_icon(), 2), - ), - #items = tuple( - # # token, display name, descriptions, icon, index - # (blk, blk, "", UTILS_icons_manager.get_floor_icon(blk), idx) - # for idx, blk in enumerate(UTILS_constants.floor_blockDict.keys()) - #), + items = _g_EnumHelper_BmeStructType.generate_items(), update = bme_struct_type_updated ) @@ -94,12 +88,22 @@ class BBP_OT_add_bme_struct(bpy.types.Operator): def draw(self, context): layout = self.layout # show type + layout.prop(self, 'bme_struct_type') + # show type for i in self.data_floats: layout.prop(i, 'prop_bool') @classmethod def draw_blc_menu(self, layout: bpy.types.UILayout): - layout.operator(self.bl_idname) + for ident in _g_EnumHelper_BmeStructType.get_bme_identifiers(): + # draw operator + cop = layout.operator( + self.bl_idname, + text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident), + icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident) + ) + # and assign its init type value + cop.bme_struct_type = _g_EnumHelper_BmeStructType.to_selection(ident) """ for item in PROP_ballance_element.BallanceElementType: item_name: str = PROP_ballance_element.get_ballance_element_name(item) diff --git a/bbp_ng/OP_ADDS_component.py b/bbp_ng/OP_ADDS_component.py index 1250c77..0b6fb47 100644 --- a/bbp_ng/OP_ADDS_component.py +++ b/bbp_ng/OP_ADDS_component.py @@ -139,6 +139,8 @@ def _get_component_icon_by_name(elename: str): else: return icon _g_EnumHelper_Component: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper( PROP_ballance_element.BallanceElementType, + lambda x: str(x.value), + lambda x: PROP_ballance_element.BallanceElementType(int(x)), lambda x: x.name, lambda x: '', lambda x: _get_component_icon_by_name(PROP_ballance_element.get_ballance_element_name(x)), diff --git a/bbp_ng/PROP_virtools_group.py b/bbp_ng/PROP_virtools_group.py index ad859aa..f194b73 100644 --- a/bbp_ng/PROP_virtools_group.py +++ b/bbp_ng/PROP_virtools_group.py @@ -241,6 +241,8 @@ def _get_group_icon_by_name(gp_name: str) -> int: # blender group name prop helper _g_EnumHelper_Group: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper( VirtoolsGroupsPreset, + lambda x: x.value, # member is string self + lambda x: VirtoolsGroupsPreset(x), # convert directly because it is StrEnum. lambda x: x.value, lambda _: '', lambda x: _get_group_icon_by_name(x.value) diff --git a/bbp_ng/PROP_virtools_material.py b/bbp_ng/PROP_virtools_material.py index b9504ac..a4b5226 100644 --- a/bbp_ng/PROP_virtools_material.py +++ b/bbp_ng/PROP_virtools_material.py @@ -521,6 +521,8 @@ def preset_virtools_material(mtl: bpy.types.Material, preset_type: MaterialPrese # create preset enum blender helper _g_Helper_MtlPreset: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper( MaterialPresetType, + lambda x: str(x.value), + lambda x: MaterialPresetType(int(x)), lambda x: x.name, lambda _: '', lambda _: '' diff --git a/bbp_ng/UTIL_bme.py b/bbp_ng/UTIL_bme.py index 16eab76..2c219b4 100644 --- a/bbp_ng/UTIL_bme.py +++ b/bbp_ng/UTIL_bme.py @@ -3,6 +3,9 @@ import os, json, enum, typing, math from . import PROP_virtools_group, PROP_bme_material from . import UTIL_functions, UTIL_icons_manager, UTIL_blender_mesh +## NOTE: Outside caller should use BME struct's unique indetifier to visit each prototype +# and drive this class' functions to work. + #region Prototype Visitor class PrototypeShowcaseCfgsTypes(enum.Enum): @@ -11,11 +14,18 @@ class PrototypeShowcaseCfgsTypes(enum.Enum): String = 'str' Face = 'face' +class PrototypeShowcaseTypes(enum.Enum): + No = 'none' + Floor = 'floor' + Rail = 'rail' + Wood = 'wood' + TOKEN_IDENTIFIER: str = 'identifier' TOKEN_SHOWCASE: str = 'showcase' TOKEN_SHOWCASE_TITLE: str = 'title' TOKEN_SHOWCASE_ICON: str = 'icon' +TOKEN_SHOWCASE_TYPE: str = 'type' TOKEN_SHOWCASE_CFGS: str = 'cfgs' TOKEN_SHOWCASE_CFGS_FIELD: str = 'field' TOKEN_SHOWCASE_CFGS_TYPE: str = 'type' @@ -61,7 +71,7 @@ _g_BMEPrototypeIndexMap: dict[str, int] = {} for walk_root, walk_dirs, walk_files in os.walk(os.path.join(os.path.dirname(__file__), 'json')): for relfile in walk_files: if not relfile.endswith('.json'): continue - with open(os.path.join(walk_root, relfile)) as fp: + with open(os.path.join(walk_root, relfile), 'r', encoding = 'utf-8') as fp: proto: dict[str, typing.Any] for proto in json.load(fp): # insert index to map @@ -71,6 +81,55 @@ for walk_root, walk_dirs, walk_files in os.walk(os.path.join(os.path.dirname(__f #endregion +#region Prototype EnumProp Visitor + +class EnumPropHelper(UTIL_functions.EnumPropHelper): + + def __init__(self): + # init parent class + UTIL_functions.EnumPropHelper.__init__( + self, + self.get_bme_identifiers(), + lambda x: x, + lambda x: x, + lambda x: self.get_bme_showcase_title(x), + lambda _: '', + lambda x: self.get_bme_showcase_icon(x) + ) + + def get_bme_identifiers(self) -> tuple[str, ...]: + """ + Get the identifier of prototype which need to be exposed to user. + Template prototype is not included. + """ + return tuple( + x[TOKEN_IDENTIFIER] # get identifier + for x in filter(lambda x: x[TOKEN_SHOWCASE] is not None, _g_BMEPrototypes) # filter() to filter no showcase template. + ) + + def get_bme_showcase_title(self, ident: str) -> str: + """ + Get BME display title by prototype identifier. + """ + # get prototype first + proto: dict[str, typing.Any] = _g_BMEPrototypes[_g_BMEPrototypeIndexMap[ident]] + # visit title field + return proto[TOKEN_SHOWCASE][TOKEN_SHOWCASE_TITLE] + + def get_bme_showcase_icon(self, ident: str) -> int: + """ + Get BME icon by prototype's identifier + """ + # get prototype specified icon name + proto: dict[str, typing.Any] = _g_BMEPrototypes[_g_BMEPrototypeIndexMap[ident]] + icon_name: str = proto[TOKEN_SHOWCASE][TOKEN_SHOWCASE_ICON] + # get icon from icon manager + cache: int | None = UTIL_icons_manager.get_bme_icon(icon_name) + if cache is None: return UTIL_icons_manager.get_empty_icon() + else: return cache + +#endregion + #region Programmable Field Calc _g_ProgFieldGlobals: dict[str, typing.Any] = { diff --git a/bbp_ng/UTIL_functions.py b/bbp_ng/UTIL_functions.py index c66632a..64f59c7 100644 --- a/bbp_ng/UTIL_functions.py +++ b/bbp_ng/UTIL_functions.py @@ -70,37 +70,51 @@ def add_into_scene_and_move_to_cursor(obj: bpy.types.Object): class EnumPropHelper(): """ - These class contain all functions related to EnumProperty creation for Python Enums + These class contain all functions related to EnumProperty, including generating `items`, + parsing data from EnumProperty string value and getting EnumProperty acceptable string format from data. """ # define some type hint - - _TEnumVar = typing.TypeVar('_TEnumVar', bound = enum.Enum) ##< Mean a variable of enum.Enum's children - _TEnum = type[_TEnumVar] ##< Mean the type self which is enum.Enum's children. - _TFctName = typing.Callable[[_TEnumVar], str] - _TFctDesc = typing.Callable[[_TEnumVar], str] - _TFctIcon = typing.Callable[[_TEnumVar], str | int] + _TFctToStr = typing.Callable[[typing.Any], str] + _TFctFromStr = typing.Callable[[str], typing.Any] + _TFctName = typing.Callable[[typing.Any], str] + _TFctDesc = typing.Callable[[typing.Any], str] + _TFctIcon = typing.Callable[[typing.Any], str | int] # define class member - __mTy: _TEnum - __mIsIntEnum: bool + __mCollections: typing.Iterable[typing.Any] + __mFctToStr: _TFctToStr + __mFctFromStr: _TFctFromStr __mFctName: _TFctName __mFctDesc: _TFctDesc __mFctIcon: _TFctIcon def __init__( self, - ty: _TEnum, + collections_: typing.Iterable[typing.Any], + fct_to_str: _TFctToStr, + fct_from_str: _TFctFromStr, fct_name: _TFctName, fct_desc: _TFctDesc, fct_icon: _TFctIcon): - # check type - if not issubclass(ty, enum.Enum): - raise BBPException('invalid type for EnumPropHelper') + """ + Initialize a EnumProperty helper. + + @param collections_ [in] The collection all available enum property entries contained. + It can be enum.Enum or a simple list/tuple/dict. + @param fct_to_str [in] A function pointer converting data collection member to its string format. + For enum.IntEnum, it can be simply `lambda x: str(x.value)` + @param fct_from_str [in] A function pointer getting data collection member from its string format. + For enum.IntEnum, it can be simply `lambda x: TEnum(int(x))` + @param fct_name [in] A function pointer converting data collection member to its display name. + @param fct_desc [in] Same as `fct_name` but return description instead. Return empty string, not None if no description. + @param fct_icon [in] Same as `fct_name` but return the used icon instead. Return empty string if no icon. + """ # assign member - self.__mTy = ty - self.__mIsIntEnum = issubclass(ty, enum.IntEnum) + self.__mCollections = collections_ + self.__mFctToStr = fct_to_str + self.__mFctFromStr = fct_from_str self.__mFctName = fct_name self.__mFctDesc = fct_desc self.__mFctIcon = fct_icon @@ -111,50 +125,27 @@ class EnumPropHelper(): """ # blender enum prop item format: # (token, display name, descriptions, icon, index) - if self.__mIsIntEnum: - # for intenum, we can use its value as index number directly. - # and use the string format of index as blender prop token. - return tuple( - ( - str(member.value), - self.__mFctName(member), - self.__mFctDesc(member), - self.__mFctIcon(member), - member.value - ) for member in self.__mTy - ) - else: - # for non-intenum, we need create number index manually for it. - # and directly use its value as blender prop token - return tuple( - ( - member.value, - self.__mFctName(member), - self.__mFctDesc(member), - self.__mFctIcon(member), - idx - ) for idx, member in enumerate(self.__mTy) - ) + return tuple( + ( + self.__mFctToStr(member), # call to_str as its token. + self.__mFctName(member), + self.__mFctDesc(member), + self.__mFctIcon(member), + idx # use hardcode index, not the collection member self. + ) for idx, member in enumerate(self.__mCollections) + ) - def get_selection(self, prop: str) -> _TEnumVar: + def get_selection(self, prop: str) -> typing.Any: """ - Return Python enum value from given Blender EnumProp. + Return collection member from given Blender EnumProp string data. """ - # for intenum, param is its string format, we need use int() to convert it first - # for non-intenum, param is just its value, we use it directly - # then we parse it to python enum type - if self.__mIsIntEnum: - return self.__mTy(int(prop)) - else: - return self.__mTy(prop) + # call from_str fct ptr + return self.__mFctFromStr(prop) - def to_selection(self, val: _TEnumVar) -> str: + def to_selection(self, val: typing.Any) -> str: """ - Parse Python enum value to Blender EnumProp acceptable string. + Parse collection member to Blender EnumProp acceptable string format. """ - # the inversed operation of get_selection(). - if self.__mIsIntEnum: - return str(val.value) - else: - return val.value + # call to_str fct ptr + return self.__mFctToStr(val) diff --git a/bbp_ng/UTIL_virtools_types.py b/bbp_ng/UTIL_virtools_types.py index 2cba316..6dd045f 100644 --- a/bbp_ng/UTIL_virtools_types.py +++ b/bbp_ng/UTIL_virtools_types.py @@ -200,14 +200,18 @@ class EnumPropHelper(UTIL_functions.EnumPropHelper): Virtools type specified Blender EnumProp helper. """ __mAnnotationDict: dict[int, EnumAnnotation] + __mEnumTy: type[enum.Enum] - def __init__(self, ty: UTIL_functions.EnumPropHelper._TEnum): - # set self annotation ref + def __init__(self, ty: type[enum.Enum]): + # set enum type and annotation ref first + self.__mEnumTy = ty self.__mAnnotationDict = _g_Annotation[ty] # init parent data UTIL_functions.EnumPropHelper.__init__( self, - ty, + self.__mEnumTy, # enum.Enum it self is iterable + lambda x: str(x.value), # convert enum.Enum's value to string + lambda x: self.__mEnumTy(int(x)), # use stored enum type and int() to get enum member lambda x: self.__mAnnotationDict[x.value].mDisplayName, lambda x: self.__mAnnotationDict[x.value].mDescription, lambda _: '' diff --git a/bbp_ng/json/trafos.json b/bbp_ng/json/trafos.json index f8f24ff..fde648f 100644 --- a/bbp_ng/json/trafos.json +++ b/bbp_ng/json/trafos.json @@ -158,6 +158,7 @@ "showcase": { "title": "Wood Trafo", "icon": "WoodTrafo", + "type": "floor", "cfgs": [ { "field": "face_", @@ -194,6 +195,7 @@ "showcase": { "title": "Stone Trafo", "icon": "StoneTrafo", + "type": "floor", "cfgs": [ { "field": "face_", @@ -230,6 +232,7 @@ "showcase": { "title": "Paper Trafo", "icon": "PaperTrafo", + "type": "floor", "cfgs": [ { "field": "face_",