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

88
editor/body_mesh/utils.py Normal file
View File

@@ -0,0 +1,88 @@
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from bpy.types import Mesh, Object
@dataclass
class MaterialInfo:
"""材质信息数据类"""
name: str
index: int
face_count: int
is_skin: bool
def find_body_mesh(armature: "Object") -> Optional["Object"]:
"""
在Armature的子对象中查找名为"Body"的网格对象
Args:
armature: Armature对象
Returns:
Body网格对象如果不存在则返回None
"""
if armature is None:
return None
for child in armature.children:
if child.type == "MESH" and child.name == "Body":
return child
return None
def is_skin_material(material_name: str) -> bool:
"""
判断材质是否为皮肤材质
Args:
material_name: 材质名称
Returns:
如果材质名包含"SKIN"则返回True不区分大小写
"""
return "SKIN" in material_name.upper()
def get_material_face_count(mesh: "Mesh", material_index: int) -> int:
"""
获取指定材质的面数量
Args:
mesh: 网格数据
material_index: 材质索引
Returns:
使用该材质的面数量
"""
count = 0
for polygon in mesh.polygons:
if polygon.material_index == material_index:
count += 1
return count
def generate_unique_name(base_name: str, existing_names: set[str]) -> str:
"""
生成唯一的对象名称
Args:
base_name: 基础名称
existing_names: 已存在的名称集合
Returns:
唯一的名称
"""
if base_name not in existing_names:
return base_name
suffix = 1
while True:
candidate = f"{base_name}.{suffix:03d}"
if candidate not in existing_names:
return candidate
suffix += 1