feat(body_mesh): Add body mesh separator module with edge dissolution

- Add body_mesh submodule with VRM_OT_separate_non_skin_faces operator
- Add VRM_PT_body_mesh_separator panel for UI integration
- Add utility functions for mesh operations and edge dissolution logic
- Add comprehensive edge dissolution configuration files for all body parts (head, torso, arms, legs, hands) with UV coordinates
- Add test utilities for validating mesh separation operations
- Add .gitignore to exclude Python cache files and test artifacts
- Register body_mesh module in editor package initialization
- Update registration.py to include new body mesh components
This commit is contained in:
2026-01-01 22:20:31 +08:00
parent 091ad6a49a
commit d70b36fd75
23 changed files with 87935 additions and 0 deletions

113
editor/body_mesh/panel.py Normal file
View File

@@ -0,0 +1,113 @@
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
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,
get_material_face_count,
is_skin_material,
)
if TYPE_CHECKING:
from bpy.types import Object
class VRM_PT_body_mesh_separator(Panel):
"""Body网格分离面板"""
bl_idname = "VRM_PT_body_mesh_separator"
bl_label = "Body Mesh Separator"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "VRM"
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")
# 收集材质信息
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_skin = is_skin_material(material.name)
materials_info.append(
MaterialInfo(
name=material.name,
index=i,
face_count=face_count,
is_skin=is_skin,
)
)
# 显示材质列表
has_non_skin = False
for mat_info in materials_info:
row = box.row()
if mat_info.is_skin:
row.label(text=f"[皮肤] {mat_info.name}", icon="CHECKMARK")
else:
row.label(text=f"[非皮肤] {mat_info.name}", icon="MESH_DATA")
has_non_skin = True
row.label(text=f"{mat_info.face_count}")
# 显示分离操作按钮
layout.separator()
col = layout.column()
col.enabled = has_non_skin
col.operator(
"vrm.separate_non_skin_faces",
text="分离非皮肤面",
icon="MESH_DATA",
)
if not has_non_skin:
layout.label(text="没有非皮肤材质可分离", icon="INFO")