- 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
732 lines
26 KiB
Python
732 lines
26 KiB
Python
# 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
|
|
)
|