diff --git a/bbp_ng/OP_ADDS_bme.py b/bbp_ng/OP_ADDS_bme.py index e49f79c..db061a4 100644 --- a/bbp_ng/OP_ADDS_bme.py +++ b/bbp_ng/OP_ADDS_bme.py @@ -7,23 +7,21 @@ from . import UTIL_functions, UTIL_icons_manager, UTIL_bme _g_EnumHelper_BmeStructType: UTIL_bme.EnumPropHelper = UTIL_bme.EnumPropHelper() -class BBP_PG_bme_adder_params(bpy.types.PropertyGroup): +class BBP_PG_bme_adder_cfgs(bpy.types.PropertyGroup): prop_int: bpy.props.IntProperty( name = 'Single Int', description = 'Single Int', min = 0, max = 64, soft_min = 0, soft_max = 32, + step = 1, default = 1, ) prop_float: bpy.props.FloatProperty( name = 'Single Float', description = 'Single Float', min = 0.0, max = 1024.0, soft_min = 0.0, soft_max = 64.0, + step = 2.5, default = 5.0, ) - prop_str: bpy.props.StringProperty( - name = 'Single Str', description = 'Single Str', - default = '' - ) prop_bool: bpy.props.BoolProperty( name = 'Single Bool', description = 'Single Bool', default = True @@ -31,30 +29,102 @@ class BBP_PG_bme_adder_params(bpy.types.PropertyGroup): class BBP_OT_add_bme_struct(bpy.types.Operator): """Add BME Struct""" - bl_idname = "bbp.dd_bme_struct" + bl_idname = "bbp.add_bme_struct" bl_label = "Add BME Struct" bl_options = {'REGISTER', 'UNDO'} + + ## There is a compromise due to the shitty Blender design. + # + # The passed `self` of Blender Property update function is not the instance of operator, + # but a simple OperatorProperties. + # It mean that I can not visit the full operator, only what I can do is visit existing + # Blender properties. + # + # So these is the solution about generating cache list according to the change of bme struct type. + # First, update function will only set a "outdated" flag for operator which is a pre-registered Blender property. + # The "outdated" flags is not showen and not saved. + # Then call a internal cache list update function at the begin of `invoke` and `draw`. + # In this internal cache list updator, check "outdated" flag first, if cache is outdated, update and reset flag. + # Otherwise do nothing. + # + # Reference: https://docs.blender.org/api/current/bpy.props.html#update-example + + ## Compromise used "outdated" flag. + outdated_flag: bpy.props.BoolProperty( + name = "Outdated Type", + description = "Internal flag.", + options = {'HIDDEN', 'SKIP_SAVE'}, + default = False + ) + + ## A BME struct cfgs descriptor cache list + # Not only the descriptor self, also the cfg associated index in bme_struct_cfgs + bme_struct_cfg_index_cache: list[tuple[UTIL_bme.PrototypeShowcaseCfgDescriptor, int]] + + def __internal_update_bme_struct_type(self) -> None: + # if not outdated, skip + if not self.outdated_flag: return + + # get available cfg entires + cfgs: typing.Iterator[UTIL_bme.PrototypeShowcaseCfgDescriptor] + cfgs = _g_EnumHelper_BmeStructType.get_bme_showcase_cfgs( + _g_EnumHelper_BmeStructType.get_selection(self.bme_struct_type) + ) + + # analyse cfgs. + # create counter first + counter_int: int = 0 + counter_float: int = 0 + counter_bool: int = 0 + # create cache list + self.bme_struct_cfg_index_cache.clear() + # iterate cfgs and register them + for cfg in cfgs: + match(cfg.get_type()): + case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer: + self.bme_struct_cfg_index_cache.append((cfg, counter_int)) + counter_int += 1 + case UTIL_bme.PrototypeShowcaseCfgsTypes.Float: + self.bme_struct_cfg_index_cache.append((cfg, counter_float)) + counter_float += 1 + case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean: + self.bme_struct_cfg_index_cache.append((cfg, counter_bool)) + counter_bool += 1 + case UTIL_bme.PrototypeShowcaseCfgsTypes.Face: + self.bme_struct_cfg_index_cache.append((cfg, counter_bool)) + counter_bool += 6 # face will occupy 6 bool. + + + # init data collection + # clear first + self.bme_struct_cfgs.clear() + # create enough entries specified by gotten cfgs + for _ in range(max(counter_int, counter_float, counter_bool)): + self.bme_struct_cfgs.add() + + # assign default value + for (cfg, cfg_index) in self.bme_struct_cfg_index_cache: + # show prop differently by cfg type + match(cfg.get_type()): + case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer: + self.bme_struct_cfgs[cfg_index].prop_int = cfg.get_default() + case UTIL_bme.PrototypeShowcaseCfgsTypes.Float: + self.bme_struct_cfgs[cfg_index].prop_float = cfg.get_default() + case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean: + self.bme_struct_cfgs[cfg_index].prop_bool = cfg.get_default() + case UTIL_bme.PrototypeShowcaseCfgsTypes.Face: + # face is just 6 bool + default_values: tuple[bool, ...] = cfg.get_default() + for i in range(6): + self.bme_struct_cfgs[cfg_index + i].prop_bool = default_values[i] + + # reset outdated flag + self.outdated_flag = False # the updator for default side value def bme_struct_type_updated(self, context): - # get floor prototype - #floor_prototype = UTILS_constants.floor_blockDict[self.floor_type] - - # try sync default value - #default_sides = floor_prototype['DefaultSideConfig'] - #self.use_2d_top = default_sides['UseTwoDTop'] - #self.use_2d_right = default_sides['UseTwoDRight'] - #self.use_2d_bottom = default_sides['UseTwoDBottom'] - #self.use_2d_left = default_sides['UseTwoDLeft'] - #self.use_3d_top = default_sides['UseThreeDTop'] - #self.use_3d_bottom = default_sides['UseThreeDBottom'] - - # init data collection - # todo: this state will add 32 items in each call. - # please clear it or resize it. - for i in range(32): - item = self.data_floats.add() - + # update outdated flag + self.outdated_flag = True # blender required return None @@ -64,41 +134,73 @@ class BBP_OT_add_bme_struct(bpy.types.Operator): items = _g_EnumHelper_BmeStructType.generate_items(), update = bme_struct_type_updated ) - - data_floats : bpy.props.CollectionProperty( - name = "Floats", - description = "Float collection.", - type = BBP_PG_bme_adder_params, + + bme_struct_cfgs : bpy.props.CollectionProperty( + name = "Cfgs", + description = "Cfg collection.", + type = BBP_PG_bme_adder_cfgs, ) - + @classmethod def poll(self, context): return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder() - + def invoke(self, context, event): + # create internal list + self.bme_struct_cfg_index_cache = [] # trigger default bme struct type updator self.bme_struct_type_updated(context) + # call internal updator + self.__internal_update_bme_struct_type() # run execute() function return self.execute(context) - + def execute(self, context): # todo: call general creator return {'FINISHED'} - + def draw(self, context): + # call internal updator + self.__internal_update_bme_struct_type() + + # start drawing layout = self.layout # show type layout.prop(self, 'bme_struct_type') - # show type - for i in self.data_floats: - layout.prop(i, 'prop_bool') + + # visit cfgs cache list to show cfg + for (cfg, cfg_index) in self.bme_struct_cfg_index_cache: + # draw title first + layout.label(text = cfg.get_title()) + # show prop differently by cfg type + match(cfg.get_type()): + case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer: + layout.prop(self.bme_struct_cfgs[cfg_index], 'prop_int', text = '') + case UTIL_bme.PrototypeShowcaseCfgsTypes.Float: + layout.prop(self.bme_struct_cfgs[cfg_index], 'prop_float', text = '') + case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean: + layout.prop(self.bme_struct_cfgs[cfg_index], 'prop_bool', text = '') + case UTIL_bme.PrototypeShowcaseCfgsTypes.Face: + # face will show a special layout (grid view) + grids = layout.grid_flow( + row_major=True, columns=3, even_columns=True, even_rows=True, align=True) + grids.alignment = 'CENTER' + grids.separator() + grids.prop(self.bme_struct_cfgs[cfg_index + 0], 'prop_bool', text = 'Top') # top + grids.prop(self.bme_struct_cfgs[cfg_index + 2], 'prop_bool', text = 'Front') # front + grids.prop(self.bme_struct_cfgs[cfg_index + 4], 'prop_bool', text = 'Left') # left + grids.label(text = '', icon = 'CUBE') # show a 3d cube as icon + grids.prop(self.bme_struct_cfgs[cfg_index + 5], 'prop_bool', text = 'Right') # right + grids.prop(self.bme_struct_cfgs[cfg_index + 3], 'prop_bool', text = 'Back') # back + grids.prop(self.bme_struct_cfgs[cfg_index + 1], 'prop_bool', text = 'Bottom') # bottom + grids.separator() @classmethod def draw_blc_menu(self, layout: bpy.types.UILayout): for ident in _g_EnumHelper_BmeStructType.get_bme_identifiers(): # draw operator cop = layout.operator( - self.bl_idname, + self.bl_idname, text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident), icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident) ) @@ -118,9 +220,9 @@ class BBP_OT_add_bme_struct(bpy.types.Operator): #endregion def register(): - bpy.utils.register_class(BBP_PG_bme_adder_params) + bpy.utils.register_class(BBP_PG_bme_adder_cfgs) bpy.utils.register_class(BBP_OT_add_bme_struct) def unregister(): bpy.utils.unregister_class(BBP_OT_add_bme_struct) - bpy.utils.unregister_class(BBP_PG_bme_adder_params) \ No newline at end of file + bpy.utils.unregister_class(BBP_PG_bme_adder_cfgs) \ No newline at end of file diff --git a/bbp_ng/UTIL_bme.py b/bbp_ng/UTIL_bme.py index 2c219b4..b908825 100644 --- a/bbp_ng/UTIL_bme.py +++ b/bbp_ng/UTIL_bme.py @@ -11,7 +11,7 @@ from . import UTIL_functions, UTIL_icons_manager, UTIL_blender_mesh class PrototypeShowcaseCfgsTypes(enum.Enum): Integer = 'int' Float = 'float' - String = 'str' + Boolean = 'bool' Face = 'face' class PrototypeShowcaseTypes(enum.Enum): @@ -63,7 +63,7 @@ TOKEN_INSTANCES_TRANSFORM: str = 'transform' #region Prototype Loader ## The list storing BME prototype. -_g_BMEPrototypes: list = [] +_g_BMEPrototypes: list[dict[str, typing.Any]] = [] ## The dict. Key is prototype identifier. value is the index of prototype in prototype list. _g_BMEPrototypeIndexMap: dict[str, int] = {} @@ -79,54 +79,8 @@ for walk_root, walk_dirs, walk_files in os.walk(os.path.join(os.path.dirname(__f # add into list _g_BMEPrototypes.append(proto) -#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 +def _get_prototype_by_identifier(ident: str) -> dict[str, typing.Any]: + return _g_BMEPrototypes[_g_BMEPrototypeIndexMap[ident]] #endregion @@ -181,6 +135,85 @@ def _eval_others(strl, str, params_vars_data: dict[str, typing.Any]) -> typing.A #endregion +#region Prototype Helper + +class PrototypeShowcaseCfgDescriptor(): + __mRawCfg: dict[str, str] + + def __init__(self, raw_cfg: dict[str, str]): + self.__mRawCfg = raw_cfg + + def get_field(self) -> str: + return self.__mRawCfg[TOKEN_SHOWCASE_CFGS_FIELD] + + def get_type(self) -> PrototypeShowcaseCfgsTypes: + return PrototypeShowcaseCfgsTypes(self.__mRawCfg[TOKEN_SHOWCASE_CFGS_TYPE]) + + def get_title(self) -> str: + return self.__mRawCfg[TOKEN_SHOWCASE_CFGS_TITLE] + + def get_desc(self) -> str: + return self.__mRawCfg[TOKEN_SHOWCASE_CFGS_DESC] + + def get_default(self) -> typing.Any: + return _eval_showcase_cfgs_default(self.__mRawCfg[TOKEN_SHOWCASE_CFGS_DEFAULT]) + +class EnumPropHelper(UTIL_functions.EnumPropHelper): + """ + The BME specialized Blender EnumProperty helper. + """ + + 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] = _get_prototype_by_identifier(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] = _get_prototype_by_identifier(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 + + def get_bme_showcase_cfgs(self, ident: str) -> typing.Iterator[PrototypeShowcaseCfgDescriptor]: + # get prototype first + proto: dict[str, typing.Any] = _get_prototype_by_identifier(ident) + # use map to batch create descriptor + return map(lambda x: PrototypeShowcaseCfgDescriptor(x), proto[TOKEN_SHOWCASE][TOKEN_SHOWCASE_CFGS]) + +#endregion + #region Core Creator def get_bme_struct_cfgs():