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:
1
editor/vrm0/__init__.py
Normal file
1
editor/vrm0/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
BIN
editor/vrm0/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
editor/vrm0/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm0/__pycache__/handler.cpython-311.pyc
Normal file
BIN
editor/vrm0/__pycache__/handler.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm0/__pycache__/menu.cpython-311.pyc
Normal file
BIN
editor/vrm0/__pycache__/menu.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm0/__pycache__/migration.cpython-311.pyc
Normal file
BIN
editor/vrm0/__pycache__/migration.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm0/__pycache__/ops.cpython-311.pyc
Normal file
BIN
editor/vrm0/__pycache__/ops.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm0/__pycache__/panel.cpython-311.pyc
Normal file
BIN
editor/vrm0/__pycache__/panel.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm0/__pycache__/property_group.cpython-311.pyc
Normal file
BIN
editor/vrm0/__pycache__/property_group.cpython-311.pyc
Normal file
Binary file not shown.
BIN
editor/vrm0/__pycache__/ui_list.cpython-311.pyc
Normal file
BIN
editor/vrm0/__pycache__/ui_list.cpython-311.pyc
Normal file
Binary file not shown.
62
editor/vrm0/gizmo_group.py
Normal file
62
editor/vrm0/gizmo_group.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
from collections.abc import Set as AbstractSet
|
||||
|
||||
from bpy.types import Armature, Context, GizmoGroup
|
||||
|
||||
from ..extension import get_armature_extension
|
||||
|
||||
|
||||
# https://gist.github.com/FujiSunflower/09fdabc7ca991f8292657abc4ef001b0
|
||||
class Vrm0FirstPersonBoneOffsetGizmoGroup(GizmoGroup):
|
||||
bl_idname = "VRM_GGT_vrm0_first_person_bone_offset"
|
||||
bl_label = "First Person Bone Offset Gizmo"
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_options: AbstractSet[str] = {"3D", "PERSISTENT"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: Context) -> bool:
|
||||
active_object = context.active_object
|
||||
if not active_object:
|
||||
return False
|
||||
return active_object.type == "ARMATURE"
|
||||
|
||||
def setup(self, context: Context) -> None:
|
||||
active_object = context.active_object
|
||||
if not active_object:
|
||||
return
|
||||
armature_data = active_object.data
|
||||
if not isinstance(armature_data, Armature):
|
||||
return
|
||||
ext = get_armature_extension(armature_data)
|
||||
first_person = ext.vrm0.first_person
|
||||
first_person_bone = armature_data.bones[
|
||||
first_person.first_person_bone.bone_name
|
||||
]
|
||||
gizmo = self.gizmos.new("GIZMO_GT_move_3d")
|
||||
gizmo.target_set_prop("offset", first_person, "first_person_bone_offset")
|
||||
gizmo.matrix_basis = first_person_bone.matrix_local
|
||||
gizmo.draw_style = "CROSS_2D"
|
||||
gizmo.draw_options = {"ALIGN_VIEW"}
|
||||
gizmo.color = 1.0, 0.5, 0.0
|
||||
gizmo.alpha = 0.5
|
||||
gizmo.color_highlight = 1.0, 0.5, 1.0
|
||||
gizmo.alpha_highlight = 0.5
|
||||
gizmo.scale_basis = 0.25
|
||||
|
||||
self.first_person_gizmo = gizmo
|
||||
|
||||
def refresh(self, context: Context) -> None:
|
||||
active_object = context.active_object
|
||||
if not active_object:
|
||||
return
|
||||
armature_data = active_object.data
|
||||
if not isinstance(armature_data, Armature):
|
||||
return
|
||||
ext = get_armature_extension(armature_data)
|
||||
gizmo = self.first_person_gizmo
|
||||
first_person = ext.vrm0.first_person
|
||||
first_person_bone = armature_data.bones[
|
||||
first_person.first_person_bone.bone_name
|
||||
]
|
||||
gizmo.matrix_basis = first_person_bone.matrix_local
|
||||
37
editor/vrm0/handler.py
Normal file
37
editor/vrm0/handler.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
import bpy
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
from ...common.logger import get_logger
|
||||
from .property_group import Vrm0BlendShapeGroupPropertyGroup
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@persistent
|
||||
def frame_change_pre(_unused: object) -> None:
|
||||
Vrm0BlendShapeGroupPropertyGroup.frame_change_post_shape_key_updates.clear()
|
||||
|
||||
|
||||
@persistent
|
||||
def frame_change_post(_unused: object) -> None:
|
||||
context = bpy.context
|
||||
|
||||
for (
|
||||
(
|
||||
shape_key_name,
|
||||
key_block_name,
|
||||
),
|
||||
value,
|
||||
) in Vrm0BlendShapeGroupPropertyGroup.frame_change_post_shape_key_updates.items():
|
||||
shape_key = context.blend_data.shape_keys.get(shape_key_name)
|
||||
if not shape_key:
|
||||
continue
|
||||
key_blocks = shape_key.key_blocks
|
||||
if not key_blocks:
|
||||
continue
|
||||
key_block = key_blocks.get(key_block_name)
|
||||
if not key_block:
|
||||
continue
|
||||
key_block.value = value
|
||||
Vrm0BlendShapeGroupPropertyGroup.frame_change_post_shape_key_updates.clear()
|
||||
24
editor/vrm0/menu.py
Normal file
24
editor/vrm0/menu.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
from bpy.types import Context, Menu
|
||||
|
||||
from ...common.logger import get_logger
|
||||
from ..ops import layout_operator
|
||||
from ..search import current_armature
|
||||
from .ops import VRM_OT_restore_vrm0_blend_shape_group_bind_object
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class VRM_MT_vrm0_blend_shape_master(Menu):
|
||||
bl_label = "Blend Shape Proxy Menu"
|
||||
bl_idname = "VRM_MT_vrm0_blend_shape_master"
|
||||
|
||||
def draw(self, context: Context) -> None:
|
||||
layout = self.layout
|
||||
|
||||
armature = current_armature(context)
|
||||
if not armature:
|
||||
return
|
||||
|
||||
op = layout_operator(layout, VRM_OT_restore_vrm0_blend_shape_group_bind_object)
|
||||
op.armature_object_name = armature.name
|
||||
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
|
||||
)
|
||||
2767
editor/vrm0/ops.py
Normal file
2767
editor/vrm0/ops.py
Normal file
File diff suppressed because it is too large
Load Diff
1249
editor/vrm0/panel.py
Normal file
1249
editor/vrm0/panel.py
Normal file
File diff suppressed because it is too large
Load Diff
1117
editor/vrm0/property_group.py
Normal file
1117
editor/vrm0/property_group.py
Normal file
File diff suppressed because it is too large
Load Diff
419
editor/vrm0/ui_list.py
Normal file
419
editor/vrm0/ui_list.py
Normal file
@@ -0,0 +1,419 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
from bpy.types import Armature, Context, Mesh, UILayout, UIList
|
||||
|
||||
from ...common.logger import get_logger
|
||||
from ..extension import get_armature_extension
|
||||
from ..property_group import BonePropertyGroup, StringPropertyGroup
|
||||
from .property_group import (
|
||||
Vrm0BlendShapeBindPropertyGroup,
|
||||
Vrm0BlendShapeGroupPropertyGroup,
|
||||
Vrm0FirstPersonPropertyGroup,
|
||||
Vrm0MaterialValueBindPropertyGroup,
|
||||
Vrm0MeshAnnotationPropertyGroup,
|
||||
Vrm0SecondaryAnimationColliderGroupPropertyGroup,
|
||||
Vrm0SecondaryAnimationColliderPropertyGroup,
|
||||
Vrm0SecondaryAnimationGroupPropertyGroup,
|
||||
)
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class VRM_UL_vrm0_first_person_mesh_annotation(UIList):
|
||||
bl_idname = "VRM_UL_vrm0_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, Vrm0FirstPersonPropertyGroup):
|
||||
return
|
||||
if not isinstance(mesh_annotation, Vrm0MeshAnnotationPropertyGroup):
|
||||
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.mesh,
|
||||
"bpy_object",
|
||||
icon=icon,
|
||||
text="",
|
||||
translate=False,
|
||||
)
|
||||
else:
|
||||
row.label(
|
||||
text=mesh_annotation.mesh.mesh_object_name,
|
||||
translate=False,
|
||||
icon=icon,
|
||||
)
|
||||
row.prop(mesh_annotation, "first_person_flag", text="", translate=False)
|
||||
|
||||
|
||||
class VRM_UL_vrm0_secondary_animation_group(UIList):
|
||||
bl_idname = "VRM_UL_vrm0_secondary_animation_group"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
_data: object,
|
||||
bone_group: object,
|
||||
_icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
_index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
if not isinstance(bone_group, Vrm0SecondaryAnimationGroupPropertyGroup):
|
||||
return
|
||||
|
||||
icon = "BONE_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
|
||||
|
||||
text = ""
|
||||
if bone_group.bones:
|
||||
text = (
|
||||
"(" + ", ".join(str(bone.bone_name) for bone in bone_group.bones) + ")"
|
||||
)
|
||||
|
||||
if bone_group.center.bone_name:
|
||||
if text:
|
||||
text = " - " + text
|
||||
text = bone_group.center.bone_name + text
|
||||
|
||||
if bone_group.comment:
|
||||
if text:
|
||||
text = " / " + text
|
||||
text = bone_group.comment + text
|
||||
|
||||
if not text:
|
||||
text = "(EMPTY)"
|
||||
|
||||
layout.label(text=text, translate=False, icon=icon)
|
||||
|
||||
|
||||
class VRM_UL_vrm0_secondary_animation_group_bone(UIList):
|
||||
bl_idname = "VRM_UL_vrm0_secondary_animation_group_bone"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
bone_group: object,
|
||||
bone: object,
|
||||
_icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
if not isinstance(bone_group, Vrm0SecondaryAnimationGroupPropertyGroup):
|
||||
return
|
||||
if not isinstance(bone, BonePropertyGroup):
|
||||
return
|
||||
armature = bone.find_armature()
|
||||
|
||||
icon = "BONE_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
|
||||
|
||||
if index == bone_group.active_bone_index:
|
||||
layout.prop_search(
|
||||
bone,
|
||||
"bone_name",
|
||||
armature,
|
||||
"bones",
|
||||
text="",
|
||||
translate=False,
|
||||
icon=icon,
|
||||
)
|
||||
else:
|
||||
layout.label(text=bone.bone_name, translate=False, icon=icon)
|
||||
|
||||
|
||||
class VRM_UL_vrm0_secondary_animation_group_collider_group(UIList):
|
||||
bl_idname = "VRM_UL_vrm0_secondary_animation_group_collider_group"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
bone_group: object,
|
||||
collider_group: object,
|
||||
_icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
if not isinstance(bone_group, Vrm0SecondaryAnimationGroupPropertyGroup):
|
||||
return
|
||||
if not isinstance(collider_group, StringPropertyGroup):
|
||||
return
|
||||
|
||||
armature_data = bone_group.id_data
|
||||
if not isinstance(armature_data, Armature):
|
||||
logger.error("Failed to find armature")
|
||||
return
|
||||
|
||||
secondary_animation = get_armature_extension(
|
||||
armature_data
|
||||
).vrm0.secondary_animation
|
||||
|
||||
icon = "PIVOT_INDIVIDUAL"
|
||||
|
||||
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 == bone_group.active_collider_group_index:
|
||||
layout.prop_search(
|
||||
collider_group,
|
||||
"value",
|
||||
secondary_animation,
|
||||
"collider_groups",
|
||||
text="",
|
||||
translate=False,
|
||||
icon=icon,
|
||||
)
|
||||
else:
|
||||
layout.label(text=collider_group.value, translate=False, icon=icon)
|
||||
|
||||
|
||||
class VRM_UL_vrm0_secondary_animation_collider_group(UIList):
|
||||
bl_idname = "VRM_UL_vrm0_secondary_animation_collider_group"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
_data: object,
|
||||
collider_group: object,
|
||||
_icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
_index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
if not isinstance(
|
||||
collider_group, Vrm0SecondaryAnimationColliderGroupPropertyGroup
|
||||
):
|
||||
return
|
||||
|
||||
icon = "SPHERE"
|
||||
|
||||
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
|
||||
|
||||
layout.label(text=collider_group.name, translate=False, icon=icon)
|
||||
|
||||
|
||||
class VRM_UL_vrm0_secondary_animation_collider_group_collider(UIList):
|
||||
bl_idname = "VRM_UL_vrm0_secondary_animation_collider_group_collider"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
collider_group: object,
|
||||
collider: object,
|
||||
_icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
if not isinstance(
|
||||
collider_group, Vrm0SecondaryAnimationColliderGroupPropertyGroup
|
||||
):
|
||||
return
|
||||
if not isinstance(collider, Vrm0SecondaryAnimationColliderPropertyGroup):
|
||||
return
|
||||
|
||||
icon = "MESH_UVSPHERE"
|
||||
|
||||
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
|
||||
|
||||
bpy_object = collider.bpy_object
|
||||
if bpy_object is None:
|
||||
return
|
||||
|
||||
row = layout.split(align=True, factor=0.7)
|
||||
if index == collider_group.active_collider_index:
|
||||
row.prop(
|
||||
bpy_object,
|
||||
"name",
|
||||
icon=icon,
|
||||
translate=False,
|
||||
text="",
|
||||
)
|
||||
row.prop(bpy_object, "empty_display_size", text="")
|
||||
else:
|
||||
row.label(text=bpy_object.name, icon=icon, translate=False)
|
||||
row.prop(bpy_object, "empty_display_size", text="", emboss=False)
|
||||
|
||||
|
||||
class VRM_UL_vrm0_blend_shape_group(UIList):
|
||||
bl_idname = "VRM_UL_vrm0_blend_shape_group"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
_data: object,
|
||||
item: object,
|
||||
_icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
_index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
blend_shape_group = item
|
||||
if not isinstance(blend_shape_group, Vrm0BlendShapeGroupPropertyGroup):
|
||||
return
|
||||
|
||||
preset = next(
|
||||
(
|
||||
preset
|
||||
for preset in blend_shape_group.preset_name_enum
|
||||
if preset.identifier == blend_shape_group.preset_name
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not preset:
|
||||
return
|
||||
|
||||
if self.layout_type == "GRID":
|
||||
layout.alignment = "CENTER"
|
||||
layout.label(text="", translate=False, icon=preset.icon)
|
||||
return
|
||||
|
||||
if self.layout_type not in {"DEFAULT", "COMPACT"}:
|
||||
return
|
||||
|
||||
text = blend_shape_group.name + " / " + preset.name
|
||||
split = layout.split(align=True, factor=0.55)
|
||||
split.label(text=text, translate=False, icon=preset.icon)
|
||||
split.prop(blend_shape_group, "preview", text="Preview")
|
||||
|
||||
|
||||
class VRM_UL_vrm0_blend_shape_bind(UIList):
|
||||
bl_idname = "VRM_UL_vrm0_blend_shape_bind"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
context: Context,
|
||||
layout: UILayout,
|
||||
_data: object,
|
||||
item: object,
|
||||
icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
_index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
blend_data = context.blend_data
|
||||
blend_shape_bind = item
|
||||
if not isinstance(blend_shape_bind, Vrm0BlendShapeBindPropertyGroup):
|
||||
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 = blend_shape_bind.mesh.mesh_object_name
|
||||
mesh_object = blend_data.objects.get(blend_shape_bind.mesh.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 blend_shape_bind.index in keys:
|
||||
name += " / " + blend_shape_bind.index
|
||||
layout.label(text=name, translate=False, icon="MESH_DATA")
|
||||
|
||||
|
||||
class VRM_UL_vrm0_material_value_bind(UIList):
|
||||
bl_idname = "VRM_UL_vrm0_material_value_bind"
|
||||
|
||||
def draw_item(
|
||||
self,
|
||||
_context: Context,
|
||||
layout: UILayout,
|
||||
_data: object,
|
||||
item: object,
|
||||
icon: int,
|
||||
_active_data: object,
|
||||
_active_prop_name: str,
|
||||
_index: int,
|
||||
_flt_flag: int,
|
||||
) -> None:
|
||||
material_value_bind = item
|
||||
if not isinstance(material_value_bind, Vrm0MaterialValueBindPropertyGroup):
|
||||
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_value_bind.material
|
||||
if material:
|
||||
name = material.name
|
||||
if material_value_bind.property_name:
|
||||
name += " / " + material_value_bind.property_name
|
||||
layout.label(text=name, translate=False, icon="MATERIAL")
|
||||
Reference in New Issue
Block a user