- 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
713 lines
25 KiB
Python
713 lines
25 KiB
Python
# SPDX-License-Identifier: MIT OR GPL-3.0-or-later
|
|
from collections.abc import Set as AbstractSet
|
|
from math import radians
|
|
from sys import float_info
|
|
from typing import TYPE_CHECKING, Optional
|
|
|
|
import bpy
|
|
from bpy.props import BoolProperty, FloatProperty, StringProperty
|
|
from bpy.types import Armature, Context, EditBone, Object, Operator
|
|
from mathutils import Matrix, Vector
|
|
|
|
from ..common.version import get_addon_version
|
|
from ..common.workspace import save_workspace
|
|
from . import migration
|
|
from .extension import get_armature_extension
|
|
from .vrm0.property_group import (
|
|
Vrm0BlendShapeGroupPropertyGroup,
|
|
Vrm0HumanoidPropertyGroup,
|
|
)
|
|
|
|
MIN_BONE_LENGTH = 0.00001 # 10μm
|
|
AUTO_BONE_CONNECTION_DISTANCE = 0.000001 # 1μm
|
|
|
|
|
|
class ICYP_OT_make_armature(Operator):
|
|
bl_idname = "icyp.make_basic_armature"
|
|
bl_label = "Add VRM Humanoid"
|
|
bl_description = "make armature and simple setup for VRM export"
|
|
bl_options: AbstractSet[str] = {"REGISTER", "UNDO"}
|
|
|
|
skip_heavy_armature_setup: BoolProperty( # type: ignore[valid-type]
|
|
default=False,
|
|
options={"HIDDEN"},
|
|
)
|
|
|
|
wip_with_template_mesh: BoolProperty( # type: ignore[valid-type]
|
|
default=False
|
|
)
|
|
|
|
# Height in meters
|
|
tall: FloatProperty( # type: ignore[valid-type]
|
|
default=1.70,
|
|
min=0.3,
|
|
step=1,
|
|
name="Bone tall",
|
|
)
|
|
|
|
# Head-to-body ratio
|
|
head_ratio: FloatProperty( # type: ignore[valid-type]
|
|
default=8.0,
|
|
min=4,
|
|
step=5,
|
|
description="height per heads",
|
|
)
|
|
|
|
head_width_ratio: FloatProperty( # type: ignore[valid-type]
|
|
default=2 / 3,
|
|
min=0.3,
|
|
max=1.2,
|
|
step=5,
|
|
description="height per heads",
|
|
)
|
|
|
|
# Leg-to-torso ratio: 0: child-like, 1: adult-like (effective for low head count)
|
|
aging_ratio: FloatProperty( # type: ignore[valid-type]
|
|
default=0.5, min=0, max=1, step=10
|
|
)
|
|
|
|
# Eye depth
|
|
eye_depth: FloatProperty( # type: ignore[valid-type]
|
|
default=-0.03, min=-0.1, max=0, step=1
|
|
)
|
|
|
|
# Shoulder width
|
|
shoulder_in_width: FloatProperty( # type: ignore[valid-type]
|
|
default=0.05,
|
|
min=0.01,
|
|
step=1,
|
|
description="Inner shoulder position",
|
|
)
|
|
|
|
shoulder_width: FloatProperty( # type: ignore[valid-type]
|
|
default=0.08,
|
|
min=0.01,
|
|
step=1,
|
|
description="shoulder roll position",
|
|
)
|
|
|
|
# Arm length ratio
|
|
arm_length_ratio: FloatProperty( # type: ignore[valid-type]
|
|
default=1, min=0.5, step=1
|
|
)
|
|
|
|
# Hand
|
|
hand_ratio: FloatProperty( # type: ignore[valid-type]
|
|
default=1, min=0.5, max=2.0, step=5
|
|
)
|
|
|
|
finger_1_2_ratio: FloatProperty( # type: ignore[valid-type]
|
|
default=0.75,
|
|
min=0.5,
|
|
max=1,
|
|
step=1,
|
|
description="proximal / intermediate",
|
|
)
|
|
|
|
finger_2_3_ratio: FloatProperty( # type: ignore[valid-type]
|
|
default=0.75,
|
|
min=0.5,
|
|
max=1,
|
|
step=1,
|
|
description="intermediate / distal",
|
|
)
|
|
|
|
nail_bone: BoolProperty( # type: ignore[valid-type]
|
|
default=False,
|
|
description="may need for finger collider",
|
|
) # Needed for fingertip collision detection
|
|
|
|
# Foot
|
|
leg_length_ratio: FloatProperty( # type: ignore[valid-type]
|
|
default=0.5,
|
|
min=0.3,
|
|
max=0.6,
|
|
step=1,
|
|
description="upper body/lower body",
|
|
)
|
|
|
|
leg_width_ratio: FloatProperty( # type: ignore[valid-type]
|
|
default=1, min=0.01, step=1
|
|
)
|
|
|
|
leg_size: FloatProperty( # type: ignore[valid-type]
|
|
default=0.26, min=0.05, step=1
|
|
)
|
|
|
|
custom_property_name: StringProperty( # type: ignore[valid-type]
|
|
options={"HIDDEN"}
|
|
)
|
|
|
|
armature_obj: Optional[Object] = None
|
|
|
|
def execute(self, context: Context) -> set[str]:
|
|
with save_workspace(context):
|
|
self.armature_obj, compare_dict = self.make_armature(context)
|
|
self.setup_as_vrm(context, self.armature_obj, compare_dict)
|
|
if self.custom_property_name:
|
|
self.armature_obj[self.custom_property_name] = True
|
|
context.view_layer.objects.active = self.armature_obj
|
|
return {"FINISHED"}
|
|
|
|
def float_prop(self, name: str) -> float:
|
|
prop = getattr(self, name)
|
|
if not isinstance(prop, float):
|
|
message = f"prop {name} is not float"
|
|
raise TypeError(message)
|
|
return prop
|
|
|
|
def head_size(self) -> float:
|
|
return self.float_prop("tall") / self.float_prop("head_ratio")
|
|
|
|
def hand_size(self) -> float:
|
|
return self.head_size() * 0.75 * self.float_prop("hand_ratio")
|
|
|
|
def make_armature(self, context: Context) -> tuple[Object, dict[str, str]]:
|
|
def bone_add(
|
|
armature_data: Armature,
|
|
name: str,
|
|
head_pos: Vector,
|
|
tail_pos: Vector,
|
|
parent_bone: Optional[EditBone] = None,
|
|
radius: float = 0.1,
|
|
roll: float = 0,
|
|
) -> EditBone:
|
|
added_bone = armature_data.edit_bones.new(name)
|
|
added_bone.head = head_pos
|
|
added_bone.tail = tail_pos
|
|
added_bone.head_radius = radius
|
|
added_bone.tail_radius = radius
|
|
added_bone.envelope_distance = 0.01
|
|
added_bone.roll = radians(roll)
|
|
if parent_bone is not None:
|
|
added_bone.parent = parent_bone
|
|
bone_dict.update({name: added_bone})
|
|
return added_bone
|
|
|
|
def x_mirror_bones_add(
|
|
armature_data: Armature,
|
|
base_name: str,
|
|
right_head_pos: Vector,
|
|
right_tail_pos: Vector,
|
|
parent_bones: tuple[EditBone, EditBone],
|
|
radius: float = 0.1,
|
|
bone_type: str = "other",
|
|
) -> tuple[EditBone, EditBone]:
|
|
right_roll = 0
|
|
left_roll = 0
|
|
if bone_type == "arm":
|
|
right_roll = 0
|
|
elif bone_type == "leg":
|
|
right_roll = 0
|
|
left_roll = 0
|
|
left_bone = bone_add(
|
|
armature_data,
|
|
base_name + ".L",
|
|
right_head_pos,
|
|
right_tail_pos,
|
|
parent_bones[0],
|
|
radius=radius,
|
|
roll=left_roll,
|
|
)
|
|
|
|
head_pos = [pos * axis for pos, axis in zip(right_head_pos, (-1, 1, 1))]
|
|
tail_pos = [pos * axis for pos, axis in zip(right_tail_pos, (-1, 1, 1))]
|
|
right_bone = bone_add(
|
|
armature_data,
|
|
base_name + ".R",
|
|
Vector((head_pos[0], head_pos[1], head_pos[2])),
|
|
Vector((tail_pos[0], tail_pos[1], tail_pos[2])),
|
|
parent_bones[1],
|
|
radius=radius,
|
|
roll=right_roll,
|
|
)
|
|
|
|
return left_bone, right_bone
|
|
|
|
def x_add(pos_a: Vector, add_x: float) -> Vector:
|
|
pos = [p_a + _add for p_a, _add in zip(pos_a, [add_x, 0, 0])]
|
|
return Vector((pos[0], pos[1], pos[2]))
|
|
|
|
def y_add(pos_a: Vector, add_y: float) -> Vector:
|
|
pos = [p_a + _add for p_a, _add in zip(pos_a, [0, add_y, 0])]
|
|
return Vector((pos[0], pos[1], pos[2]))
|
|
|
|
def z_add(pos_a: Vector, add_z: float) -> Vector:
|
|
pos = [p_a + _add for p_a, _add in zip(pos_a, [0, 0, add_z])]
|
|
return Vector((pos[0], pos[1], pos[2]))
|
|
|
|
def fingers(
|
|
armature_data: Armature,
|
|
finger_name: str,
|
|
proximal_pos: Vector,
|
|
finger_len_sum: float,
|
|
) -> tuple[
|
|
tuple[EditBone, EditBone],
|
|
tuple[EditBone, EditBone],
|
|
tuple[EditBone, EditBone],
|
|
]:
|
|
finger_normalize = 1 / (
|
|
self.finger_1_2_ratio * self.finger_2_3_ratio
|
|
+ self.finger_1_2_ratio
|
|
+ 1
|
|
)
|
|
proximal_finger_len = finger_len_sum * finger_normalize
|
|
intermediate_finger_len = (
|
|
finger_len_sum * finger_normalize * self.finger_1_2_ratio
|
|
)
|
|
distal_finger_len = (
|
|
finger_len_sum
|
|
* finger_normalize
|
|
* self.finger_1_2_ratio
|
|
* self.finger_2_3_ratio
|
|
)
|
|
proximal_bones = x_mirror_bones_add(
|
|
armature_data,
|
|
f"{finger_name}_proximal",
|
|
proximal_pos,
|
|
x_add(proximal_pos, proximal_finger_len),
|
|
hands,
|
|
self.hand_size() / 18,
|
|
bone_type="arm",
|
|
)
|
|
intermediate_bones = x_mirror_bones_add(
|
|
armature_data,
|
|
f"{finger_name}_intermediate",
|
|
proximal_bones[0].tail,
|
|
x_add(proximal_bones[0].tail, intermediate_finger_len),
|
|
proximal_bones,
|
|
self.hand_size() / 18,
|
|
bone_type="arm",
|
|
)
|
|
distal_bones = x_mirror_bones_add(
|
|
armature_data,
|
|
f"{finger_name}_distal",
|
|
intermediate_bones[0].tail,
|
|
x_add(intermediate_bones[0].tail, distal_finger_len),
|
|
intermediate_bones,
|
|
self.hand_size() / 18,
|
|
bone_type="arm",
|
|
)
|
|
if self.nail_bone:
|
|
x_mirror_bones_add(
|
|
armature_data,
|
|
f"{finger_name}_nail",
|
|
distal_bones[0].tail,
|
|
x_add(distal_bones[0].tail, distal_finger_len),
|
|
distal_bones,
|
|
self.hand_size() / 20,
|
|
bone_type="arm",
|
|
)
|
|
return proximal_bones, intermediate_bones, distal_bones
|
|
|
|
bpy.ops.object.add(type="ARMATURE", enter_editmode=True, location=(0, 0, 0))
|
|
armature = context.object
|
|
if not armature:
|
|
message = "armature is not created"
|
|
raise ValueError(message)
|
|
armature_data = armature.data
|
|
if not isinstance(armature_data, Armature):
|
|
message = "armature data is not an Armature"
|
|
raise TypeError(message)
|
|
get_armature_extension(armature_data).addon_version = get_addon_version()
|
|
|
|
bone_dict: dict[str, EditBone] = {}
|
|
|
|
# bone_type = "leg" or "arm" for roll setting
|
|
|
|
head_size = self.head_size()
|
|
# down side (previously the lower leg ratio of upper leg/lower leg for
|
|
# 8-head proportions, later linearly interpolated with age factor for
|
|
# 4-head proportions)(breaks if upper leg is too high)
|
|
eight_upside_ratio, four_upside_ratio = (
|
|
1 - self.leg_length_ratio,
|
|
(2.5 / 4) * (1 - self.aging_ratio)
|
|
+ (1 - self.leg_length_ratio) * self.aging_ratio,
|
|
)
|
|
hip_up_down_ratio = (
|
|
eight_upside_ratio * (1 - (8 - self.head_ratio) / 4)
|
|
+ four_upside_ratio * (8 - self.head_ratio) / 4
|
|
)
|
|
# Torso
|
|
# Groin
|
|
body_separate = self.tall * (1 - hip_up_down_ratio)
|
|
# Neck length
|
|
neck_len = head_size * 2 / 3
|
|
# Sacrum (pelvic spine base)
|
|
hips_tall = body_separate + head_size * 3 / 4
|
|
# Thoracic spine total length # 1/3 of neck is hidden behind the jaw
|
|
backbone_len = self.tall - hips_tall - head_size - neck_len / 2
|
|
# TODO: Verify the ratio of thoracic spine to vertebrae
|
|
# Main flexion point located at the base of the spine, and another flexion
|
|
# point located at the base of the thoracic cage
|
|
# by Humanoid Doc
|
|
spine_len = backbone_len * 5 / 17
|
|
|
|
root = bone_add(armature_data, "root", Vector((0, 0, 0)), Vector((0, 0, 0.3)))
|
|
# Sacrum base
|
|
hips = bone_add(
|
|
armature_data,
|
|
"hips",
|
|
Vector((0, 0, body_separate)),
|
|
Vector((0, 0, hips_tall)),
|
|
root,
|
|
roll=0,
|
|
)
|
|
# Pelvic base -> Thoracic cage base
|
|
spine = bone_add(
|
|
armature_data, "spine", hips.tail, z_add(hips.tail, spine_len), hips, roll=0
|
|
)
|
|
# Thoracic cage base -> Neck base
|
|
chest = bone_add(
|
|
armature_data,
|
|
"chest",
|
|
spine.tail,
|
|
z_add(hips.tail, backbone_len),
|
|
spine,
|
|
roll=0,
|
|
)
|
|
neck = bone_add(
|
|
armature_data,
|
|
"neck",
|
|
Vector((0, 0, self.tall - head_size - neck_len / 2)),
|
|
Vector((0, 0, self.tall - head_size + neck_len / 2)),
|
|
chest,
|
|
roll=0,
|
|
)
|
|
# Half of the neck is hidden behind the jaw
|
|
head = bone_add(
|
|
armature_data,
|
|
"head",
|
|
Vector((0, 0, self.tall - head_size + neck_len / 2)),
|
|
Vector((0, 0, self.tall)),
|
|
neck,
|
|
roll=0,
|
|
)
|
|
|
|
# Eyes
|
|
eye_depth = self.eye_depth
|
|
eyes = x_mirror_bones_add(
|
|
armature_data,
|
|
"eye",
|
|
Vector(
|
|
(head_size * self.head_width_ratio / 5, 0, self.tall - head_size / 2)
|
|
),
|
|
Vector(
|
|
(
|
|
head_size * self.head_width_ratio / 5,
|
|
eye_depth,
|
|
self.tall - head_size / 2,
|
|
)
|
|
),
|
|
(head, head),
|
|
)
|
|
# Legs
|
|
leg_width = head_size / 4 * self.leg_width_ratio
|
|
leg_size = self.leg_size
|
|
|
|
leg_bone_length = (body_separate + head_size * 3 / 8 - self.tall * 0.05) / 2
|
|
upside_legs = x_mirror_bones_add(
|
|
armature_data,
|
|
"upper_leg",
|
|
x_add(Vector((0, 0, body_separate + head_size * 3 / 8)), leg_width),
|
|
x_add(
|
|
Vector(
|
|
z_add(
|
|
Vector((0, 0, body_separate + head_size * 3 / 8)),
|
|
-leg_bone_length,
|
|
)
|
|
),
|
|
leg_width,
|
|
),
|
|
(hips, hips),
|
|
radius=leg_width * 0.9,
|
|
bone_type="leg",
|
|
)
|
|
lower_legs = x_mirror_bones_add(
|
|
armature_data,
|
|
"lower_leg",
|
|
upside_legs[0].tail,
|
|
Vector((leg_width, 0, self.tall * 0.05)),
|
|
upside_legs,
|
|
radius=leg_width * 0.9,
|
|
bone_type="leg",
|
|
)
|
|
foots = x_mirror_bones_add(
|
|
armature_data,
|
|
"foot",
|
|
lower_legs[0].tail,
|
|
Vector((leg_width, -leg_size * (2 / 3), 0)),
|
|
lower_legs,
|
|
radius=leg_width * 0.9,
|
|
bone_type="leg",
|
|
)
|
|
toes = x_mirror_bones_add(
|
|
armature_data,
|
|
"toes",
|
|
foots[0].tail,
|
|
Vector((leg_width, -leg_size, 0)),
|
|
foots,
|
|
radius=leg_width * 0.5,
|
|
bone_type="leg",
|
|
)
|
|
|
|
# Shoulder to fingers
|
|
shoulder_in_pos = self.shoulder_in_width / 2
|
|
|
|
shoulder_parent = chest
|
|
shoulders = x_mirror_bones_add(
|
|
armature_data,
|
|
"shoulder",
|
|
x_add(shoulder_parent.tail, shoulder_in_pos),
|
|
x_add(shoulder_parent.tail, shoulder_in_pos + self.shoulder_width),
|
|
(shoulder_parent, shoulder_parent),
|
|
radius=self.hand_size() * 0.4,
|
|
bone_type="arm",
|
|
)
|
|
|
|
arm_length = (
|
|
head_size
|
|
* (1 * (1 - (self.head_ratio - 6) / 2) + 1.5 * ((self.head_ratio - 6) / 2))
|
|
* self.arm_length_ratio
|
|
)
|
|
arms = x_mirror_bones_add(
|
|
armature_data,
|
|
"upper_arm",
|
|
shoulders[0].tail,
|
|
x_add(shoulders[0].tail, arm_length),
|
|
shoulders,
|
|
radius=self.hand_size() * 0.4,
|
|
bone_type="arm",
|
|
)
|
|
|
|
# When making a fist, it becomes about half the size of an open palm.
|
|
# When making a fist, the length of the forearm including the hand
|
|
# is roughly the same as the length of the upper arm,
|
|
# but it breaks down if the hand is too big.
|
|
forearm_length = max(arm_length - self.hand_size() / 2, arm_length * 0.8)
|
|
forearms = x_mirror_bones_add(
|
|
armature_data,
|
|
"lower_arm",
|
|
arms[0].tail,
|
|
x_add(arms[0].tail, forearm_length),
|
|
arms,
|
|
radius=self.hand_size() * 0.4,
|
|
bone_type="arm",
|
|
)
|
|
hands = x_mirror_bones_add(
|
|
armature_data,
|
|
"hand",
|
|
forearms[0].tail,
|
|
x_add(forearms[0].tail, self.hand_size() / 2),
|
|
forearms,
|
|
radius=self.hand_size() / 4,
|
|
bone_type="arm",
|
|
)
|
|
|
|
finger_y_offset = -self.hand_size() / 16
|
|
thumbs = fingers(
|
|
armature_data,
|
|
"thumb",
|
|
y_add(hands[0].head, finger_y_offset * 3),
|
|
self.hand_size() / 2,
|
|
)
|
|
|
|
mats = [
|
|
Matrix.Translation(vec)
|
|
for vec in [thumbs[0][i].matrix.translation for i in [0, 1]]
|
|
]
|
|
for j in range(3):
|
|
for n, angle in enumerate([-45, 45]):
|
|
thumbs[j][n].transform(mats[n].inverted(), scale=False, roll=False)
|
|
thumbs[j][n].transform(Matrix.Rotation(radians(angle), 4, "Z"))
|
|
thumbs[j][n].transform(mats[n], scale=False, roll=False)
|
|
thumbs[j][n].roll = 0
|
|
|
|
index_fingers = fingers(
|
|
armature_data,
|
|
"index",
|
|
y_add(hands[0].tail, finger_y_offset * 3),
|
|
(self.hand_size() / 2) - (1 / 2.3125) * (self.hand_size() / 2) / 3,
|
|
)
|
|
middle_fingers = fingers(
|
|
armature_data,
|
|
"middle",
|
|
y_add(hands[0].tail, finger_y_offset),
|
|
self.hand_size() / 2,
|
|
)
|
|
ring_fingers = fingers(
|
|
armature_data,
|
|
"ring",
|
|
y_add(hands[0].tail, -finger_y_offset),
|
|
(self.hand_size() / 2) - (1 / 2.3125) * (self.hand_size() / 2) / 3,
|
|
)
|
|
little_fingers = fingers(
|
|
armature_data,
|
|
"little",
|
|
y_add(hands[0].tail, -finger_y_offset * 3),
|
|
((self.hand_size() / 2) - (1 / 2.3125) * (self.hand_size() / 2) / 3)
|
|
* ((1 / 2.3125) + (1 / 2.3125) * 0.75),
|
|
)
|
|
|
|
body_dict = {
|
|
"hips": hips.name,
|
|
"spine": spine.name,
|
|
"chest": chest.name,
|
|
"neck": neck.name,
|
|
"head": head.name,
|
|
}
|
|
|
|
left_right_body_dict = {
|
|
f"{left_right}{bone_name}": bones[lr].name
|
|
for bone_name, bones in {
|
|
"Eye": eyes,
|
|
"UpperLeg": upside_legs,
|
|
"LowerLeg": lower_legs,
|
|
"Foot": foots,
|
|
"Toes": toes,
|
|
"Shoulder": shoulders,
|
|
"UpperArm": arms,
|
|
"LowerArm": forearms,
|
|
"Hand": hands,
|
|
}.items()
|
|
for lr, left_right in enumerate(["left", "right"])
|
|
}
|
|
|
|
# VRM finger like name key
|
|
fingers_dict = {
|
|
f"{left_right}{finger_name}{position}": finger[i][lr].name
|
|
for finger_name, finger in zip(
|
|
["Thumb", "Index", "Middle", "Ring", "Little"],
|
|
[thumbs, index_fingers, middle_fingers, ring_fingers, little_fingers],
|
|
)
|
|
for i, position in enumerate(["Proximal", "Intermediate", "Distal"])
|
|
for lr, left_right in enumerate(["left", "right"])
|
|
}
|
|
|
|
# VRM bone name : blender bone name
|
|
bone_name_all_dict: dict[str, str] = {}
|
|
bone_name_all_dict.update(body_dict)
|
|
bone_name_all_dict.update(left_right_body_dict)
|
|
bone_name_all_dict.update(fingers_dict)
|
|
|
|
armature_data = armature.data
|
|
if isinstance(armature_data, Armature):
|
|
connect_parent_tail_and_child_head_if_very_close_position(armature_data)
|
|
|
|
context.scene.view_layers.update()
|
|
bpy.ops.object.mode_set(mode="OBJECT")
|
|
context.scene.view_layers.update()
|
|
return armature, bone_name_all_dict
|
|
|
|
def setup_as_vrm(
|
|
self, context: Context, armature: Object, compare_dict: dict[str, str]
|
|
) -> None:
|
|
armature_data = armature.data
|
|
if not isinstance(armature_data, Armature):
|
|
message = "armature data is not an Armature"
|
|
raise TypeError(message)
|
|
Vrm0HumanoidPropertyGroup.fixup_human_bones(armature)
|
|
ext = get_armature_extension(armature_data)
|
|
vrm0_humanoid = ext.vrm0.humanoid
|
|
vrm1_humanoid = ext.vrm1.humanoid
|
|
if not self.skip_heavy_armature_setup:
|
|
for vrm_bone_name, bpy_bone_name in compare_dict.items():
|
|
for human_bone in vrm0_humanoid.human_bones:
|
|
if human_bone.bone == vrm_bone_name:
|
|
human_bone.node.bone_name = bpy_bone_name
|
|
break
|
|
vrm0_humanoid.pose = vrm0_humanoid.POSE_REST_POSITION_POSE.identifier
|
|
vrm1_humanoid.pose = vrm1_humanoid.POSE_REST_POSITION_POSE.identifier
|
|
self.make_extension_setting_and_metas(
|
|
armature,
|
|
offset_from_head_bone=(-self.eye_depth, self.head_size() / 6, 0),
|
|
)
|
|
if not self.skip_heavy_armature_setup:
|
|
migration.migrate(context, armature.name)
|
|
|
|
@classmethod
|
|
def make_extension_setting_and_metas(
|
|
cls,
|
|
armature: Object,
|
|
offset_from_head_bone: tuple[float, float, float] = (0, 0, 0),
|
|
) -> None:
|
|
armature_data = armature.data
|
|
if not isinstance(armature_data, Armature):
|
|
return
|
|
vrm0 = get_armature_extension(armature_data).vrm0
|
|
vrm1 = get_armature_extension(armature_data).vrm1
|
|
vrm0.first_person.first_person_bone.bone_name = "head"
|
|
vrm0.first_person.first_person_bone_offset = (0, 0, 0.06)
|
|
vrm1.look_at.offset_from_head_bone = offset_from_head_bone
|
|
vrm0.first_person.look_at_horizontal_inner.y_range = 8
|
|
vrm0.first_person.look_at_horizontal_outer.y_range = 12
|
|
vrm0.meta.author = "undefined"
|
|
vrm0.meta.contact_information = "undefined"
|
|
vrm0.meta.other_license_url = "undefined"
|
|
vrm0.meta.other_permission_url = "undefined"
|
|
vrm0.meta.reference = "undefined"
|
|
vrm0.meta.title = "undefined"
|
|
vrm0.meta.version = "undefined"
|
|
for preset in Vrm0BlendShapeGroupPropertyGroup.preset_name_enum:
|
|
if (
|
|
preset.identifier
|
|
== Vrm0BlendShapeGroupPropertyGroup.PRESET_NAME_UNKNOWN.identifier
|
|
):
|
|
continue
|
|
blend_shape_group = vrm0.blend_shape_master.blend_shape_groups.add()
|
|
blend_shape_group.name = preset.name.replace(" ", "")
|
|
blend_shape_group.preset_name = preset.identifier
|
|
|
|
if TYPE_CHECKING:
|
|
# This code is auto generated.
|
|
# To regenerate, run the `uv run tools/property_typing.py` command.
|
|
skip_heavy_armature_setup: bool # type: ignore[no-redef]
|
|
wip_with_template_mesh: bool # type: ignore[no-redef]
|
|
tall: float # type: ignore[no-redef]
|
|
head_ratio: float # type: ignore[no-redef]
|
|
head_width_ratio: float # type: ignore[no-redef]
|
|
aging_ratio: float # type: ignore[no-redef]
|
|
eye_depth: float # type: ignore[no-redef]
|
|
shoulder_in_width: float # type: ignore[no-redef]
|
|
shoulder_width: float # type: ignore[no-redef]
|
|
arm_length_ratio: float # type: ignore[no-redef]
|
|
hand_ratio: float # type: ignore[no-redef]
|
|
finger_1_2_ratio: float # type: ignore[no-redef]
|
|
finger_2_3_ratio: float # type: ignore[no-redef]
|
|
nail_bone: bool # type: ignore[no-redef]
|
|
leg_length_ratio: float # type: ignore[no-redef]
|
|
leg_width_ratio: float # type: ignore[no-redef]
|
|
leg_size: float # type: ignore[no-redef]
|
|
custom_property_name: str # type: ignore[no-redef]
|
|
|
|
|
|
def connect_parent_tail_and_child_head_if_very_close_position(
|
|
armature: Armature,
|
|
) -> None:
|
|
bones = [bone for bone in armature.edit_bones if not bone.parent]
|
|
while bones:
|
|
bone = bones.pop()
|
|
|
|
children_by_distance = sorted(
|
|
bone.children,
|
|
key=lambda child: (child.parent.tail - child.head).length_squared
|
|
if child.parent
|
|
else 0.0,
|
|
)
|
|
for child in children_by_distance:
|
|
if (bone.tail - child.head).length < AUTO_BONE_CONNECTION_DISTANCE and (
|
|
bone.head - child.head
|
|
).length >= MIN_BONE_LENGTH:
|
|
bone.tail = child.head
|
|
break
|
|
|
|
bones.extend(bone.children)
|
|
|
|
bones = [bone for bone in armature.edit_bones if not bone.parent]
|
|
while bones:
|
|
bone = bones.pop()
|
|
for child in bone.children:
|
|
if (bone.tail - child.head).length < float_info.epsilon:
|
|
child.use_connect = True
|
|
bones.append(child)
|