From 7e74e42bd7270ff6c66e8a5191215d9c40148cca Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Mon, 25 Aug 2025 13:07:55 +0800 Subject: [PATCH] feat: add BME category display in blender. - add BME category display in blender, including add menu and side menu. --- bbp_ng/OP_ADDS_bme.py | 28 +++++++++++------- bbp_ng/OP_EXPORT_virtools.py | 2 +- bbp_ng/UTIL_bme.py | 56 +++++++++++++++++++++++++++--------- bbp_ng/UTIL_translation.py | 16 ++++++++--- 4 files changed, 73 insertions(+), 29 deletions(-) diff --git a/bbp_ng/OP_ADDS_bme.py b/bbp_ng/OP_ADDS_bme.py index da71874..4a0716c 100644 --- a/bbp_ng/OP_ADDS_bme.py +++ b/bbp_ng/OP_ADDS_bme.py @@ -280,16 +280,24 @@ class BBP_OT_add_bme_struct(bpy.types.Operator): @classmethod def draw_blc_menu(cls, layout: bpy.types.UILayout): - for ident in _g_EnumHelper_BmeStructType.get_bme_identifiers(): - # draw operator - cop = layout.operator( - cls.bl_idname, - text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident), - icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident), - text_ctxt = UTIL_translation.build_prototype_showcase_context(ident), - ) - # and assign its init type value - cop.bme_struct_type = _g_EnumHelper_BmeStructType.to_selection(ident) + for category, idents in _g_EnumHelper_BmeStructType.get_bme_categories().items(): + # draw category label + layout.label(text=category, text_ctxt=UTIL_translation.build_prototype_showcase_category_context()) + + # draw prototypes list + for ident in idents: + # draw operator + cop = layout.operator( + cls.bl_idname, + text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident), + icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident), + text_ctxt = UTIL_translation.build_prototype_showcase_title_context(ident), + ) + # and assign its init type value + cop.bme_struct_type = _g_EnumHelper_BmeStructType.to_selection(ident) + + # draw separator + layout.separator() #endregion diff --git a/bbp_ng/OP_EXPORT_virtools.py b/bbp_ng/OP_EXPORT_virtools.py index 22c9359..645374c 100644 --- a/bbp_ng/OP_EXPORT_virtools.py +++ b/bbp_ng/OP_EXPORT_virtools.py @@ -21,7 +21,7 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool def execute(self, context): # check selecting first - objls: tuple[bpy.types.Object] | None = self.general_get_export_objects(context) + objls: tuple[bpy.types.Object, ...] | None = self.general_get_export_objects(context) if objls is None: self.report({'ERROR'}, 'No selected target!') return {'CANCELLED'} diff --git a/bbp_ng/UTIL_bme.py b/bbp_ng/UTIL_bme.py index 6374707..9e11eb6 100644 --- a/bbp_ng/UTIL_bme.py +++ b/bbp_ng/UTIL_bme.py @@ -24,6 +24,7 @@ TOKEN_IDENTIFIER: str = 'identifier' TOKEN_SHOWCASE: str = 'showcase' TOKEN_SHOWCASE_TITLE: str = 'title' +TOKEN_SHOWCASE_CATEGORY: str = 'category' TOKEN_SHOWCASE_ICON: str = 'icon' TOKEN_SHOWCASE_TYPE: str = 'type' TOKEN_SHOWCASE_CFGS: str = 'cfgs' @@ -64,10 +65,10 @@ TOKEN_INSTANCES_TRANSFORM: str = 'transform' #region Prototype Loader -## The list storing BME prototype. _g_BMEPrototypes: list[dict[str, typing.Any]] = [] -## The dict. Key is prototype identifier. value is the index of prototype in prototype list. +"""The list storing BME prototype.""" _g_BMEPrototypeIndexMap: dict[str, int] = {} +"""The dict. Key is prototype identifier. Value is the index of prototype in prototype list.""" # the core loader for walk_root, walk_dirs, walk_files in os.walk(os.path.join(os.path.dirname(__file__), 'jsons')): @@ -99,7 +100,7 @@ def _env_fct_angle(x1: float, y1: float, x2: float, y2: float) -> float: # second, its direction (clockwise is positive) is opposite with blender rotation direction (counter-clockwise is positive). diff = mathutils.Vector((x2, y2)) - mathutils.Vector((x1, y1)) bld_angle = math.degrees(mathutils.Vector((1,0)).angle_signed(diff, 0)) - + # flip it first bld_angle = -bld_angle # process positove number and negative number respectively @@ -141,7 +142,7 @@ _g_ProgFieldGlobals: dict[str, typing.Any] = { 'rot': lambda x, y, z: mathutils.Matrix.LocRotScale(None, mathutils.Euler((math.radians(x), math.radians(y), math.radians(z)), 'XYZ'), None), 'scale': lambda x, y, z: mathutils.Matrix.LocRotScale(None, None, (x, y, z)), 'ident': lambda: mathutils.Matrix.Identity(4), - + # my misc custom functions 'distance': _env_fct_distance, 'angle': _env_fct_angle, @@ -191,8 +192,32 @@ class EnumPropHelper(UTIL_functions.EnumPropHelper[str]): """ The BME specialized Blender EnumProperty helper. """ + + showcase_identifiers: tuple[str, ...] + showcase_categories: dict[str, tuple[str, ...]] def __init__(self): + # build cache for showcase identifiers and categories + # prepare cache value + identifiers: list[str] = [] + categories: dict[str, list[str]] = {} + # iterate showcase prototypes + for x in filter(lambda x: x[TOKEN_SHOWCASE] is not None, _g_BMEPrototypes): + # fetch identifier and category + identifier = typing.cast(str, x[TOKEN_IDENTIFIER]) + category = typing.cast(str, x[TOKEN_SHOWCASE][TOKEN_SHOWCASE_CATEGORY]) + # add into identifier list + identifiers.append(identifier) + # add into categories + categories_inner = categories.get(category, None) + if categories_inner is None: + categories_inner = [] + categories[category] = categories_inner + categories_inner.append(identifier) + # tuple the result + self.showcase_identifiers = tuple(identifiers) + self.showcase_categories = {k: tuple(v) for k, v in categories.items()} + # init parent class super().__init__( self.get_bme_identifiers(), @@ -202,17 +227,20 @@ class EnumPropHelper(UTIL_functions.EnumPropHelper[str]): 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. + In other words, 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. - ) + return self.showcase_identifiers + def get_bme_categories(self) -> dict[str, tuple[str, ...]]: + """ + Get user-oriented identifier list grouped by category. + """ + return self.showcase_categories + def get_bme_showcase_title(self, ident: str) -> str: """ Get BME display title by prototype identifier. @@ -326,14 +354,14 @@ def create_bme_struct( # create mtl slot remap to help following mesh adding # because mesh writer do not accept string format mtl slot visiting, # it only accept int based mtl slot index. - # + # # Also we build face used mtl slot index at the same time. # So we do not analyse texture field again when providing face data. # The result is in `prebuild_face_mtl_idx` and please note it will store all face's mtl index. # For example: if face 0 is skipped and face 1 is used, the first entry in `prebuild_face_mtl_idx` # will be the mtl slot index used by face 0, not 1. And its length is equal to the face count. # However, because face 0 is skipped, so the entry is not used and default set to 0. - # + # # NOTE: since Python 3.6, the item of builtin dict is ordered by inserting order. # we rely on this to implement following features. mtl_remap: dict[str, int] = {} @@ -351,7 +379,7 @@ def create_bme_struct( # if existing, no need to add into remap # but we need get its index from remap prebuild_face_mtl_idx[face_idx] = mtl_remap.get(mtl_name, 0) - + # pre-compute vertices data because we may need used later. # Because if face normal data is null, it mean that we need to compute it # by given vertices. @@ -366,7 +394,7 @@ def create_bme_struct( cache_bv = typing.cast(mathutils.Vector, transform @ cache_bv) # get result prebuild_vec_data.append((cache_bv.x, cache_bv.y, cache_bv.z)) - + # Check whether given transform is mirror matrix # because mirror matrix will reverse triangle indice order. # If matrix is mirror matrix, we need reverse it again in following procession, diff --git a/bbp_ng/UTIL_translation.py b/bbp_ng/UTIL_translation.py index 930d899..d22b044 100644 --- a/bbp_ng/UTIL_translation.py +++ b/bbp_ng/UTIL_translation.py @@ -55,14 +55,22 @@ import bpy CTX_BBP: str = 'BBP' # The universal translation context prefix for BME module in BBP_NG plugin. -CTX_BBP_BME: str = CTX_BBP + '/BME' -def build_prototype_showcase_context(identifier: str) -> str: +CTX_BBP_BME: str = f'{CTX_BBP}/BME' +CTX_BBP_BME_CATEGORY: str = f'{CTX_BBP_BME}/Category' +CTX_BBP_BME_PROTOTYPE: str = f'{CTX_BBP_BME}/Proto' +def build_prototype_showcase_category_context() -> str: + """ + Build the context for getting the translation for BME prototype showcase category. + @return The context for getting translation. + """ + return CTX_BBP_BME_CATEGORY +def build_prototype_showcase_title_context(identifier: str) -> str: """ Build the context for getting the translation for BME prototype showcase title. @param[in] identifier The identifier of this prototype. @return The context for getting translation. """ - return CTX_BBP_BME + '/' + identifier + return f'{CTX_BBP_BME_PROTOTYPE}/{identifier}' def build_prototype_showcase_cfg_context(identifier: str, cfg_index: int) -> str: """ Build the context for getting the translation for BME prototype showcase configuration title or description. @@ -70,7 +78,7 @@ def build_prototype_showcase_cfg_context(identifier: str, cfg_index: int) -> str @param[in] cfg_index The index of this configuration in this prototype showcase. @return The context for getting translation. """ - return CTX_BBP_BME + f'/{identifier}/[{cfg_index}]' + return f'{CTX_BBP_BME_PROTOTYPE}/{identifier}/[{cfg_index}]' #endregion