refactor. and finish basic of adding component

This commit is contained in:
yyc12345 2023-12-07 21:28:23 +08:00
parent ae9a848864
commit ebb22c9ec1
6 changed files with 475 additions and 392 deletions

View File

@ -1,22 +1,100 @@
import bpy import bpy
from . import UTIL_functions, UTIL_icons_manager from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convension
from . import PROP_preferences, PROP_ballance_element, PROP_virtools_group from . import PROP_ballance_element, PROP_virtools_group
_g_UniqueElements = { #region Help Classes & Functions
"PS_FourFlames": 'PS_FourFlames_01',
"PE_Balloon": 'PE_Balloon_01'
}
def _get_component_name(comp_name: str, comp_sector: int) -> str: def _get_component_info(comp_type: PROP_ballance_element.BallanceElementType, comp_sector: int) -> UTIL_naming_convension.BallanceObjectInfo:
return '{}_{:0>2d}_'.format(comp_name, comp_sector) match(comp_type):
# process special for 2 unique components
case PROP_ballance_element.BallanceElementType.PS_FourFlames:
return UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.LEVEL_START)
case PROP_ballance_element.BallanceElementType.PE_Balloon:
return UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.LEVEL_END)
# process naming convention required special components
case PROP_ballance_element.BallanceElementType.PC_TwoFlames:
return UTIL_naming_convension.BallanceObjectInfo.create_from_checkpoint(comp_sector)
case PROP_ballance_element.BallanceElementType.PR_Resetpoint:
return UTIL_naming_convension.BallanceObjectInfo.create_from_resetpoint(comp_sector)
# process for other components
case _:
return UTIL_naming_convension.BallanceObjectInfo.create_from_component(comp_type.name, comp_sector)
def _set_component_by_info(obj: bpy.types.Object, info: UTIL_naming_convension.BallanceObjectInfo) -> None:
# set component name and grouping it into virtools group at the same time
# set name first
if not UTIL_naming_convension.YYCToolchainConvention.set_to_object(obj, info, None):
raise UTIL_functions.BBPException('impossible fail to set component name.')
# set vt group next
if not UTIL_naming_convension.VirtoolsGroupConvention.set_to_object(obj, info, None):
raise UTIL_functions.BBPException('impossible fail to set component virtools groups.')
def _check_component_existance(comp_type: PROP_ballance_element.BallanceElementType, comp_sector: int) -> str | None:
"""
Check the existance of 4 special components name, PS, PE, PC, PR
These 4 components will have special name.
@return Return name if selected component is one of PS, PE, PC, PR and there already is a name conflict, otherwise None.
"""
# check component type requirements
match(comp_type):
case PROP_ballance_element.BallanceElementType.PS_FourFlames | PROP_ballance_element.BallanceElementType.PE_Balloon | PROP_ballance_element.BallanceElementType.PC_TwoFlames | PROP_ballance_element.BallanceElementType.PR_Resetpoint:
pass # exit match and start check
case _:
return None # return, do not check
# get info
comp_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector)
# get expected name
expect_name: str | None = UTIL_naming_convension.YYCToolchainConvention.set_to_name(comp_info, None)
if expect_name is None:
raise UTIL_functions.BBPException('impossible fail to get component name.')
# check expected name
if expect_name in bpy.data.objects: return expect_name
else: return None
class EnumPropHelper():
"""
Generate component types for this module's operator
"""
@staticmethod
def generate_items() -> tuple[tuple, ...]:
# token, display name, descriptions, icon, index
return tuple(
(
str(item.value),
item.name,
"",
UTIL_icons_manager.get_element_icon(item.name),
item.value
) for item in PROP_ballance_element.BallanceElementType
)
@staticmethod
def get_selection(prop: str) -> PROP_ballance_element.BallanceElementType:
# prop will return identifier which is defined as the string type of int value.
# so we parse it to int and then parse it to enum type.
return PROP_ballance_element.BallanceElementType(int(prop))
@staticmethod
def to_selection(val: PROP_ballance_element.BallanceElementType) -> str:
# like get_selection, we need get it int value, then convert it to string as the indetifier of enum props
# them enum property will accept it.
return str(val.value)
#endregion
class BBP_OT_add_component(bpy.types.Operator): class BBP_OT_add_component(bpy.types.Operator):
"""Add Element""" """Add Component"""
bl_idname = "bbp.add_component" bl_idname = "bbp.add_component"
bl_label = "Add Element" bl_label = "Add Component"
bl_options = {'UNDO'} bl_options = {'UNDO'}
element_sector: bpy.props.IntProperty( component_sector: bpy.props.IntProperty(
name = "Sector", name = "Sector",
description = "Define which sector the object will be grouped in", description = "Define which sector the object will be grouped in",
min = 1, max = 999, min = 1, max = 999,
@ -24,15 +102,10 @@ class BBP_OT_add_component(bpy.types.Operator):
default = 1, default = 1,
) )
element_type: bpy.props.EnumProperty( component_type: bpy.props.EnumProperty(
name = "Type", name = "Type",
description = "This element type", description = "This component type",
#items=tuple(map(lambda x: (x, x, ""), UTILS_constants.bmfile_componentList)), items = EnumPropHelper.generate_items(),
items = tuple(
# token, display name, descriptions, icon, index
(str(item.value), item.name, "", UTIL_icons_manager.get_element_icon(item.name), item.value)
for item in PROP_ballance_element.BallanceElementType
),
) )
def invoke(self, context, event): def invoke(self, context, event):
@ -41,22 +114,30 @@ class BBP_OT_add_component(bpy.types.Operator):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.prop(self, "element_type") # show type
layout.prop(self, "element_sector") layout.prop(self, "component_type")
# check for unique name and show warning # only show sector for non-PE/PS component
elename: str | None = _g_UniqueElements.get(PROP_ballance_element.BallanceElementType(int(self.element_type)).name, None) eletype: PROP_ballance_element.BallanceElementType = EnumPropHelper.get_selection(self.component_type)
if elename is not None and elename in bpy.data.objects: if eletype != PROP_ballance_element.BallanceElementType.PS_FourFlames and eletype != PROP_ballance_element.BallanceElementType.PE_Balloon:
layout.label(f'Warning: {elename} already exist.') layout.prop(self, "component_sector")
# check for some special components and show warning
elename: str | None = _check_component_existance(EnumPropHelper.get_selection(self.component_type), self.component_sector)
if elename is not None:
layout.label(text = f'Warning: {elename} already exist.')
def execute(self, context): def execute(self, context):
# create by ballance elements # create by ballance components
eletype: PROP_ballance_element.BallanceElementType = PROP_ballance_element.BallanceElementType(int(self.element_type)) eletype: PROP_ballance_element.BallanceElementType = EnumPropHelper.get_selection(self.component_type)
eleinfo: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(eletype, self.component_sector)
with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator: with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator:
obj = bpy.data.objects.new( # create with empty name first
_get_component_name(eletype.name, self.element_sector), obj = bpy.data.objects.new('', creator.get_element(eletype.value))
creator.get_element(eletype.value) # assign its props, including name
) _set_component_by_info(obj, eleinfo)
# scene cursor
UTIL_functions.add_into_scene_and_move_to_cursor(obj) UTIL_functions.add_into_scene_and_move_to_cursor(obj)
return {'FINISHED'} return {'FINISHED'}
@ -67,7 +148,7 @@ class BBP_OT_add_component(bpy.types.Operator):
cop = layout.operator( cop = layout.operator(
self.bl_idname, text = item.name, self.bl_idname, text = item.name,
icon_value = UTIL_icons_manager.get_element_icon(item.name)) icon_value = UTIL_icons_manager.get_element_icon(item.name))
cop.element_type = str(item.value) cop.component_type = EnumPropHelper.to_selection(item)
def register(): def register():
# register all classes # register all classes

