- 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
717 lines
25 KiB
Python
717 lines
25 KiB
Python
# 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,
|
|
)
|