feat: Add VRM Blender addon with complete import/export functionality

- Add core VRM addon infrastructure with manifest and registration
- Add common utilities module with file system, logging, and conversion helpers
- Add human bone mapper with support for multiple rigging standards (Mixamo, MMD, Unreal, Rigify, etc.)
- Add VRM 0.x and 1.x format support with property groups and handlers
- Add editor UI panels for VRM metadata, spring bones, and MToon materials
- Add exporter with glTF2 extension support for VRM format serialization
- Add importer with scene reconstruction and armature generation
- Add MToon shader support with auto-setup and material migration
- Add spring bone physics simulation with constraint handling
- Add node constraint editor for advanced rigging control
- Add comprehensive validation and error handling with user dialogs
- Add scene watcher for real-time property synchronization
- Add workspace management and preference system
- Include Python cache files and Blender manifest configuration
- This is the initial commit establishing the complete VRM addon ecosystem for Blender
This commit is contained in:
2026-01-01 14:21:56 +08:00
commit 091ad6a49a
243 changed files with 60636 additions and 0 deletions

302
editor/vrm1/ui_list.py Normal file
View File

@@ -0,0 +1,302 @@
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
from bpy.types import Context, Mesh, UILayout, UIList
from ...common.logger import get_logger
from ..property_group import StringPropertyGroup
from .property_group import (
Vrm1ExpressionsPropertyGroup,
Vrm1FirstPersonPropertyGroup,
Vrm1MaterialColorBindPropertyGroup,
Vrm1MeshAnnotationPropertyGroup,
Vrm1MetaPropertyGroup,
Vrm1MorphTargetBindPropertyGroup,
Vrm1TextureTransformBindPropertyGroup,
)
logger = get_logger(__name__)
class VRM_UL_vrm1_meta_author(UIList):
bl_idname = "VRM_UL_vrm1_meta_author"
def draw_item(
self,
_context: Context,
layout: UILayout,
meta: object,
author: object,
_icon: int,
_active_data: object,
_active_prop_name: str,
index: int,
_flt_flag: int,
) -> None:
if not isinstance(meta, Vrm1MetaPropertyGroup):
return
if not isinstance(author, StringPropertyGroup):
return
icon = "USER"
if self.layout_type == "GRID":
layout.alignment = "CENTER"
layout.label(text="", translate=False, icon=icon)
return
if self.layout_type not in {"DEFAULT", "COMPACT"}:
return
if index == meta.active_author_index:
layout.prop(author, "value", icon=icon, text="", translate=False)
else:
layout.label(text=author.value, icon=icon, translate=False)
class VRM_UL_vrm1_meta_reference(UIList):
bl_idname = "VRM_UL_vrm1_meta_reference"
def draw_item(
self,
_context: Context,
layout: UILayout,
meta: object,
reference: object,
_icon: int,
_active_data: object,
_active_prop_name: str,
index: int,
_flt_flag: int,
) -> None:
if not isinstance(meta, Vrm1MetaPropertyGroup):
return
if not isinstance(reference, StringPropertyGroup):
return
icon = "URL"
if self.layout_type == "GRID":
layout.alignment = "CENTER"
layout.label(text="", translate=False, icon=icon)
return
if self.layout_type not in {"DEFAULT", "COMPACT"}:
return
if index == meta.active_reference_index:
layout.prop(reference, "value", icon=icon, text="", translate=False)
else:
layout.label(text=reference.value, icon=icon, translate=False)
class VRM_UL_vrm1_first_person_mesh_annotation(UIList):
bl_idname = "VRM_UL_vrm1_first_person_mesh_annotation"
def draw_item(
self,
_context: Context,
layout: UILayout,
first_person: object,
mesh_annotation: object,
_icon: int,
_active_data: object,
_active_prop_name: str,
index: int,
_flt_flag: int,
) -> None:
if not isinstance(first_person, Vrm1FirstPersonPropertyGroup):
return
if not isinstance(mesh_annotation, Vrm1MeshAnnotationPropertyGroup):
return
icon = "OUTLINER_OB_MESH"
if self.layout_type == "GRID":
layout.alignment = "CENTER"
layout.label(text="", translate=False, icon=icon)
return
if self.layout_type not in {"DEFAULT", "COMPACT"}:
return
row = layout.split(factor=0.6, align=True)
if index == first_person.active_mesh_annotation_index:
row.prop(
mesh_annotation.node,
"bpy_object",
text="",
translate=False,
icon=icon,
)
else:
row.label(
text=mesh_annotation.node.mesh_object_name,
translate=False,
icon=icon,
)
row.prop(mesh_annotation, "type", text="", translate=False)
class VRM_UL_vrm1_expression(UIList):
bl_idname = "VRM_UL_vrm1_expression"
def draw_item(
self,
_context: Context,
layout: UILayout,
expressions: object,
_item: object,
_icon: int,
_active_data: object,
_active_prop_name: str,
index: int,
_flt_flag: int,
) -> None:
if not isinstance(expressions, Vrm1ExpressionsPropertyGroup):
return
preset_expression_items = list(
expressions.preset.name_to_expression_dict().items()
)
if index < len(preset_expression_items):
name, expression = preset_expression_items[index]
icon = expressions.preset.get_icon(name)
if not icon:
logger.error("Unknown preset expression: %s", name)
icon = "SHAPEKEY_DATA"
else:
custom_expressions = expressions.custom
custom_index = index - len(preset_expression_items)
if custom_index >= len(custom_expressions):
return
expression = custom_expressions[custom_index]
name = expression.custom_name
icon = "SHAPEKEY_DATA"
if self.layout_type == "GRID":
layout.alignment = "CENTER"
layout.label(text="", translate=False, icon=icon)
return
if self.layout_type not in {"DEFAULT", "COMPACT"}:
return
split = layout.split(align=True, factor=0.55)
split.label(text=name, translate=False, icon=icon)
split.prop(expression, "preview", text="Preview")
class VRM_UL_vrm1_morph_target_bind(UIList):
bl_idname = "VRM_UL_vrm1_morph_target_bind"
def draw_item(
self,
context: Context,
layout: UILayout,
_data: object,
morph_target_bind: object,
icon: int,
_active_data: object,
_active_prop_name: str,
_index: int,
_flt_flag: int,
) -> None:
blend_data = context.blend_data
if not isinstance(morph_target_bind, Vrm1MorphTargetBindPropertyGroup):
return
if self.layout_type == "GRID":
layout.alignment = "CENTER"
layout.label(text="", icon_value=icon)
return
if self.layout_type not in {"DEFAULT", "COMPACT"}:
return
name = morph_target_bind.node.mesh_object_name
mesh_object = blend_data.objects.get(morph_target_bind.node.mesh_object_name)
if mesh_object:
mesh_data = mesh_object.data
if isinstance(mesh_data, Mesh):
shape_keys = mesh_data.shape_keys
if shape_keys:
keys = shape_keys.key_blocks.keys()
if morph_target_bind.index in keys:
name += " / " + morph_target_bind.index
layout.label(text=name, translate=False, icon="MESH_DATA")
class VRM_UL_vrm1_material_color_bind(UIList):
bl_idname = "VRM_UL_vrm0_material_color_bind"
def draw_item(
self,
_context: Context,
layout: UILayout,
_data: object,
material_color_bind: object,
icon: int,
_active_data: object,
_active_prop_name: str,
_index: int,
_flt_flag: int,
) -> None:
if not isinstance(material_color_bind, Vrm1MaterialColorBindPropertyGroup):
return
if self.layout_type == "GRID":
layout.alignment = "CENTER"
layout.label(text="", translate=False, icon_value=icon)
return
if self.layout_type not in {"DEFAULT", "COMPACT"}:
return
name = ""
material = material_color_bind.material
if material:
name = material.name
type_name = next(
(
enum.name
for enum in Vrm1MaterialColorBindPropertyGroup.type_enum
if enum.identifier == material_color_bind.type
),
None,
)
if type_name:
name += " / " + type_name
layout.label(text=name, translate=False, icon="MATERIAL")
class VRM_UL_vrm1_texture_transform_bind(UIList):
bl_idname = "VRM_UL_vrm1_texture_transform_bind"
def draw_item(
self,
_context: Context,
layout: UILayout,
_data: object,
texture_transform_bind: object,
icon: int,
_active_data: object,
_active_prop_name: str,
_index: int,
_flt_flag: int,
) -> None:
if not isinstance(
texture_transform_bind, Vrm1TextureTransformBindPropertyGroup
):
return
if self.layout_type == "GRID":
layout.alignment = "CENTER"
layout.label(text="", translate=False, icon_value=icon)
return
if self.layout_type not in {"DEFAULT", "COMPACT"}:
return
name = ""
material = texture_transform_bind.material
if material:
name = material.name
layout.label(text=name, translate=False, icon="MATERIAL")