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:
731
editor/vrm0/migration.py
Normal file
731
editor/vrm0/migration.py
Normal file
@@ -0,0 +1,731 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
import contextlib
|
||||
import json
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from bpy.types import ID, Armature, Context, Mesh, Object, Text
|
||||
|
||||
from ...common import convert, ops
|
||||
from ...common.convert import Json
|
||||
from ...common.deep import make_json
|
||||
from ...common.vrm0.human_bone import HumanBoneSpecifications
|
||||
from ..extension import get_armature_extension, get_bone_extension
|
||||
from ..property_group import BonePropertyGroup
|
||||
from .property_group import (
|
||||
Vrm0BlendShapeMasterPropertyGroup,
|
||||
Vrm0FirstPersonPropertyGroup,
|
||||
Vrm0HumanoidPropertyGroup,
|
||||
Vrm0MeshAnnotationPropertyGroup,
|
||||
Vrm0MetaPropertyGroup,
|
||||
Vrm0PropertyGroup,
|
||||
Vrm0SecondaryAnimationPropertyGroup,
|
||||
)
|
||||
|
||||
|
||||
def read_textblock_json(context: Context, armature: Object, armature_key: str) -> Json:
|
||||
text_key = armature.get(armature_key)
|
||||
if isinstance(text_key, Text):
|
||||
textblock: Optional[Text] = text_key
|
||||
elif not isinstance(text_key, str):
|
||||
return None
|
||||
else:
|
||||
textblock = context.blend_data.texts.get(text_key)
|
||||
if not isinstance(textblock, Text):
|
||||
return None
|
||||
textblock_str = "".join(line.body for line in textblock.lines)
|
||||
with contextlib.suppress(json.JSONDecodeError):
|
||||
return make_json(json.loads(textblock_str))
|
||||
return None
|
||||
|
||||
|
||||
def migrate_vrm0_meta(
|
||||
context: Context, meta: Vrm0MetaPropertyGroup, armature: Object
|
||||
) -> None:
|
||||
allowed_user_name = armature.get("allowedUserName")
|
||||
if (
|
||||
isinstance(allowed_user_name, str)
|
||||
and allowed_user_name
|
||||
in Vrm0MetaPropertyGroup.allowed_user_name_enum.identifiers()
|
||||
):
|
||||
meta.allowed_user_name = allowed_user_name
|
||||
|
||||
author = armature.get("author")
|
||||
if isinstance(author, str):
|
||||
meta.author = author
|
||||
|
||||
commercial_ussage_name = armature.get("commercialUssageName")
|
||||
if (
|
||||
isinstance(commercial_ussage_name, str)
|
||||
and commercial_ussage_name
|
||||
in Vrm0MetaPropertyGroup.commercial_ussage_name_enum.identifiers()
|
||||
):
|
||||
meta.commercial_ussage_name = commercial_ussage_name
|
||||
|
||||
contact_information = armature.get("contactInformation")
|
||||
if isinstance(contact_information, str):
|
||||
meta.contact_information = contact_information
|
||||
|
||||
license_name = armature.get("licenseName")
|
||||
if (
|
||||
isinstance(license_name, str)
|
||||
and license_name in Vrm0MetaPropertyGroup.license_name_enum.identifiers()
|
||||
):
|
||||
meta.license_name = license_name
|
||||
|
||||
other_license_url = armature.get("otherLicenseUrl")
|
||||
if isinstance(other_license_url, str):
|
||||
meta.other_license_url = other_license_url
|
||||
|
||||
other_permission_url = armature.get("otherPermissionUrl")
|
||||
if isinstance(other_permission_url, str):
|
||||
meta.other_permission_url = other_permission_url
|
||||
|
||||
reference = armature.get("reference")
|
||||
if isinstance(reference, str):
|
||||
meta.reference = reference
|
||||
|
||||
sexual_ussage_name = armature.get("sexualUssageName")
|
||||
if (
|
||||
isinstance(sexual_ussage_name, str)
|
||||
and sexual_ussage_name
|
||||
in Vrm0MetaPropertyGroup.sexual_ussage_name_enum.identifiers()
|
||||
):
|
||||
meta.sexual_ussage_name = sexual_ussage_name
|
||||
|
||||
title = armature.get("title")
|
||||
if isinstance(title, str):
|
||||
meta.title = title
|
||||
|
||||
version = armature.get("version")
|
||||
if isinstance(version, str):
|
||||
meta.version = version
|
||||
|
||||
violent_ussage_name = armature.get("violentUssageName")
|
||||
if (
|
||||
isinstance(violent_ussage_name, str)
|
||||
and violent_ussage_name
|
||||
in Vrm0MetaPropertyGroup.violent_ussage_name_enum.identifiers()
|
||||
):
|
||||
meta.violent_ussage_name = violent_ussage_name
|
||||
|
||||
texture = armature.get("texture")
|
||||
if isinstance(texture, str):
|
||||
texture_image = context.blend_data.images.get(texture)
|
||||
if texture_image:
|
||||
meta.texture = texture_image
|
||||
|
||||
|
||||
def migrate_vrm0_humanoid(
|
||||
humanoid: Vrm0HumanoidPropertyGroup, humanoid_dict: Json
|
||||
) -> None:
|
||||
if not isinstance(humanoid_dict, dict):
|
||||
return
|
||||
|
||||
arm_stretch = convert.float_or_none(humanoid_dict.get("armStretch"))
|
||||
if arm_stretch is not None:
|
||||
humanoid.arm_stretch = arm_stretch
|
||||
|
||||
leg_stretch = convert.float_or_none(humanoid_dict.get("legStretch"))
|
||||
if leg_stretch is not None:
|
||||
humanoid.leg_stretch = leg_stretch
|
||||
|
||||
upper_arm_twist = convert.float_or_none(humanoid_dict.get("upperArmTwist"))
|
||||
if upper_arm_twist is not None:
|
||||
humanoid.upper_arm_twist = upper_arm_twist
|
||||
|
||||
lower_arm_twist = convert.float_or_none(humanoid_dict.get("lowerArmTwist"))
|
||||
if lower_arm_twist is not None:
|
||||
humanoid.lower_arm_twist = lower_arm_twist
|
||||
|
||||
upper_leg_twist = convert.float_or_none(humanoid_dict.get("upperLegTwist"))
|
||||
if upper_leg_twist is not None:
|
||||
humanoid.upper_leg_twist = upper_leg_twist
|
||||
|
||||
lower_leg_twist = convert.float_or_none(humanoid_dict.get("lowerLegTwist"))
|
||||
if lower_leg_twist is not None:
|
||||
humanoid.lower_leg_twist = lower_leg_twist
|
||||
|
||||
feet_spacing = convert.float_or_none(humanoid_dict.get("feetSpacing"))
|
||||
if feet_spacing is not None:
|
||||
humanoid.feet_spacing = feet_spacing
|
||||
|
||||
has_translation_dof = humanoid_dict.get("hasTranslationDoF")
|
||||
if isinstance(has_translation_dof, bool):
|
||||
humanoid.has_translation_dof = has_translation_dof
|
||||
|
||||
|
||||
def migrate_vrm0_first_person(
|
||||
context: Context,
|
||||
first_person: Vrm0FirstPersonPropertyGroup,
|
||||
first_person_dict: Json,
|
||||
) -> None:
|
||||
if not isinstance(first_person_dict, dict):
|
||||
return
|
||||
|
||||
first_person_bone = first_person_dict.get("firstPersonBone")
|
||||
if isinstance(first_person_bone, str):
|
||||
first_person.first_person_bone.bone_name = first_person_bone
|
||||
|
||||
first_person_bone_offset = convert.vrm_json_vector3_to_tuple(
|
||||
first_person_dict.get("firstPersonBoneOffset")
|
||||
)
|
||||
if first_person_bone_offset is not None:
|
||||
# Axis confusing
|
||||
(x, y, z) = first_person_bone_offset
|
||||
first_person.first_person_bone_offset = (x, z, y)
|
||||
|
||||
mesh_annotation_dicts = first_person_dict.get("meshAnnotations")
|
||||
if isinstance(mesh_annotation_dicts, list):
|
||||
for mesh_annotation_dict in mesh_annotation_dicts:
|
||||
mesh_annotation = first_person.mesh_annotations.add()
|
||||
|
||||
if not isinstance(mesh_annotation_dict, dict):
|
||||
continue
|
||||
|
||||
mesh = mesh_annotation_dict.get("mesh")
|
||||
if isinstance(mesh, str):
|
||||
if mesh in context.blend_data.meshes:
|
||||
for obj in context.blend_data.objects:
|
||||
if obj.data == context.blend_data.meshes[mesh]:
|
||||
mesh_annotation.mesh.mesh_object_name = obj.name
|
||||
break
|
||||
elif (
|
||||
mesh in context.blend_data.objects
|
||||
and context.blend_data.objects[mesh].type == "MESH"
|
||||
):
|
||||
mesh_annotation.mesh.mesh_object_name = context.blend_data.objects[
|
||||
mesh
|
||||
].name
|
||||
|
||||
first_person_flag = mesh_annotation_dict.get("firstPersonFlag")
|
||||
if (
|
||||
isinstance(first_person_flag, str)
|
||||
and first_person_flag
|
||||
in Vrm0MeshAnnotationPropertyGroup.first_person_flag_enum.identifiers()
|
||||
):
|
||||
mesh_annotation.first_person_flag = first_person_flag
|
||||
|
||||
look_at_type_name = first_person_dict.get("lookAtTypeName")
|
||||
if (
|
||||
isinstance(look_at_type_name, str)
|
||||
and look_at_type_name in first_person.look_at_type_name_enum.identifiers()
|
||||
):
|
||||
first_person.look_at_type_name = look_at_type_name
|
||||
|
||||
for look_at, look_at_dict in [
|
||||
(
|
||||
first_person.look_at_horizontal_inner,
|
||||
first_person_dict.get("lookAtHorizontalInner"),
|
||||
),
|
||||
(
|
||||
first_person.look_at_horizontal_outer,
|
||||
first_person_dict.get("lookAtHorizontalOuter"),
|
||||
),
|
||||
(
|
||||
first_person.look_at_vertical_down,
|
||||
first_person_dict.get("lookAtVerticalDown"),
|
||||
),
|
||||
(
|
||||
first_person.look_at_vertical_up,
|
||||
first_person_dict.get("lookAtVerticalUp"),
|
||||
),
|
||||
]:
|
||||
if not isinstance(look_at_dict, dict):
|
||||
continue
|
||||
|
||||
curve = convert.vrm_json_curve_to_list(look_at_dict.get("curve"))
|
||||
if curve is not None:
|
||||
look_at.curve = curve
|
||||
|
||||
x_range = look_at_dict.get("xRange")
|
||||
if isinstance(x_range, (float, int)):
|
||||
look_at.x_range = x_range
|
||||
|
||||
y_range = look_at_dict.get("yRange")
|
||||
if isinstance(y_range, (float, int)):
|
||||
look_at.y_range = y_range
|
||||
|
||||
|
||||
def migrate_vrm0_blend_shape_groups(
|
||||
context: Context,
|
||||
blend_shape_master: Vrm0BlendShapeMasterPropertyGroup,
|
||||
blend_shape_group_dicts: Json,
|
||||
) -> None:
|
||||
if not isinstance(blend_shape_group_dicts, list):
|
||||
return
|
||||
|
||||
for blend_shape_group_dict in blend_shape_group_dicts:
|
||||
blend_shape_group = blend_shape_master.blend_shape_groups.add()
|
||||
if not isinstance(blend_shape_group_dict, dict):
|
||||
continue
|
||||
|
||||
name = blend_shape_group_dict.get("name")
|
||||
if isinstance(name, str):
|
||||
blend_shape_group.name = name
|
||||
|
||||
preset_name = blend_shape_group_dict.get("presetName")
|
||||
if (
|
||||
isinstance(preset_name, str)
|
||||
and preset_name in blend_shape_group.preset_name_enum.identifiers()
|
||||
):
|
||||
blend_shape_group.preset_name = preset_name
|
||||
|
||||
bind_dicts = blend_shape_group_dict.get("binds")
|
||||
if isinstance(bind_dicts, list):
|
||||
for bind_dict in bind_dicts:
|
||||
bind = blend_shape_group.binds.add()
|
||||
|
||||
if not isinstance(bind_dict, dict):
|
||||
continue
|
||||
|
||||
mesh_name = bind_dict.get("mesh")
|
||||
if isinstance(mesh_name, str):
|
||||
if mesh_name in context.blend_data.meshes:
|
||||
mesh: Optional[ID] = context.blend_data.meshes[mesh_name]
|
||||
for obj in context.blend_data.objects:
|
||||
if obj.data == mesh:
|
||||
bind.mesh.mesh_object_name = obj.name
|
||||
break
|
||||
elif (
|
||||
mesh_name in context.blend_data.objects
|
||||
and context.blend_data.objects[mesh_name].type == "MESH"
|
||||
):
|
||||
obj = context.blend_data.objects[mesh_name]
|
||||
bind.mesh.mesh_object_name = obj.name
|
||||
mesh = obj.data
|
||||
else:
|
||||
mesh = None
|
||||
|
||||
if isinstance(mesh, Mesh):
|
||||
index = bind_dict.get("index")
|
||||
shape_keys = mesh.shape_keys
|
||||
if (
|
||||
isinstance(index, str)
|
||||
and shape_keys
|
||||
and index in shape_keys.key_blocks
|
||||
):
|
||||
bind.index = index
|
||||
|
||||
weight = convert.float_or_none(bind_dict.get("weight"))
|
||||
if weight is not None:
|
||||
bind.weight = weight
|
||||
|
||||
material_value_dicts = blend_shape_group_dict.get("materialValues")
|
||||
if isinstance(material_value_dicts, list):
|
||||
for material_value_dict in material_value_dicts:
|
||||
material_value = blend_shape_group.material_values.add()
|
||||
|
||||
if not isinstance(material_value_dict, dict):
|
||||
continue
|
||||
|
||||
material_name = material_value_dict.get("materialName")
|
||||
if (
|
||||
isinstance(material_name, str)
|
||||
and material_name in context.blend_data.materials
|
||||
):
|
||||
material_value.material = context.blend_data.materials[
|
||||
material_name
|
||||
]
|
||||
|
||||
property_name = material_value_dict.get("propertyName")
|
||||
if isinstance(property_name, str):
|
||||
material_value.property_name = property_name
|
||||
|
||||
target_value_vector = material_value_dict.get("targetValue")
|
||||
if isinstance(target_value_vector, list):
|
||||
for v in target_value_vector:
|
||||
material_value.target_value.add().value = convert.float_or(
|
||||
v, 0.0
|
||||
)
|
||||
|
||||
is_binary = blend_shape_group_dict.get("isBinary")
|
||||
if isinstance(is_binary, bool):
|
||||
blend_shape_group.is_binary = is_binary
|
||||
|
||||
|
||||
def migrate_vrm0_secondary_animation(
|
||||
secondary_animation: Vrm0SecondaryAnimationPropertyGroup,
|
||||
bone_group_dicts: Json,
|
||||
armature: Object,
|
||||
armature_data: Armature,
|
||||
) -> None:
|
||||
bone_name_to_collider_objects: dict[str, list[Object]] = {}
|
||||
for collider_object in [
|
||||
child
|
||||
for child in armature.children
|
||||
if child.type == "EMPTY"
|
||||
and child.empty_display_type == "SPHERE"
|
||||
and child.parent_type == "BONE"
|
||||
and child.parent_bone in armature_data.bones
|
||||
]:
|
||||
if collider_object.parent_bone not in bone_name_to_collider_objects:
|
||||
bone_name_to_collider_objects[collider_object.parent_bone] = []
|
||||
bone_name_to_collider_objects[collider_object.parent_bone].append(
|
||||
collider_object
|
||||
)
|
||||
|
||||
for bone_name, collider_objects in bone_name_to_collider_objects.items():
|
||||
collider_group = secondary_animation.collider_groups.add()
|
||||
collider_group.uuid = uuid.uuid4().hex
|
||||
collider_group.node.bone_name = bone_name
|
||||
for collider_object in collider_objects:
|
||||
collider_prop = collider_group.colliders.add()
|
||||
collider_prop.bpy_object = collider_object
|
||||
|
||||
for collider_group in secondary_animation.collider_groups:
|
||||
collider_group.refresh(armature)
|
||||
|
||||
if not isinstance(bone_group_dicts, list):
|
||||
bone_group_dicts = []
|
||||
|
||||
for bone_group_dict in bone_group_dicts:
|
||||
bone_group = secondary_animation.bone_groups.add()
|
||||
|
||||
if not isinstance(bone_group_dict, dict):
|
||||
continue
|
||||
|
||||
comment = bone_group_dict.get("comment")
|
||||
if isinstance(comment, str):
|
||||
bone_group.comment = comment
|
||||
|
||||
stiffiness = convert.float_or_none(bone_group_dict.get("stiffiness"))
|
||||
if stiffiness is not None:
|
||||
bone_group.stiffiness = stiffiness
|
||||
|
||||
gravity_power = convert.float_or_none(bone_group_dict.get("gravityPower"))
|
||||
if gravity_power is not None:
|
||||
bone_group.gravity_power = gravity_power
|
||||
|
||||
gravity_dir = convert.vrm_json_vector3_to_tuple(
|
||||
bone_group_dict.get("gravityDir")
|
||||
)
|
||||
if gravity_dir is not None:
|
||||
# Axis confusing
|
||||
(x, y, z) = gravity_dir
|
||||
bone_group.gravity_dir = (x, z, y)
|
||||
|
||||
drag_force = convert.float_or_none(bone_group_dict.get("dragForce"))
|
||||
if drag_force is not None:
|
||||
bone_group.drag_force = drag_force
|
||||
|
||||
center = bone_group_dict.get("center")
|
||||
if isinstance(center, str):
|
||||
bone_group.center.bone_name = center
|
||||
|
||||
hit_radius = convert.float_or_none(bone_group_dict.get("hitRadius"))
|
||||
if hit_radius is not None:
|
||||
bone_group.hit_radius = hit_radius
|
||||
|
||||
bones = bone_group_dict.get("bones")
|
||||
if isinstance(bones, list):
|
||||
for bone in bones:
|
||||
bone_prop = bone_group.bones.add()
|
||||
if not isinstance(bone, str):
|
||||
continue
|
||||
bone_prop.bone_name = bone
|
||||
|
||||
collider_group_node_names = bone_group_dict.get("colliderGroups")
|
||||
if not isinstance(collider_group_node_names, list):
|
||||
continue
|
||||
|
||||
for collider_group_node_name in collider_group_node_names:
|
||||
if not isinstance(collider_group_node_name, str):
|
||||
continue
|
||||
for collider_group in secondary_animation.collider_groups:
|
||||
if collider_group.node.bone_name != collider_group_node_name:
|
||||
continue
|
||||
collider_group_name = bone_group.collider_groups.add()
|
||||
collider_group_name.value = collider_group.name
|
||||
break
|
||||
|
||||
for bone_group in secondary_animation.bone_groups:
|
||||
bone_group.refresh(armature)
|
||||
|
||||
|
||||
def migrate_legacy_custom_properties(
|
||||
context: Context, armature: Object, armature_data: Armature
|
||||
) -> None:
|
||||
ext = get_armature_extension(armature_data)
|
||||
if tuple(ext.addon_version) >= (2, 0, 1):
|
||||
return
|
||||
|
||||
migrate_vrm0_meta(context, ext.vrm0.meta, armature)
|
||||
migrate_vrm0_blend_shape_groups(
|
||||
context,
|
||||
ext.vrm0.blend_shape_master,
|
||||
read_textblock_json(context, armature, "blendshape_group"),
|
||||
)
|
||||
migrate_vrm0_first_person(
|
||||
context,
|
||||
ext.vrm0.first_person,
|
||||
read_textblock_json(context, armature, "firstPerson_params"),
|
||||
)
|
||||
migrate_vrm0_humanoid(
|
||||
ext.vrm0.humanoid, read_textblock_json(context, armature, "humanoid_params")
|
||||
)
|
||||
migrate_vrm0_secondary_animation(
|
||||
ext.vrm0.secondary_animation,
|
||||
read_textblock_json(context, armature, "spring_bone"),
|
||||
armature,
|
||||
armature_data,
|
||||
)
|
||||
|
||||
assigned_bpy_bone_names: list[str] = []
|
||||
for human_bone_name in HumanBoneSpecifications.all_names:
|
||||
bpy_bone_name = armature_data.get(human_bone_name)
|
||||
if (
|
||||
not isinstance(bpy_bone_name, str)
|
||||
or not bpy_bone_name
|
||||
or bpy_bone_name in assigned_bpy_bone_names
|
||||
):
|
||||
continue
|
||||
assigned_bpy_bone_names.append(bpy_bone_name)
|
||||
|
||||
for human_bone in ext.vrm0.humanoid.human_bones:
|
||||
if human_bone.bone == human_bone_name:
|
||||
human_bone.node.bone_name = bpy_bone_name
|
||||
break
|
||||
|
||||
|
||||
def migrate_blender_object(armature_data: Armature) -> None:
|
||||
ext = get_armature_extension(armature_data)
|
||||
if tuple(ext.addon_version) >= (2, 3, 27):
|
||||
return
|
||||
|
||||
for collider_group in ext.vrm0.secondary_animation.collider_groups:
|
||||
for collider in collider_group.colliders:
|
||||
bpy_object = collider.pop("blender_object", None)
|
||||
if isinstance(bpy_object, Object):
|
||||
collider.bpy_object = bpy_object
|
||||
|
||||
|
||||
def migrate_link_to_bone_object(
|
||||
context: Context, armature: Object, armature_data: Armature
|
||||
) -> None:
|
||||
ext = get_armature_extension(armature_data)
|
||||
if tuple(ext.addon_version) >= (2, 3, 27):
|
||||
return
|
||||
|
||||
for (
|
||||
bone_property_group,
|
||||
_bone_property_group_type,
|
||||
) in BonePropertyGroup.get_all_bone_property_groups(armature):
|
||||
link_to_bone = bone_property_group.get("link_to_bone")
|
||||
if not isinstance(link_to_bone, Object) or not link_to_bone.parent_bone:
|
||||
continue
|
||||
parent = link_to_bone.parent
|
||||
if not parent or not parent.name or parent.type != "ARMATURE":
|
||||
continue
|
||||
parent_data = parent.data
|
||||
if not isinstance(parent_data, Armature):
|
||||
continue
|
||||
bone = parent_data.bones.get(link_to_bone.parent_bone)
|
||||
if not bone:
|
||||
continue
|
||||
bone_extension = get_bone_extension(bone)
|
||||
if not bone_extension.uuid:
|
||||
bone_extension.uuid = uuid.uuid4().hex
|
||||
bone_property_group.bone_uuid = bone_extension.uuid
|
||||
|
||||
for (
|
||||
bone_property_group,
|
||||
_bone_property_group_type,
|
||||
) in BonePropertyGroup.get_all_bone_property_groups(armature):
|
||||
link_to_bone = bone_property_group.pop("link_to_bone", None)
|
||||
if not isinstance(link_to_bone, Object):
|
||||
continue
|
||||
if link_to_bone.parent_type != "OBJECT":
|
||||
link_to_bone.parent_type = "OBJECT"
|
||||
if link_to_bone.parent_bone:
|
||||
link_to_bone.parent_bone = ""
|
||||
if link_to_bone.parent is not None:
|
||||
link_to_bone.parent = None
|
||||
|
||||
Vrm0HumanoidPropertyGroup.update_all_node_candidates(
|
||||
context,
|
||||
armature_data.name,
|
||||
force=True,
|
||||
)
|
||||
|
||||
|
||||
def migrate_link_to_mesh_object(armature_data: Armature) -> None:
|
||||
ext = get_armature_extension(armature_data)
|
||||
if tuple(ext.addon_version) >= (2, 3, 23):
|
||||
return
|
||||
|
||||
meshes = [
|
||||
mesh_annotation.mesh
|
||||
for mesh_annotation in ext.vrm0.first_person.mesh_annotations
|
||||
] + [
|
||||
bind.mesh
|
||||
for blend_shape_group in ext.vrm0.blend_shape_master.blend_shape_groups
|
||||
for bind in blend_shape_group.binds
|
||||
]
|
||||
|
||||
for mesh in meshes:
|
||||
if not mesh:
|
||||
continue
|
||||
link_to_mesh = mesh.get("link_to_mesh")
|
||||
if (
|
||||
not isinstance(link_to_mesh, Object)
|
||||
or not link_to_mesh.parent
|
||||
or not link_to_mesh.parent.name
|
||||
or link_to_mesh.parent.type != "MESH"
|
||||
):
|
||||
continue
|
||||
mesh.mesh_object_name = link_to_mesh.parent.name
|
||||
|
||||
|
||||
def remove_link_to_mesh_object(armature_data: Armature) -> None:
|
||||
ext = get_armature_extension(armature_data)
|
||||
if tuple(ext.addon_version) >= (2, 3, 27):
|
||||
return
|
||||
|
||||
meshes = [
|
||||
mesh_annotation.mesh
|
||||
for mesh_annotation in ext.vrm0.first_person.mesh_annotations
|
||||
] + [
|
||||
bind.mesh
|
||||
for blend_shape_group in ext.vrm0.blend_shape_master.blend_shape_groups
|
||||
for bind in blend_shape_group.binds
|
||||
]
|
||||
|
||||
for mesh in meshes:
|
||||
if not mesh:
|
||||
continue
|
||||
link_to_mesh = mesh.pop("link_to_mesh", None)
|
||||
if not isinstance(link_to_mesh, Object):
|
||||
continue
|
||||
if link_to_mesh.parent_type != "OBJECT":
|
||||
link_to_mesh.parent_type = "OBJECT"
|
||||
if link_to_mesh.parent_bone:
|
||||
link_to_mesh.parent_bone = ""
|
||||
if link_to_mesh.parent is not None:
|
||||
link_to_mesh.parent = None
|
||||
|
||||
|
||||
def fixup_gravity_dir(armature_data: Armature) -> None:
|
||||
ext = get_armature_extension(armature_data)
|
||||
if tuple(ext.addon_version) >= (2, 15, 4):
|
||||
return
|
||||
|
||||
for bone_group in ext.vrm0.secondary_animation.bone_groups:
|
||||
gravity_dir = list(bone_group.gravity_dir)
|
||||
bone_group.gravity_dir = (gravity_dir[0] + 1, 0, 0) # Make a change
|
||||
bone_group.gravity_dir = gravity_dir
|
||||
|
||||
|
||||
def fixup_humanoid_feet_spacing(armature_data: Armature) -> None:
|
||||
ext = get_armature_extension(armature_data)
|
||||
if tuple(ext.addon_version) >= (2, 18, 2):
|
||||
return
|
||||
humanoid = ext.vrm0.humanoid
|
||||
feet_spacing = convert.float_or_none(humanoid.get("feet_spacing"))
|
||||
if feet_spacing is not None:
|
||||
humanoid.feet_spacing = float(feet_spacing)
|
||||
|
||||
|
||||
def migrate_pose(context: Context, armature: Object, armature_data: Armature) -> None:
|
||||
ext = get_armature_extension(armature_data)
|
||||
if tuple(ext.addon_version) >= (2, 20, 34):
|
||||
return
|
||||
|
||||
humanoid = ext.vrm0.humanoid
|
||||
if isinstance(humanoid.get("pose"), int):
|
||||
return
|
||||
|
||||
if tuple(ext.addon_version) == ext.INITIAL_ADDON_VERSION:
|
||||
if ext.has_vrm_model_metadata(armature):
|
||||
humanoid.pose = humanoid.POSE_CURRENT_POSE.identifier
|
||||
return
|
||||
|
||||
action = humanoid.pose_library
|
||||
if action and action.name in context.blend_data.actions:
|
||||
humanoid.pose = humanoid.POSE_CUSTOM_POSE.identifier
|
||||
elif armature_data.pose_position == "REST":
|
||||
humanoid.pose = humanoid.POSE_REST_POSITION_POSE.identifier
|
||||
else:
|
||||
humanoid.pose = humanoid.POSE_CURRENT_POSE.identifier
|
||||
|
||||
|
||||
def migrate_auto_pose(_context: Context, armature_data: Armature) -> None:
|
||||
ext = get_armature_extension(armature_data)
|
||||
if tuple(ext.addon_version) == ext.INITIAL_ADDON_VERSION or tuple(
|
||||
ext.addon_version
|
||||
) >= (2, 20, 81):
|
||||
return
|
||||
|
||||
humanoid = ext.vrm0.humanoid
|
||||
if not isinstance(humanoid.get("pose"), int):
|
||||
humanoid.pose = humanoid.POSE_CURRENT_POSE.identifier
|
||||
|
||||
|
||||
def migrate_saved_mesh_object_name_to_restore(
|
||||
_context: Context, armature_data: Armature
|
||||
) -> None:
|
||||
ext = get_armature_extension(armature_data)
|
||||
if tuple(ext.addon_version) == ext.INITIAL_ADDON_VERSION or tuple(
|
||||
ext.addon_version
|
||||
) >= (3, 9, 0):
|
||||
return
|
||||
|
||||
for blend_shape_group in ext.vrm0.blend_shape_master.blend_shape_groups:
|
||||
for bind in blend_shape_group.binds:
|
||||
bind.mesh.saved_mesh_object_name_to_restore = bind.mesh.name
|
||||
|
||||
|
||||
def is_unnecessary(vrm0: Vrm0PropertyGroup) -> bool:
|
||||
if vrm0.humanoid.initial_automatic_bone_assignment:
|
||||
return False
|
||||
if vrm0.first_person.first_person_bone.bone_name:
|
||||
return True
|
||||
return all(
|
||||
(human_bone.bone != "head" or not human_bone.node.bone_name)
|
||||
for human_bone in vrm0.humanoid.human_bones
|
||||
)
|
||||
|
||||
|
||||
def migrate(context: Context, vrm0: Vrm0PropertyGroup, armature: Object) -> None:
|
||||
armature_data = armature.data
|
||||
if not isinstance(armature_data, Armature):
|
||||
return
|
||||
|
||||
migrate_blender_object(armature_data)
|
||||
migrate_link_to_bone_object(context, armature, armature_data)
|
||||
Vrm0HumanoidPropertyGroup.fixup_human_bones(armature)
|
||||
|
||||
for collider_group in vrm0.secondary_animation.collider_groups:
|
||||
collider_group.refresh(armature)
|
||||
for bone_group in vrm0.secondary_animation.bone_groups:
|
||||
bone_group.refresh(armature)
|
||||
|
||||
if not vrm0.first_person.first_person_bone.bone_name:
|
||||
for human_bone in vrm0.humanoid.human_bones:
|
||||
if human_bone.bone == "head":
|
||||
vrm0.first_person.first_person_bone.bone_name = (
|
||||
human_bone.node.bone_name
|
||||
)
|
||||
break
|
||||
|
||||
migrate_legacy_custom_properties(context, armature, armature_data)
|
||||
migrate_link_to_mesh_object(armature_data)
|
||||
remove_link_to_mesh_object(armature_data)
|
||||
fixup_gravity_dir(armature_data)
|
||||
fixup_humanoid_feet_spacing(armature_data)
|
||||
migrate_pose(context, armature, armature_data)
|
||||
migrate_auto_pose(context, armature_data)
|
||||
migrate_saved_mesh_object_name_to_restore(context, armature_data)
|
||||
|
||||
Vrm0HumanoidPropertyGroup.update_all_node_candidates(
|
||||
context,
|
||||
armature_data.name,
|
||||
force=True,
|
||||
)
|
||||
|
||||
if vrm0.humanoid.initial_automatic_bone_assignment:
|
||||
vrm0.humanoid.initial_automatic_bone_assignment = False
|
||||
if all(not b.node.bone_name for b in vrm0.humanoid.human_bones):
|
||||
ops.vrm.assign_vrm0_humanoid_human_bones_automatically(
|
||||
armature_object_name=armature.name
|
||||
)
|
||||
Reference in New Issue
Block a user