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:
716
editor/t_pose.py
Normal file
716
editor/t_pose.py
Normal file
@@ -0,0 +1,716 @@
|
||||
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
||||
import math
|
||||
from collections.abc import Iterator, Mapping
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from sys import float_info
|
||||
from typing import Optional, Union
|
||||
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
Armature,
|
||||
Context,
|
||||
Object,
|
||||
Pose,
|
||||
PoseBone,
|
||||
)
|
||||
from mathutils import Euler, Matrix, Quaternion, Vector
|
||||
|
||||
from ..common import ops
|
||||
from ..common.logger import get_logger
|
||||
from ..common.rotation import (
|
||||
get_rotation_as_quaternion,
|
||||
set_rotation_without_mode_change,
|
||||
)
|
||||
from ..common.vrm0.human_bone import HumanBoneName as Vrm0HumanBoneName
|
||||
from ..common.vrm1.human_bone import HumanBoneName as Vrm1HumanBoneName
|
||||
from ..common.workspace import save_workspace
|
||||
from .extension import VrmAddonArmatureExtensionPropertyGroup, get_armature_extension
|
||||
from .vrm0.property_group import Vrm0HumanoidPropertyGroup
|
||||
from .vrm1.property_group import Vrm1HumanoidPropertyGroup
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
def set_bone_direction_to_align_child_bone(
|
||||
context: Context,
|
||||
armature: Object,
|
||||
direction: Vector,
|
||||
bone: PoseBone,
|
||||
child_bone: PoseBone,
|
||||
) -> None:
|
||||
world_bone_matrix = armature.matrix_world @ bone.matrix
|
||||
world_child_bone_matrix = armature.matrix_world @ child_bone.matrix
|
||||
|
||||
world_bone_length = (
|
||||
world_child_bone_matrix.translation - world_bone_matrix.translation
|
||||
).length
|
||||
|
||||
world_child_bone_from_translation = world_child_bone_matrix.translation
|
||||
world_child_bone_to_translation = (
|
||||
world_bone_matrix.translation + direction * world_bone_length
|
||||
)
|
||||
|
||||
bone_local_child_bone_from_translation = (
|
||||
armature.matrix_world @ bone.matrix
|
||||
).inverted_safe() @ world_child_bone_from_translation
|
||||
bone_local_child_bone_to_translation = (
|
||||
armature.matrix_world @ bone.matrix
|
||||
).inverted_safe() @ world_child_bone_to_translation
|
||||
|
||||
if bone_local_child_bone_from_translation.length_squared < float_info.epsilon:
|
||||
return
|
||||
if bone_local_child_bone_to_translation.length_squared < float_info.epsilon:
|
||||
return
|
||||
|
||||
rotation = bone_local_child_bone_from_translation.rotation_difference(
|
||||
bone_local_child_bone_to_translation
|
||||
)
|
||||
|
||||
set_rotation_without_mode_change(bone, rotation @ get_rotation_as_quaternion(bone))
|
||||
|
||||
context.view_layer.update()
|
||||
|
||||
|
||||
def set_bone_direction_to_align_z_world_location(
|
||||
context: Context,
|
||||
armature: Object,
|
||||
direction: Vector,
|
||||
bone: PoseBone,
|
||||
from_world_location: Vector,
|
||||
) -> None:
|
||||
world_bone_translation = (armature.matrix_world @ bone.matrix).to_translation()
|
||||
aligned_from_world_location = Vector(
|
||||
(
|
||||
from_world_location.x,
|
||||
world_bone_translation.y,
|
||||
from_world_location.z,
|
||||
)
|
||||
)
|
||||
aligned_to_world_location = world_bone_translation + direction
|
||||
|
||||
bone_local_aligned_from_world_location = (
|
||||
armature.matrix_world @ bone.matrix
|
||||
).inverted_safe() @ aligned_from_world_location
|
||||
bone_local_aligned_to_world_location = (
|
||||
armature.matrix_world @ bone.matrix
|
||||
).inverted_safe() @ aligned_to_world_location
|
||||
|
||||
if bone_local_aligned_from_world_location.length_squared < float_info.epsilon:
|
||||
return
|
||||
if bone_local_aligned_to_world_location.length_squared < float_info.epsilon:
|
||||
return
|
||||
|
||||
rotation = bone_local_aligned_from_world_location.rotation_difference(
|
||||
bone_local_aligned_to_world_location
|
||||
)
|
||||
|
||||
set_rotation_without_mode_change(bone, rotation @ get_rotation_as_quaternion(bone))
|
||||
|
||||
context.view_layer.update()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ChainSingleChild:
|
||||
direction: Vector
|
||||
vrm0_human_bone_names: tuple[Vrm0HumanBoneName, ...]
|
||||
vrm1_human_bone_names: tuple[Vrm1HumanBoneName, ...]
|
||||
|
||||
def execute(self, context: Context, armature: Object) -> None:
|
||||
armature_data = armature.data
|
||||
if not isinstance(armature_data, Armature):
|
||||
raise TypeError
|
||||
|
||||
ext = get_armature_extension(armature_data)
|
||||
if ext.is_vrm0():
|
||||
humanoid = ext.vrm0.humanoid
|
||||
bones: list[PoseBone] = []
|
||||
for human_bone in humanoid.human_bones:
|
||||
human_bone_name = Vrm0HumanBoneName.from_str(human_bone.bone)
|
||||
if not human_bone_name:
|
||||
continue
|
||||
if human_bone_name not in self.vrm0_human_bone_names:
|
||||
continue
|
||||
bone = armature.pose.bones.get(human_bone.node.bone_name)
|
||||
if not bone:
|
||||
continue
|
||||
bones.append(bone)
|
||||
else:
|
||||
human_bones = ext.vrm1.humanoid.human_bones
|
||||
human_bone_name_to_human_bone = human_bones.human_bone_name_to_human_bone()
|
||||
bones = [
|
||||
bone
|
||||
for bone in [
|
||||
armature.pose.bones.get(human_bone.node.bone_name)
|
||||
for human_bone in [
|
||||
human_bone_name_to_human_bone.get(human_bone_name)
|
||||
for human_bone_name in self.vrm1_human_bone_names
|
||||
]
|
||||
if human_bone
|
||||
]
|
||||
if bone
|
||||
]
|
||||
|
||||
if len(bones) < 2:
|
||||
return
|
||||
|
||||
root_bone = bones[0]
|
||||
tip_bone = bones[-1]
|
||||
|
||||
chained_bones: list[PoseBone] = []
|
||||
searching_bone: Optional[PoseBone] = tip_bone
|
||||
while True:
|
||||
if not searching_bone:
|
||||
return
|
||||
chained_bones.insert(0, searching_bone)
|
||||
if searching_bone == root_bone:
|
||||
break
|
||||
searching_bone = searching_bone.parent
|
||||
|
||||
for bone, child_bone in zip(chained_bones, chained_bones[1:]):
|
||||
set_bone_direction_to_align_child_bone(
|
||||
context, armature, self.direction, bone, child_bone
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ChainHorizontalMultipleChildren:
|
||||
direction: Vector
|
||||
vrm0_parent_human_bone_name: Vrm0HumanBoneName
|
||||
vrm0_human_bone_names: tuple[Vrm0HumanBoneName, ...]
|
||||
vrm1_parent_human_bone_name: Vrm1HumanBoneName
|
||||
vrm1_human_bone_names: tuple[Vrm1HumanBoneName, ...]
|
||||
|
||||
def execute(self, context: Context, armature: Object) -> None:
|
||||
armature_data = armature.data
|
||||
if not isinstance(armature_data, Armature):
|
||||
raise TypeError
|
||||
|
||||
ext = get_armature_extension(armature_data)
|
||||
if ext.is_vrm0():
|
||||
humanoid = ext.vrm0.humanoid
|
||||
bones: list[PoseBone] = []
|
||||
parent_bone: Optional[PoseBone] = None
|
||||
for human_bone in humanoid.human_bones:
|
||||
human_bone_name = Vrm0HumanBoneName.from_str(human_bone.bone)
|
||||
if not human_bone_name:
|
||||
continue
|
||||
if human_bone_name == self.vrm0_parent_human_bone_name:
|
||||
parent_bone = armature.pose.bones.get(human_bone.node.bone_name)
|
||||
elif human_bone_name in self.vrm0_human_bone_names:
|
||||
bone = armature.pose.bones.get(human_bone.node.bone_name)
|
||||
if bone:
|
||||
bones.append(bone)
|
||||
else:
|
||||
human_bones = ext.vrm1.humanoid.human_bones
|
||||
human_bone_name_to_human_bone = human_bones.human_bone_name_to_human_bone()
|
||||
bones = [
|
||||
bone
|
||||
for bone in [
|
||||
armature.pose.bones.get(human_bone.node.bone_name)
|
||||
for human_bone in [
|
||||
human_bone_name_to_human_bone.get(human_bone_name)
|
||||
for human_bone_name in self.vrm1_human_bone_names
|
||||
]
|
||||
if human_bone
|
||||
]
|
||||
if bone
|
||||
]
|
||||
parent_human_bone = human_bone_name_to_human_bone.get(
|
||||
self.vrm1_parent_human_bone_name
|
||||
)
|
||||
parent_bone = None
|
||||
if parent_human_bone:
|
||||
parent_bone = armature.pose.bones.get(parent_human_bone.node.bone_name)
|
||||
if not bones:
|
||||
return
|
||||
world_location = Vector((0, 0, 0))
|
||||
for bone in bones:
|
||||
world_location += (armature.matrix_world @ bone.matrix).to_translation()
|
||||
world_location /= len(bones)
|
||||
|
||||
if not parent_bone:
|
||||
return
|
||||
|
||||
set_bone_direction_to_align_z_world_location(
|
||||
context, armature, self.direction, parent_bone, world_location
|
||||
)
|
||||
|
||||
|
||||
def reset_root_to_human_bone_translation(
|
||||
pose: Pose, ext: VrmAddonArmatureExtensionPropertyGroup
|
||||
) -> None:
|
||||
# Reset positions from root bone to any Human bone
|
||||
# No particular reason for not resetting all bones, just intuition
|
||||
bones: list[PoseBone] = [bone for bone in pose.bones if not bone.parent]
|
||||
while bones:
|
||||
bone = bones.pop()
|
||||
bone.location = Vector((0, 0, 0))
|
||||
|
||||
if ext.is_vrm1():
|
||||
human_bone_name_to_human_bones = (
|
||||
ext.vrm1.humanoid.human_bones.human_bone_name_to_human_bone()
|
||||
)
|
||||
if any(
|
||||
True
|
||||
for human_bone in human_bone_name_to_human_bones.values()
|
||||
if human_bone.node.bone_name == bone.name
|
||||
):
|
||||
continue
|
||||
|
||||
if ext.is_vrm0() and any(
|
||||
True
|
||||
for human_bone in ext.vrm0.humanoid.human_bones
|
||||
if human_bone.node.bone_name == bone.name
|
||||
):
|
||||
continue
|
||||
|
||||
bones.extend(bone.children)
|
||||
|
||||
|
||||
def set_estimated_humanoid_t_pose(context: Context, armature: Object) -> bool:
|
||||
armature_data = armature.data
|
||||
if not isinstance(armature_data, Armature):
|
||||
return False
|
||||
|
||||
ext = get_armature_extension(armature_data)
|
||||
if ext.is_vrm0():
|
||||
if not ext.vrm0.humanoid.all_required_bones_are_assigned():
|
||||
return False
|
||||
else:
|
||||
human_bones = ext.vrm1.humanoid.human_bones
|
||||
if not human_bones.all_required_bones_are_assigned():
|
||||
return False
|
||||
|
||||
for bone in armature.pose.bones:
|
||||
set_rotation_without_mode_change(bone, Quaternion())
|
||||
|
||||
reset_root_to_human_bone_translation(armature.pose, ext)
|
||||
|
||||
context.view_layer.update()
|
||||
|
||||
# https://github.com/vrm-c/vrm-specification/blob/73855bb77d431a3374212551a4fa48e043be3ced/specification/VRMC_vrm-1.0/tpose.md
|
||||
chains: tuple[Union[ChainSingleChild, ChainHorizontalMultipleChildren], ...] = (
|
||||
ChainSingleChild(
|
||||
Vector((-1, 0, 0)),
|
||||
(
|
||||
Vrm0HumanBoneName.RIGHT_UPPER_ARM,
|
||||
Vrm0HumanBoneName.RIGHT_LOWER_ARM,
|
||||
Vrm0HumanBoneName.RIGHT_HAND,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.RIGHT_UPPER_ARM,
|
||||
Vrm1HumanBoneName.RIGHT_LOWER_ARM,
|
||||
Vrm1HumanBoneName.RIGHT_HAND,
|
||||
),
|
||||
),
|
||||
ChainHorizontalMultipleChildren(
|
||||
Vector((-1, 0, 0)),
|
||||
Vrm0HumanBoneName.RIGHT_HAND,
|
||||
(
|
||||
Vrm0HumanBoneName.RIGHT_INDEX_PROXIMAL,
|
||||
Vrm0HumanBoneName.RIGHT_MIDDLE_PROXIMAL,
|
||||
Vrm0HumanBoneName.RIGHT_RING_PROXIMAL,
|
||||
Vrm0HumanBoneName.RIGHT_LITTLE_PROXIMAL,
|
||||
),
|
||||
Vrm1HumanBoneName.RIGHT_HAND,
|
||||
(
|
||||
Vrm1HumanBoneName.RIGHT_INDEX_PROXIMAL,
|
||||
Vrm1HumanBoneName.RIGHT_MIDDLE_PROXIMAL,
|
||||
Vrm1HumanBoneName.RIGHT_RING_PROXIMAL,
|
||||
Vrm1HumanBoneName.RIGHT_LITTLE_PROXIMAL,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((-math.sqrt(0.5), -math.sqrt(0.5), 0)),
|
||||
(
|
||||
Vrm0HumanBoneName.RIGHT_THUMB_PROXIMAL,
|
||||
Vrm0HumanBoneName.RIGHT_THUMB_INTERMEDIATE,
|
||||
Vrm0HumanBoneName.RIGHT_THUMB_DISTAL,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.RIGHT_THUMB_METACARPAL,
|
||||
Vrm1HumanBoneName.RIGHT_THUMB_PROXIMAL,
|
||||
Vrm1HumanBoneName.RIGHT_THUMB_DISTAL,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((-1, 0, 0)),
|
||||
(
|
||||
Vrm0HumanBoneName.RIGHT_INDEX_PROXIMAL,
|
||||
Vrm0HumanBoneName.RIGHT_INDEX_INTERMEDIATE,
|
||||
Vrm0HumanBoneName.RIGHT_INDEX_DISTAL,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.RIGHT_INDEX_PROXIMAL,
|
||||
Vrm1HumanBoneName.RIGHT_INDEX_INTERMEDIATE,
|
||||
Vrm1HumanBoneName.RIGHT_INDEX_DISTAL,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((-1, 0, 0)),
|
||||
(
|
||||
Vrm0HumanBoneName.RIGHT_MIDDLE_PROXIMAL,
|
||||
Vrm0HumanBoneName.RIGHT_MIDDLE_INTERMEDIATE,
|
||||
Vrm0HumanBoneName.RIGHT_MIDDLE_DISTAL,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.RIGHT_MIDDLE_PROXIMAL,
|
||||
Vrm1HumanBoneName.RIGHT_MIDDLE_INTERMEDIATE,
|
||||
Vrm1HumanBoneName.RIGHT_MIDDLE_DISTAL,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((-1, 0, 0)),
|
||||
(
|
||||
Vrm0HumanBoneName.RIGHT_RING_PROXIMAL,
|
||||
Vrm0HumanBoneName.RIGHT_RING_INTERMEDIATE,
|
||||
Vrm0HumanBoneName.RIGHT_RING_DISTAL,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.RIGHT_RING_PROXIMAL,
|
||||
Vrm1HumanBoneName.RIGHT_RING_INTERMEDIATE,
|
||||
Vrm1HumanBoneName.RIGHT_RING_DISTAL,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((-1, 0, 0)),
|
||||
(
|
||||
Vrm0HumanBoneName.RIGHT_LITTLE_PROXIMAL,
|
||||
Vrm0HumanBoneName.RIGHT_LITTLE_INTERMEDIATE,
|
||||
Vrm0HumanBoneName.RIGHT_LITTLE_DISTAL,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.RIGHT_LITTLE_PROXIMAL,
|
||||
Vrm1HumanBoneName.RIGHT_LITTLE_INTERMEDIATE,
|
||||
Vrm1HumanBoneName.RIGHT_LITTLE_DISTAL,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((0, 0, -1)),
|
||||
(
|
||||
Vrm0HumanBoneName.RIGHT_UPPER_LEG,
|
||||
Vrm0HumanBoneName.RIGHT_LOWER_LEG,
|
||||
Vrm0HumanBoneName.RIGHT_FOOT,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.RIGHT_UPPER_LEG,
|
||||
Vrm1HumanBoneName.RIGHT_LOWER_LEG,
|
||||
Vrm1HumanBoneName.RIGHT_FOOT,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((1, 0, 0)),
|
||||
(
|
||||
Vrm0HumanBoneName.LEFT_UPPER_ARM,
|
||||
Vrm0HumanBoneName.LEFT_LOWER_ARM,
|
||||
Vrm0HumanBoneName.LEFT_HAND,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.LEFT_UPPER_ARM,
|
||||
Vrm1HumanBoneName.LEFT_LOWER_ARM,
|
||||
Vrm1HumanBoneName.LEFT_HAND,
|
||||
),
|
||||
),
|
||||
ChainHorizontalMultipleChildren(
|
||||
Vector((1, 0, 0)),
|
||||
Vrm0HumanBoneName.LEFT_HAND,
|
||||
(
|
||||
Vrm0HumanBoneName.LEFT_INDEX_PROXIMAL,
|
||||
Vrm0HumanBoneName.LEFT_MIDDLE_PROXIMAL,
|
||||
Vrm0HumanBoneName.LEFT_RING_PROXIMAL,
|
||||
Vrm0HumanBoneName.LEFT_LITTLE_PROXIMAL,
|
||||
),
|
||||
Vrm1HumanBoneName.LEFT_HAND,
|
||||
(
|
||||
Vrm1HumanBoneName.LEFT_INDEX_PROXIMAL,
|
||||
Vrm1HumanBoneName.LEFT_MIDDLE_PROXIMAL,
|
||||
Vrm1HumanBoneName.LEFT_RING_PROXIMAL,
|
||||
Vrm1HumanBoneName.LEFT_LITTLE_PROXIMAL,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((math.sqrt(0.5), -math.sqrt(0.5), 0)),
|
||||
(
|
||||
Vrm0HumanBoneName.LEFT_THUMB_PROXIMAL,
|
||||
Vrm0HumanBoneName.LEFT_THUMB_INTERMEDIATE,
|
||||
Vrm0HumanBoneName.LEFT_THUMB_DISTAL,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.LEFT_THUMB_METACARPAL,
|
||||
Vrm1HumanBoneName.LEFT_THUMB_PROXIMAL,
|
||||
Vrm1HumanBoneName.LEFT_THUMB_DISTAL,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((1, 0, 0)),
|
||||
(
|
||||
Vrm0HumanBoneName.LEFT_INDEX_PROXIMAL,
|
||||
Vrm0HumanBoneName.LEFT_INDEX_INTERMEDIATE,
|
||||
Vrm0HumanBoneName.LEFT_INDEX_DISTAL,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.LEFT_INDEX_PROXIMAL,
|
||||
Vrm1HumanBoneName.LEFT_INDEX_INTERMEDIATE,
|
||||
Vrm1HumanBoneName.LEFT_INDEX_DISTAL,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((1, 0, 0)),
|
||||
(
|
||||
Vrm0HumanBoneName.LEFT_MIDDLE_PROXIMAL,
|
||||
Vrm0HumanBoneName.LEFT_MIDDLE_INTERMEDIATE,
|
||||
Vrm0HumanBoneName.LEFT_MIDDLE_DISTAL,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.LEFT_MIDDLE_PROXIMAL,
|
||||
Vrm1HumanBoneName.LEFT_MIDDLE_INTERMEDIATE,
|
||||
Vrm1HumanBoneName.LEFT_MIDDLE_DISTAL,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((1, 0, 0)),
|
||||
(
|
||||
Vrm0HumanBoneName.LEFT_RING_PROXIMAL,
|
||||
Vrm0HumanBoneName.LEFT_RING_INTERMEDIATE,
|
||||
Vrm0HumanBoneName.LEFT_RING_DISTAL,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.LEFT_RING_PROXIMAL,
|
||||
Vrm1HumanBoneName.LEFT_RING_INTERMEDIATE,
|
||||
Vrm1HumanBoneName.LEFT_RING_DISTAL,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((1, 0, 0)),
|
||||
(
|
||||
Vrm0HumanBoneName.LEFT_LITTLE_PROXIMAL,
|
||||
Vrm0HumanBoneName.LEFT_LITTLE_INTERMEDIATE,
|
||||
Vrm0HumanBoneName.LEFT_LITTLE_DISTAL,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.LEFT_LITTLE_PROXIMAL,
|
||||
Vrm1HumanBoneName.LEFT_LITTLE_INTERMEDIATE,
|
||||
Vrm1HumanBoneName.LEFT_LITTLE_DISTAL,
|
||||
),
|
||||
),
|
||||
ChainSingleChild(
|
||||
Vector((0, 0, -1)),
|
||||
(
|
||||
Vrm0HumanBoneName.LEFT_UPPER_LEG,
|
||||
Vrm0HumanBoneName.LEFT_LOWER_LEG,
|
||||
Vrm0HumanBoneName.LEFT_FOOT,
|
||||
),
|
||||
(
|
||||
Vrm1HumanBoneName.LEFT_UPPER_LEG,
|
||||
Vrm1HumanBoneName.LEFT_LOWER_LEG,
|
||||
Vrm1HumanBoneName.LEFT_FOOT,
|
||||
),
|
||||
),
|
||||
)
|
||||
for chain in chains:
|
||||
chain.execute(context, armature)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PoseBonePose:
|
||||
matrix_basis: Matrix
|
||||
rotation_mode: str
|
||||
rotation_quaternion: Quaternion
|
||||
rotation_axis_angle: tuple[float, float, float, float]
|
||||
rotation_euler: Euler
|
||||
scale: Vector
|
||||
location: Vector
|
||||
bone_select: bool
|
||||
|
||||
@staticmethod
|
||||
def save(pose: Pose) -> Mapping[str, "PoseBonePose"]:
|
||||
return {
|
||||
bone.name: PoseBonePose(
|
||||
matrix_basis=bone.matrix_basis.copy(),
|
||||
rotation_mode=bone.rotation_mode,
|
||||
rotation_quaternion=bone.rotation_quaternion.copy(),
|
||||
rotation_axis_angle=(
|
||||
bone.rotation_axis_angle[0],
|
||||
bone.rotation_axis_angle[1],
|
||||
bone.rotation_axis_angle[2],
|
||||
bone.rotation_axis_angle[3],
|
||||
),
|
||||
rotation_euler=bone.rotation_euler.copy(),
|
||||
scale=bone.scale.copy(),
|
||||
location=bone.location.copy(),
|
||||
bone_select=(bone.bone.select if bpy.app.version < (5, 0) else False),
|
||||
)
|
||||
for bone in pose.bones
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def load(
|
||||
context: Context,
|
||||
pose: Pose,
|
||||
bone_name_to_pose_bone_pose: Mapping[str, "PoseBonePose"],
|
||||
) -> None:
|
||||
context.view_layer.update()
|
||||
|
||||
bones = [bone for bone in pose.bones if not bone.parent]
|
||||
while bones:
|
||||
bone = bones.pop()
|
||||
pose_bone_pose = bone_name_to_pose_bone_pose.get(bone.name)
|
||||
if pose_bone_pose:
|
||||
bone.matrix_basis = pose_bone_pose.matrix_basis.copy()
|
||||
# Directly restoring bone.matrix seems more efficient, but doing so
|
||||
# can cause problems when constraints are attached
|
||||
# https://github.com/saturday06/VRM-Addon-for-Blender/issues/671
|
||||
bone.rotation_mode = pose_bone_pose.rotation_mode
|
||||
bone.rotation_axis_angle = list(pose_bone_pose.rotation_axis_angle)
|
||||
bone.rotation_quaternion = pose_bone_pose.rotation_quaternion.copy()
|
||||
bone.rotation_euler = pose_bone_pose.rotation_euler.copy()
|
||||
bone.scale = pose_bone_pose.scale.copy()
|
||||
bone.location = pose_bone_pose.location.copy()
|
||||
if bpy.app.version < (5, 0):
|
||||
bone.bone.select = pose_bone_pose.bone_select
|
||||
bones.extend(bone.children)
|
||||
|
||||
context.view_layer.update()
|
||||
|
||||
|
||||
def leave_setup_humanoid_t_pose(
|
||||
context: Context,
|
||||
armature: Object,
|
||||
saved_pose_bone_pose: Mapping[str, PoseBonePose],
|
||||
saved_pose_position: str,
|
||||
*,
|
||||
saved_vrm1_look_at_preview: bool,
|
||||
) -> None:
|
||||
with save_workspace(context, armature):
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bpy.ops.object.mode_set(mode="POSE")
|
||||
PoseBonePose.load(context, armature.pose, saved_pose_bone_pose)
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
|
||||
armature_data = armature.data
|
||||
if not isinstance(armature_data, Armature):
|
||||
return
|
||||
armature_data.pose_position = saved_pose_position
|
||||
ext = get_armature_extension(armature_data)
|
||||
if (
|
||||
ext.is_vrm1()
|
||||
and ext.vrm1.look_at.enable_preview != saved_vrm1_look_at_preview
|
||||
):
|
||||
ext.vrm1.look_at.enable_preview = saved_vrm1_look_at_preview
|
||||
|
||||
|
||||
@contextmanager
|
||||
def setup_humanoid_t_pose(
|
||||
context: Context,
|
||||
armature: Object,
|
||||
) -> Iterator[None]:
|
||||
armature_data = armature.data
|
||||
if not isinstance(armature_data, Armature):
|
||||
raise TypeError
|
||||
ext = get_armature_extension(armature_data)
|
||||
if ext.is_vrm0():
|
||||
humanoid: Union[Vrm0HumanoidPropertyGroup, Vrm1HumanoidPropertyGroup] = (
|
||||
ext.vrm0.humanoid
|
||||
)
|
||||
else:
|
||||
humanoid = ext.vrm1.humanoid
|
||||
pose = humanoid.pose
|
||||
action = humanoid.pose_library
|
||||
pose_marker_name = humanoid.pose_marker_name
|
||||
|
||||
if pose != humanoid.POSE_CUSTOM_POSE.identifier:
|
||||
action = None
|
||||
pose_marker_name = ""
|
||||
|
||||
if (
|
||||
pose == humanoid.POSE_CURRENT_POSE.identifier
|
||||
and armature_data.pose_position == "REST"
|
||||
):
|
||||
yield
|
||||
return
|
||||
|
||||
if pose == humanoid.POSE_REST_POSITION_POSE.identifier or (
|
||||
pose == humanoid.POSE_CUSTOM_POSE.identifier
|
||||
and not (action and action.name in context.blend_data.actions)
|
||||
):
|
||||
saved_pose_position = armature_data.pose_position
|
||||
armature_data.pose_position = "REST"
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
armature_data.pose_position = saved_pose_position
|
||||
return
|
||||
|
||||
with save_workspace(context, armature):
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
bpy.ops.object.mode_set(mode="POSE")
|
||||
|
||||
saved_pose_position = armature_data.pose_position
|
||||
if armature_data.pose_position != "POSE":
|
||||
armature_data.pose_position = "POSE"
|
||||
|
||||
context.view_layer.update()
|
||||
|
||||
saved_pose_bone_pose = PoseBonePose.save(armature.pose)
|
||||
|
||||
if bpy.app.version < (5, 0):
|
||||
for bone in armature.pose.bones:
|
||||
bone.bone.select = False
|
||||
|
||||
ext = get_armature_extension(armature_data)
|
||||
saved_vrm1_look_at_preview = ext.vrm1.look_at.enable_preview
|
||||
if ext.is_vrm1() and ext.vrm1.look_at.enable_preview:
|
||||
# TODO: It would be helpful to warn in advance if this is
|
||||
# reached during export
|
||||
ext.vrm1.look_at.enable_preview = False
|
||||
if ext.vrm1.look_at.type == ext.vrm1.look_at.TYPE_BONE.identifier:
|
||||
human_bones = ext.vrm1.humanoid.human_bones
|
||||
|
||||
left_eye_bone_name = human_bones.left_eye.node.bone_name
|
||||
left_eye_bone = armature.pose.bones.get(left_eye_bone_name)
|
||||
if left_eye_bone:
|
||||
set_rotation_without_mode_change(left_eye_bone, Quaternion())
|
||||
|
||||
right_eye_bone_name = human_bones.right_eye.node.bone_name
|
||||
right_eye_bone = armature.pose.bones.get(right_eye_bone_name)
|
||||
if right_eye_bone:
|
||||
set_rotation_without_mode_change(right_eye_bone, Quaternion())
|
||||
|
||||
if pose == humanoid.POSE_AUTO_POSE.identifier:
|
||||
ops.vrm.make_estimated_humanoid_t_pose(armature_object_name=armature.name)
|
||||
elif pose == humanoid.POSE_CUSTOM_POSE.identifier:
|
||||
if action and action.name in context.blend_data.actions:
|
||||
pose_marker_frame = 0
|
||||
if pose_marker_name:
|
||||
for search_pose_marker in action.pose_markers.values():
|
||||
if search_pose_marker.name == pose_marker_name:
|
||||
pose_marker_frame = search_pose_marker.frame
|
||||
break
|
||||
armature.pose.apply_pose_from_action(
|
||||
action, evaluation_time=pose_marker_frame
|
||||
)
|
||||
else:
|
||||
# TODO: It would be helpful to warn in advance if this is
|
||||
# reached during export
|
||||
ops.vrm.make_estimated_humanoid_t_pose(
|
||||
armature_object_name=armature.name
|
||||
)
|
||||
|
||||
context.view_layer.update()
|
||||
|
||||
try:
|
||||
yield
|
||||
# After yield, bpy native objects may be deleted or frames may advance
|
||||
# making them invalid. Accessing them in this state can cause crashes,
|
||||
# so be careful not to access potentially invalid native objects after yield
|
||||
finally:
|
||||
leave_setup_humanoid_t_pose(
|
||||
context,
|
||||
armature,
|
||||
saved_pose_bone_pose,
|
||||
saved_pose_position,
|
||||
saved_vrm1_look_at_preview=saved_vrm1_look_at_preview,
|
||||
)
|
||||
Reference in New Issue
Block a user