BallanceBlenderHelper/bbp_ng/UTIL_naming_convension.py
2023-11-29 22:12:04 +08:00

331 lines
11 KiB
Python

import bpy
import typing, enum
from . import UTIL_functions, UTIL_icons_manager
from . import PROP_virtools_group
#region Rename Error Reporter
class _RenameErrorType(enum.IntEnum):
ERROR = enum.auto()
WARNING = enum.auto()
INFO = enum.auto()
class _RenameErrorItem():
mErrType: _RenameErrorType
mDescription: str
def __init__(self, err_t: _RenameErrorType, description: str):
self.mErrType = err_t
self.mDescription = description
class _RenameErrorReporter():
mErrList: list[_RenameErrorItem]
mOldName: str
def __init__(self):
self.mErrList = []
self.mOldName = ""
def add_error(self, description: str):
self.mErrList.append(_RenameErrorItem(_RenameErrorType.ERROR, description))
def add_warning(self, description: str):
self.mErrList.append(_RenameErrorItem(_RenameErrorType.WARNING, description))
def add_info(self, description: str):
self.mErrList.append(_RenameErrorItem(_RenameErrorType.INFO, description))
def begin_object(self, obj: bpy.types.Object) -> None:
# assign old name
self.mOldName = obj.name
def end_object(self, obj:bpy.types.Object) -> None:
# if error list is empty, no need to report
if len(self.mErrList) == 0: return
# output header
# if new name is different with old name, output both of them
new_name: str = obj.name
if self.mOldName == new_name:
print(f'For object "{new_name}"')
else:
print(f'For object "{new_name}" (Old name: "{self.mOldName}")')
# output error list with indent
for item in self.mErrList:
print('\t' + _RenameErrorReporter.__erritem_to_string(item))
# clear error list for next object
self.mErrList.clear()
@staticmethod
def __errtype_to_string(err_v: _RenameErrorType) -> str:
match(err_v):
case _RenameErrorType.ERROR: return 'ERROR'
case _RenameErrorType.WARNING: return 'WARN'
case _RenameErrorType.INFO: return 'INFO'
case _: raise UTIL_functions.BBPException("Unknown error type.")
@staticmethod
def __erritem_to_string(item: _RenameErrorItem) -> str:
return f'[{_RenameErrorReporter.__errtype_to_string(item.mErrType)}]\t{item.mDescription}'
#endregion
#region Naming Convention Used Types
class BallanceObjectType(enum.IntEnum):
COMPONENT = enum.auto()
FLOOR = enum.auto()
RAIL = enum.auto()
WOOD = enum.auto()
STOPPER = enum.auto()
LEVEL_START = enum.auto()
LEVEL_END = enum.auto()
CHECKPOINT = enum.auto()
RESETPOINT = enum.auto()
DEPTH_CUBE = enum.auto()
SKYLAYER = enum.auto()
DECORATION = enum.auto()
class BallanceObjectInfo():
mBasicType: BallanceObjectType
## Only available for COMPONENT basic type
mComponentType: str | None
## Only available for COMPONENT, CHECKPOINT, RESETPOINT basic type
# For COMPONENT, it indicate which sector this component belong to.
# For CHECKPOINT, RESETPOINT, it indicate the index of this object.
# In CHECKPOINT, RESETPOINT mode, the sector actually is the suffix number of these objects' name. So checkpoint starts with 1, not 0.
mSector: int | None
def __init__(self, basic_type: BallanceObjectType):
self.mBasicType = basic_type
@classmethod
def create_from_component(cls, comp_type: str, sector: int):
inst = cls(BallanceObjectType.COMPONENT)
inst.mComponentType = comp_type
inst.mSector = sector
return inst
@classmethod
def create_from_checkpoint(cls, sector: int):
inst = cls(BallanceObjectType.CHECKPOINT)
inst.mSector = sector
return inst
@classmethod
def create_from_resetpoint(cls, sector: int):
inst = cls(BallanceObjectType.RESETPOINT)
inst.mSector = sector
return inst
@classmethod
def create_from_others(cls, basic_type: BallanceObjectType):
return cls(basic_type)
class _NamingConventionProfile():
_TNameFct = typing.Callable[[], str]
_TDescFct = typing.Callable[[], str]
_TParseFct = typing.Callable[[bpy.types.Object, _RenameErrorReporter | None], BallanceObjectInfo | None]
_TSetFct = typing.Callable[[bpy.types.Object,BallanceObjectInfo, _RenameErrorReporter | None], bool]
mNameFct: _TNameFct
mDescFct: _TDescFct
mParseFct: _TParseFct
mSetFct: _TSetFct
def __init__(self, name_fct: _TNameFct, desc_fct: _TDescFct, parse_fct: _TParseFct, set_fct: _TSetFct):
self.mNameFct = name_fct
self.mDescFct = desc_fct
self.mParseFct = parse_fct
self.mSetFct = set_fct
#endregion
#region Naming Convention Declaration
class _VirtoolsGroupConvention():
@staticmethod
def parse_from_object(obj: bpy.types.Object, reporter: _RenameErrorReporter | None) -> BallanceObjectInfo | None:
return None
@staticmethod
def set_to_object(obj: bpy.types.Object, info: BallanceObjectInfo, reporter: _RenameErrorReporter | None) -> bool:
return False
@staticmethod
def register() -> _NamingConventionProfile:
return _NamingConventionProfile(
lambda: 'Virtools Group',
lambda: 'Virtools Group',
_VirtoolsGroupConvention.parse_from_object,
_VirtoolsGroupConvention.set_to_object
)
class _YYCToolchainConvention():
@staticmethod
def parse_from_object(obj: bpy.types.Object, reporter: _RenameErrorReporter | None) -> BallanceObjectInfo | None:
return None
@staticmethod
def set_to_object(obj: bpy.types.Object, info: BallanceObjectInfo, reporter: _RenameErrorReporter | None) -> bool:
return False
@staticmethod
def register() -> _NamingConventionProfile:
return _NamingConventionProfile(
lambda: 'YYC Toolchain',
lambda: 'YYC Toolchain name standard.',
_YYCToolchainConvention.parse_from_object,
_YYCToolchainConvention.set_to_object
)
class _ImengyuConvention():
@staticmethod
def parse_from_object(obj: bpy.types.Object, reporter: _RenameErrorReporter | None) -> BallanceObjectInfo | None:
return None
@staticmethod
def set_to_object(obj: bpy.types.Object, info: BallanceObjectInfo, reporter: _RenameErrorReporter | None) -> bool:
return False
@staticmethod
def register() -> _NamingConventionProfile:
return _NamingConventionProfile(
lambda: 'Imengyu Ballance',
lambda: 'Auto grouping name standard for Imengyu/Ballance.',
_ImengyuConvention.parse_from_object,
_ImengyuConvention.set_to_object
)
#endregion
#region Nameing Convention Register
## All available naming conventions
# Each naming convention should have a identifier for visiting them.
# The identifier is its index in this tuple.
_g_NamingConventions: list[_NamingConventionProfile] = []
def _register_naming_convention_with_index(profile: _NamingConventionProfile) -> int:
global _g_NamingConventions
ret: int = len(_g_NamingConventions)
_g_NamingConventions.append(profile)
return ret
# register native and default one and others
# but only native one and default one need keep its index
#
# The native naming convention is Virtools Group
# We treat it as naming convention because we want use a universal interface to process naming converting.
# So Virtools Group can no be seen as a naming convention, but we treat it like naming convention in code.
# The "native" mean this is
#
# The default fallback naming convention is YYC toolchain
#
_g_NativeNamingConventionIndex: int = _register_naming_convention_with_index(_VirtoolsGroupConvention.register())
_g_DefaultNamingConventionIndex: int = _register_naming_convention_with_index(_YYCToolchainConvention.register())
_register_naming_convention_with_index(_ImengyuConvention.register())
class _EnumPropHelper():
"""
Operate like UTIL_virtools_types.EnumPropHelper
Return the identifier (index) of naming convention.
"""
@staticmethod
def generate_items() -> tuple[tuple, ...]:
# create a function to filter Virtools Group profile
# and return index at the same time
def naming_convention_iter() -> typing.Iterator[tuple[int, _NamingConventionProfile]]:
for idx, item in enumerate(_g_NamingConventions):
if idx != _g_NativeNamingConventionIndex:
yield (idx, item)
# token, display name, descriptions, icon, index
return tuple(
(
str(idx),
item.mNameFct(),
item.mDescFct(),
"",
idx
) for idx, item in naming_convention_iter()
)
@staticmethod
def get_selection(prop: str) -> int:
return int(prop)
@staticmethod
def to_selection(val: int) -> str:
return str(val)
@staticmethod
def get_virtools_group_identifier() -> int:
return _g_NativeNamingConventionIndex
@staticmethod
def get_default_naming_identifier() -> int:
return _g_DefaultNamingConventionIndex
#endregion
def name_setter_core(ident: int, info: BallanceObjectInfo, obj: bpy.types.Object) -> None:
# get profile
profile: _NamingConventionProfile = _g_NamingConventions[ident]
# set name. don't care whether success.
profile.mSetFct(obj, info, None)
def name_converting_core(src_ident: int, dst_ident: int, objs: typing.Iterable[bpy.types.Object]) -> None:
# no convert needed
if src_ident == dst_ident: return
# get convert profile
src: _NamingConventionProfile = _g_NamingConventions[src_ident]
dst: _NamingConventionProfile = _g_NamingConventions[dst_ident]
# create reporter and success counter
failed_obj_counter: int = 0
all_obj_counter: int = 0
err_reporter: _RenameErrorReporter = _RenameErrorReporter()
# print console report header
print('============')
print('Rename Report')
print('------------')
# start converting
for obj in objs:
# inc counter all
all_obj_counter += 1
# begin object processing
err_reporter.begin_object(obj)
# parsing from src and set by dst
# inc failed counter if failed
obj_info: BallanceObjectInfo | None= src.mParseFct(obj, err_reporter)
if obj_info is not None:
ret: bool = dst.mSetFct(obj, obj_info, err_reporter)
if not ret: failed_obj_counter += 1
else:
failed_obj_counter += 1
# end object processing and output err list
err_reporter.end_object(obj)
# print console report tail
print('------------')
print(f'All / Failed - {all_obj_counter} / {failed_obj_counter}')
print('============')
# popup blender window to notice user
UTIL_functions.message_box(
(
'View console to get more detail.',
f'All: {all_obj_counter}',
f'Failed: {failed_obj_counter}',
),
"Rename Report",
UTIL_icons_manager.BlenderPresetIcons.Info.value
)