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:
712
editor/make_armature.py
Normal file
712
editor/make_armature.py
Normal file
@@ -0,0 +1,712 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user