update texture manager

This commit is contained in:
yyc12345 2023-10-15 21:13:56 +08:00
parent 21345b8251
commit 1d1de08bd7
5 changed files with 332 additions and 328 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -95,4 +95,9 @@ class ImportDirectory(bpy_extras.io_utils.ImportHelper):
def general_get_directory(self) -> str:
return self.directory
def register() -> None:
pass # nothing to register
def unregister() -> None:
pass

View File

@ -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__":