From 1a2dd08092ec41e9c55bc743647d42be4e949581 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Thu, 16 Nov 2023 22:41:03 +0800 Subject: [PATCH] update plugin. - sync PyBMap work. use different library name in different OS. - add BMap encoding default value according to different OS (Windows and non-Windows) because non-Windows OS, we use libiconv as encoding converter. - move all pointer properties to a single module and give corresponding visitor. - add shared importer exporter parameters module thus bmfile import/export also can ref it. --- bbp_ng/OP_EXPORT_virtools.py | 49 +++++++++++- bbp_ng/OP_IMPORT_virtools.py | 24 +++--- bbp_ng/OP_UV_rail_uv.py | 31 +------- bbp_ng/PROP_ptrprop_resolver.py | 51 +++++++++++++ bbp_ng/PyBMap/bmap.py | 15 +++- bbp_ng/UTIL_functions.py | 15 +++- bbp_ng/UTIL_ioport_shared.py | 130 ++++++++++++++++++++++++++++++++ bbp_ng/__init__.py | 6 +- 8 files changed, 273 insertions(+), 48 deletions(-) create mode 100644 bbp_ng/PROP_ptrprop_resolver.py create mode 100644 bbp_ng/UTIL_ioport_shared.py diff --git a/bbp_ng/OP_EXPORT_virtools.py b/bbp_ng/OP_EXPORT_virtools.py index 1622547..130675c 100644 --- a/bbp_ng/OP_EXPORT_virtools.py +++ b/bbp_ng/OP_EXPORT_virtools.py @@ -1,8 +1,11 @@ import bpy -from . import PROP_preferences, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh +from bpy_extras.wm_utils.progress_report import ProgressReport +import tempfile, os, typing +from . import PROP_preferences, PROP_ptrprop_resolver, UTIL_ioport_shared +from . import UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_icons_manager from .PyBMap import bmap_wrapper as bmap -class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtoolsFile): +class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtoolsFile, UTIL_ioport_shared.ExportParams, UTIL_ioport_shared.VirtoolsParams): """Export Virtools File""" bl_idname = "bbp.export_virtools" bl_label = "Export Virtools File" @@ -15,12 +18,50 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool and bmap.is_bmap_available()) def execute(self, context): - UTIL_functions.message_box((self.general_get_filename(), ), 'Export Virtools File Path', 'INFO') + # check selecting first + objls: tuple[bpy.types.Object] | None = self.general_get_export_objects() + if objls is None: + UTIL_functions.message_box( + ('No selected target!', ), + 'Lost Parameters', + UTIL_icons_manager.BlenderPresetIcons.Error.value + ) + return {'CANCELLED'} + + # start exporting + with UTIL_ioport_shared.ExportEditModeBackup() as editmode_guard: + _export_virtools( + self.general_get_filename(), + self.general_get_vt_encodings(), + objls + ) + self.report({'INFO'}, "Virtools File Exporting Finished.") return {'FINISHED'} def draw(self, context): - pass + layout = self.layout + layout.label(text = 'Export Target') + self.draw_export_params(layout) + layout.separator() + layout.label(text = 'Virtools Params') + self.draw_virtools_params(layout) + +def _export_virtools(file_name_: str, encodings_: tuple[str], export_objects: tuple[bpy.types.Object]) -> None: + # create temp folder + with tempfile.TemporaryDirectory() as vt_temp_folder: + print(f'Virtools Engine Temp: {vt_temp_folder}') + + # create virtools reader context + with bmap.BMFileWriter( + vt_temp_folder, + PROP_preferences.get_raw_preferences().mBallanceTextureFolder, + encodings_) as writer: + + # prepare progress reporter + with ProgressReport(wm = bpy.context.window_manager) as progress: + pass + def register() -> None: bpy.utils.register_class(BBP_OT_export_virtools) diff --git a/bbp_ng/OP_IMPORT_virtools.py b/bbp_ng/OP_IMPORT_virtools.py index 1e64722..7f6f399 100644 --- a/bbp_ng/OP_IMPORT_virtools.py +++ b/bbp_ng/OP_IMPORT_virtools.py @@ -1,23 +1,17 @@ import bpy from bpy_extras.wm_utils.progress_report import ProgressReport import tempfile, os, typing -from . import PROP_preferences +from . import PROP_preferences, UTIL_ioport_shared from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh -from . import PROP_ballance_element, PROP_virtools_group, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh +from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh from .PyBMap import bmap_wrapper as bmap -class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile): +class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams): """Import Virtools File""" bl_idname = "bbp.import_virtools" bl_label = "Import Virtools File" bl_options = {'PRESET', 'UNDO'} - vt_encodings: bpy.props.StringProperty( - name = "Encodings", - description = "The encoding list used by Virtools engine to resolve object name. Use `;` to split multiple encodings", - default = "1252" - ) - @classmethod def poll(self, context): return ( @@ -25,15 +19,21 @@ class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtool and bmap.is_bmap_available()) def execute(self, context): - # get encoding, split it by `;` and strip blank chars. - encodings: str = self.vt_encodings _import_virtools( self.general_get_filename(), - tuple(map(lambda x: x.strip(), encodings.split(';'))) + self.general_get_vt_encodings() ) self.report({'INFO'}, "Virtools File Importing Finished.") return {'FINISHED'} + def draw(self, context): + layout = self.layout + layout.label(text = 'Conflict Options') + self.draw_import_params(layout) + layout.separator() + layout.label(text = 'Virtools Params') + self.draw_virtools_params(layout) + def _import_virtools(file_name_: str, encodings_: tuple[str]) -> None: # create temp folder with tempfile.TemporaryDirectory() as vt_temp_folder: diff --git a/bbp_ng/OP_UV_rail_uv.py b/bbp_ng/OP_UV_rail_uv.py index 197ccd8..b7d723e 100644 --- a/bbp_ng/OP_UV_rail_uv.py +++ b/bbp_ng/OP_UV_rail_uv.py @@ -1,27 +1,8 @@ import bpy, bmesh, mathutils import typing +from . import PROP_ptrprop_resolver from . import UTIL_virtools_types, UTIL_icons_manager, UTIL_functions -#region Material Pointer Property Resolver - -class BBP_PG_patch_rail_uv(bpy.types.PropertyGroup): - rail_mtl: bpy.props.PointerProperty( - name = "Material", - description = "The material used for rail", - type = bpy.types.Material, - ) - -def get_rail_uv_patch() -> BBP_PG_patch_rail_uv: - return bpy.context.scene.bbp_patch_rail_uv - -def get_raw_rail_uv_patch() -> bpy.types.Material: - return get_rail_uv_patch().rail_mtl - -def draw_rail_uv_patch(layout: bpy.types.UILayout) -> None: - layout.prop(get_rail_uv_patch(), 'rail_mtl') - -#endregion - class BBP_OT_rail_uv(bpy.types.Operator): """Create UV for Rail as Ballance Showen (TT_ReflectionMapping)""" bl_idname = "bbp.rail_uv" @@ -38,7 +19,7 @@ class BBP_OT_rail_uv(bpy.types.Operator): def execute(self, context): # check material - mtl: bpy.types.Material = get_raw_rail_uv_patch() + mtl: bpy.types.Material = PROP_ptrprop_resolver.get_rail_uv_material() if mtl is None: UTIL_functions.message_box( ("No specific material", ), @@ -53,7 +34,7 @@ class BBP_OT_rail_uv(bpy.types.Operator): def draw(self, context): layout: bpy.types.UILayout = self.layout - draw_rail_uv_patch(layout) + PROP_ptrprop_resolver.draw_rail_uv_material(layout) #region Real Worker Functions @@ -200,14 +181,8 @@ def _create_rail_uv(meshes: typing.Iterable[bpy.types.Mesh], mtl: bpy.types.Mate #endregion def register() -> None: - # register patch first - bpy.utils.register_class(BBP_PG_patch_rail_uv) - bpy.types.Scene.bbp_patch_rail_uv = bpy.props.PointerProperty(type = BBP_PG_patch_rail_uv) - bpy.utils.register_class(BBP_OT_rail_uv) def unregister() -> None: - del bpy.types.Scene.bbp_patch_rail_uv - bpy.utils.unregister_class(BBP_OT_rail_uv) diff --git a/bbp_ng/PROP_ptrprop_resolver.py b/bbp_ng/PROP_ptrprop_resolver.py new file mode 100644 index 0000000..1599ca0 --- /dev/null +++ b/bbp_ng/PROP_ptrprop_resolver.py @@ -0,0 +1,51 @@ +import bpy + +## Intent +# Operator is not allowed to register Pointer Properties. +# The solution is register pointer properties in Scene and reference it when drawing operator window. +# This module contains all pointer properties used by other operators. + +class BBP_PG_ptrprop_resolver(bpy.types.PropertyGroup): + rail_uv_material: bpy.props.PointerProperty( + name = "Material", + description = "The material used for rail", + type = bpy.types.Material, + ) + + export_collection: bpy.props.PointerProperty( + type = bpy.types.Collection, + name = "Collection", + description = "The collection exported. Nested collections allowed." + ) + + export_object: bpy.props.PointerProperty( + type = bpy.types.Object, + name = "Object", + description = "The object exported" + ) + +def get_ptrprop_resolver() -> BBP_PG_ptrprop_resolver: + return bpy.context.scene.bbp_ptrprop_resolver + +def get_rail_uv_material() -> bpy.types.Material: + return get_ptrprop_resolver().rail_uv_material +def draw_rail_uv_material(layout: bpy.types.UILayout) -> None: + layout.prop(get_ptrprop_resolver(), 'rail_uv_material') + +def get_export_collection() -> bpy.types.Collection: + return get_ptrprop_resolver().export_collection +def draw_export_collection(layout: bpy.types.UILayout) -> None: + layout.prop(get_ptrprop_resolver(), 'export_collection') + +def get_export_object() -> bpy.types.Object: + return get_ptrprop_resolver().export_object +def draw_export_object(layout: bpy.types.UILayout) -> None: + layout.prop(get_ptrprop_resolver(), 'export_object') + +def register(): + bpy.utils.register_class(BBP_PG_ptrprop_resolver) + bpy.types.Scene.bbp_ptrprop_resolver = bpy.props.PointerProperty(type = BBP_PG_ptrprop_resolver) + +def unregister(): + del bpy.types.Scene.bbp_ptrprop_resolver + bpy.utils.unregister_class(BBP_PG_ptrprop_resolver) diff --git a/bbp_ng/PyBMap/bmap.py b/bbp_ng/PyBMap/bmap.py index 466faf6..8dcab34 100644 --- a/bbp_ng/PyBMap/bmap.py +++ b/bbp_ng/PyBMap/bmap.py @@ -1,4 +1,4 @@ -import ctypes, os +import ctypes, os, sys #region Type Defines @@ -67,10 +67,21 @@ bm_VxMatrix_p = ctypes.POINTER(bm_VxMatrix) #region BMap Loader +_g_BMapLibName: str + +if sys.platform.startswith('win32') or sys.platform.startswith('cygwin'): + _g_BMapLibName = "BMap.dll" +elif sys.platform.startswith('linux') or sys.platform.startswith('freebsd'): + _g_BMapLibName = "BMap.so" +elif sys.platform.startswith('darwin'): + _g_BMapLibName = "BMap.dylib" +else: + _g_BMapLibName = "BMap.bin" + _g_BMapModule: ctypes.CDLL = None try: _g_BMapModule = ctypes.cdll.LoadLibrary( - os.path.join(os.path.dirname(__file__), "BMap.dll") + os.path.join(os.path.dirname(__file__), _g_BMapLibName) ) except: _g_BMapModule = None diff --git a/bbp_ng/UTIL_functions.py b/bbp_ng/UTIL_functions.py index 4d37847..bd5b115 100644 --- a/bbp_ng/UTIL_functions.py +++ b/bbp_ng/UTIL_functions.py @@ -1,5 +1,5 @@ import bpy -import math, typing, enum +import math, typing, enum, sys class BBPException(Exception): """ @@ -84,5 +84,18 @@ def generate_vt_enums_for_bl_enumprop(enum_data: type[InheritingIntEnum_t], anno (str(member.value), get_display_name(member.value, member.name), get_description(member.value, ""), "", member.value) for member in enum_data ) +#endregion + +#region Default Encoding of BMap + +# Use semicolon split each encodings. Support Western European and Simplified Chinese in default. + +g_PyBMapDefaultEncoding: str +if sys.platform.startswith('win32') or sys.platform.startswith('cygwin'): + # See: https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers + g_PyBMapDefaultEncoding = "1252;936" +else: + # See: https://www.gnu.org/software/libiconv/ + g_PyBMapDefaultEncoding = "CP1252;CP936" #endregion diff --git a/bbp_ng/UTIL_ioport_shared.py b/bbp_ng/UTIL_ioport_shared.py new file mode 100644 index 0000000..422e428 --- /dev/null +++ b/bbp_ng/UTIL_ioport_shared.py @@ -0,0 +1,130 @@ +import bpy +import enum +from . import UTIL_functions +from . import PROP_ptrprop_resolver + +## Intent +# Some importer or exporter may share same properties. +# So we create some shared class and user just need inherit them +# and call general getter to get user selected data. +# Also provide draw function thus caller do not need draw the params themselves. + +class ImportParams(): + texture_conflict_strategy: bpy.props.EnumProperty( + name = "Texture name conflict", + items = ( + ('NEW', "New Instance", "Create a new instance"), + ('CURRENT', "Use Current", "Use current one"), + ), + description = "Define how to process texture name conflict", + default = 'CURRENT', + ) + + material_conflict_strategy: bpy.props.EnumProperty( + name = "Material name conflict", + items = ( + ('RENAME', "Rename", "Rename the new one"), + ('CURRENT', "Use Current", "Use current one"), + ), + description = "Define how to process material name conflict", + default = 'RENAME', + ) + + mesh_conflict_strategy: bpy.props.EnumProperty( + name = "Mesh name conflict", + items = ( + ('RENAME', "Rename", "Rename the new one"), + ('CURRENT', "Use Current", "Use current one"), + ), + description = "Define how to process mesh name conflict", + default = 'RENAME', + ) + + object_conflict_strategy: bpy.props.EnumProperty( + name = "Object name conflict", + items = ( + ('RENAME', "Rename", "Rename the new one"), + ('CURRENT', "Use Current", "Use current one"), + ), + description = "Define how to process object name conflict", + default = 'RENAME', + ) + + def draw_import_params(self, layout: bpy.types.UILayout) -> None: + layout.prop(self, 'object_conflict_strategy') + layout.prop(self, 'mesh_conflict_strategy') + layout.prop(self, 'material_conflict_strategy') + layout.prop(self, 'texture_conflict_strategy') + +class ExportParams(): + export_mode: bpy.props.EnumProperty( + name = "Export Mode", + items = ( + ('COLLECTION', "Collection", "Export a collection"), + ('OBJECT', "Object", "Export an object"), + ), + ) + + def draw_export_params(self, layout: bpy.types.UILayout) -> None: + # draw switch + layout.prop(self, "export_mode", expand = True) + # draw picker + if self.export_mode == 'COLLECTION': + PROP_ptrprop_resolver.draw_export_collection(layout) + elif self.export_mode == 'OBJECT': + PROP_ptrprop_resolver.draw_export_object(layout) + + def general_get_export_objects(self) -> tuple[bpy.types.Object] | None: + """ + Return resolved exported objects or None if no selection. + """ + if self.export_mode == 'COLLECTION': + col: bpy.types.Collection = PROP_ptrprop_resolver.get_export_collection() + if col is None: return None + else: + return tuple(col.all_objects) + else: + obj: bpy.types.Object = PROP_ptrprop_resolver.get_export_object() + if obj is None: return None + else: return (obj, ) + +class VirtoolsParams(): + vt_encodings: bpy.props.StringProperty( + name = "Encodings", + description = "The encoding list used by Virtools engine to resolve object name. Use `;` to split multiple encodings", + default = UTIL_functions.g_PyBMapDefaultEncoding + ) + + def draw_virtools_params(self, layout: bpy.types.UILayout) -> None: + layout.prop(self, 'vt_encodings') + + def general_get_vt_encodings(self) -> tuple[str]: + # get encoding, split it by `;` and strip blank chars. + encodings: str = self.vt_encodings + return tuple(map(lambda x: x.strip(), encodings.split(';'))) + +class ExportEditModeBackup(): + """ + The class which save Edit Mode when exporting and restore it after exporting. + Because edit mode is not allowed when exporting. + Support `with` statement. + """ + mInEditMode: bool + + def __init__(self): + if bpy.context.object and bpy.context.object.mode == "EDIT": + # set and toggle it. otherwise exporting will failed. + self.mInEditMode = True + bpy.ops.object.editmode_toggle() + else: + self.mInEditMode = False + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + if self.mInEditMode: + bpy.ops.object.editmode_toggle() + self.mInEditMode = False + + diff --git a/bbp_ng/__init__.py b/bbp_ng/__init__.py index 72ad304..7f1adf6 100644 --- a/bbp_ng/__init__.py +++ b/bbp_ng/__init__.py @@ -23,7 +23,7 @@ if "bpy" in locals(): #endregion -from . import PROP_preferences, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_ballance_element, PROP_virtools_group +from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_ballance_element, PROP_virtools_group from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools from . import OP_UV_flatten_uv, OP_UV_rail_uv @@ -85,6 +85,8 @@ g_BldMenus: tuple[MenuEntry, ...] = ( def register() -> None: # register module PROP_preferences.register() + PROP_ptrprop_resolver.register() + PROP_virtools_material.register() PROP_virtools_texture.register() PROP_virtools_mesh.register() @@ -133,6 +135,8 @@ def unregister() -> None: PROP_virtools_mesh.unregister() PROP_virtools_texture.unregister() PROP_virtools_material.unregister() + + PROP_ptrprop_resolver.unregister() PROP_preferences.unregister() if __name__ == "__main__":