add placeholder for importer and exporter

This commit is contained in:
yyc12345 2023-10-18 21:23:04 +08:00
parent 484a4101ad
commit 4f10b1a9e9
12 changed files with 194 additions and 66 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
import bpy, mathutils, bmesh import bpy, mathutils, bmesh
class FlattenParamBySize(): class _FlattenParamBySize():
mScaleSize: float mScaleSize: float
def __init__(self, scale_size: float) -> None: def __init__(self, scale_size: float) -> None:
self.mScaleSize = scale_size self.mScaleSize = scale_size
class FlattenParamByRefPoint(): class _FlattenParamByRefPoint():
mReferencePoint: int mReferencePoint: int
mReferenceUV: float mReferenceUV: float
@ -14,21 +14,21 @@ class FlattenParamByRefPoint():
self.mReferencePoint = ref_point self.mReferencePoint = ref_point
self.mReferenceUV = ref_point_uv self.mReferenceUV = ref_point_uv
class FlattenParam(): class _FlattenParam():
mUseRefPoint: bool 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.mUseRefPoint = use_ref_point
self.mParamData = data self.mParamData = data
@classmethod @classmethod
def CreateByScaleSize(cls, scale_num: float): def CreateByScaleSize(cls, scale_num: float):
return cls(False, FlattenParamBySize(scale_num)) return cls(False, _FlattenParamBySize(scale_num))
@classmethod @classmethod
def CreateByRefPoint(cls, ref_point: int, ref_point_uv: float): 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): class BBP_OT_flatten_uv(bpy.types.Operator):
"""Flatten selected face UV. Only works for convex face""" """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): def execute(self, context):
# construct scale data # construct scale data
if self.scale_mode == 'NUM': if self.scale_mode == 'NUM':
scale_data: FlattenParam = FlattenParam.CreateByScaleSize(self.scale_number) scale_data: _FlattenParam = _FlattenParam.CreateByScaleSize(self.scale_number)
else: 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 # 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) self.reference_edge, scale_data)
if no_processed_count != 0: if no_processed_count != 0:
print("[Flatten UV] {} faces are not be processed correctly because process failed." 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_point")
layout.prop(self, "reference_uv") layout.prop(self, "reference_uv")
def real_flatten_uv(mesh: bpy.types.Mesh, reference_edge: int, def _real_flatten_uv(mesh: bpy.types.Mesh, reference_edge: int,
scale_data: FlattenParam) -> int: scale_data: _FlattenParam) -> int:
no_processed_count: int = 0 no_processed_count: int = 0
# if no uv, create it # 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 # sync the result to view port
bmesh.update_edit_mesh(mesh) bmesh.update_edit_mesh(mesh)
return no_processed_count 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)

View File

@ -1,6 +1,6 @@
import bpy, bpy_extras import bpy, bpy_extras
import os, typing import os, typing
from . import UTIL_preferences, UTIL_functions from . import PROP_preferences, 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
@ -55,7 +55,7 @@ from . import UTIL_preferences, UTIL_functions
#region Assist Functions #region Assist Functions
def get_ballance_texture_folder() -> str: def _get_ballance_texture_folder() -> str:
"""! """!
Get Ballance texture folder from preferences. Get Ballance texture folder from preferences.
@ -64,13 +64,13 @@ def get_ballance_texture_folder() -> str:
@return The path to Ballance texture folder. @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(): if not pref.has_valid_blc_tex_folder():
raise UTIL_functions.BBPException("No valid Ballance texture folder in preferences.") raise UTIL_functions.BBPException("No valid Ballance texture folder in preferences.")
return pref.mBallanceTextureFolder 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. 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 filename not in g_ballanceTextureSet: 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)
if not is_path_equal(probe, texpath): return None if not _is_path_equal(probe, texpath): return None
return filename return filename
@ -228,7 +228,7 @@ def get_texture_filepath(tex: bpy.types.Image) -> str:
# resolve image path # resolve image path
absfilepath: str = bpy_extras.io_utils.path_reference( 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 'ABSOLUTE', "", None, None
) )
@ -273,7 +273,7 @@ def load_ballance_texture(texname: str) -> bpy.types.Image:
# load image # load image
# check existing image in any case. because we need make sure ballance texture is unique. # 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) return bpy.data.images.load(filepath, check_existing = True)
def load_other_texture(texname: str) -> bpy.types.Image: 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) tex.save(filepath)
#endregion #endregion
def register() -> None:
pass # nothing to register
def unregister() -> None:
pass

View File

@ -33,31 +33,31 @@ class FaceData():
def is_indices_legal(self) -> bool: def is_indices_legal(self) -> bool:
return len(self.mIndices) >= 3 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: for entry in it:
yield entry.x yield entry.x
yield entry.y yield entry.y
yield entry.z 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: for entry in it:
yield entry.x yield entry.x
yield entry.y 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: for idx in nml_idx:
pos: int = idx * 3 pos: int = idx * 3
yield nml_array[pos] yield nml_array[pos]
yield nml_array[pos + 1] yield nml_array[pos + 1]
yield nml_array[pos + 2] 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: for idx in uv_idx:
pos: int = idx * 2 pos: int = idx * 2
yield uv_array[pos] yield uv_array[pos]
yield uv_array[pos + 1] 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: for idx in nml_idx:
pos: int = idx * 3 pos: int = idx * 3
yield (nml_array[pos], nml_array[pos + 1], nml_array[pos + 2]) yield (nml_array[pos], nml_array[pos + 1], nml_array[pos + 2])
@ -161,11 +161,11 @@ class MeshWriter():
# add vertex data # add vertex data
prev_vertex_pos_count: int = len(self.__mVertexPos) 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) 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) 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 # add material slot data and create mtl remap
mtl_remap: list[int] = [] mtl_remap: list[int] = []
@ -203,7 +203,6 @@ class MeshWriter():
if self.is_valid(): if self.is_valid():
# write mesh # write mesh
self.__write_mesh() self.__write_mesh()
# reset mesh # reset mesh
self.__mAssocMesh = None self.__mAssocMesh = None
@ -236,11 +235,11 @@ class MeshWriter():
self.__mAssocMesh.loops.foreach_set('vertex_index', self.__mFacePosIndices) self.__mAssocMesh.loops.foreach_set('vertex_index', self.__mFacePosIndices)
# add face vertex nml by function # add face vertex nml by function
self.__mAssocMesh.loops.foreach_set('normal', 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 # add face vertex uv by function
self.__mAssocMesh.uv_layers[0].uv.foreach_set('vector', 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 ) # NOTE: blender 3.5 changed. UV must be visited by .uv, not the .data
# iterate face to set face data # iterate face to set face data
@ -271,7 +270,7 @@ class MeshWriter():
# set custom split normal data # set custom split normal data
self.__mAssocMesh.normals_split_custom_set( 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 # enable auto smooth. it is IMPORTANT
self.__mAssocMesh.use_auto_smooth = True self.__mAssocMesh.use_auto_smooth = True
@ -285,13 +284,5 @@ class MeshWriter():
# clear mtl slot because clear_geometry will not do this. # clear mtl slot because clear_geometry will not do this.
self.__mAssocMesh.materials.clear() self.__mAssocMesh.materials.clear()
class MeshUVModifier(): class MeshUVModifier():
pass pass
def register() -> None:
pass # nothing to register
def unregister() -> None:
pass

View File

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

View File

@ -1,3 +1,4 @@
import bpy
import math, typing import math, typing
class BBPException(Exception): 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. # It is okey because it naturally stop the iteration of this generator.
yield next(it) yield next(it)
counter += 1 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)

View File

@ -76,17 +76,17 @@ class VxColor():
self.a = _a self.a = _a
self.regulate() 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) 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) 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.r, self.g, self.b, self.a) = val
self.regulate() 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.r, self.g, self.b) = val
self.a = 1.0 self.a = 1.0
self.regulate() self.regulate()

View File

@ -23,8 +23,8 @@ if "bpy" in locals():
#endregion #endregion
from . import UTIL_preferences, UTIL_file_browser, UTIL_ballance_texture from . import PROP_preferences, PROP_virtools_material
from . import PROP_virtools_material from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools
from . import OP_UV_flatten_uv from . import OP_UV_flatten_uv
#region Menu #region Menu
@ -45,8 +45,18 @@ class BBP_MT_View3DMenu(bpy.types.Menu):
MenuDrawer_t = typing.Callable[[typing.Any, typing.Any], None] 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): def menu_drawer_view3d(self, context):
layout = self.layout layout: bpy.types.UILayout = self.layout
layout.menu(BBP_MT_View3DMenu.bl_idname) layout.menu(BBP_MT_View3DMenu.bl_idname)
#endregion #endregion
@ -54,7 +64,6 @@ def menu_drawer_view3d(self, context):
#region Register and Unregister. #region Register and Unregister.
g_BldClasses: tuple[typing.Any, ...] = ( g_BldClasses: tuple[typing.Any, ...] = (
OP_UV_flatten_uv.BBP_OT_flatten_uv,
BBP_MT_View3DMenu, BBP_MT_View3DMenu,
) )
@ -69,15 +78,22 @@ class MenuEntry():
g_BldMenus: tuple[MenuEntry, ...] = ( g_BldMenus: tuple[MenuEntry, ...] = (
MenuEntry(bpy.types.VIEW3D_MT_editor_menus, False, menu_drawer_view3d), 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: def register() -> None:
# register module # register module
UTIL_preferences.register() PROP_preferences.register()
UTIL_file_browser.register()
UTIL_ballance_texture.register()
PROP_virtools_material.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 # register other classes
for cls in g_BldClasses: for cls in g_BldClasses:
bpy.utils.register_class(cls) bpy.utils.register_class(cls)
@ -99,10 +115,15 @@ def unregister() -> None:
bpy.utils.unregister_class(cls) bpy.utils.unregister_class(cls)
# unregister modules # 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() PROP_virtools_material.unregister()
UTIL_ballance_texture.unregister() PROP_preferences.unregister()
UTIL_file_browser.unregister()
UTIL_preferences.unregister()
if __name__ == "__main__": if __name__ == "__main__":
register() register()