diff --git a/bbp_ng/OP_EXPORT_bmfile.py b/bbp_ng/OP_EXPORT_bmfile.py index e69de29..4536cf6 100644 --- a/bbp_ng/OP_EXPORT_bmfile.py +++ b/bbp_ng/OP_EXPORT_bmfile.py @@ -0,0 +1,26 @@ +import bpy +from . import PROP_preferences, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh + +class BBP_OT_export_bmfile(bpy.types.Operator, UTIL_file_browser.ExportBmxFile): + """Save a Ballance Map File (BM File Spec 1.4)""" + bl_idname = "bbp.export_bmfile" + bl_label = "Export BM (Ballance Map) File" + bl_options = {'PRESET'} + + @classmethod + def poll(self, context): + return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder() + + def execute(self, context): + UTIL_functions.message_box((self.general_get_filename(), ), 'Export BM File Path', 'INFO') + self.report({'INFO'}, "BM File Exporting Finished.") + return {'FINISHED'} + + def draw(self, context): + layout = self.layout + +def register() -> None: + bpy.utils.register_class(BBP_OT_export_bmfile) + +def unregister() -> None: + bpy.utils.unregister_class(BBP_OT_export_bmfile) diff --git a/bbp_ng/OP_EXPORT_virtools.py b/bbp_ng/OP_EXPORT_virtools.py index e69de29..e1211c5 100644 --- a/bbp_ng/OP_EXPORT_virtools.py +++ b/bbp_ng/OP_EXPORT_virtools.py @@ -0,0 +1,26 @@ +import bpy +from . import PROP_preferences, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh + +class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtoolsFile): + """Export Virtools File""" + bl_idname = "bbp.export_virtools" + bl_label = "Export Virtools File" + bl_options = {'PRESET'} + + @classmethod + def poll(self, context): + return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder() + + def execute(self, context): + UTIL_functions.message_box((self.general_get_filename(), ), 'Export Virtools File Path', 'INFO') + self.report({'INFO'}, "Virtools File Exporting Finished.") + return {'FINISHED'} + + def draw(self, context): + layout = self.layout + +def register() -> None: + bpy.utils.register_class(BBP_OT_export_virtools) + +def unregister() -> None: + bpy.utils.unregister_class(BBP_OT_export_virtools) diff --git a/bbp_ng/OP_IMPORT_bmfile.py b/bbp_ng/OP_IMPORT_bmfile.py index e69de29..863f1f6 100644 --- a/bbp_ng/OP_IMPORT_bmfile.py +++ b/bbp_ng/OP_IMPORT_bmfile.py @@ -0,0 +1,26 @@ +import bpy +from . import PROP_preferences, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh + +class BBP_OT_import_bmfile(bpy.types.Operator, UTIL_file_browser.ImportBmxFile): + """Load a Ballance Map File (BM File Spec 1.4)""" + bl_idname = "bbp.import_bmfile" + bl_label = "Import BM (Ballance Map) File" + bl_options = {'PRESET', 'UNDO'} + + @classmethod + def poll(self, context): + return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder() + + def execute(self, context): + UTIL_functions.message_box((self.general_get_filename(), ), 'Import BM File Path', 'INFO') + self.report({'INFO'}, "BM File Importing Finished.") + return {'FINISHED'} + + def draw(self, context): + layout = self.layout + +def register() -> None: + bpy.utils.register_class(BBP_OT_import_bmfile) + +def unregister() -> None: + bpy.utils.unregister_class(BBP_OT_import_bmfile) diff --git a/bbp_ng/OP_IMPORT_virtools.py b/bbp_ng/OP_IMPORT_virtools.py index e69de29..caa10f3 100644 --- a/bbp_ng/OP_IMPORT_virtools.py +++ b/bbp_ng/OP_IMPORT_virtools.py @@ -0,0 +1,26 @@ +import bpy +from . import PROP_preferences, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh + +class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile): + """Import Virtools File""" + bl_idname = "bbp.import_virtools" + bl_label = "Import Virtools File" + bl_options = {'PRESET', 'UNDO'} + + @classmethod + def poll(self, context): + return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder() + + def execute(self, context): + UTIL_functions.message_box((self.general_get_filename(), ), 'Import Virtools File Path', 'INFO') + self.report({'INFO'}, "Virtools File Importing Finished.") + return {'FINISHED'} + + def draw(self, context): + layout = self.layout + +def register() -> None: + bpy.utils.register_class(BBP_OT_import_virtools) + +def unregister() -> None: + bpy.utils.unregister_class(BBP_OT_import_virtools) diff --git a/bbp_ng/OP_UV_flatten_uv.py b/bbp_ng/OP_UV_flatten_uv.py index ad263c3..388f66d 100644 --- a/bbp_ng/OP_UV_flatten_uv.py +++ b/bbp_ng/OP_UV_flatten_uv.py @@ -1,12 +1,12 @@ import bpy, mathutils, bmesh -class FlattenParamBySize(): +class _FlattenParamBySize(): mScaleSize: float def __init__(self, scale_size: float) -> None: self.mScaleSize = scale_size -class FlattenParamByRefPoint(): +class _FlattenParamByRefPoint(): mReferencePoint: int mReferenceUV: float @@ -14,21 +14,21 @@ class FlattenParamByRefPoint(): self.mReferencePoint = ref_point self.mReferenceUV = ref_point_uv -class FlattenParam(): +class _FlattenParam(): mUseRefPoint: bool - mParamData: FlattenParamBySize | FlattenParamByRefPoint + mParamData: _FlattenParamBySize | _FlattenParamByRefPoint - def __init__(self, use_ref_point: bool, data: FlattenParamBySize | FlattenParamByRefPoint) -> None: + def __init__(self, use_ref_point: bool, data: _FlattenParamBySize | _FlattenParamByRefPoint) -> None: self.mUseRefPoint = use_ref_point self.mParamData = data @classmethod def CreateByScaleSize(cls, scale_num: float): - return cls(False, FlattenParamBySize(scale_num)) + return cls(False, _FlattenParamBySize(scale_num)) @classmethod def CreateByRefPoint(cls, ref_point: int, ref_point_uv: float): - return cls(True, FlattenParamByRefPoint(ref_point, ref_point_uv)) + return cls(True, _FlattenParamByRefPoint(ref_point, ref_point_uv)) class BBP_OT_flatten_uv(bpy.types.Operator): """Flatten selected face UV. Only works for convex face""" @@ -97,12 +97,12 @@ class BBP_OT_flatten_uv(bpy.types.Operator): def execute(self, context): # construct scale data if self.scale_mode == 'NUM': - scale_data: FlattenParam = FlattenParam.CreateByScaleSize(self.scale_number) + scale_data: _FlattenParam = _FlattenParam.CreateByScaleSize(self.scale_number) else: - scale_data: FlattenParam = FlattenParam.CreateByRefPoint(self.reference_point, self.reference_uv) + scale_data: _FlattenParam = _FlattenParam.CreateByRefPoint(self.reference_point, self.reference_uv) # do flatten uv and report - no_processed_count = real_flatten_uv(bpy.context.active_object.data, + no_processed_count = _real_flatten_uv(bpy.context.active_object.data, self.reference_edge, scale_data) if no_processed_count != 0: print("[Flatten UV] {} faces are not be processed correctly because process failed." @@ -127,8 +127,8 @@ class BBP_OT_flatten_uv(bpy.types.Operator): layout.prop(self, "reference_point") layout.prop(self, "reference_uv") -def real_flatten_uv(mesh: bpy.types.Mesh, reference_edge: int, - scale_data: FlattenParam) -> int: +def _real_flatten_uv(mesh: bpy.types.Mesh, reference_edge: int, + scale_data: _FlattenParam) -> int: no_processed_count: int = 0 # if no uv, create it @@ -271,3 +271,9 @@ def real_flatten_uv(mesh: bpy.types.Mesh, reference_edge: int, # sync the result to view port bmesh.update_edit_mesh(mesh) return no_processed_count + +def register() -> None: + bpy.utils.register_class(BBP_OT_flatten_uv) + +def unregister() -> None: + bpy.utils.unregister_class(BBP_OT_flatten_uv) diff --git a/bbp_ng/UTIL_preferences.py b/bbp_ng/PROP_preferences.py similarity index 100% rename from bbp_ng/UTIL_preferences.py rename to bbp_ng/PROP_preferences.py diff --git a/bbp_ng/UTIL_ballance_texture.py b/bbp_ng/UTIL_ballance_texture.py index 4cb1b90..b4a60f2 100644 --- a/bbp_ng/UTIL_ballance_texture.py +++ b/bbp_ng/UTIL_ballance_texture.py @@ -1,6 +1,6 @@ import bpy, bpy_extras import os, typing -from . import UTIL_preferences, UTIL_functions +from . import PROP_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 @@ -55,7 +55,7 @@ from . import UTIL_preferences, UTIL_functions #region Assist Functions -def get_ballance_texture_folder() -> str: +def _get_ballance_texture_folder() -> str: """! Get Ballance texture folder from preferences. @@ -64,13 +64,13 @@ def get_ballance_texture_folder() -> str: @return The path to Ballance texture folder. """ - pref: UTIL_preferences.RawPreferences = UTIL_preferences.get_raw_preferences() + pref: PROP_preferences.RawPreferences = PROP_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: +def _is_path_equal(path1: str, path2: str) -> bool: """! Check whether 2 path are equal. @@ -189,8 +189,8 @@ def get_ballance_texture_filename(texpath: str) -> str | None: 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 + probe: str = os.path.join(_get_ballance_texture_folder(), filename) + if not _is_path_equal(probe, texpath): return None return filename @@ -228,7 +228,7 @@ def get_texture_filepath(tex: bpy.types.Image) -> str: # resolve image path absfilepath: str = bpy_extras.io_utils.path_reference( - tex.filepath, bpy.data.filepath, get_ballance_texture_folder(), + tex.filepath, bpy.data.filepath, _get_ballance_texture_folder(), 'ABSOLUTE', "", None, None ) @@ -273,7 +273,7 @@ def load_ballance_texture(texname: str) -> bpy.types.Image: # 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) + 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: @@ -331,9 +331,3 @@ def save_other_texture(tex: bpy.types.Image, filepath: str) -> str: tex.save(filepath) #endregion - -def register() -> None: - pass # nothing to register - -def unregister() -> None: - pass diff --git a/bbp_ng/UTIL_blender_mesh.py b/bbp_ng/UTIL_blender_mesh.py index e58796e..e54ee69 100644 --- a/bbp_ng/UTIL_blender_mesh.py +++ b/bbp_ng/UTIL_blender_mesh.py @@ -33,31 +33,31 @@ class FaceData(): def is_indices_legal(self) -> bool: return len(self.mIndices) >= 3 -def flat_vxvector3(it: typing.Iterator[UTIL_virtools_types.VxVector3]) -> typing.Iterator[float]: +def _flat_vxvector3(it: typing.Iterator[UTIL_virtools_types.VxVector3]) -> typing.Iterator[float]: for entry in it: yield entry.x yield entry.y yield entry.z -def flat_vxvector2(it: typing.Iterator[UTIL_virtools_types.VxVector2]) -> typing.Iterator[float]: +def _flat_vxvector2(it: typing.Iterator[UTIL_virtools_types.VxVector2]) -> typing.Iterator[float]: for entry in it: yield entry.x yield entry.y -def flat_face_nml_index(nml_idx: array.array, nml_array: array.array) -> typing.Iterator[float]: +def _flat_face_nml_index(nml_idx: array.array, nml_array: array.array) -> typing.Iterator[float]: for idx in nml_idx: pos: int = idx * 3 yield nml_array[pos] yield nml_array[pos + 1] yield nml_array[pos + 2] -def flat_face_uv_index(uv_idx: array.array, uv_array: array.array) -> typing.Iterator[float]: +def _flat_face_uv_index(uv_idx: array.array, uv_array: array.array) -> typing.Iterator[float]: for idx in uv_idx: pos: int = idx * 2 yield uv_array[pos] yield uv_array[pos + 1] -def nest_custom_split_normal(nml_idx: array.array, nml_array: array.array) -> typing.Iterator[UTIL_virtools_types.ConstVxVector3]: +def _nest_custom_split_normal(nml_idx: array.array, nml_array: array.array) -> typing.Iterator[UTIL_virtools_types.ConstVxVector3]: for idx in nml_idx: pos: int = idx * 3 yield (nml_array[pos], nml_array[pos + 1], nml_array[pos + 2]) @@ -161,11 +161,11 @@ class MeshWriter(): # add vertex data prev_vertex_pos_count: int = len(self.__mVertexPos) - self.__mVertexPos.extend(flat_vxvector3(data.mVertexPosition)) + self.__mVertexPos.extend(_flat_vxvector3(data.mVertexPosition)) prev_vertex_nml_count: int = len(self.__mVertexNormal) - self.__mVertexNormal.extend(flat_vxvector3(data.mVertexNormal)) + self.__mVertexNormal.extend(_flat_vxvector3(data.mVertexNormal)) prev_vertex_uv_count: int = len(self.__mVertexUV) - self.__mVertexUV.extend(flat_vxvector2(data.mVertexUV)) + self.__mVertexUV.extend(_flat_vxvector2(data.mVertexUV)) # add material slot data and create mtl remap mtl_remap: list[int] = [] @@ -203,7 +203,6 @@ class MeshWriter(): if self.is_valid(): # write mesh self.__write_mesh() - # reset mesh self.__mAssocMesh = None @@ -236,11 +235,11 @@ class MeshWriter(): self.__mAssocMesh.loops.foreach_set('vertex_index', self.__mFacePosIndices) # add face vertex nml by function self.__mAssocMesh.loops.foreach_set('normal', - list(flat_face_nml_index(self.__mFaceNmlIndices, self.__mVertexNormal)) + list(_flat_face_nml_index(self.__mFaceNmlIndices, self.__mVertexNormal)) ) # add face vertex uv by function self.__mAssocMesh.uv_layers[0].uv.foreach_set('vector', - list(flat_face_uv_index(self.__mFaceUvIndices, self.__mVertexUV)) + list(_flat_face_uv_index(self.__mFaceUvIndices, self.__mVertexUV)) ) # NOTE: blender 3.5 changed. UV must be visited by .uv, not the .data # iterate face to set face data @@ -271,7 +270,7 @@ class MeshWriter(): # set custom split normal data self.__mAssocMesh.normals_split_custom_set( - tuple(nest_custom_split_normal(self.__mFaceNmlIndices, self.__mVertexNormal)) + tuple(_nest_custom_split_normal(self.__mFaceNmlIndices, self.__mVertexNormal)) ) # enable auto smooth. it is IMPORTANT self.__mAssocMesh.use_auto_smooth = True @@ -285,13 +284,5 @@ class MeshWriter(): # clear mtl slot because clear_geometry will not do this. self.__mAssocMesh.materials.clear() - - class MeshUVModifier(): pass - -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 881274f..9dbfeee 100644 --- a/bbp_ng/UTIL_file_browser.py +++ b/bbp_ng/UTIL_file_browser.py @@ -95,9 +95,4 @@ 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 + \ No newline at end of file diff --git a/bbp_ng/UTIL_functions.py b/bbp_ng/UTIL_functions.py index 5aa27e8..727f1c5 100644 --- a/bbp_ng/UTIL_functions.py +++ b/bbp_ng/UTIL_functions.py @@ -1,3 +1,4 @@ +import bpy import math, typing class BBPException(Exception): @@ -53,3 +54,19 @@ def limit_iterator(it: typing.Iterator[_TLimitIterator], limit_count: int) -> ty # It is okey because it naturally stop the iteration of this generator. yield next(it) counter += 1 + +def message_box(message: tuple[str], title: str, icon: str): + """ + Show a message box in Blender. Non-block mode. + + @param message[in] The text this message box displayed. Each item in this param will show as a single line. + @param title[in] Message box title text. + @param icon[in] The icon this message box displayed. + """ + def draw(self, context: bpy.types.Context): + layout = self.layout + for item in message: + layout.label(text=item, translate=False) + + bpy.context.window_manager.popup_menu(draw, title = title, icon = icon) + diff --git a/bbp_ng/UTIL_virtools_types.py b/bbp_ng/UTIL_virtools_types.py index f88b1a3..8a3067b 100644 --- a/bbp_ng/UTIL_virtools_types.py +++ b/bbp_ng/UTIL_virtools_types.py @@ -76,17 +76,17 @@ class VxColor(): self.a = _a self.regulate() - def to_tuple_rgba(self) -> tuple[float, float, float, float]: + def to_tuple_rgba(self) -> ConstVxColorRGBA: return (self.r, self.g, self.b, self.a) - def to_tuple_rgb(self) -> tuple[float, float, float]: + def to_tuple_rgb(self) -> ConstVxColorRGB: return (self.r, self.g, self.b) - def from_tuple_rgba(self, val: tuple[float, float, float, float]) -> None: + def from_tuple_rgba(self, val: ConstVxColorRGBA) -> None: (self.r, self.g, self.b, self.a) = val self.regulate() - def from_tuple_rgb(self, val: tuple[float, float, float]) -> None: + def from_tuple_rgb(self, val: ConstVxColorRGB) -> None: (self.r, self.g, self.b) = val self.a = 1.0 self.regulate() diff --git a/bbp_ng/__init__.py b/bbp_ng/__init__.py index e841d60..2390672 100644 --- a/bbp_ng/__init__.py +++ b/bbp_ng/__init__.py @@ -23,8 +23,8 @@ if "bpy" in locals(): #endregion -from . import UTIL_preferences, UTIL_file_browser, UTIL_ballance_texture -from . import PROP_virtools_material +from . import PROP_preferences, PROP_virtools_material +from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools from . import OP_UV_flatten_uv #region Menu @@ -45,8 +45,18 @@ class BBP_MT_View3DMenu(bpy.types.Menu): MenuDrawer_t = typing.Callable[[typing.Any, typing.Any], None] +def menu_drawer_import(self, context): + layout: bpy.types.UILayout = self.layout + layout.operator(OP_IMPORT_bmfile.BBP_OT_import_bmfile.bl_idname, text = "Ballance Map (.bmx)") + layout.operator(OP_IMPORT_virtools.BBP_OT_import_virtools.bl_idname, text = "Virtools File (.nmo/.cmo/.vmo) (experimental)") + +def menu_drawer_export(self, context): + layout: bpy.types.UILayout = self.layout + layout.operator(OP_EXPORT_bmfile.BBP_OT_export_bmfile.bl_idname, text = "Ballance Map (.bmx)") + layout.operator(OP_EXPORT_virtools.BBP_OT_export_virtools.bl_idname, text = "Virtools File (.nmo/.cmo/.vmo) (experimental)") + def menu_drawer_view3d(self, context): - layout = self.layout + layout: bpy.types.UILayout = self.layout layout.menu(BBP_MT_View3DMenu.bl_idname) #endregion @@ -54,7 +64,6 @@ def menu_drawer_view3d(self, context): #region Register and Unregister. g_BldClasses: tuple[typing.Any, ...] = ( - OP_UV_flatten_uv.BBP_OT_flatten_uv, BBP_MT_View3DMenu, ) @@ -69,15 +78,22 @@ class MenuEntry(): g_BldMenus: tuple[MenuEntry, ...] = ( MenuEntry(bpy.types.VIEW3D_MT_editor_menus, False, menu_drawer_view3d), + MenuEntry(bpy.types.TOPBAR_MT_file_import, True, menu_drawer_import), + MenuEntry(bpy.types.TOPBAR_MT_file_export, True, menu_drawer_export), ) def register() -> None: # register module - UTIL_preferences.register() - UTIL_file_browser.register() - UTIL_ballance_texture.register() + PROP_preferences.register() PROP_virtools_material.register() + OP_IMPORT_bmfile.register() + OP_EXPORT_bmfile.register() + OP_IMPORT_virtools.register() + OP_EXPORT_virtools.register() + + OP_UV_flatten_uv.register() + # register other classes for cls in g_BldClasses: bpy.utils.register_class(cls) @@ -99,10 +115,15 @@ def unregister() -> None: bpy.utils.unregister_class(cls) # unregister modules + OP_UV_flatten_uv.unregister() + + OP_EXPORT_virtools.unregister() + OP_IMPORT_virtools.unregister() + OP_EXPORT_bmfile.unregister() + OP_IMPORT_bmfile.unregister() + PROP_virtools_material.unregister() - UTIL_ballance_texture.unregister() - UTIL_file_browser.unregister() - UTIL_preferences.unregister() + PROP_preferences.unregister() if __name__ == "__main__": register()