Files
vrm-mesh-tool/panel.py
小煜 3bbb46bca7 feat: 添加VRM网格工具插件,支持网格分离和UV边融并功能
添加VRM网格工具插件,包含以下主要功能:
- Body网格分离:将非皮肤材质面分离为独立对象
- UV边融并:根据预定义UV数据将三角面转换为四边面
- 提供独立面板和操作符,与VRM官方插件兼容
- 包含材质处理顺序和自动转换逻辑
- 添加JSON数据文件和工具函数
2026-01-02 14:11:43 +08:00

135 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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")