View File

@ -5,16 +5,13 @@ from . import UTIL_naming_convension
class RawPreferences(): class RawPreferences():
cBallanceTextureFolder: typing.ClassVar[str] = "" cBallanceTextureFolder: typing.ClassVar[str] = ""
cNoComponentCollection: typing.ClassVar[str] = "" cNoComponentCollection: typing.ClassVar[str] = ""
cDefaultNamingConvention: typing.ClassVar[UTIL_naming_convension.NamingConvention] = UTIL_naming_convension._EnumPropHelper.get_default_naming_identifier()
mBallanceTextureFolder: str mBallanceTextureFolder: str
mNoComponentCollection: str mNoComponentCollection: str
mDefaultNamingConvention: UTIL_naming_convension.NamingConvention
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.mBallanceTextureFolder = kwargs.get("mBallanceTextureFolder", "") self.mBallanceTextureFolder = kwargs.get("mBallanceTextureFolder", "")
self.mNoComponentCollection = kwargs.get("mNoComponentCollection", "") self.mNoComponentCollection = kwargs.get("mNoComponentCollection", "")
self.mDefaultNamingConvention = kwargs.get('mDefaultNamingConvention', UTIL_naming_convension._EnumPropHelper.get_default_naming_identifier())
def has_valid_blc_tex_folder(self) -> bool: def has_valid_blc_tex_folder(self) -> bool:
return os.path.isdir(self.mBallanceTextureFolder) return os.path.isdir(self.mBallanceTextureFolder)
@ -35,13 +32,6 @@ class BBPPreferences(bpy.types.AddonPreferences):
default = RawPreferences.cNoComponentCollection, default = RawPreferences.cNoComponentCollection,
) )
default_naming_convention: bpy.props.EnumProperty(
name = "Default Naming Convention",
description = "The default naming convention when creating objects, import and export BM files.",
items = UTIL_naming_convension._EnumPropHelper.generate_items(),
default = UTIL_naming_convension._EnumPropHelper.to_selection(RawPreferences.cDefaultNamingConvention),
)
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
@ -51,8 +41,6 @@ class BBPPreferences(bpy.types.AddonPreferences):
col.prop(self, "ballance_texture_folder", text = "") col.prop(self, "ballance_texture_folder", text = "")
col.label(text = "No Component Collection") col.label(text = "No Component Collection")
col.prop(self, "no_component_collection", text = "") col.prop(self, "no_component_collection", text = "")
col.label(text = "Default Naming Convention")
col.prop(self, "default_naming_convention", text = "")
def get_preferences() -> BBPPreferences: def get_preferences() -> BBPPreferences:
return bpy.context.preferences.addons[__package__].preferences return bpy.context.preferences.addons[__package__].preferences
@ -63,7 +51,6 @@ def get_raw_preferences() -> RawPreferences:
rawdata.mBallanceTextureFolder = pref.ballance_texture_folder rawdata.mBallanceTextureFolder = pref.ballance_texture_folder
rawdata.mNoComponentCollection = pref.no_component_collection rawdata.mNoComponentCollection = pref.no_component_collection
rawdata.mDefaultNamingConvention = UTIL_naming_convension._EnumPropHelper.get_selection(pref.default_naming_convention)
return rawdata return rawdata

View File

