From ac7ded1061fd4010575680aa5893d64ce516dc12 Mon Sep 17 00:00:00 2001 From: NicoMico Date: Wed, 24 Jul 2024 10:54:26 +0800 Subject: [PATCH] optimize --- MMT/__init__.py | 3 +- MMT/mesh_operator.py | 93 +++++++++++++++++++++++++++++++++------ MMT/migoto_format.py | 33 +++++++------- MMT/panel.py | 47 ++++++++++---------- README | 52 ++++++++++++++++++++++ README.md => README_EN.md | 0 6 files changed, 173 insertions(+), 55 deletions(-) create mode 100644 README rename README.md => README_EN.md (100%) diff --git a/MMT/__init__.py b/MMT/__init__.py index ab78872..a2a04e9 100644 --- a/MMT/__init__.py +++ b/MMT/__init__.py @@ -16,7 +16,7 @@ "original author": "DarkStarSword", "description": "Special fork version of DarkStarSword's blender_3dmigoto.py", "blender": (3, 6, 0), - "version": (1, 5, 3), + "version": (1, 5, 4), "location": "View3D", "warning": "", "category": "Generic" @@ -47,6 +47,7 @@ MMTCancelAutoSmooth, MMTShowIndexedVertices, MMTSetAutoSmooth89, + SplitMeshByCommonVertexGroup, # MMT的一键导入导出 MMTImportAllTextModel, diff --git a/MMT/mesh_operator.py b/MMT/mesh_operator.py index 03adc1f..8502f6a 100644 --- a/MMT/mesh_operator.py +++ b/MMT/mesh_operator.py @@ -26,7 +26,7 @@ def remove_unused_vertex_group(self, context): class RemoveUnusedVertexGroupOperator(bpy.types.Operator): bl_idname = "object.remove_unused_vertex_group" - bl_label = "Remove Unused Vertex Group" + bl_label = "移除未使用的空顶点组" def execute(self, context): return remove_unused_vertex_group(self, context) @@ -109,7 +109,7 @@ class Fatal(Exception): class MergeVertexGroupsWithSameNumber(bpy.types.Operator): bl_idname = "object.merge_vertex_group_with_same_number" - bl_label = "Merge Vertex Groups" + bl_label = "合并具有相同数字前缀名称的顶点组" def execute(self, context): return merge_vertex_group_with_same_number(self, context) @@ -147,7 +147,7 @@ def fill_vertex_group_gaps(self, context): class FillVertexGroupGaps(bpy.types.Operator): bl_idname = "object.fill_vertex_group_gaps" - bl_label = "Fill Vertex Group Gaps" + bl_label = "填充数字顶点组的间隙" def execute(self, context): return fill_vertex_group_gaps(self, context) @@ -199,7 +199,7 @@ def add_bone_from_vertex_group(self, context): class AddBoneFromVertexGroup(bpy.types.Operator): bl_idname = "object.add_bone_from_vertex_group" - bl_label = "Add Bone From Vertex Group" + bl_label = "根据顶点组自动生成骨骼" def execute(self, context): return add_bone_from_vertex_group(self, context) @@ -217,7 +217,7 @@ def remove_not_number_vertex_group(self, context): class RemoveNotNumberVertexGroup(bpy.types.Operator): bl_idname = "object.remove_not_number_vertex_group" - bl_label = "Remove Not Number Vertex Group" + bl_label = "移除非数字名称的顶点组" def execute(self, context): return remove_not_number_vertex_group(self, context) @@ -286,7 +286,7 @@ def convert_to_fragment(self, context): class ConvertToFragmentOperator(bpy.types.Operator): bl_idname = "object.convert_to_fragment" - bl_label = "Convert To Fragment" + bl_label = "转换为一个3Dmigoto碎片用于合并" def execute(self, context): return convert_to_fragment(self, context) @@ -311,7 +311,7 @@ def delete_loose(self, context): class MMTDeleteLoose(bpy.types.Operator): bl_idname = "object.mmt_delete_loose" - bl_label = "Delete Mesh's Loose Vertex" + bl_label = "删除物体的松散点" def execute(self, context): return delete_loose(self, context) @@ -333,7 +333,7 @@ def mmt_reset_rotation(self, context): class MMTResetRotation(bpy.types.Operator): bl_idname = "object.mmt_reset_rotation" - bl_label = "Reset x,y,z rotation number to 0 (UE Model)" + bl_label = "重置x,y,z的旋转角度为0 (UE Model)" def execute(self, context): return mmt_reset_rotation(self, context) @@ -350,7 +350,7 @@ def mmt_cancel_auto_smooth(self, context): class MMTCancelAutoSmooth(bpy.types.Operator): bl_idname = "object.mmt_cancel_auto_smooth" - bl_label = "Cancel Auto Smooth for NORMAL (UE Model)" + bl_label = "取消自动平滑 (UE Model)" def execute(self, context): return mmt_cancel_auto_smooth(self, context) @@ -369,7 +369,7 @@ def mmt_set_auto_smooth_89(self, context): class MMTSetAutoSmooth89(bpy.types.Operator): bl_idname = "object.mmt_set_auto_smooth_89" - bl_label = "Set Auto Smooth to 89° (Unity)" + bl_label = "设置Normal的自动平滑为89° (Unity)" def execute(self, context): return mmt_set_auto_smooth_89(self, context) @@ -432,12 +432,69 @@ def show_indexed_vertices(self, context): class MMTShowIndexedVertices(bpy.types.Operator): bl_idname = "object.mmt_show_indexed_vertices" - bl_label = "Show Indexed Vertices and Indexes Number" + bl_label = "展示Indexed Vertices和Indexes Number" def execute(self, context): return show_indexed_vertices(self, context) +def split_mesh_by_common_vertex_group(self, context): + for obj in bpy.context.selected_objects: + origin_name = obj.name + keys = obj.vertex_groups.keys() + real_keys = [] + for gr in keys: + bpy.ops.object.mode_set(mode="EDIT") + # Set the vertex group as active + obj.vertex_groups.active_index = obj.vertex_groups[gr].index + + # Deselect all verts and select only current VG + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.object.vertex_group_select() + + try: + bpy.ops.mesh.separate(type="SELECTED") + real_keys.append(gr) + except: + pass + + for i in range(1, len(real_keys) + 1): + new_obj = bpy.data.objects['{}.{:03d}'.format(origin_name, i)] + new_obj.name = '{}.{}'.format(origin_name, real_keys[i - 1]) + # for obj in bpy.context.selected_objects: + # origin_name = obj.name + # keys = obj.vertex_groups.keys() + # real_keys = [] + # for gr in keys: + # bpy.ops.object.mode_set(mode="EDIT") + # # Set the vertex group as active + # bpy.ops.object.vertex_group_set_active(group=gr) + # + # # Deselect all verts and select only current VG + # bpy.ops.mesh.select_all(action='DESELECT') + # bpy.ops.object.vertex_group_select() + # # bpy.ops.mesh.select_all(action='INVERT') + # try: + # bpy.ops.mesh.separate(type="SELECTED") + # real_keys.append(gr) + # except: + # pass + # for i in range(1, len(real_keys) + 1): + # bpy.data.objects['{}.{:03d}'.format(origin_name, i)].name = '{}.{}'.format( + # origin_name, real_keys[i - 1]) + + return {'FINISHED'} + + +class SplitMeshByCommonVertexGroup(bpy.types.Operator): + bl_idname = "object.split_mesh_by_common_vertex_group" + bl_label = "根据相同的顶点组分割物体" + + def execute(self, context): + return split_mesh_by_common_vertex_group(self, context) + + + # -----------------------------------这个属于右键菜单注册,单独的函数要往上面放--------------------------------------- class MigotoRightClickMenu(bpy.types.Menu): bl_idname = "VIEW3D_MT_object_3Dmigoto" @@ -456,6 +513,7 @@ def draw(self, context): layout.operator("object.mmt_cancel_auto_smooth") layout.operator("object.mmt_set_auto_smooth_89") layout.operator("object.mmt_show_indexed_vertices") + layout.operator("object.split_mesh_by_common_vertex_group") # 定义菜单项的注册函数 @@ -486,6 +544,7 @@ def execute(self, context): output_folder_path = mmt_path + "Games\\" + current_game + "\\3Dmigoto\\Mods\\output\\" + # 这里是根据Config.json中的DrawIB来决定导入时导入具体哪个IB import_folder_path_list = [] for ib_config in game_config_json: draw_ib = ib_config["DrawIB"] @@ -514,6 +573,10 @@ def execute(self, context): # 使用 glob.glob 获取匹配的文件列表 txt_file_list = glob(file_pattern) for txt_file_path in txt_file_list: + # 如果文件名不包含-则属于我们自动导出的文件名,则不计入统计 + if os.path.basename(txt_file_path).find("-") == -1: + continue + # self.report({'INFO'}, "txt file: " + txt_file_path) txt_file_splits = os.path.basename(txt_file_path).split("-") ib_file_name = txt_file_splits[0] + "-" + txt_file_splits[1] @@ -582,9 +645,11 @@ def execute(self, context): selected_collection = bpy.context.collection # 遍历选中的对象 + export_time = 0 for obj in selected_collection.objects: # 判断对象是否为网格对象 if obj.type == 'MESH': + export_time = export_time + 1 bpy.context.view_layer.objects.active = obj mesh = obj.data # 获取网格数据 @@ -610,7 +675,9 @@ def execute(self, context): # FIXME: ExportHelper will check for overwriting vb_path, but not ib_path export_3dmigoto(self, context, vb_path, ib_path, fmt_path) - - self.report({'INFO'}, "Export Success!") + if export_time == 0: + self.report({'ERROR'}, "导出失败!请选择一个集合后再点一键导出!") + else: + self.report({'INFO'}, "一键导出成功!成功导出的部位数量:" + str(export_time)) return {'FINISHED'} diff --git a/MMT/migoto_format.py b/MMT/migoto_format.py index 08807ae..1c63091 100644 --- a/MMT/migoto_format.py +++ b/MMT/migoto_format.py @@ -1,4 +1,6 @@ # This migoto_format.py is only used in 3dmigoto import and export options. +import os.path + from .panel import * # 这里使用type关键字创建了一个类,类名是DummyIOOBJOrientationHelper,(object,)表示继承自object对象,{}表示没定义属性和方法 @@ -882,6 +884,9 @@ def create_material_with_texture(obj, mesh_name, directory): material_name = f"{mesh_name}_Material" texture_name = f"{mesh_name}-DiffuseMap.jpg" + texture_prefix = str(mesh_name).split("-")[0] # Hash值 + texture_suffix = "-DiffuseMap.jpg" + # Создание нового материала (Create new materials) material = bpy.data.materials.new(name=material_name) material.use_nodes = True @@ -893,7 +898,7 @@ def create_material_with_texture(obj, mesh_name, directory): if bsdf: # Поиск текстуры (Search for textures) - texture_path = find_texture(texture_name, directory, False) + texture_path = find_texture(texture_prefix, texture_suffix, directory) if texture_path: tex_image = material.node_tree.nodes.new('ShaderNodeTexImage') tex_image.image = bpy.data.images.load(texture_path) @@ -906,22 +911,12 @@ def create_material_with_texture(obj, mesh_name, directory): obj.data.materials.append(material) -def find_texture(texture_name, directory, debug=False): - if debug: - print(f"Ищем текстуру (Searching for textures): {texture_name} в директории (catalogue): {directory}") +def find_texture(texture_prefix, texture_suffix, directory): for root, dirs, files in os.walk(directory): - if debug: - print(f"Проверяем директорию (check directories): {root}") for file in files: - if debug: - print(f"Найден файл (file found): {file}") - if file.endswith(texture_name): + if file.endswith(texture_suffix) and file.startswith(texture_prefix): texture_path = os.path.join(root, file) - if debug: - print(f"Найдена текстура (Find texture): {texture_path}") return texture_path - if debug: - print(f"Текстура не найдена (Unable to find texture): {texture_name}") return None @@ -981,10 +976,10 @@ def import_3dmigoto_vb_ib(operator, context, paths, flip_texcoord_v=True, axis_f bm.from_mesh(mesh) # 删除松散点 delete loose before get this - bm.verts.ensure_lookup_table() - for v in bm.verts: - if not v.link_faces: - bm.verts.remove(v) + # bm.verts.ensure_lookup_table() + # for v in bm.verts: + # if not v.link_faces: + # bm.verts.remove(v) # 将 BMesh 更新回原始网格 bm.to_mesh(mesh) @@ -1410,7 +1405,9 @@ def execute(self, context): migoto_raw_import_options = self.as_keywords(ignore=('filepath', 'files', 'filter_glob')) # 我们需要添加到一个新建的集合里,方便后续操作 - collection = bpy.data.collections.new("MMT-Import") + # 这里集合的名称需要为当前文件夹的名称 + collection_name = os.path.basename(os.path.dirname(self.filepath)) + collection = bpy.data.collections.new(collection_name) bpy.context.scene.collection.children.link(collection) done = set() diff --git a/MMT/panel.py b/MMT/panel.py index b29c77b..1073797 100644 --- a/MMT/panel.py +++ b/MMT/panel.py @@ -68,7 +68,7 @@ def load_path(): class MMTPathProperties(bpy.types.PropertyGroup): path: bpy.props.StringProperty( - name="MMT Path", + name="主路径", description="Select a folder path of MMT", default=load_path(), subtype='DIR_PATH' @@ -87,7 +87,7 @@ def execute(self, context): # MMT的侧边栏 class MMTPanel(bpy.types.Panel): - bl_label = "MMT Panel" + bl_label = "MMT面板" bl_idname = "VIEW3D_PT_MMT_panel" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' @@ -100,9 +100,9 @@ def draw(self, context): row = layout.row() # 在这里添加你的侧边栏内容 - row.label(text="MMT's Blender Plugin V1.5.3") + row.label(text="版本:V1.5.4") - row.operator("wm.url_open", text="Check Update", icon='URL').url = "https://github.com/StarBobis/MMT-Blender-Plugin" + row.operator("wm.url_open", text="检查更新", icon='URL').url = "https://github.com/StarBobis/MMT-Blender-Plugin" props = context.scene.mmt_props layout.prop(props, "path") @@ -110,9 +110,9 @@ def draw(self, context): mmt_path = os.path.join(context.scene.mmt_props.path, "MMT.exe") mmt_location = os.path.dirname(mmt_path) if os.path.exists(mmt_path): - layout.label(text="MMT: " + mmt_path) + layout.label(text="MMT主程序: " + mmt_path) else: - layout.label(text="Error: Invalid MMT path! ", icon='ERROR') + layout.label(text="错误:未选择MMT主路径 ", icon='ERROR') # 读取MainSetting.json中当前游戏名称 current_game = "" @@ -122,50 +122,51 @@ def draw(self, context): main_setting_json = json.load(main_setting_file) main_setting_file.close() current_game = main_setting_json["GameName"] - layout.label(text="Working game: " + current_game) + layout.label(text="当前游戏: " + current_game) else: - layout.label(text="Error: Can't GameName find Main.json! ", icon='ERROR') + layout.label(text="错误:未选择MMT主路径 ", icon='ERROR') # 根据当前游戏名称,读取GameSetting中的OutputFolder路径并设置 output_folder_path = mmt_location + "\\Games\\" + current_game + "\\3Dmigoto\\Mods\\output\\" layout.separator() - layout.label(text="Seperate import&export in OutputFolder") + layout.label(text="在OutputFolder中导入或导出") # 快速导入,点这个之后默认路径为OutputFolder,这样直接就能去导入不用翻很久文件夹找路径了 - operator_import_txt = self.layout.operator("import_mesh.migoto_frame_analysis_mmt", text="Import .txt models ") + operator_import_txt = self.layout.operator("import_mesh.migoto_frame_analysis_mmt", text="导入 .txt 模型文件") operator_import_txt.directory = output_folder_path # 新增快速导入buf文件 - operator_import_ib_vb = self.layout.operator("import_mesh.migoto_raw_buffers_mmt", text="Import .ib & .vb models ") + operator_import_ib_vb = self.layout.operator("import_mesh.migoto_raw_buffers_mmt", text="导入 .ib & .vb 模型文件") operator_import_ib_vb.filepath = output_folder_path # 快速导出同理,点这个之后默认路径为OutputFolder,这样直接就能去导出不用翻很久文件夹找路径了 - operator_export_ibvb = self.layout.operator("export_mesh.migoto_mmt", text="Export .ib & .vb files ") + operator_export_ibvb = self.layout.operator("export_mesh.migoto_mmt", text="导出 .ib & .vb 模型文件") operator_export_ibvb.filepath = output_folder_path + "1.vb" # 添加分隔符 layout.separator() # 一键快速导入所有位于OutputFolder下的.txt模型 - layout.label(text="Fast import&export in OutputFolder") - operator_fast_import = self.layout.operator("mmt.import_all", text="Import All .ib .vb model in OutputFolder ") + layout.label(text="在OutputFolder中一键导入导出") + operator_fast_import = self.layout.operator("mmt.import_all", text="一键导入所有.ib & .vb模型文件") # 一键快速导出当前选中Collection中的所有model到对应的hash值文件夹中,并直接调用MMT.exe的Mod生成方法,做到导出完即可游戏里F10刷新看效果。 - operator_export_ibvb = self.layout.operator("mmt.export_all", text="Export selected collection's vb model to OutputFolder") + operator_export_ibvb = self.layout.operator("mmt.export_all", text="一键导出选中的MMT集合") # 添加分隔符 layout.separator() # 导出MMD的Bone Matrix,连续骨骼变换矩阵,并生成ini文件 - row = layout.row() - row.label(text="MMD Animation Mod") - operator_export_mmd_bone_matrix = row.operator("mmt.export_mmd_animation_mod", text="Export MMD Mod") - operator_export_mmd_bone_matrix.output_folder = output_folder_path - row = layout.row() - row.prop(context.scene, "mmt_mmd_animation_mod_start_frame") - row.prop(context.scene, "mmt_mmd_animation_mod_end_frame") - row.prop(context.scene, "mmt_mmd_animation_mod_play_speed") + # TODO 暂时用不上,在Blender里进行这个动画Mod的ini生成不够优雅,后续考虑更好的办法吧。 + # row = layout.row() + # row.label(text="MMD Animation Mod") + # operator_export_mmd_bone_matrix = row.operator("mmt.export_mmd_animation_mod", text="Export MMD Mod") + # operator_export_mmd_bone_matrix.output_folder = output_folder_path + # row = layout.row() + # row.prop(context.scene, "mmt_mmd_animation_mod_start_frame") + # row.prop(context.scene, "mmt_mmd_animation_mod_end_frame") + # row.prop(context.scene, "mmt_mmd_animation_mod_play_speed") # # # 添加分隔符 diff --git a/README b/README new file mode 100644 index 0000000..c2d18cc --- /dev/null +++ b/README @@ -0,0 +1,52 @@ +# MMT的Blender插件 + +本插件从DarkStartSword的3Dfixs仓库fork并做了一些改进来更好的适应我们的需求,主要是为MMT-Community(MigotoModTool)开发的配套插件, +也可以作为通用插件使用。 + +原仓库地址:https://github.com/DarkStarSword/3d-fixes + +# 注意事项 +1,如何使用? + +克隆项目后,把MMT文件夹打包为MMT.zip然后直接在Blender里安装即可,或者你也可以下载Release里的版本。 + +2,本项目的代码可能会直接复制或者从其它人的代码中学习,当然是在遵守开源协议的前提下,对应用到的代码会给出具体的来源和署名。 + +3,本项目仍在不断改进中,任何API都可能随时改变,如果你发现版本更新后代码产生了较大的变动,不要感到惊讶,这是正常的,永远使用最新版本即可。 + +# 用户使用环境 +本插件只能在Blender官方版本使用,其它版本比如Steam版本可能存在兼容性问题。 + +当前支持的Blender版本:Blender 3.6 LTS (Windows Installer) + +https://www.blender.org/download/lts/3-6/ + +# 开发环境配置 +- 操作系统: Windows 11 Pro +- IDE集成开发环境: Pycharm Community 2023.3 +- Pycharm插件: Pycharm-Blender-Plugin(https://github.com/BlackStartx/PyCharm-Blender-Plugin) 2023.3 +- Blender版本: Blender3.6LTS + +额外需要导入的包: +- Fake bpy: https://github.com/nutti/fake-bpy-module (pip install fake-bpy-module-3.6) +- Numpy: pip install numpy + +注意如果你要向本仓库提交代码,请确保代码的注释和可读性拉满,Python不是为了让你炫技而是为了方便团队合作所以不要使用过于高级而罕见的语法特性。 + +# LICENSE开源协议 +- GNU GENERAL PUBLIC LICENSE Version 3 +- Mozilla Public License 2.0 + +# 致谢 +The original code is mainly forked from @Ian Munsie (darkstarsword@gmail.com), +see https://github.com/DarkStarSword/3d-fixes, +big thanks to his original blender plugin design. + +And part of the code is learned or copied from these projects below, +their codes use their project's LICENSE ,you can find LICENSE file in LICENSE folder, +huge thanks for their great code: +- https://github.com/SilentNightSound/GI-Model-Importer +- https://github.com/SilentNightSound/SR-Model-Importer +- https://github.com/leotorrez/LeoTools +- https://github.com/falling-ts/free-model +- https://github.com/SpectrumQT/WWMI-TOOLS \ No newline at end of file diff --git a/README.md b/README_EN.md similarity index 100% rename from README.md rename to README_EN.md