From 1d1de08bd7ffef9ef0bcec0a2aaae182f3ecec00 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Sun, 15 Oct 2023 21:13:56 +0800 Subject: [PATCH] update texture manager --- bbp_ng/PROP_ballance_texture.py | 321 ------------------------------- bbp_ng/PROP_virtools_material.py | 12 +- bbp_ng/UTIL_ballance_texture.py | 314 ++++++++++++++++++++++++++++++ bbp_ng/UTIL_file_browser.py | 7 +- bbp_ng/__init__.py | 6 +- 5 files changed, 332 insertions(+), 328 deletions(-) delete mode 100644 bbp_ng/PROP_ballance_texture.py create mode 100644 bbp_ng/UTIL_ballance_texture.py diff --git a/bbp_ng/PROP_ballance_texture.py b/bbp_ng/PROP_ballance_texture.py deleted file mode 100644 index e927605..0000000 --- a/bbp_ng/PROP_ballance_texture.py +++ /dev/null @@ -1,321 +0,0 @@ -import bpy, bpy_extras -import os, typing -from . import UTIL_preferences, UTIL_functions - -#region Texture Functions - -g_ballanceTextureSet = 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 is_ballance_texture_path(imgpath: str) -> bool: - """ - Check whether the given path is Ballance texture. - - @param imgpath[in] Path to check. - @return True if it is Ballance texture. - """ - - filename: str = os.path.basename(imgpath) - return filename in g_ballanceTextureSet - -def is_ballance_texture(tex: bpy.types.Image) -> bool: - """ - Check whether the provided image is Ballance texture according to its referenced file path. - - @remark - + Throw exception if no valid Ballance texture folder set in preferences. - - @param tex[in] The image. - @return True if it is Ballance texture. - """ - - # check preference - pref: UTIL_preferences.RawPreferences = UTIL_preferences.get_raw_preferences() - if not pref.has_valid_blc_tex_folder(): - raise UTIL_functions.BBPException("No valid Ballance texture folder in preferences.") - - # resolve image path - absfilepath: str = bpy_extras.io_utils.path_reference( - tex.filepath, bpy.data.filepath, pref.mBallanceTextureFolder, - 'ABSOLUTE', "", None, None - ) - - # test gotten file path - return is_ballance_texture_path(absfilepath) - -def load_ballance_texture(texname: str) -> bpy.types.Image: - """ - Load Ballance texture. - - @remark - + The returned image may be redirected to a existing image according to its file path, because all Ballance textures are shared. - + The loaded image is saved as external. No pack will be operated because plugin assume all user have Ballance texture folder. - + An exception will be thrown if user do not set Ballance texture path in preferences. - - @param texname[in] the file name (not the path) of loading Ballance texture. Invalid file name will raise exception. - @return The loaded image. - """ - - # check preference - pref: UTIL_preferences.RawPreferences = UTIL_preferences.get_raw_preferences() - if not pref.has_valid_blc_tex_folder(): - raise UTIL_functions.BBPException("No valid Ballance texture folder in preferences.") - - # check texture name - if texname not in g_ballanceTextureSet: - raise UTIL_functions.BBPException("Invalid Ballance texture file name.") - - # load image - # check existing image in any case. because we need make sure ballance texture is unique. - filepath: str = os.path.join(pref.mBallanceTextureFolder, texname) - return bpy.data.images.load(filepath, check_existing = True) - -def load_other_texture(texname: str) -> bpy.types.Image: - """ - Load the Texture which is not a part of Ballance texture. - - This function is different with load_ballance_texture(). It can be seen as the opposition of load_ballance_texture(). - This function is used in loading the temp images created by BMX file resolving or Virtools engine. - Because these tmep file will be deleted after importing, this function need pack the loaded file into blender file immediately after loading. - - @remark - + The loaded texture will be immediately packed into blender file. - + Loading will NOT check any loaded image according to file path. - - @param texname[in] the FULL path to the loading image. - @return The loaded image. - """ - - # load image first - # always do not check the same image. - ret: bpy.types.Image = bpy.data.images.load(texname, check_existing = False) - - # then immediately pack it into file. - ret.pack() - - # return image - return ret - -def save_other_texture(tex: bpy.types.Image, filepath: str) -> None: - """ - Save the texture which is not a part of Ballance texture. - - @remark - + This function is the reverse operation of load_other_texture() - + This function accept textures which is packed or not packed in blender file. - - @param tex[in] The saving texture - @param filepath[in] The dest path to saving texture. - """ - tex.save(filepath) - -#endregion - -#region Ballance Textures Constance in Blender - -class BBP_PG_ballance_texture_item(bpy.types.PropertyGroup): - texture_name: bpy.props.StringProperty( - name = "Texture Name", - default = "", - ) - - texture_pointer: bpy.props.PointerProperty( - name = "Texture Pointer", - type = bpy.types.Image, - ) - -class TextureManager(): - """ - A wrapper for texture visiting. - - All texture visiting should be passed by this class, including getting, adding and removing. - This class is mainly served for importing and exporting. In these situation, texture need to be loaded or saved. - This class support `with` syntax. - This class is not thread safe and only can have one instance at the same time. - """ - - gSingletonOccupation: typing.ClassVar[bool] = False - - mIsValid: bool - mTextureDict: dict[str, bpy.types.Image] - - def __init__(self): - self.mIsValid = False - self.mTextureDict = {} - - def __enter__(self): - # check singleton - if TextureManager.gSingletonOccupation: - raise UTIL_functions.BBPException('TextureManager fail to create as a singleton.') - - # check preference - pref: UTIL_preferences.RawPreferences = UTIL_preferences.get_raw_preferences() - if not pref.has_valid_blc_tex_folder(): - raise UTIL_functions.BBPException("No valid Ballance texture folder in preferences.") - - # parse ballance textures - blctexs: bpy.types.CollectionProperty = bpy.context.scene.ballance_textures - item: BBP_PG_ballance_texture_item - for item in blctexs: - # only add valid one - if item.texture_pointer: - self.mTextureDict[item.texture_name] = item.texture_pointer - - # set to valid - TextureManager.gSingletonOccupation = True - self.mIsValid = True - - # return self - return self - - def __exit__(self, exc_type, exc_value, traceback): - if self.mIsValid: - # sync self dict to global blc textures - blctexs: bpy.types.CollectionProperty = bpy.context.scene.ballance_textures - invalid_idx: list[int] = [] - # update existing one. - item: BBP_PG_ballance_texture_item - for idx, item in enumerate(blctexs): - newtex: bpy.types.Image | None = self.mTextureDict.get(item.texture_name, None) - if newtex: - item.texture_pointer = newtex - del self.mTextureDict[item.texture_name] # delete for future using - - # if this entry still is None, add it in remove list - if item.texture_pointer is None: - invalid_idx.append(idx) - # remove invalid index, from tail to head - invalid_idx.reverse() - for idx in invalid_idx: - blctexs.remove(idx) - # add new one - # because all old one has bee synced to collection and deleted - # so the remain pairs in dict is the new one - # we can simply iterate it and add them - for k, v in self.mTextureDict: - it: BBP_PG_ballance_texture_item = blctexs.add() - it.texture_name = k - it.texture_pointer = v - - # release singleton - TextureManager.gSingletonOccupation = False - self.mIsValid = False - - def dispose(self): - self.__exit__(self, None, None, None) - - def get_image(self, filepath: str) -> bpy.types.Image: - if not self.mIsValid: - raise UTIL_functions.BBPException('Try to call invalid TextureManager class.') - - if is_ballance_texture_path(filepath): - filename: str = os.path.basename(filepath) - # load as ballance image - tex: bpy.types.Image = load_ballance_texture(filename) - # update tex - self.mTextureDict[filename] = tex - # return img - return tex - else: - # simply load it - return load_other_texture(filepath) - - def save_image(self, tex: bpy.types.Image, filepath: str): - if not self.mIsValid: - raise UTIL_functions.BBPException('Try to call invalid TextureManager class.') - - # file only need to be saved for non-ballance image - if not is_ballance_texture(tex): - save_other_texture(tex, filepath) - -#endregion - -def register() -> None: - # register ballance texture collection into scene - bpy.utils.register_class(BBP_PG_ballance_texture_item) - bpy.types.Scene.ballance_textures = bpy.props.CollectionProperty(type = BBP_PG_ballance_texture_item) - -def unregister() -> None: - del bpy.types.Scene.ballance_textures - bpy.utils.unregister_class(BBP_PG_ballance_texture_item) diff --git a/bbp_ng/PROP_virtools_material.py b/bbp_ng/PROP_virtools_material.py index c2f9c96..722dc65 100644 --- a/bbp_ng/PROP_virtools_material.py +++ b/bbp_ng/PROP_virtools_material.py @@ -608,6 +608,10 @@ g_MaterialPresets: dict[int, MaterialPresetData] = { ), } +def preset_virtools_material(mtl: bpy.types.Material, preset_type: MaterialPresetType) -> None: + preset_data: MaterialPresetData = g_MaterialPresets[preset_type.value] + set_raw_virtools_material(mtl, preset_data.mData) + def generate_mtl_presets_for_bl_enumprop() -> tuple[BlenderEnumPropEntry_t, ...]: # define 2 assist functions def get_display_name(v: int): @@ -663,14 +667,12 @@ class BBP_OT_preset_virtools_material(bpy.types.Operator): self.layout.prop(self, "preset_type") def execute(self, context): - # get preset data + # get essential value + mtl: bpy.types.Material = context.material expected_preset: MaterialPresetType = MaterialPresetType(int(self.preset_type)) - preset_data: MaterialPresetData = g_MaterialPresets[expected_preset.value] # apply preset to material - mtl = context.material - set_raw_virtools_material(mtl, preset_data.mData) - + preset_virtools_material(mtl, expected_preset) return {'FINISHED'} #endregion diff --git a/bbp_ng/UTIL_ballance_texture.py b/bbp_ng/UTIL_ballance_texture.py new file mode 100644 index 0000000..4eb6ba7 --- /dev/null +++ b/bbp_ng/UTIL_ballance_texture.py @@ -0,0 +1,314 @@ +import bpy, bpy_extras +import os, typing +from . import UTIL_preferences, UTIL_functions + +## 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 +# (it mean that if user force to add multiple textures, we can not stop them) +# +# All image loading and saving operation should be operated via this module, no matter what your are loading is or is not Ballance textures. +# This module provide a universal way to check whether texture is a part of Ballance textures and use different strategy to load them. +# +# The loading and saving of textures frequently happend when importing or exporting, there is 2 example about them. +# ``` +# # bmx loading example +# bmx_texture = blabla() +# if bmx_texture.is_external(): +# tex = UTIL_ballance_texture.load_ballance_texture(bmx_texture.filename) +# else: +# tex = UTIL_ballance_texture.load_other_texture(os.path.join(tempfolder, 'Textures', bmx_texture.filename)) +# texture_process(tex) # process loaded texture +# +# # nmo loading example +# vt_texture = blabla() +# place_to_load = "" +# if vt_texture.is_raw_data(): +# place_to_load = allocate_place() +# save_vt_raw_data_texture(vt_texture, place_to_load) +# if vt_texture.is_original_file() or vt_texture.is_external(): +# place_to_load = vt_texture.filename +# +# try_filename = UTIL_ballance_texture.get_ballance_texture_filename(place_to_load) +# if try_filename: +# # load as ballance texture +# tex = UTIL_ballance_texture.load_ballance_texture(try_filename) +# else: +# # load as other texture +# tex = UTIL_ballance_texture.load_other_texture(place_to_load) +# texture_process(tex) # process loaded texture +# +# ``` +# +# ``` +# # bmx saving example +# tex: bpy.types.Image = texture_getter() +# try_filename = UTIL_ballance_texture.get_ballance_texture_filename( +# UTIL_ballance_texture.get_texture_filepath(tex)) +# if try_filename: +# write_external_filename(try_filename) +# else: +# realpath = UTIL_ballance_texture.save_other_texture(tex, tempfolder) +# write_filename(realpath) +# +# ``` + +g_ballanceTextureSet = 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: + """ + Get Ballance texture folder from preferences. + + @exception BBPException Ballance texture folder is not set in preferences + + @return The path to Ballance texture folder. + """ + + pref: UTIL_preferences.RawPreferences = UTIL_preferences.get_raw_preferences() + if not pref.has_valid_blc_tex_folder(): + raise UTIL_functions.BBPException("No valid Ballance texture folder in preferences.") + + return pref.mBallanceTextureFolder + +def is_path_equal(path1: str, path2: str) -> bool: + """ + Check whether 2 path are equal. + + The checker will call os.path.normcase and os.path.normpath in series to regulate the give path. + + @param path1[in] The given absolute path 1 + @param path2[in] The given absolute path 2 + @return True if equal. + """ + + return os.path.normpath(os.path.normcase(path1)) == os.path.normpath(os.path.normcase(path2)) + +def get_ballance_texture_filename(texpath: str) -> str | None: + """ + Return the filename part for valid Ballance texture path. + + If the file name part of given path is not a entry of Ballance texture file name list, function will return None immediately. + Otherwise, function will check whether the given file path is really point to the Ballance texture folder. + + @exception BBPException Ballance texture folder is not set in preferences + + @param imgpath[in] Absolute path to texture. + @return File name part of given texture path if given path is a valid Ballance texture path, or None if the path not point to a valid Ballance texture. + """ + + # check file name first + filename: str = os.path.basename(texpath) + if filename not in g_ballanceTextureSet: return None + + # if file name matched, check whether it located in ballance texture folder + probe: str = os.path.join(get_ballance_texture_folder(), filename) + if not is_path_equal(probe, texpath): return None + + return filename + +def is_ballance_texture_path(texpath: str) -> bool: + """ + Check whether the given path is a valid Ballance texture. + + Simply call get_ballance_texture_filename() and check whether it return string or None. + + @exception BBPException Ballance texture folder is not set in preferences + + @param imgpath[in] Absolute path to texture. + @return True if it is Ballance texture. + @see get_ballance_texture_filename + """ + + return get_ballance_texture_filename(texpath) is not None + +def get_texture_filepath(tex: bpy.types.Image) -> str: + """ + Get the file path referenced by the given texture. + + This function will try getting the referenced file path of given texture, including packed or not packed texture. + + This function will try resolving the file path when given texture is packed according to the path of + current opend blender file and Ballance texture folder speficied in preferences. + + If resolving failed, it may return blender packed data url, for example `\\./xxx.bmp` + + @exception BBPException Ballance texture folder is not set in preferences + + @param tex[in] The image where the file name need to be got. + @return The resolved absolute file path. + """ + + # resolve image path + absfilepath: str = bpy_extras.io_utils.path_reference( + tex.filepath, bpy.data.filepath, get_ballance_texture_folder(), + 'ABSOLUTE', "", None, None + ) + + # return resolved path + return absfilepath + +def is_ballance_texture(tex: bpy.types.Image) -> bool: + """ + Check whether the provided image is Ballance texture according to its referenced file path. + + A simply calling combination of get_texture_filepath and is_ballance_texture_path + + @exception BBPException Ballance texture folder is not set in preferences + + @param tex[in] The texture to check. + @return True if it is Ballance texture. + @see get_texture_filepath, is_ballance_texture_path + """ + + return is_ballance_texture_path(get_texture_filepath(tex)) + +def load_ballance_texture(texname: str) -> bpy.types.Image: + """ + Load Ballance texture. + + + The returned image may be redirected to a existing image according to its file path, because all Ballance textures are shared. + + The loaded image is saved as external. No pack will be operated because plugin assume all user have Ballance texture folder. + + @exception BBPException Ballance texture folder is not set in preferences, or provided file name is invalid. + + @param texname[in] the file name (not the path) of loading Ballance texture. Invalid file name will raise exception. + @return The loaded image. + """ + + # check texture name + if texname not in g_ballanceTextureSet: + raise UTIL_functions.BBPException("Invalid Ballance texture file name.") + + # load image + # check existing image in any case. because we need make sure ballance texture is unique. + filepath: str = os.path.join(get_ballance_texture_folder(), texname) + return bpy.data.images.load(filepath, check_existing = True) + +def load_other_texture(texname: str) -> bpy.types.Image: + """ + Load the Texture which is not a part of Ballance texture. + + This function is different with load_ballance_texture(). It can be seen as the opposition of load_ballance_texture(). + This function is used when loading the temp images created by BMX file resolving or Virtools engine. + Because these temp file will be deleted after importing, this function need pack the loaded file into blender file immediately after loading. + + @remark + + The loaded texture will be immediately packed into blender file. + + Loading will NOT check any loaded image according to file path. + + @param texname[in] the absolute path to the loading image. + @return The loaded image. + """ + + # load image first + # always do not check the same image. + ret: bpy.types.Image = bpy.data.images.load(texname, check_existing = False) + + # then immediately pack it into file. + ret.pack() + + # return image + return ret + +def save_other_texture(tex: bpy.types.Image, file_folder: str) -> str: + """ + Save the texture which is not a part of Ballance texture. + + This function is the reverse operation of other 2 loading functions. Frequently used when exporting something. + This function accept textures which is packed or not packed in blender file. + + @param tex[in] The saving texture + @param filepath[in] The absolute path to the folder where the texture will be saved. + @return The path to saved file. + """ + + filepath: os.path.join(file_folder, os.path.basename(get_texture_filepath(tex))) + tex.save(filepath) + return filepath + +def register() -> None: + pass # nothing to register + +def unregister() -> None: + pass diff --git a/bbp_ng/UTIL_file_browser.py b/bbp_ng/UTIL_file_browser.py index 9dbfeee..881274f 100644 --- a/bbp_ng/UTIL_file_browser.py +++ b/bbp_ng/UTIL_file_browser.py @@ -95,4 +95,9 @@ class ImportDirectory(bpy_extras.io_utils.ImportHelper): def general_get_directory(self) -> str: return self.directory - \ No newline at end of file + +def register() -> None: + pass # nothing to register + +def unregister() -> None: + pass diff --git a/bbp_ng/__init__.py b/bbp_ng/__init__.py index ab03062..e841d60 100644 --- a/bbp_ng/__init__.py +++ b/bbp_ng/__init__.py @@ -23,7 +23,7 @@ if "bpy" in locals(): #endregion -from . import UTIL_preferences +from . import UTIL_preferences, UTIL_file_browser, UTIL_ballance_texture from . import PROP_virtools_material from . import OP_UV_flatten_uv @@ -74,6 +74,8 @@ g_BldMenus: tuple[MenuEntry, ...] = ( def register() -> None: # register module UTIL_preferences.register() + UTIL_file_browser.register() + UTIL_ballance_texture.register() PROP_virtools_material.register() # register other classes @@ -98,6 +100,8 @@ def unregister() -> None: # unregister modules PROP_virtools_material.unregister() + UTIL_ballance_texture.unregister() + UTIL_file_browser.unregister() UTIL_preferences.unregister() if __name__ == "__main__":