@ -651,9 +651,13 @@ class BBP_OT_direct_set_virtools_texture(bpy.types.Operator, UTIL_file_browser.I
if try_filepath is None: if try_filepath is None:
# load as other texture # load as other texture
tex = UTIL_ballance_texture.load_other_texture(texture_filepath) tex = UTIL_ballance_texture.load_other_texture(texture_filepath)
# set texture props
PROP_virtools_texture.set_raw_virtools_texture(tex, PROP_virtools_texture.get_nonballance_texture_preset())
else: else:
# load as ballance texture # load as ballance texture
tex = UTIL_ballance_texture.load_ballance_texture(try_filepath) tex = UTIL_ballance_texture.load_ballance_texture(try_filepath)
# set texture props
PROP_virtools_texture.set_raw_virtools_texture(tex, PROP_virtools_texture.get_ballance_texture_preset(try_filepath))
# assign texture # assign texture
rawmtl.mTexture = tex rawmtl.mTexture = tex

View File

@ -79,6 +79,121 @@ def draw_virtools_texture(img: bpy.types.Image, layout: bpy.types.UILayout):
#endregion #endregion
#region Ballance Texture Preset
_g_OpaqueBallanceTexturePreset: RawVirtoolsTexture = RawVirtoolsTexture(
mSaveOptions = UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL,
mVideoFormat = UTIL_virtools_types.VX_PIXELFORMAT._16_ARGB1555,
)
_g_TransparentBallanceTexturePreset: RawVirtoolsTexture = RawVirtoolsTexture(
mSaveOptions = UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL,
mVideoFormat = UTIL_virtools_types.VX_PIXELFORMAT._32_ARGB8888,
)
_g_NonBallanceTexturePreset: RawVirtoolsTexture = RawVirtoolsTexture(
mSaveOptions = UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_RAWDATA,
mVideoFormat = UTIL_virtools_types.VX_PIXELFORMAT._32_ARGB8888,
)
## The preset collection of all Ballance texture.
# Key is texture name and can be used as file name checking.
# Value is its preset which can be assigned.
_g_BallanceTexturePresets: dict[str, RawVirtoolsTexture] = {
# "atari.avi": _g_TransparentBallanceTexturePreset,
"atari.bmp": _g_OpaqueBallanceTexturePreset,
"Ball_LightningSphere1.bmp": _g_OpaqueBallanceTexturePreset,
"Ball_LightningSphere2.bmp": _g_OpaqueBallanceTexturePreset,
"Ball_LightningSphere3.bmp": _g_OpaqueBallanceTexturePreset,
"Ball_Paper.bmp": _g_OpaqueBallanceTexturePreset,
"Ball_Stone.bmp": _g_OpaqueBallanceTexturePreset,
"Ball_Wood.bmp": _g_OpaqueBallanceTexturePreset,
"Brick.bmp": _g_OpaqueBallanceTexturePreset,
"Button01_deselect.tga": _g_TransparentBallanceTexturePreset,
"Button01_select.tga": _g_TransparentBallanceTexturePreset,
"Button01_special.tga": _g_TransparentBallanceTexturePreset,
"Column_beige.bmp": _g_OpaqueBallanceTexturePreset,
"Column_beige_fade.tga": _g_TransparentBallanceTexturePreset,
"Column_blue.bmp": _g_OpaqueBallanceTexturePreset,
"Cursor.tga": _g_TransparentBallanceTexturePreset,
"Dome.bmp": _g_OpaqueBallanceTexturePreset,
"DomeEnvironment.bmp": _g_OpaqueBallanceTexturePreset,
"DomeShadow.tga": _g_TransparentBallanceTexturePreset,
"ExtraBall.bmp": _g_OpaqueBallanceTexturePreset,
"ExtraParticle.bmp": _g_OpaqueBallanceTexturePreset,
"E_Holzbeschlag.bmp": _g_OpaqueBallanceTexturePreset,
"FloorGlow.bmp": _g_OpaqueBallanceTexturePreset,
"Floor_Side.bmp": _g_OpaqueBallanceTexturePreset,
"Floor_Top_Border.bmp": _g_OpaqueBallanceTexturePreset,
"Floor_Top_Borderless.bmp": _g_OpaqueBallanceTexturePreset,
"Floor_Top_Checkpoint.bmp": _g_OpaqueBallanceTexturePreset,
"Floor_Top_Flat.bmp": _g_OpaqueBallanceTexturePreset,
"Floor_Top_Profil.bmp": _g_OpaqueBallanceTexturePreset,
"Floor_Top_ProfilFlat.bmp": _g_OpaqueBallanceTexturePreset,
"Font_1.tga": _g_TransparentBallanceTexturePreset,
"Gravitylogo_intro.bmp": _g_OpaqueBallanceTexturePreset,
"HardShadow.bmp": _g_OpaqueBallanceTexturePreset,
"Laterne_Glas.bmp": _g_OpaqueBallanceTexturePreset,
"Laterne_Schatten.tga": _g_TransparentBallanceTexturePreset,
"Laterne_Verlauf.tga": _g_TransparentBallanceTexturePreset,
"Logo.bmp": _g_OpaqueBallanceTexturePreset,
"Metal_stained.bmp": _g_OpaqueBallanceTexturePreset,
"Misc_Ufo.bmp": _g_OpaqueBallanceTexturePreset,
"Misc_UFO_Flash.bmp": _g_OpaqueBallanceTexturePreset,
"Modul03_Floor.bmp": _g_OpaqueBallanceTexturePreset,
"Modul03_Wall.bmp": _g_OpaqueBallanceTexturePreset,
"Modul11_13_Wood.bmp": _g_OpaqueBallanceTexturePreset,
"Modul11_Wood.bmp": _g_OpaqueBallanceTexturePreset,
"Modul15.bmp": _g_OpaqueBallanceTexturePreset,
"Modul16.bmp": _g_OpaqueBallanceTexturePreset,
"Modul18.bmp": _g_OpaqueBallanceTexturePreset,
"Modul18_Gitter.tga": _g_TransparentBallanceTexturePreset,
"Modul30_d_Seiten.bmp": _g_OpaqueBallanceTexturePreset,
"Particle_Flames.bmp": _g_OpaqueBallanceTexturePreset,
"Particle_Smoke.bmp": _g_OpaqueBallanceTexturePreset,
"PE_Bal_balloons.bmp": _g_OpaqueBallanceTexturePreset,
"PE_Bal_platform.bmp": _g_OpaqueBallanceTexturePreset,
"PE_Ufo_env.bmp": _g_OpaqueBallanceTexturePreset,
"Pfeil.tga": _g_TransparentBallanceTexturePreset,
"P_Extra_Life_Oil.bmp": _g_OpaqueBallanceTexturePreset,
"P_Extra_Life_Particle.bmp": _g_OpaqueBallanceTexturePreset,
"P_Extra_Life_Shadow.bmp": _g_OpaqueBallanceTexturePreset,
"Rail_Environment.bmp": _g_OpaqueBallanceTexturePreset,
"sandsack.bmp": _g_OpaqueBallanceTexturePreset,
"SkyLayer.bmp": _g_OpaqueBallanceTexturePreset,
"Sky_Vortex.bmp": _g_OpaqueBallanceTexturePreset,
"Stick_Bottom.tga": _g_TransparentBallanceTexturePreset,
"Stick_Stripes.bmp": _g_OpaqueBallanceTexturePreset,
"Target.bmp": _g_OpaqueBallanceTexturePreset,
"Tower_Roof.bmp": _g_OpaqueBallanceTexturePreset,
"Trafo_Environment.bmp": _g_OpaqueBallanceTexturePreset,
"Trafo_FlashField.bmp": _g_OpaqueBallanceTexturePreset,
"Trafo_Shadow_Big.tga": _g_TransparentBallanceTexturePreset,
"Tut_Pfeil01.tga": _g_TransparentBallanceTexturePreset,
"Tut_Pfeil_Hoch.tga": _g_TransparentBallanceTexturePreset,
"Wolken_intro.tga": _g_TransparentBallanceTexturePreset,
"Wood_Metal.bmp": _g_OpaqueBallanceTexturePreset,
"Wood_MetalStripes.bmp": _g_OpaqueBallanceTexturePreset,
"Wood_Misc.bmp": _g_OpaqueBallanceTexturePreset,
"Wood_Nailed.bmp": _g_OpaqueBallanceTexturePreset,
"Wood_Old.bmp": _g_OpaqueBallanceTexturePreset,
"Wood_Panel.bmp": _g_OpaqueBallanceTexturePreset,
"Wood_Plain.bmp": _g_OpaqueBallanceTexturePreset,
"Wood_Plain2.bmp": _g_OpaqueBallanceTexturePreset,
"Wood_Raft.bmp": _g_OpaqueBallanceTexturePreset,
}
def get_ballance_texture_preset(texname: str) -> RawVirtoolsTexture:
try_preset: RawVirtoolsTexture | None = _g_BallanceTexturePresets.get(texname, None)
if try_preset is None:
# fallback to non-ballance one
try_preset = _g_NonBallanceTexturePreset
return try_preset
def get_nonballance_texture_preset() -> RawVirtoolsTexture:
return _g_NonBallanceTexturePreset
#endregion
def register(): def register():
bpy.utils.register_class(BBP_PG_virtools_texture) bpy.utils.register_class(BBP_PG_virtools_texture)

View File

@ -1,7 +1,7 @@
import bpy, bpy_extras import bpy, bpy_extras
import typing, os import typing, os
from . import PROP_preferences, PROP_virtools_texture from . import PROP_preferences
from . import UTIL_virtools_types, UTIL_functions from . import UTIL_functions
## Ballance Texture Usage ## Ballance Texture Usage
# The aim of this module is to make sure every Ballance texture only have 1 instance in Blender as much as we can # The aim of this module is to make sure every Ballance texture only have 1 instance in Blender as much as we can
@ -56,6 +56,90 @@ from . import UTIL_virtools_types, UTIL_functions
#region Ballance Texture Assist Functions #region Ballance Texture Assist Functions
_g_BallanceTextureFileNames: set[str] = set((
# "atari.avi",
"atari.bmp",
"Ball_LightningSphere1.bmp",
"Ball_LightningSphere2.bmp",
"Ball_LightningSphere3.bmp",
"Ball_Paper.bmp",
"Ball_Stone.bmp",
"Ball_Wood.bmp",
"Brick.bmp",
"Button01_deselect.tga",
"Button01_select.tga",
"Button01_special.tga",
"Column_beige.bmp",
"Column_beige_fade.tga",
"Column_blue.bmp",
"Cursor.tga",
"Dome.bmp",
"DomeEnvironment.bmp",
"DomeShadow.tga",
"ExtraBall.bmp",
"ExtraParticle.bmp",
"E_Holzbeschlag.bmp",
"FloorGlow.bmp",
"Floor_Side.bmp",
"Floor_Top_Border.bmp",
"Floor_Top_Borderless.bmp",
"Floor_Top_Checkpoint.bmp",
"Floor_Top_Flat.bmp",
"Floor_Top_Profil.bmp",
"Floor_Top_ProfilFlat.bmp",
"Font_1.tga",
"Gravitylogo_intro.bmp",
"HardShadow.bmp",
"Laterne_Glas.bmp",
"Laterne_Schatten.tga",
"Laterne_Verlauf.tga",
"Logo.bmp",
"Metal_stained.bmp",
"Misc_Ufo.bmp",
"Misc_UFO_Flash.bmp",
"Modul03_Floor.bmp",
"Modul03_Wall.bmp",
"Modul11_13_Wood.bmp",
"Modul11_Wood.bmp",
"Modul15.bmp",
"Modul16.bmp",
"Modul18.bmp",
"Modul18_Gitter.tga",
"Modul30_d_Seiten.bmp",
"Particle_Flames.bmp",
"Particle_Smoke.bmp",
"PE_Bal_balloons.bmp",
"PE_Bal_platform.bmp",
"PE_Ufo_env.bmp",
"Pfeil.tga",
"P_Extra_Life_Oil.bmp",
"P_Extra_Life_Particle.bmp",
"P_Extra_Life_Shadow.bmp",
"Rail_Environment.bmp",
"sandsack.bmp",
"SkyLayer.bmp",
"Sky_Vortex.bmp",
"Stick_Bottom.tga",
"Stick_Stripes.bmp",
"Target.bmp",
"Tower_Roof.bmp",
"Trafo_Environment.bmp",
"Trafo_FlashField.bmp",
"Trafo_Shadow_Big.tga",
"Tut_Pfeil01.tga",
"Tut_Pfeil_Hoch.tga",
"Wolken_intro.tga",
"Wood_Metal.bmp",
"Wood_MetalStripes.bmp",
"Wood_Misc.bmp",
"Wood_Nailed.bmp",
"Wood_Old.bmp",
"Wood_Panel.bmp",
"Wood_Plain.bmp",
"Wood_Plain2.bmp",
"Wood_Raft.bmp",
))
def _get_ballance_texture_folder() -> str: def _get_ballance_texture_folder() -> str:
"""! """!
Get Ballance texture folder from preferences. Get Ballance texture folder from preferences.
@ -88,106 +172,6 @@ def _is_path_equal(path1: str, path2: str) -> bool:
#region Ballance Texture Detect Functions #region Ballance Texture Detect Functions
g_OpaqueBallanceTexturePreset: PROP_virtools_texture.RawVirtoolsTexture = PROP_virtools_texture.RawVirtoolsTexture(
mSaveOptions = UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL,
mVideoFormat = UTIL_virtools_types.VX_PIXELFORMAT._16_ARGB1555,
)
g_TransparentBallanceTexturePreset: PROP_virtools_texture.RawVirtoolsTexture = PROP_virtools_texture.RawVirtoolsTexture(
mSaveOptions = UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL,
mVideoFormat = UTIL_virtools_types.VX_PIXELFORMAT._32_ARGB8888,
)
g_NonBallanceTexturePreset: PROP_virtools_texture.RawVirtoolsTexture = PROP_virtools_texture.RawVirtoolsTexture(
mSaveOptions = UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_RAWDATA,
mVideoFormat = UTIL_virtools_types.VX_PIXELFORMAT._32_ARGB8888,
)
## The preset collection of all Ballance texture.
# Key is texture name and can be used as file name checking.
# Value is its preset which can be assigned.
g_BallanceTexturePresets: dict[str, PROP_virtools_texture.RawVirtoolsTexture] = {
# "atari.avi": g_TransparentBallanceTexturePreset,
"atari.bmp": g_OpaqueBallanceTexturePreset,
"Ball_LightningSphere1.bmp": g_OpaqueBallanceTexturePreset,
"Ball_LightningSphere2.bmp": g_OpaqueBallanceTexturePreset,
"Ball_LightningSphere3.bmp": g_OpaqueBallanceTexturePreset,
"Ball_Paper.bmp": g_OpaqueBallanceTexturePreset,
"Ball_Stone.bmp": g_OpaqueBallanceTexturePreset,
"Ball_Wood.bmp": g_OpaqueBallanceTexturePreset,
"Brick.bmp": g_OpaqueBallanceTexturePreset,
"Button01_deselect.tga": g_TransparentBallanceTexturePreset,
"Button01_select.tga": g_TransparentBallanceTexturePreset,
"Button01_special.tga": g_TransparentBallanceTexturePreset,
"Column_beige.bmp": g_OpaqueBallanceTexturePreset,
"Column_beige_fade.tga": g_TransparentBallanceTexturePreset,
"Column_blue.bmp": g_OpaqueBallanceTexturePreset,
"Cursor.tga": g_TransparentBallanceTexturePreset,
"Dome.bmp": g_OpaqueBallanceTexturePreset,
"DomeEnvironment.bmp": g_OpaqueBallanceTexturePreset,
"DomeShadow.tga": g_TransparentBallanceTexturePreset,
"ExtraBall.bmp": g_OpaqueBallanceTexturePreset,
"ExtraParticle.bmp": g_OpaqueBallanceTexturePreset,
"E_Holzbeschlag.bmp": g_OpaqueBallanceTexturePreset,
"FloorGlow.bmp": g_OpaqueBallanceTexturePreset,
"Floor_Side.bmp": g_OpaqueBallanceTexturePreset,
"Floor_Top_Border.bmp": g_OpaqueBallanceTexturePreset,
"Floor_Top_Borderless.bmp": g_OpaqueBallanceTexturePreset,
"Floor_Top_Checkpoint.bmp": g_OpaqueBallanceTexturePreset,
"Floor_Top_Flat.bmp": g_OpaqueBallanceTexturePreset,
"Floor_Top_Profil.bmp": g_OpaqueBallanceTexturePreset,
"Floor_Top_ProfilFlat.bmp": g_OpaqueBallanceTexturePreset,
"Font_1.tga": g_TransparentBallanceTexturePreset,
"Gravitylogo_intro.bmp": g_OpaqueBallanceTexturePreset,
"HardShadow.bmp": g_OpaqueBallanceTexturePreset,
"Laterne_Glas.bmp": g_OpaqueBallanceTexturePreset,
"Laterne_Schatten.tga": g_TransparentBallanceTexturePreset,
"Laterne_Verlauf.tga": g_TransparentBallanceTexturePreset,
"Logo.bmp": g_OpaqueBallanceTexturePreset,
"Metal_stained.bmp": g_OpaqueBallanceTexturePreset,
"Misc_Ufo.bmp": g_OpaqueBallanceTexturePreset,
"Misc_UFO_Flash.bmp": g_OpaqueBallanceTexturePreset,
"Modul03_Floor.bmp": g_OpaqueBallanceTexturePreset,
"Modul03_Wall.bmp": g_OpaqueBallanceTexturePreset,
"Modul11_13_Wood.bmp": g_OpaqueBallanceTexturePreset,
"Modul11_Wood.bmp": g_OpaqueBallanceTexturePreset,
"Modul15.bmp": g_OpaqueBallanceTexturePreset,
"Modul16.bmp": g_OpaqueBallanceTexturePreset,
"Modul18.bmp": g_OpaqueBallanceTexturePreset,
"Modul18_Gitter.tga": g_TransparentBallanceTexturePreset,
"Modul30_d_Seiten.bmp": g_OpaqueBallanceTexturePreset,
"Particle_Flames.bmp": g_OpaqueBallanceTexturePreset,
"Particle_Smoke.bmp": g_OpaqueBallanceTexturePreset,
"PE_Bal_balloons.bmp": g_OpaqueBallanceTexturePreset,
"PE_Bal_platform.bmp": g_OpaqueBallanceTexturePreset,
"PE_Ufo_env.bmp": g_OpaqueBallanceTexturePreset,
"Pfeil.tga": g_TransparentBallanceTexturePreset,
"P_Extra_Life_Oil.bmp": g_OpaqueBallanceTexturePreset,
"P_Extra_Life_Particle.bmp": g_OpaqueBallanceTexturePreset,
"P_Extra_Life_Shadow.bmp": g_OpaqueBallanceTexturePreset,
"Rail_Environment.bmp": g_OpaqueBallanceTexturePreset,
"sandsack.bmp": g_OpaqueBallanceTexturePreset,
"SkyLayer.bmp": g_OpaqueBallanceTexturePreset,
"Sky_Vortex.bmp": g_OpaqueBallanceTexturePreset,
"Stick_Bottom.tga": g_TransparentBallanceTexturePreset,
"Stick_Stripes.bmp": g_OpaqueBallanceTexturePreset,
"Target.bmp": g_OpaqueBallanceTexturePreset,
"Tower_Roof.bmp": g_OpaqueBallanceTexturePreset,
"Trafo_Environment.bmp": g_OpaqueBallanceTexturePreset,
"Trafo_FlashField.bmp": g_OpaqueBallanceTexturePreset,
"Trafo_Shadow_Big.tga": g_TransparentBallanceTexturePreset,
"Tut_Pfeil01.tga": g_TransparentBallanceTexturePreset,
"Tut_Pfeil_Hoch.tga": g_TransparentBallanceTexturePreset,
"Wolken_intro.tga": g_TransparentBallanceTexturePreset,
"Wood_Metal.bmp": g_OpaqueBallanceTexturePreset,
"Wood_MetalStripes.bmp": g_OpaqueBallanceTexturePreset,
"Wood_Misc.bmp": g_OpaqueBallanceTexturePreset,
"Wood_Nailed.bmp": g_OpaqueBallanceTexturePreset,
"Wood_Old.bmp": g_OpaqueBallanceTexturePreset,
"Wood_Panel.bmp": g_OpaqueBallanceTexturePreset,
"Wood_Plain.bmp": g_OpaqueBallanceTexturePreset,
"Wood_Plain2.bmp": g_OpaqueBallanceTexturePreset,
"Wood_Raft.bmp": g_OpaqueBallanceTexturePreset,
}
def get_ballance_texture_filename(texpath: str) -> str | None: def get_ballance_texture_filename(texpath: str) -> str | None:
"""! """!
Return the filename part for valid Ballance texture path. Return the filename part for valid Ballance texture path.
@ -203,7 +187,7 @@ def get_ballance_texture_filename(texpath: str) -> str | None:
# check file name first # check file name first
filename: str = os.path.basename(texpath) filename: str = os.path.basename(texpath)
if filename not in g_BallanceTexturePresets: return None if filename not in _g_BallanceTextureFileNames: return None
# if file name matched, check whether it located in ballance texture folder # if file name matched, check whether it located in ballance texture folder
probe: str = os.path.join(_get_ballance_texture_folder(), filename) probe: str = os.path.join(_get_ballance_texture_folder(), filename)
@ -284,9 +268,8 @@ def load_ballance_texture(texname: str) -> bpy.types.Image:
@return The loaded image. @return The loaded image.
""" """
# try getting preset (also check texture name) # check texture name
tex_preset: PROP_virtools_texture.RawVirtoolsTexture | None = g_BallanceTexturePresets.get(texname, None) if texname not in _g_BallanceTextureFileNames:
if tex_preset is None:
raise UTIL_functions.BBPException("Invalid Ballance texture file name.") raise UTIL_functions.BBPException("Invalid Ballance texture file name.")
# load image # load image
@ -294,8 +277,6 @@ def load_ballance_texture(texname: str) -> bpy.types.Image:
filepath: str = os.path.join(_get_ballance_texture_folder(), texname) filepath: str = os.path.join(_get_ballance_texture_folder(), texname)
ret: bpy.types.Image = bpy.data.images.load(filepath, check_existing = True) ret: bpy.types.Image = bpy.data.images.load(filepath, check_existing = True)
# apply preset and return
PROP_virtools_texture.set_raw_virtools_texture(ret, tex_preset)
return ret return ret
def load_other_texture(texname: str) -> bpy.types.Image: def load_other_texture(texname: str) -> bpy.types.Image:
@ -321,8 +302,6 @@ def load_other_texture(texname: str) -> bpy.types.Image:
# then immediately pack it into file. # then immediately pack it into file.
ret.pack() ret.pack()
# apply general non-ballance texture preset and return image
PROP_virtools_texture.set_raw_virtools_texture(ret, g_NonBallanceTexturePreset)
return ret return ret
def generate_other_texture_save_path(tex: bpy.types.Image, file_folder: str) -> str: def generate_other_texture_save_path(tex: bpy.types.Image, file_folder: str) -> str:

View File

@ -1,6 +1,6 @@
import bpy import bpy
import typing, enum, re import typing, enum, re
from . import UTIL_functions, UTIL_icons_manager from . import UTIL_functions
from . import PROP_virtools_group from . import PROP_virtools_group
#region Rename Error Reporter #region Rename Error Reporter
@ -18,28 +18,85 @@ class _RenameErrorItem():
self.mErrType = err_t self.mErrType = err_t
self.mDescription = description self.mDescription = description
class _RenameErrorReporter(): class RenameErrorReporter():
"""
A basic 'rename error report' using simple prints in console.
This object can be used as a context manager.
It supports multiple levels of 'substeps' - you shall always enter at least one substep (because level 0
has only one single step, representing the whole 'area' of the progress stuff).
You should give the object renaming of substeps each time you enter a new one.
Leaving a substep automatically steps by one the parent level.
```
with RenameErrorReporter() as reporter:
progress.enter_object(obj)
# process for object with reporter
reporter.add_error('fork!')
progress.leave_object()
```
"""
mAllObjCounter: int
mFailedObjCounter: int
mErrList: list[_RenameErrorItem] mErrList: list[_RenameErrorItem]
mOldName: str mOldName: str
mHasError: bool
def __init__(self): def __init__(self):
self.mAllObjCounter = 0
self.mFailedObjCounter = 0
self.mErrList = [] self.mErrList = []
self.mOldName = "" self.mOldName = ""
self.mHasError = False
def add_error(self, description: str): def add_error(self, description: str):
self.mHasError = True
self.mErrList.append(_RenameErrorItem(_RenameErrorType.ERROR, description)) self.mErrList.append(_RenameErrorItem(_RenameErrorType.ERROR, description))
def add_warning(self, description: str): def add_warning(self, description: str):
self.mErrList.append(_RenameErrorItem(_RenameErrorType.WARNING, description)) self.mErrList.append(_RenameErrorItem(_RenameErrorType.WARNING, description))
def add_info(self, description: str): def add_info(self, description: str):
self.mErrList.append(_RenameErrorItem(_RenameErrorType.INFO, description)) self.mErrList.append(_RenameErrorItem(_RenameErrorType.INFO, description))
def begin_object(self, obj: bpy.types.Object) -> None: def get_all_objs_count(self) -> int: return self.mAllObjCounter
def get_failed_objs_count(self) -> int: return self.mFailedObjCounter
def __enter__(self):
# print console report header
print('============')
print('Rename Report')
print('------------')
# return self as context
return self
def __exit__(self, exc_type, exc_value, traceback):
# print console report tail
print('------------')
print(f'All / Failed - {self.mAllObjCounter} / {self.mFailedObjCounter}')
print('============')
# reset variables
self.mAllObjCounter = 0
self.mFailedObjCounter = 0
def enter_object(self, obj: bpy.types.Object) -> None:
# inc all counter
self.mAllObjCounter += 1
# assign old name # assign old name
self.mOldName = obj.name self.mOldName = obj.name
def end_object(self, obj:bpy.types.Object) -> None: def leave_object(self, obj:bpy.types.Object) -> None:
# if error list is empty, no need to report # if error list is empty, no need to report
if len(self.mErrList) == 0: return if len(self.mErrList) == 0: return
# inc failed if necessary
if self.mHasError:
self.mFailedObjCounter += 1
# output header # output header
# if new name is different with old name, output both of them # if new name is different with old name, output both of them
new_name: str = obj.name new_name: str = obj.name
@ -50,10 +107,11 @@ class _RenameErrorReporter():
# output error list with indent # output error list with indent
for item in self.mErrList: for item in self.mErrList:
print('\t' + _RenameErrorReporter.__erritem_to_string(item)) print('\t' + RenameErrorReporter.__erritem_to_string(item))
# clear error list for next object # clear error list for next object
self.mErrList.clear() self.mErrList.clear()
self.mHasError = False
@staticmethod @staticmethod
def __errtype_to_string(err_v: _RenameErrorType) -> str: def __errtype_to_string(err_v: _RenameErrorType) -> str:
@ -64,7 +122,7 @@ class _RenameErrorReporter():
case _: raise UTIL_functions.BBPException("Unknown error type.") case _: raise UTIL_functions.BBPException("Unknown error type.")
@staticmethod @staticmethod
def __erritem_to_string(item: _RenameErrorItem) -> str: def __erritem_to_string(item: _RenameErrorItem) -> str:
return f'[{_RenameErrorReporter.__errtype_to_string(item.mErrType)}]\t{item.mDescription}' return f'[{RenameErrorReporter.__errtype_to_string(item.mErrType)}]\t{item.mDescription}'
#endregion #endregion
@ -124,21 +182,6 @@ class BallanceObjectInfo():
def create_from_others(cls, basic_type: BallanceObjectType): def create_from_others(cls, basic_type: BallanceObjectType):
return cls(basic_type) return cls(basic_type)
class _NamingConventionProfile():
_TParseFct = typing.Callable[[bpy.types.Object, _RenameErrorReporter | None], BallanceObjectInfo | None]
_TSetFct = typing.Callable[[bpy.types.Object,BallanceObjectInfo, _RenameErrorReporter | None], bool]
mName: str
mDesc: str
mParseFct: _TParseFct
mSetFct: _TSetFct
def __init__(self, name: str, desc: str, parse_fct: _TParseFct, set_fct: _TSetFct):
self.mName = name
self.mDesc = desc
self.mParseFct = parse_fct
self.mSetFct = set_fct
#endregion #endregion
#region Naming Convention Declaration #region Naming Convention Declaration
@ -183,20 +226,20 @@ _g_BlcWood: set[str] = set((
PROP_virtools_group.VirtoolsGroupsPreset.Sound_RollID_02.value, PROP_virtools_group.VirtoolsGroupsPreset.Sound_RollID_02.value,
)) ))
class _VirtoolsGroupConvention(): class VirtoolsGroupConvention():
cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$') cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + ')_(0[1-9]|[1-9][0-9])_.*$') cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + ')_(0[1-9]|[1-9][0-9])_.*$')
cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_TwoFlames_(0[1-7])$') cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_TwoFlames_(0[1-7])$')
cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_Resetpoint_(0[1-8])$') cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_Resetpoint_(0[1-8])$')
@staticmethod @staticmethod
def __get_pcpr_from_name(name: str, reporter: _RenameErrorReporter | None) -> BallanceObjectInfo | None: def __get_pcpr_from_name(name: str, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
regex_result = _VirtoolsGroupConvention.cRegexPC.match(name) regex_result = VirtoolsGroupConvention.cRegexPC.match(name)
if regex_result is not None: if regex_result is not None:
return BallanceObjectInfo.create_from_checkpoint( return BallanceObjectInfo.create_from_checkpoint(
int(regex_result.group(1)) int(regex_result.group(1))
) )
regex_result = _VirtoolsGroupConvention.cRegexPR.match(name) regex_result = VirtoolsGroupConvention.cRegexPR.match(name)
if regex_result is not None: if regex_result is not None:
return BallanceObjectInfo.create_from_resetpoint( return BallanceObjectInfo.create_from_resetpoint(
int(regex_result.group(1)) int(regex_result.group(1))
@ -212,7 +255,7 @@ class _VirtoolsGroupConvention():
counter: int = 0 counter: int = 0
last_matched_sector: int = 0 last_matched_sector: int = 0
for i in gps: for i in gps:
regex_result = _VirtoolsGroupConvention.cRegexGroupSector.match(i) regex_result = VirtoolsGroupConvention.cRegexGroupSector.match(i)
if regex_result is not None: if regex_result is not None:
last_matched_sector = int(regex_result.group(1)) last_matched_sector = int(regex_result.group(1))
counter += 1 counter += 1
@ -220,9 +263,8 @@ class _VirtoolsGroupConvention():
if counter != 1: return None if counter != 1: return None
else: return last_matched_sector else: return last_matched_sector
@staticmethod @staticmethod
def parse_from_object(obj: bpy.types.Object, reporter: _RenameErrorReporter | None) -> BallanceObjectInfo | None: def parse_from_object(obj: bpy.types.Object, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
# create visitor # create visitor
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp: with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
# if no group, we should consider it is decoration or skylayer # if no group, we should consider it is decoration or skylayer
@ -241,7 +283,7 @@ class _VirtoolsGroupConvention():
return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_END) return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_END)
case PROP_virtools_group.VirtoolsGroupsPreset.PC_Checkpoints.value | PROP_virtools_group.VirtoolsGroupsPreset.PR_Resetpoints.value: case PROP_virtools_group.VirtoolsGroupsPreset.PC_Checkpoints.value | PROP_virtools_group.VirtoolsGroupsPreset.PR_Resetpoints.value:
# these type's data should be gotten from its name # these type's data should be gotten from its name
return _VirtoolsGroupConvention.__get_pcpr_from_name(obj.name, reporter) return VirtoolsGroupConvention.__get_pcpr_from_name(obj.name, reporter)
case _: case _:
if reporter: reporter.add_error("The match of Unique Component lost.") if reporter: reporter.add_error("The match of Unique Component lost.")
return None return None
@ -255,7 +297,7 @@ class _VirtoolsGroupConvention():
# get it # get it
# now try get its sector # now try get its sector
gotten_elements: str = (tuple(inter_gps))[0] gotten_elements: str = (tuple(inter_gps))[0]
gotten_sector: int | None = _VirtoolsGroupConvention.__get_sector_from_groups(gp.iterate_groups()) gotten_sector: int | None = VirtoolsGroupConvention.__get_sector_from_groups(gp.iterate_groups())
if gotten_sector is None: if gotten_sector is None:
# fail to get sector # fail to get sector
if reporter: reporter.add_error("Component detected. But couldn't get sector from CKGroup data.") if reporter: reporter.add_error("Component detected. But couldn't get sector from CKGroup data.")
@ -294,7 +336,7 @@ class _VirtoolsGroupConvention():
return None return None
@staticmethod @staticmethod
def set_to_object(obj: bpy.types.Object, info: BallanceObjectInfo, reporter: _RenameErrorReporter | None) -> bool: def set_to_object(obj: bpy.types.Object, info: BallanceObjectInfo, reporter: RenameErrorReporter | None) -> bool:
# create visitor # create visitor
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp: with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
# match by basic type # match by basic type
@ -346,20 +388,11 @@ class _VirtoolsGroupConvention():
return True return True
class YYCToolchainConvention():
@staticmethod @staticmethod
def register() -> _NamingConventionProfile: def parse_from_name(name: str, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
return _NamingConventionProfile(
'Virtools Group',
'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:
# check component first # check component first
regex_result = _VirtoolsGroupConvention.cRegexComponent.match(obj.name) regex_result = VirtoolsGroupConvention.cRegexComponent.match(name) # use vt one because they are same
if regex_result is not None: if regex_result is not None:
return BallanceObjectInfo.create_from_component( return BallanceObjectInfo.create_from_component(
regex_result.group(1), regex_result.group(1),
@ -367,103 +400,105 @@ class _YYCToolchainConvention():
) )
# check PC PR elements # check PC PR elements
regex_result = _VirtoolsGroupConvention.cRegexPC.match(obj.name) regex_result = VirtoolsGroupConvention.cRegexPC.match(name) # use vt one because they are same
if regex_result is not None: if regex_result is not None:
return BallanceObjectInfo.create_from_checkpoint( return BallanceObjectInfo.create_from_checkpoint(
int(regex_result.group(1)) int(regex_result.group(1))
) )
regex_result = _VirtoolsGroupConvention.cRegexPR.match(obj.name) regex_result = VirtoolsGroupConvention.cRegexPR.match(name) # use vt one because they are same
if regex_result is not None: if regex_result is not None:
return BallanceObjectInfo.create_from_resetpoint( return BallanceObjectInfo.create_from_resetpoint(
int(regex_result.group(1)) int(regex_result.group(1))
) )
# check other unique elements # check other unique elements
if obj.name == "PS_FourFlames_01": if name == "PS_FourFlames_01":
return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_START) return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_START)
if obj.name == "PE_Balloon_01": if name == "PE_Balloon_01":
return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_END) return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_END)
# process floors # process floors
if obj.name.startswith("A_Floor"): if name.startswith("A_Floor"):
return BallanceObjectInfo.create_from_others(BallanceObjectType.FLOOR) return BallanceObjectInfo.create_from_others(BallanceObjectType.FLOOR)
if obj.name.startswith("A_Rail"): if name.startswith("A_Rail"):
return BallanceObjectInfo.create_from_others(BallanceObjectType.RAIL) return BallanceObjectInfo.create_from_others(BallanceObjectType.RAIL)
if obj.name.startswith("A_Wood"): if name.startswith("A_Wood"):
return BallanceObjectInfo.create_from_others(BallanceObjectType.WOOD) return BallanceObjectInfo.create_from_others(BallanceObjectType.WOOD)
if obj.name.startswith("A_Stopper"): if name.startswith("A_Stopper"):
return BallanceObjectInfo.create_from_others(BallanceObjectType.STOPPER) return BallanceObjectInfo.create_from_others(BallanceObjectType.STOPPER)
# process others # process others
if obj.name.startswith("DepthCubes"): if name.startswith("DepthCubes"):
return BallanceObjectInfo.create_from_others(BallanceObjectType.DEPTH_CUBE) return BallanceObjectInfo.create_from_others(BallanceObjectType.DEPTH_CUBE)
if obj.name.startswith("D_"): if name.startswith("D_"):
return BallanceObjectInfo.create_from_others(BallanceObjectType.DECORATION) return BallanceObjectInfo.create_from_others(BallanceObjectType.DECORATION)
if obj.name == 'SkyLayer': if name == 'SkyLayer':
return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER) return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER)
if reporter is not None: if reporter is not None:
reporter.add_error("Name match lost.") reporter.add_error("Name match lost.")
return None return None
@staticmethod @staticmethod
def set_to_object(obj: bpy.types.Object, info: BallanceObjectInfo, reporter: _RenameErrorReporter | None) -> bool: def parse_from_object(obj: bpy.types.Object, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
return YYCToolchainConvention.parse_from_name(obj.name, reporter)
@staticmethod
def set_to_name(info: BallanceObjectInfo, reporter: RenameErrorReporter | None) -> str | None:
match(info.mBasicType): match(info.mBasicType):
case BallanceObjectType.DECORATION: case BallanceObjectType.DECORATION:
obj.name = 'D_' return 'D_'
case BallanceObjectType.SKYLAYER: case BallanceObjectType.SKYLAYER:
obj.name = 'SkyLayer' return 'SkyLayer'
case BallanceObjectType.LEVEL_START: case BallanceObjectType.LEVEL_START:
obj.name = 'PS_FourFlames_01' return 'PS_FourFlames_01'
case BallanceObjectType.LEVEL_END: case BallanceObjectType.LEVEL_END:
obj.name = 'PE_Balloon_01' return 'PE_Balloon_01'
case BallanceObjectType.CHECKPOINT: case BallanceObjectType.CHECKPOINT:
obj.name = f'PR_Resetpoint_{info.mSector:0>2d}' return f'PR_Resetpoint_{info.mSector:0>2d}'
case BallanceObjectType.RESETPOINT: case BallanceObjectType.RESETPOINT:
obj.name = f'PC_TwoFlames_{info.mSector:0>2d}' return f'PC_TwoFlames_{info.mSector:0>2d}'
case BallanceObjectType.DEPTH_CUBE: case BallanceObjectType.DEPTH_CUBE:
obj.name = 'DepthCubes_' return 'DepthCubes_'
case BallanceObjectType.FLOOR: case BallanceObjectType.FLOOR:
obj.name = 'A_Floor_' return 'A_Floor_'
case BallanceObjectType.RAIL: case BallanceObjectType.RAIL:
obj.name = 'A_Wood_' return 'A_Wood_'
case BallanceObjectType.WOOD: case BallanceObjectType.WOOD:
obj.name = 'A_Rail_' return 'A_Rail_'
case BallanceObjectType.STOPPER: case BallanceObjectType.STOPPER:
obj.name = 'A_Stopper_' return 'A_Stopper_'
case BallanceObjectType.COMPONENT: case BallanceObjectType.COMPONENT:
obj.name = '{}_{:0>2d}_'.format( return '{}_{:0>2d}_'.format(
info.mComponentType, info.mSector) info.mComponentType, info.mSector)
case _: case _:
if reporter is not None: if reporter is not None:
reporter.add_error('No matched info.') reporter.add_error('No matched info.')
return False return None
return True
@staticmethod @staticmethod
def register() -> _NamingConventionProfile: def set_to_object(obj: bpy.types.Object, info: BallanceObjectInfo, reporter: RenameErrorReporter | None) -> bool:
return _NamingConventionProfile( expect_name: str | None = YYCToolchainConvention.set_to_name(info, reporter)
'YYC Toolchain', if expect_name is None: return False
'YYC Toolchain name standard.',
_YYCToolchainConvention.parse_from_object,
_YYCToolchainConvention.set_to_object
)
class _ImengyuConvention(): obj.name = expect_name
return True
class ImengyuConvention():
cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + '):[^:]*:([1-9]|[1-9][0-9])$') cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + '):[^:]*:([1-9]|[1-9][0-9])$')
cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_CheckPoint:([0-9]+)$') cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_CheckPoint:([0-9]+)$')
cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_ResetPoint:([0-9]+)$') cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_ResetPoint:([0-9]+)$')
@staticmethod @staticmethod
def parse_from_object(obj: bpy.types.Object, reporter: _RenameErrorReporter | None) -> BallanceObjectInfo | None: def parse_from_name(name: str, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
# check component first # check component first
regex_result = _ImengyuConvention.cRegexComponent.match(obj.name) regex_result = ImengyuConvention.cRegexComponent.match(name)
if regex_result is not None: if regex_result is not None:
return BallanceObjectInfo.create_from_component( return BallanceObjectInfo.create_from_component(
regex_result.group(1), regex_result.group(1),
@ -471,39 +506,39 @@ class _ImengyuConvention():
) )
# check PC PR elements # check PC PR elements
regex_result = _ImengyuConvention.cRegexPC.match(obj.name) regex_result = ImengyuConvention.cRegexPC.match(name)
if regex_result is not None: if regex_result is not None:
return BallanceObjectInfo.create_from_checkpoint( return BallanceObjectInfo.create_from_checkpoint(
int(regex_result.group(1)) int(regex_result.group(1))
) )
regex_result = _ImengyuConvention.cRegexPR.match(obj.name) regex_result = ImengyuConvention.cRegexPR.match(name)
if regex_result is not None: if regex_result is not None:
return BallanceObjectInfo.create_from_resetpoint( return BallanceObjectInfo.create_from_resetpoint(
int(regex_result.group(1)) int(regex_result.group(1))
) )
# check other unique elements # check other unique elements
if obj.name == "PS_LevelStart": if name == "PS_LevelStart":
return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_START) return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_START)
if obj.name == "PE_LevelEnd": if name == "PE_LevelEnd":
return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_END) return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_END)
# process floors # process floors
if obj.name.startswith("S_Floors"): if name.startswith("S_Floors"):
return BallanceObjectInfo.create_from_others(BallanceObjectType.FLOOR) return BallanceObjectInfo.create_from_others(BallanceObjectType.FLOOR)
if obj.name.startswith("S_FloorRails"): if name.startswith("S_FloorRails"):
return BallanceObjectInfo.create_from_others(BallanceObjectType.RAIL) return BallanceObjectInfo.create_from_others(BallanceObjectType.RAIL)
if obj.name.startswith("S_FloorWoods"): if name.startswith("S_FloorWoods"):
return BallanceObjectInfo.create_from_others(BallanceObjectType.WOOD) return BallanceObjectInfo.create_from_others(BallanceObjectType.WOOD)
if obj.name.startswith("S_FloorStopper"): if name.startswith("S_FloorStopper"):
return BallanceObjectInfo.create_from_others(BallanceObjectType.STOPPER) return BallanceObjectInfo.create_from_others(BallanceObjectType.STOPPER)
# process others # process others
if obj.name.startswith("DepthTestCubes"): if name.startswith("DepthTestCubes"):
return BallanceObjectInfo.create_from_others(BallanceObjectType.DEPTH_CUBE) return BallanceObjectInfo.create_from_others(BallanceObjectType.DEPTH_CUBE)
if obj.name.startswith("O_"): if name.startswith("O_"):
return BallanceObjectInfo.create_from_others(BallanceObjectType.DECORATION) return BallanceObjectInfo.create_from_others(BallanceObjectType.DECORATION)
if obj.name == 'SkyLayer': if name == 'SkyLayer':
return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER) return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER)
if reporter is not None: if reporter is not None:
@ -511,175 +546,57 @@ class _ImengyuConvention():
return None return None
@staticmethod @staticmethod
def set_to_object(obj: bpy.types.Object, info: BallanceObjectInfo, reporter: _RenameErrorReporter | None) -> bool: def parse_from_object(obj: bpy.types.Object, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
return ImengyuConvention.parse_from_name(obj.name, reporter)
@staticmethod
def set_to_name(info: BallanceObjectInfo, oldname: str | None, reporter: RenameErrorReporter | None) -> str | None:
match(info.mBasicType): match(info.mBasicType):
case BallanceObjectType.DECORATION: case BallanceObjectType.DECORATION:
obj.name = 'O_' return 'O_'
case BallanceObjectType.SKYLAYER: case BallanceObjectType.SKYLAYER:
obj.name = 'SkyLayer' return 'SkyLayer'
case BallanceObjectType.LEVEL_START: case BallanceObjectType.LEVEL_START:
obj.name = 'PS_LevelStart' return 'PS_LevelStart'
case BallanceObjectType.LEVEL_END: case BallanceObjectType.LEVEL_END:
obj.name = 'PE_LevelEnd' return 'PE_LevelEnd'
case BallanceObjectType.CHECKPOINT: case BallanceObjectType.CHECKPOINT:
obj.name = f'PR_ResetPoint:{info.mSector:d}' return f'PR_ResetPoint:{info.mSector:d}'
case BallanceObjectType.RESETPOINT: case BallanceObjectType.RESETPOINT:
obj.name = f'PC_CheckPoint:{info.mSector:d}' return f'PC_CheckPoint:{info.mSector:d}'
case BallanceObjectType.DEPTH_CUBE: case BallanceObjectType.DEPTH_CUBE:
obj.name = 'DepthTestCubes' return 'DepthTestCubes'
case BallanceObjectType.FLOOR: case BallanceObjectType.FLOOR:
obj.name = 'S_Floors' return 'S_Floors'
case BallanceObjectType.RAIL: case BallanceObjectType.RAIL:
obj.name = 'S_FloorWoods' return 'S_FloorWoods'
case BallanceObjectType.WOOD: case BallanceObjectType.WOOD:
obj.name = 'S_FloorRails' return 'S_FloorRails'
case BallanceObjectType.STOPPER: case BallanceObjectType.STOPPER:
obj.name = 'S_FloorStopper' return 'S_FloorStopper'
case BallanceObjectType.COMPONENT: case BallanceObjectType.COMPONENT:
obj.name = '{}:{}:{:d}'.format( return '{}:{}:{:d}'.format(
info.mComponentType, obj.name.replace(':', '_'), info.mSector) info.mComponentType,
oldname.replace(':', '_') if oldname is not None else '',
info.mSector
)
case _: case _:
if reporter is not None: if reporter is not None:
reporter.add_error('No matched info.') reporter.add_error('No matched info.')
return False return None
@staticmethod
def set_to_object(obj: bpy.types.Object, info: BallanceObjectInfo, reporter: RenameErrorReporter | None) -> bool:
expect_name: str | None = ImengyuConvention.set_to_name(info, obj.name, reporter)
if expect_name is None: return False
obj.name = expect_name
return True return True
@staticmethod
def register() -> _NamingConventionProfile:
return _NamingConventionProfile(
'Imengyu Ballance',
'Auto grouping name standard for Imengyu/Ballance.',
_ImengyuConvention.parse_from_object,
_ImengyuConvention.set_to_object
)
#endregion #endregion
#region Naming 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 and assign to a enum
class NamingConvention(enum.IntEnum):
VirtoolsGroup = _register_naming_convention_with_index(_VirtoolsGroupConvention.register())
YYCToolchain = _register_naming_convention_with_index(_YYCToolchainConvention.register())
Imengyu = _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 item in NamingConvention:
if item != NamingConvention.VirtoolsGroup:
yield (item.value, _g_NamingConventions[item.value])
# token, display name, descriptions, icon, index
return tuple(
(
str(idx),
item.mName,
item.mDesc,
"",
idx
) for idx, item in naming_convention_iter()
)
@staticmethod
def get_selection(prop: str) -> NamingConvention:
return NamingConvention(int(prop))
@staticmethod
def to_selection(val: NamingConvention) -> str:
return str(val.value)
@staticmethod
def get_virtools_group_identifier() -> NamingConvention:
# 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.
return NamingConvention.VirtoolsGroup
@staticmethod
def get_default_naming_identifier() -> NamingConvention:
# The default fallback naming convention is YYC toolchain
return NamingConvention.YYCToolchain
#endregion
def name_setter_core(ident: NamingConvention, info: BallanceObjectInfo, obj: bpy.types.Object) -> None:
# get profile
profile: _NamingConventionProfile = _g_NamingConventions[ident.value]
# set name. don't care whether success.
profile.mSetFct(obj, info, None)
def name_converting_core(src_ident: NamingConvention, dst_ident: NamingConvention, 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.value]
dst: _NamingConventionProfile = _g_NamingConventions[dst_ident.value]
# 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
)