feat(vrm_mesh_tools): Add standalone VRM mesh tools addon with separation and UV merging

- Add VRM Mesh Tools addon as independent Blender extension with complete functionality
- Implement Body Mesh Separator operator to isolate non-skin materials (CLOTH, etc.) from Body mesh
- Implement UV Edge Merger operator to convert triangles to quads based on UV coordinates
- Add comprehensive UI panel in sidebar with material list and operation buttons
- Include UV edge data JSON files for 10 VRM material types (Body, Face, Eyes, Hair, etc.)
- Add detailed README with installation instructions, usage guide, and compatibility notes
- Configure addon manifest for Blender 4.2+ with proper namespace isolation (vrm_mesh_tools)
- Ensure full compatibility with official VRM addon through independent naming conventions
- Add utility functions for mesh operations, material detection, and JSON data loading
This commit is contained in:
2026-01-02 14:06:18 +08:00
parent 21222044c4
commit 77806a351f
18 changed files with 96964 additions and 0 deletions

134
vrm_mesh_tools/panel.py Normal file
View File

@@ -0,0 +1,134 @@
# SPDX-License-Identifier: MIT
# VRM Mesh Tools - UI Panel for mesh separation and UV edge merging
from collections.abc import Set as AbstractSet
from typing import TYPE_CHECKING, Optional
from bpy.types import Context, Panel
from .utils import (
MaterialInfo,
find_body_mesh,
find_face_mesh,
get_material_face_count,
is_retained_material,
)
if TYPE_CHECKING:
from bpy.types import Object
class VRM_TOOLS_PT_mesh_separator(Panel):
"""VRM Mesh Tools 面板 - 提供网格分离和UV边融并功能"""
bl_idname = "VRM_TOOLS_PT_mesh_separator"
bl_label = "VRM Mesh Tools"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "VRM Tools" # 独立的侧边栏分类
bl_options: AbstractSet[str] = set()
@classmethod
def poll(cls, context: Context) -> bool:
"""检查是否应该显示面板 - 仅在选中包含Body网格的Armature时显示"""
armature = cls._get_armature(context)
if armature is None:
return False
body_mesh = find_body_mesh(armature)
return body_mesh is not None
@classmethod
def _get_armature(cls, context: Context) -> Optional["Object"]:
"""获取当前选中的Armature对象"""
active_object = context.active_object
if active_object is None:
return None
if active_object.type == "ARMATURE":
return active_object
# 如果选中的是网格检查其父对象是否为Armature
if active_object.parent is not None and active_object.parent.type == "ARMATURE":
return active_object.parent
return None
def draw(self, context: Context) -> None:
"""绘制面板UI"""
layout = self.layout
armature = self._get_armature(context)
if armature is None:
layout.label(text="未找到Armature对象", icon="ERROR")
return
body_mesh = find_body_mesh(armature)
if body_mesh is None:
layout.label(text="未找到Body网格对象", icon="ERROR")
return
mesh_data = body_mesh.data
if mesh_data is None:
layout.label(text="Body网格数据无效", icon="ERROR")
return
# 显示材质统计信息(区分保留材质和可分离材质)
box = layout.box()
box.label(text="材质列表", icon="MATERIAL")
# 收集材质信息使用is_retained_material分类
materials_info: list[MaterialInfo] = []
for i, material in enumerate(mesh_data.materials):
if material is not None:
face_count = get_material_face_count(mesh_data, i)
is_retained = is_retained_material(material.name)
materials_info.append(
MaterialInfo(
name=material.name,
index=i,
face_count=face_count,
is_skin=is_retained, # 使用is_skin字段存储保留状态
)
)
# 显示材质列表区分保留材质SKIN/HAIR和可分离材质CLOTH和其他
has_separable = False
for mat_info in materials_info:
row = box.row()
if mat_info.is_skin: # is_skin字段现在表示是否为保留材质
# 保留材质SKIN/HAIR标记为"[保留]"
row.label(text=f"[保留] {mat_info.name}", icon="CHECKMARK")
else:
# 可分离材质CLOTH和其他标记为"[分离]"
row.label(text=f"[分离] {mat_info.name}", icon="MESH_DATA")
has_separable = True
row.label(text=f"{mat_info.face_count}")
# 显示分离操作按钮
layout.separator()
col = layout.column()
col.enabled = has_separable
col.operator(
"vrm_tools.separate_non_skin_faces",
text="分离非皮肤面",
icon="MESH_DATA",
)
if not has_separable:
layout.label(text="没有可分离材质", icon="INFO")
# 显示UV边融并按钮
layout.separator()
face_mesh = find_face_mesh(armature) if armature else None
has_vrm_structure = body_mesh is not None and face_mesh is not None
col = layout.column()
col.enabled = has_vrm_structure
col.operator(
"vrm_tools.merge_uv_edges",
text="根据UV转四边面",
icon="MOD_EDGESPLIT",
)
if not has_vrm_structure:
layout.label(text="需要Body和Face网格", icon="INFO")