Merge pull request #6 from Yusarina/Alpha-4

Alpha 4
This commit is contained in:
Yusarina
2025-08-03 15:52:32 +01:00
committed by GitHub
13 changed files with 485 additions and 212 deletions
+3 -3
View File
@@ -32,8 +32,8 @@ See everything Avatar Toolkit has ot offer [here](https://avatartoolkit.xyz/lega
## Requirements ## Requirements
1) Blender Version 1) Blender Version
- Blender 4.4 or newer is required - Blender 4.5 or newer is required
- Blender 4.4 is the current recommended version - Blender 4.5 is the current recommended version
2) Python Requirements 2) Python Requirements
@@ -42,7 +42,7 @@ See everything Avatar Toolkit has ot offer [here](https://avatartoolkit.xyz/lega
3) Recommended Setup 3) Recommended Setup
- Download Blender directly from https://blender.org - Download Blender directly from https://blender.org
- Use Blender 4.4 for the best experience - Use Blender 4.5 for the best experience
#### Additional Plugins Requirements. #### Additional Plugins Requirements.
Currently None. Currently None.
+2 -2
View File
@@ -3,13 +3,13 @@
schema_version = "1.0.0" schema_version = "1.0.0"
id = "avatar_toolkit" id = "avatar_toolkit"
version = "0.3.0" version = "0.4.0"
name = "Avatar Toolkit" name = "Avatar Toolkit"
tagline = "A modern tool for importing and optimizing models for VRChat, Resonite, and other similar games." tagline = "A modern tool for importing and optimizing models for VRChat, Resonite, and other similar games."
maintainer = "Team NekoNeo" maintainer = "Team NekoNeo"
type = "add-on" type = "add-on"
blender_version_min = "4.4.0" blender_version_min = "4.5.0"
license = [ license = [
"SPDX:GPL-3.0-or-later", "SPDX:GPL-3.0-or-later",
+155 -1
View File
@@ -211,9 +211,163 @@ def validate_bone_hierarchy(bones: Dict[str, Bone], parent_name: str, child_name
return False return False
return bones[child_name].parent == bones[parent_name] return bones[child_name].parent == bones[parent_name]
def extract_bone_side_info(bone_name: str) -> Tuple[str, str]:
"""
Extract base bone name and side indicator from a bone name.
Returns (base_name, side) where side is 'L', 'R', or ''
"""
normalized = simplify_bonename(bone_name)
original = bone_name
# Common left/right patterns to check
left_patterns = [
'left', 'l', 'lft', 'lt',
'.l', '_l', '-l', ' l',
'', 'ひだり'
]
right_patterns = [
'right', 'r', 'rgt', 'rt',
'.r', '_r', '-r', ' r',
'', 'みぎ'
]
# Check for left patterns
for pattern in left_patterns:
pattern_norm = simplify_bonename(pattern)
if normalized.startswith(pattern_norm):
base = normalized[len(pattern_norm):]
if base: # Make sure there's something left
return base, 'L'
elif normalized.endswith(pattern_norm):
base = normalized[:-len(pattern_norm)]
if base:
return base, 'L'
elif pattern_norm in normalized:
# Handle cases like ArmLeft
parts = normalized.split(pattern_norm)
if len(parts) == 2:
base = parts[0] + parts[1]
if base:
return base, 'L'
# Check for right patterns
for pattern in right_patterns:
pattern_norm = simplify_bonename(pattern)
if normalized.startswith(pattern_norm):
base = normalized[len(pattern_norm):]
if base:
return base, 'R'
elif normalized.endswith(pattern_norm):
base = normalized[:-len(pattern_norm)]
if base:
return base, 'R'
elif pattern_norm in normalized:
parts = normalized.split(pattern_norm)
if len(parts) == 2:
base = parts[0] + parts[1]
if base:
return base, 'R'
return normalized, ''
def find_symmetric_bone_pairs(bones: Dict[str, Bone]) -> Dict[str, Tuple[List[str], List[str]]]:
"""
Automatically find symmetric bone pairs in the armature.
Returns dict mapping base_name to (left_bones, right_bones)
"""
bone_groups = {}
for bone_name in bones.keys():
base, side = extract_bone_side_info(bone_name)
if side:
if base not in bone_groups:
bone_groups[base] = {'L': [], 'R': []}
bone_groups[base][side].append(bone_name)
symmetric_pairs = {}
for base, sides in bone_groups.items():
if sides['L'] and sides['R']:
symmetric_pairs[base] = (sides['L'], sides['R'])
return symmetric_pairs
def validate_armature_symmetry(armature: Object) -> Tuple[bool, List[str]]:
"""
Comprehensive symmetry validation that provides detailed feedback
"""
if not armature or armature.type != 'ARMATURE':
return False, ["Invalid armature"]
bones = {bone.name: bone for bone in armature.data.bones}
symmetric_pairs = find_symmetric_bone_pairs(bones)
messages = []
is_symmetric = True
if symmetric_pairs:
messages.append("Found symmetric bone pairs:")
for base, (left_bones, right_bones) in symmetric_pairs.items():
left_count = len(left_bones)
right_count = len(right_bones)
if left_count == right_count:
messages.append(f"{base}: {left_count} bones on each side")
for l_bone, r_bone in zip(sorted(left_bones), sorted(right_bones)):
messages.append(f" {l_bone}{r_bone}")
else:
is_symmetric = False
messages.append(f"{base}: {left_count} left, {right_count} right bones")
messages.append(f" Left: {', '.join(sorted(left_bones))}")
messages.append(f" Right: {', '.join(sorted(right_bones))}")
else:
messages.append("No symmetric bone pairs detected")
is_symmetric = False
return is_symmetric, messages
def validate_symmetry(bones: Dict[str, Bone], base: str, left: str, right: str) -> bool: def validate_symmetry(bones: Dict[str, Bone], base: str, left: str, right: str) -> bool:
"""Validate if matching left and right bones exist for a given base bone name""" """Validate if matching left and right bones exist for a given base bone name"""
# Extract left and right bone names from both hierarchies # First try the new intelligent detection
symmetric_pairs = find_symmetric_bone_pairs(bones)
# Look for bones that match the requested base type
matching_left_bones = []
matching_right_bones = []
# Check each detected symmetric pair
for pair_base, (left_bones, right_bones) in symmetric_pairs.items():
if base.lower() in pair_base.lower() or pair_base.lower() in base.lower():
matching_left_bones.extend(left_bones)
matching_right_bones.extend(right_bones)
if matching_left_bones or matching_right_bones:
left_bases = {}
right_bases = {}
for bone_name in matching_left_bones:
bone_base, side = extract_bone_side_info(bone_name)
if bone_base not in left_bases:
left_bases[bone_base] = []
left_bases[bone_base].append(bone_name)
for bone_name in matching_right_bones:
bone_base, side = extract_bone_side_info(bone_name)
if bone_base not in right_bases:
right_bases[bone_base] = []
right_bases[bone_base].append(bone_name)
all_bases = set(left_bases.keys()) | set(right_bases.keys())
for bone_base in all_bases:
left_count = len(left_bases.get(bone_base, []))
right_count = len(right_bases.get(bone_base, []))
if left_count != right_count:
return False
return len(all_bases) > 0
# Fallback to original dictionary-based method
left_bone_names = set() left_bone_names = set()
right_bone_names = set() right_bone_names = set()
+3
View File
@@ -653,6 +653,9 @@ def store_breaking_settings_armature(armature: bpy.types.Object) -> ArmatureData
return (armature_data.use_mirror_x, armature.pose.use_mirror_x) return (armature_data.use_mirror_x, armature.pose.use_mirror_x)
def restore_breaking_settings_armature(armature: bpy.types.Object, data: ArmatureData) -> None: def restore_breaking_settings_armature(armature: bpy.types.Object, data: ArmatureData) -> None:
# Check if armature object is still valid (not removed)
if not armature or armature.name not in bpy.data.objects:
return
armature_data: bpy.types.Armature = armature.data armature_data: bpy.types.Armature = armature.data
armature_data.use_mirror_x, armature.pose.use_mirror_x = data armature_data.use_mirror_x, armature.pose.use_mirror_x = data
+190 -136
View File
@@ -457,62 +457,62 @@ standard_bones = {
'head': 'Head', 'head': 'Head',
# Arms # Arms
'left_shoulder': 'Shoulder.L', 'left_shoulder': 'Shoulder_L',
'left_arm': 'UpperArm.L', 'left_arm': 'UpperArm_L',
'left_elbow': 'LowerArm.L', 'left_elbow': 'LowerArm_L',
'left_wrist': 'Hand.L', 'left_wrist': 'Hand_L',
'right_shoulder': 'Shoulder.R', 'right_shoulder': 'Shoulder_R',
'right_arm': 'UpperArm.R', 'right_arm': 'UpperArm_R',
'right_elbow': 'LowerArm.R', 'right_elbow': 'LowerArm_R',
'right_wrist': 'Hand.R', 'right_wrist': 'Hand_R',
# Legs # Legs
'left_leg': 'UpperLeg.L', 'left_leg': 'UpperLeg_L',
'left_knee': 'LowerLeg.L', 'left_knee': 'LowerLeg_L',
'left_ankle': 'Foot.L', 'left_ankle': 'Foot_L',
'left_toe': 'Toes.L', 'left_toe': 'Toe_L',
'right_leg': 'UpperLeg.R', 'right_leg': 'UpperLeg_R',
'right_knee': 'LowerLeg.R', 'right_knee': 'LowerLeg_R',
'right_ankle': 'Foot.R', 'right_ankle': 'Foot_R',
'right_toe': 'Toes.R', 'right_toe': 'Toe_R',
# Fingers Left # Fingers Left
'thumb_1_l': 'Thumb1.L', 'thumb_1_l': 'Thumb_L',
'thumb_2_l': 'Thumb2.L', 'thumb_2_l': 'Thumb_L.001',
'thumb_3_l': 'Thumb3.L', 'thumb_3_l': 'Thumb_L.002',
'index_1_l': 'Index1.L', 'index_1_l': 'Index_L',
'index_2_l': 'Index2.L', 'index_2_l': 'Index_L.001',
'index_3_l': 'Index3.L', 'index_3_l': 'Index_L.002',
'middle_1_l': 'Middle1.L', 'middle_1_l': 'Middle_L',
'middle_2_l': 'Middle2.L', 'middle_2_l': 'Middle_L.001',
'middle_3_l': 'Middle3.L', 'middle_3_l': 'Middle_L.002',
'ring_1_l': 'Ring1.L', 'ring_1_l': 'Ring_L',
'ring_2_l': 'Ring2.L', 'ring_2_l': 'Ring_L.001',
'ring_3_l': 'Ring3.L', 'ring_3_l': 'Ring_L.002',
'pinkie_1_l': 'Pinky1.L', 'pinkie_1_l': 'Pinky_L',
'pinkie_2_l': 'Pinky2.L', 'pinkie_2_l': 'Pinky_L.001',
'pinkie_3_l': 'Pinky3.L', 'pinkie_3_l': 'Pinky_L.002',
# Fingers Right # Fingers Right
'thumb_1_r': 'Thumb1.R', 'thumb_1_r': 'Thumb_R',
'thumb_2_r': 'Thumb2.R', 'thumb_2_r': 'Thumb_R.001',
'thumb_3_r': 'Thumb3.R', 'thumb_3_r': 'Thumb_R.002',
'index_1_r': 'Index1.R', 'index_1_r': 'Index_R',
'index_2_r': 'Index2.R', 'index_2_r': 'Index_R.001',
'index_3_r': 'Index3.R', 'index_3_r': 'Index_R.002',
'middle_1_r': 'Middle1.R', 'middle_1_r': 'Middle_R',
'middle_2_r': 'Middle2.R', 'middle_2_r': 'Middle_R.001',
'middle_3_r': 'Middle3.R', 'middle_3_r': 'Middle_R.002',
'ring_1_r': 'Ring1.R', 'ring_1_r': 'Ring_R',
'ring_2_r': 'Ring2.R', 'ring_2_r': 'Ring_R.001',
'ring_3_r': 'Ring3.R', 'ring_3_r': 'Ring_R.002',
'pinkie_1_r': 'Pinky1.R', 'pinkie_1_r': 'Pinky_R',
'pinkie_2_r': 'Pinky2.R', 'pinkie_2_r': 'Pinky_R.001',
'pinkie_3_r': 'Pinky3.R', 'pinkie_3_r': 'Pinky_R.002',
# Eyes # Eyes
'left_eye': 'Eye.L', 'left_eye': 'Eye_L',
'right_eye': 'Eye.R', 'right_eye': 'Eye_R'
# Breast bones # Breast bones
'breast_1_l': 'Breast1_L', 'breast_1_l': 'Breast1_L',
@@ -529,46 +529,48 @@ bone_hierarchy = [
('Chest', 'Chest.Up'), ('Chest', 'Chest.Up'),
('Chest.Up', 'Neck'), ('Chest.Up', 'Neck'),
('Neck', 'Head'), ('Neck', 'Head'),
('Head', 'Eye.L'), ('Head', 'Eye_L'),
('Head', 'Eye.R'), ('Head', 'Eye_R'),
# Left Arm Chain # Left Arm Chain
('Chest.Up', 'UpperArm.L'), ('Chest.Up', 'Shoulder_L'),
('UpperArm.L', 'LowerArm.L'), ('Shoulder_L', 'UpperArm_L'),
('LowerArm.L', 'Hand.L'), ('UpperArm_L', 'LowerArm_L'),
('LowerArm_L', 'Hand_L'),
# Right Arm Chain # Right Arm Chain
('Chest.Up', 'UpperArm.R'), ('Chest.Up', 'Shoulder_R'),
('UpperArm.R', 'LowerArm.R'), ('Shoulder_R', 'UpperArm_R'),
('LowerArm.R', 'Hand.R'), ('UpperArm_R', 'LowerArm_R'),
('LowerArm_R', 'Hand_R'),
# Left Leg Chain # Left Leg Chain
('Hips', 'UpperLeg.L'), ('Hips', 'UpperLeg_L'),
('UpperLeg.L', 'LowerLeg.L'), ('UpperLeg_L', 'LowerLeg_L'),
('LowerLeg.L', 'Foot.L'), ('LowerLeg_L', 'Foot_L'),
('Foot.L', 'Toes.L'), ('Foot_L', 'Toe_L'),
# Right Leg Chain # Right Leg Chain
('Hips', 'UpperLeg.R'), ('Hips', 'UpperLeg_R'),
('UpperLeg.R', 'LowerLeg.R'), ('UpperLeg_R', 'LowerLeg_R'),
('LowerLeg.R', 'Foot.R'), ('LowerLeg_R', 'Foot_R'),
('Foot.R', 'Toes.R') ('Foot_R', 'Toe_R')
] ]
finger_hierarchy = { finger_hierarchy = {
'left': [ 'left': [
('Hand.L', 'Thumb1.L', 'Thumb2.L', 'Thumb3.L'), ('Hand_L', 'Thumb_L', 'Thumb_L.001', 'Thumb_L.002'),
('Hand.L', 'Index1.L', 'Index2.L', 'Index3.L'), ('Hand_L', 'Index_L', 'Index_L.001', 'Index_L.002'),
('Hand.L', 'Middle1.L', 'Middle2.L', 'Middle3.L'), ('Hand_L', 'Middle_L', 'Middle_L.001', 'Middle_L.002'),
('Hand.L', 'Ring1.L', 'Ring2.L', 'Ring3.L'), ('Hand_L', 'Ring_L', 'Ring_L.001', 'Ring_L.002'),
('Hand.L', 'Pinky1.L', 'Pinky2.L', 'Pinky3.L') ('Hand_L', 'Pinky_L', 'Pinky_L.001', 'Pinky_L.002')
], ],
'right': [ 'right': [
('Hand.R', 'Thumb1.R', 'Thumb2.R', 'Thumb3.R'), ('Hand_R', 'Thumb_R', 'Thumb_R.001', 'Thumb_R.002'),
('Hand.R', 'Index1.R', 'Index2.R', 'Index3.R'), ('Hand_R', 'Index_R', 'Index_R.001', 'Index_R.002'),
('Hand.R', 'Middle1.R', 'Middle2.R', 'Middle3.R'), ('Hand_R', 'Middle_R', 'Middle_R.001', 'Middle_R.002'),
('Hand.R', 'Ring1.R', 'Ring2.R', 'Ring3.R'), ('Hand_R', 'Ring_R', 'Ring_R.001', 'Ring_R.002'),
('Hand.R', 'Pinky1.R', 'Pinky2.R', 'Pinky3.R') ('Hand_R', 'Pinky_R', 'Pinky_R.001', 'Pinky_R.002')
] ]
} }
@@ -601,6 +603,8 @@ acceptable_bone_hierarchy = [
('Head', 'Eye_R'), ('Head', 'Eye_R'),
('Head', 'LeftEye'), ('Head', 'LeftEye'),
('Head', 'RightEye'), ('Head', 'RightEye'),
('Head', 'Eye.L'),
('Head', 'Eye.R'),
# Unity humanoid naming # Unity humanoid naming
('Hips', 'Spine'), ('Hips', 'Spine'),
@@ -611,6 +615,40 @@ acceptable_bone_hierarchy = [
('Head', 'LeftEye'), ('Head', 'LeftEye'),
('Head', 'RightEye'), ('Head', 'RightEye'),
# Old standard bone hierarchy patterns
('Chest.Up', 'UpperArm.L'),
('UpperArm.L', 'LowerArm.L'),
('LowerArm.L', 'Hand.L'),
('Chest.Up', 'UpperArm.R'),
('UpperArm.R', 'LowerArm.R'),
('LowerArm.R', 'Hand.R'),
('Hips', 'UpperLeg.L'),
('UpperLeg.L', 'LowerLeg.L'),
('LowerLeg.L', 'Foot.L'),
('Foot.L', 'Toes.L'),
('Hips', 'UpperLeg.R'),
('UpperLeg.R', 'LowerLeg.R'),
('LowerLeg.R', 'Foot.R'),
('Foot.R', 'Toes.R'),
# New standard bone hierarchy patterns (with shoulders)
('Chest.Up', 'Shoulder_L'),
('Shoulder_L', 'UpperArm_L'),
('UpperArm_L', 'LowerArm_L'),
('LowerArm_L', 'Hand_L'),
('Chest.Up', 'Shoulder_R'),
('Shoulder_R', 'UpperArm_R'),
('UpperArm_R', 'LowerArm_R'),
('LowerArm_R', 'Hand_R'),
('Hips', 'UpperLeg_L'),
('UpperLeg_L', 'LowerLeg_L'),
('LowerLeg_L', 'Foot_L'),
('Foot_L', 'Toe_L'),
('Hips', 'UpperLeg_R'),
('UpperLeg_R', 'LowerLeg_R'),
('LowerLeg_R', 'Foot_R'),
('Foot_R', 'Toe_R'),
] ]
acceptable_bone_names = { acceptable_bone_names = {
@@ -618,59 +656,75 @@ acceptable_bone_names = {
'chest': ['Chest', 'spine1', 'Spine1', 'spine_01', 'SPINE1', 'Spine01'], 'chest': ['Chest', 'spine1', 'Spine1', 'spine_01', 'SPINE1', 'Spine01'],
'neck': ['Neck', 'neck_01', 'Neck01'], 'neck': ['Neck', 'neck_01', 'Neck01'],
'head': ['Head', 'head_01', 'Head01'], 'head': ['Head', 'head_01', 'Head01'],
'eye_l': ['Eye_L', 'LeftEye', 'lefteye', 'eye_left', 'EyeLeft'], 'eye_l': ['Eye_L', 'LeftEye', 'lefteye', 'eye_left', 'EyeLeft', 'Eye.L'],
'eye_r': ['Eye_R', 'RightEye', 'righteye', 'eye_right', 'EyeRight'], 'eye_r': ['Eye_R', 'RightEye', 'righteye', 'eye_right', 'EyeRight', 'Eye.R'],
'shoulder_r': ['Shoulder.R', 'clavicle_r', 'ClavicleRight', 'RightShoulder'], 'shoulder_r': ['Shoulder.R', 'clavicle_r', 'ClavicleRight', 'RightShoulder', 'Shoulder_R'],
'arm_r': ['Arm.R', 'upperarm_r', 'UpperArmRight', 'RightArm'], 'arm_r': ['Arm.R', 'upperarm_r', 'UpperArmRight', 'RightArm', 'UpperArm.R', 'UpperArm_R'],
'elbow_r': ['Elbow.R', 'lowerarm_r', 'ForearmRight', 'RightForeArm'], 'elbow_r': ['Elbow.R', 'lowerarm_r', 'ForearmRight', 'RightForeArm', 'LowerArm.R', 'LowerArm_R'],
'wrist_r': ['Wrist.R', 'hand_r', 'HandRight', 'RightHand'], 'wrist_r': ['Wrist.R', 'hand_r', 'HandRight', 'RightHand', 'Hand.R', 'Hand_R'],
'leg_r': ['Leg.R', 'thigh_r', 'ThighRight', 'RightLeg', 'RightUpLeg'], 'leg_r': ['Leg.R', 'thigh_r', 'ThighRight', 'RightLeg', 'RightUpLeg', 'UpperLeg.R', 'UpperLeg_R'],
'knee_r': ['Knee.R', 'calf_r', 'CalfRight', 'RightShin', 'RightLowerLeg'], 'knee_r': ['Knee.R', 'calf_r', 'CalfRight', 'RightShin', 'RightLowerLeg', 'LowerLeg.R', 'LowerLeg_R'],
'foot_r': ['Foot.R', 'foot_r', 'FootRight', 'RightFoot'], 'foot_r': ['Foot.R', 'foot_r', 'FootRight', 'RightFoot', 'Foot_R'],
'toes_r': ['Toes.R', 'ball_r', 'ToeRight', 'RightToeBase'], 'toes_r': ['Toes.R', 'ball_r', 'ToeRight', 'RightToeBase', 'Toe_R'],
'shoulder_l': ['Shoulder.L', 'clavicle_l', 'ClavicleLeft', 'LeftShoulder'], 'shoulder_l': ['Shoulder.L', 'clavicle_l', 'ClavicleLeft', 'LeftShoulder', 'Shoulder_L'],
'arm_l': ['Arm.L', 'upperarm_l', 'UpperArmLeft', 'LeftArm'], 'arm_l': ['Arm.L', 'upperarm_l', 'UpperArmLeft', 'LeftArm', 'UpperArm.L', 'UpperArm_L'],
'elbow_l': ['Elbow.L', 'lowerarm_l', 'ForearmLeft', 'LeftForeArm'], 'elbow_l': ['Elbow.L', 'lowerarm_l', 'ForearmLeft', 'LeftForeArm', 'LowerArm.L', 'LowerArm_L'],
'wrist_l': ['Wrist.L', 'hand_l', 'HandLeft', 'LeftHand'], 'wrist_l': ['Wrist.L', 'hand_l', 'HandLeft', 'LeftHand', 'Hand.L', 'Hand_L'],
'leg_l': ['Leg.L', 'thigh_l', 'ThighLeft', 'LeftLeg', 'LeftUpLeg'], 'leg_l': ['Leg.L', 'thigh_l', 'ThighLeft', 'LeftLeg', 'LeftUpLeg', 'UpperLeg.L', 'UpperLeg_L'],
'knee_l': ['Knee.L', 'calf_l', 'CalfLeft', 'LeftShin', 'LeftLowerLeg'], 'knee_l': ['Knee.L', 'calf_l', 'CalfLeft', 'LeftShin', 'LeftLowerLeg', 'LowerLeg.L', 'LowerLeg_L'],
'foot_l': ['Foot.L', 'foot_l', 'FootLeft', 'LeftFoot'], 'foot_l': ['Foot.L', 'foot_l', 'FootLeft', 'LeftFoot', 'Foot_L'],
'toes_l': ['Toes.L', 'ball_l', 'ToeLeft', 'LeftToeBase'], 'toes_l': ['Toes.L', 'ball_l', 'ToeLeft', 'LeftToeBase', 'Toe_L'],
# Add finger bones for left hand # Add finger bones for left hand
'thumb_0_l': ['Thumb0_L'], 'thumb_0_l': ['Thumb0_L', 'Thumb0.L'],
'thumb_1_l': ['Thumb1_L'], 'thumb_1_l': ['Thumb1_L', 'Thumb1.L', 'Thumb_L'],
'thumb_2_l': ['Thumb2_L'], 'thumb_2_l': ['Thumb2_L', 'Thumb2.L', 'Thumb_L.001'],
'index_1_l': ['IndexFinger1_L'], 'thumb_3_l': ['Thumb3_L', 'Thumb3.L', 'Thumb_L.002'],
'index_2_l': ['IndexFinger2_L'], 'index_1_l': ['IndexFinger1_L', 'IndexFinger1.L', 'Index1.L', 'Index_L'],
'index_3_l': ['IndexFinger3_L'], 'index_2_l': ['IndexFinger2_L', 'IndexFinger2.L', 'Index2.L', 'Index_L.001'],
'middle_1_l': ['MiddleFinger1_L'], 'index_3_l': ['IndexFinger3_L', 'IndexFinger3.L', 'Index3.L', 'Index_L.002'],
'middle_2_l': ['MiddleFinger2_L'], 'middle_1_l': ['MiddleFinger1_L', 'MiddleFinger1.L', 'Middle1.L', 'Middle_L'],
'middle_3_l': ['MiddleFinger3_L'], 'middle_2_l': ['MiddleFinger2_L', 'MiddleFinger2.L', 'Middle2.L', 'Middle_L.001'],
'ring_1_l': ['RingFinger1_L'], 'middle_3_l': ['MiddleFinger3_L', 'MiddleFinger3.L', 'Middle3.L', 'Middle_L.002'],
'ring_2_l': ['RingFinger2_L'], 'ring_1_l': ['RingFinger1_L', 'RingFinger1.L', 'Ring1.L', 'Ring_L'],
'ring_3_l': ['RingFinger3_L'], 'ring_2_l': ['RingFinger2_L', 'RingFinger2.L', 'Ring2.L', 'Ring_L.001'],
'ring_3_l': ['RingFinger3_L', 'RingFinger3.L', 'Ring3.L', 'Ring_L.002'],
'pinky_1_l': ['Pinky1_L', 'Pinky1.L', 'Pinky_L'],
'pinky_2_l': ['Pinky2_L', 'Pinky2.L', 'Pinky_L.001'],
'pinky_3_l': ['Pinky3_L', 'Pinky3.L', 'Pinky_L.002'],
# Add finger bones for right hand # Add finger bones for right hand
'thumb_0_r': ['Thumb0_R', 'ThumbO_R'], 'thumb_0_r': ['Thumb0_R', 'Thumb0.R', 'ThumbO_R'],
'thumb_1_r': ['Thumb1_R'], 'thumb_1_r': ['Thumb1_R', 'Thumb1.R', 'Thumb_R'],
'thumb_2_r': ['Thumb2_R'], 'thumb_2_r': ['Thumb2_R', 'Thumb2.R', 'Thumb_R.001'],
'index_1_r': ['IndexFinger1_R'], 'thumb_3_r': ['Thumb3_R', 'Thumb3.R', 'Thumb_R.002'],
'index_2_r': ['IndexFinger2_R'], 'index_1_r': ['IndexFinger1_R', 'IndexFinger1.R', 'Index1.R', 'Index_R'],
'index_3_r': ['IndexFinger3_R'], 'index_2_r': ['IndexFinger2_R', 'IndexFinger2.R', 'Index2.R', 'Index_R.001'],
'middle_1_r': ['MiddleFinger1_R'], 'index_3_r': ['IndexFinger3_R', 'IndexFinger3.R', 'Index3.R', 'Index_R.002'],
'middle_2_r': ['MiddleFinger2_R'], 'middle_1_r': ['MiddleFinger1_R', 'MiddleFinger1.R', 'Middle1.R', 'Middle_R'],
'middle_3_r': ['MiddleFinger3_R'], 'middle_2_r': ['MiddleFinger2_R', 'MiddleFinger2.R', 'Middle2.R', 'Middle_R.001'],
'ring_1_r': ['RingFinger1_R'], 'middle_3_r': ['MiddleFinger3_R', 'MiddleFinger3.R', 'Middle3.R', 'Middle_R.002'],
'ring_2_r': ['RingFinger2_R'], 'ring_1_r': ['RingFinger1_R', 'RingFinger1.R', 'Ring1.R', 'Ring_R'],
'ring_3_r': ['RingFinger3_R'], 'ring_2_r': ['RingFinger2_R', 'RingFinger2.R', 'Ring2.R', 'Ring_R.001'],
'ring_3_r': ['RingFinger3_R', 'RingFinger3.R', 'Ring3.R', 'Ring_R.002'],
'pinky_1_r': ['Pinky1_R', 'Pinky1.R', 'Pinky_R'],
'pinky_2_r': ['Pinky2_R', 'Pinky2.R', 'Pinky_R.001'],
'pinky_3_r': ['Pinky3_R', 'Pinky3.R', 'Pinky_R.002'],
'breast_upper_1_l': ['BreastUpper1_L'], 'breast_upper_1_l': ['BreastUpper1_L', 'BreastUpper1.L'],
'breast_upper_2_l': ['BreastUpper2_L'], 'breast_upper_2_l': ['BreastUpper2_L', 'BreastUpper2.L'],
'breast_upper_1_r': ['BreastUpper1_R'], 'breast_upper_1_r': ['BreastUpper1_R', 'BreastUpper1.R'],
'breast_upper_2_r': ['BreastUpper2_R'], 'breast_upper_2_r': ['BreastUpper2_R', 'BreastUpper2.R'],
# Little finger bones
'little_finger_1_l': ['LittleFinger1_L', 'LittleFinger1.L'],
'little_finger_2_l': ['LittleFinger2_L', 'LittleFinger2.L'],
'little_finger_3_l': ['LittleFinger3_L', 'LittleFinger3.L'],
'little_finger_1_r': ['LittleFinger1_R', 'LittleFinger1.R'],
'little_finger_2_r': ['LittleFinger2_R', 'LittleFinger2.R'],
'little_finger_3_r': ['LittleFinger3_R', 'LittleFinger3.R'],
'ear_upper_l': ['UpperEar.L', 'Upper Ear.L', 'Upper Ear_L'], 'ear_upper_l': ['UpperEar.L', 'Upper Ear.L', 'Upper Ear_L'],
'ear_upper_r': ['UpperEar.R', 'Upper Ear.R', 'Upper Ear_R'], 'ear_upper_r': ['UpperEar.R', 'Upper Ear.R', 'Upper Ear_R'],
@@ -790,17 +844,17 @@ non_standard_mappings = {
'left_arm': [ 'left_arm': [
'mixamorig:LeftArm', 'mixamorig_LeftArm', 'mixamorig:LeftArm', 'mixamorig_LeftArm',
'ORG-upper_arm.L', 'upper_arm.L', 'ORG-upper_arm.L', 'upper_arm.L',
'lShldrBend', 'lShldrTwist', 'lArm' 'lShldrBend', 'lShldrTwist', 'lArm', 'UpperArm.L'
], ],
'left_elbow': [ 'left_elbow': [
'mixamorig:LeftForeArm', 'mixamorig_LeftForeArm', 'mixamorig:LeftForeArm', 'mixamorig_LeftForeArm',
'ORG-forearm.L', 'forearm.L', 'ORG-forearm.L', 'forearm.L',
'lForearmBend', 'lElbow', 'lForeArm' 'lForearmBend', 'lElbow', 'lForeArm', 'LowerArm.L'
], ],
'left_wrist': [ 'left_wrist': [
'mixamorig:LeftHand', 'mixamorig_LeftHand', 'mixamorig:LeftHand', 'mixamorig_LeftHand',
'ORG-hand.L', 'hand.L', 'ORG-hand.L', 'hand.L',
'lHand', 'lWrist' 'lHand', 'lWrist', 'Hand.L'
], ],
'right_shoulder': [ 'right_shoulder': [
@@ -811,59 +865,59 @@ non_standard_mappings = {
'right_arm': [ 'right_arm': [
'mixamorig:RightArm', 'mixamorig_RightArm', 'mixamorig:RightArm', 'mixamorig_RightArm',
'ORG-upper_arm.R', 'upper_arm.R', 'ORG-upper_arm.R', 'upper_arm.R',
'rShldrBend', 'rShldrTwist', 'rArm' 'rShldrBend', 'rShldrTwist', 'rArm', 'UpperArm.R'
], ],
'right_elbow': [ 'right_elbow': [
'mixamorig:RightForeArm', 'mixamorig_RightForeArm', 'mixamorig:RightForeArm', 'mixamorig_RightForeArm',
'ORG-forearm.R', 'forearm.R', 'ORG-forearm.R', 'forearm.R',
'rForearmBend', 'rElbow', 'rForeArm' 'rForearmBend', 'rElbow', 'rForeArm', 'LowerArm.R'
], ],
'right_wrist': [ 'right_wrist': [
'mixamorig:RightHand', 'mixamorig_RightHand', 'mixamorig:RightHand', 'mixamorig_RightHand',
'ORG-hand.R', 'hand.R', 'ORG-hand.R', 'hand.R',
'rHand', 'rWrist' 'rHand', 'rWrist', 'Hand.R'
], ],
'left_leg': [ 'left_leg': [
'mixamorig:LeftUpLeg', 'mixamorig_LeftUpLeg', 'mixamorig:LeftUpLeg', 'mixamorig_LeftUpLeg',
'ORG-thigh.L', 'thigh.L', 'ORG-thigh.L', 'thigh.L',
'lThighBend', 'lThigh' 'lThighBend', 'lThigh', 'UpperLeg.L'
], ],
'left_knee': [ 'left_knee': [
'mixamorig:LeftLeg', 'mixamorig_LeftLeg', 'mixamorig:LeftLeg', 'mixamorig_LeftLeg',
'ORG-shin.L', 'shin.L', 'ORG-shin.L', 'shin.L',
'lShin', 'lKnee', 'lLeg' 'lShin', 'lKnee', 'lLeg', 'LowerLeg.L'
], ],
'left_ankle': [ 'left_ankle': [
'mixamorig:LeftFoot', 'mixamorig_LeftFoot', 'mixamorig:LeftFoot', 'mixamorig_LeftFoot',
'ORG-foot.L', 'foot.L', 'ORG-foot.L', 'foot.L',
'lFoot', 'lAnkle' 'lFoot', 'lAnkle', 'Foot.L'
], ],
'left_toe': [ 'left_toe': [
'mixamorig:LeftToeBase', 'mixamorig_LeftToeBase', 'mixamorig:LeftToeBase', 'mixamorig_LeftToeBase',
'ORG-toe.L', 'toe.L', 'ORG-toe.L', 'toe.L',
'lToe' 'lToe', 'Toes.L'
], ],
'right_leg': [ 'right_leg': [
'mixamorig:RightUpLeg', 'mixamorig_RightUpLeg', 'mixamorig:RightUpLeg', 'mixamorig_RightUpLeg',
'ORG-thigh.R', 'thigh.R', 'ORG-thigh.R', 'thigh.R',
'rThighBend', 'rThigh' 'rThighBend', 'rThigh', 'UpperLeg.R'
], ],
'right_knee': [ 'right_knee': [
'mixamorig:RightLeg', 'mixamorig_RightLeg', 'mixamorig:RightLeg', 'mixamorig_RightLeg',
'ORG-shin.R', 'shin.R', 'ORG-shin.R', 'shin.R',
'rShin', 'rKnee', 'rLeg' 'rShin', 'rKnee', 'rLeg', 'LowerLeg.R'
], ],
'right_ankle': [ 'right_ankle': [
'mixamorig:RightFoot', 'mixamorig_RightFoot', 'mixamorig:RightFoot', 'mixamorig_RightFoot',
'ORG-foot.R', 'foot.R', 'ORG-foot.R', 'foot.R',
'rFoot', 'rAnkle' 'rFoot', 'rAnkle', 'Foot.R'
], ],
'right_toe': [ 'right_toe': [
'mixamorig:RightToeBase', 'mixamorig_RightToeBase', 'mixamorig:RightToeBase', 'mixamorig_RightToeBase',
'ORG-toe.R', 'toe.R', 'ORG-toe.R', 'toe.R',
'rToe' 'rToe', 'Toes.R'
], ],
'thumb_1_l': [ 'thumb_1_l': [
@@ -1029,12 +1083,12 @@ non_standard_mappings = {
'left_eye': [ 'left_eye': [
'mixamorig:LeftEye', 'mixamorig_LeftEye', 'mixamorig:LeftEye', 'mixamorig_LeftEye',
'ORG-eye.L', 'eye.L', 'ORG-eye.L', 'eye.L',
'lEye' 'lEye', 'Eye.L'
], ],
'right_eye': [ 'right_eye': [
'mixamorig:RightEye', 'mixamorig_RightEye', 'mixamorig:RightEye', 'mixamorig_RightEye',
'ORG-eye.R', 'eye.R', 'ORG-eye.R', 'eye.R',
'rEye' 'rEye', 'Eye.R'
] ]
} }
+1 -1
View File
@@ -20,7 +20,7 @@ GITHUB_REPO = "teamneoneko/Avatar-Toolkit"
# Define which version series this installation can update to # Define which version series this installation can update to
# For example: ["0.1"] means only look for 0.1.x updates # For example: ["0.1"] means only look for 0.1.x updates
# ["0.2", "0.3"] would look for both 0.2.x and 0.3.x updates # ["0.2", "0.3"] would look for both 0.2.x and 0.3.x updates
ALLOWED_VERSION_SERIES = ["0.3"] ALLOWED_VERSION_SERIES = ["0.4"]
is_checking_for_update: bool = False is_checking_for_update: bool = False
update_needed: bool = False update_needed: bool = False
+8 -2
View File
@@ -49,6 +49,9 @@ class AvatarToolkit_OT_MergeArmature(bpy.types.Operator):
data_breaking_base = store_breaking_settings_armature(base_armature) data_breaking_base = store_breaking_settings_armature(base_armature)
data_breaking_merge = store_breaking_settings_armature(merge_armature) data_breaking_merge = store_breaking_settings_armature(merge_armature)
# Store the merge armature name before it gets removed during join
merge_armature_name_stored = merge_armature.name
# Remove Rigid Bodies and Joints # Remove Rigid Bodies and Joints
delete_rigidbodies_and_joints(base_armature) delete_rigidbodies_and_joints(base_armature)
delete_rigidbodies_and_joints(merge_armature) delete_rigidbodies_and_joints(merge_armature)
@@ -77,14 +80,17 @@ class AvatarToolkit_OT_MergeArmature(bpy.types.Operator):
wm.progress_end() wm.progress_end()
restore_breaking_settings_armature(base_armature, data_breaking_base) restore_breaking_settings_armature(base_armature, data_breaking_base)
restore_breaking_settings_armature(merge_armature, data_breaking_merge)
if merge_armature_name_stored in bpy.data.objects:
merge_armature_obj = bpy.data.objects[merge_armature_name_stored]
restore_breaking_settings_armature(merge_armature_obj, data_breaking_merge)
self.report({'INFO'}, t('MergeArmature.success')) self.report({'INFO'}, t('MergeArmature.success'))
return {'FINISHED'} return {'FINISHED'}
except Exception as e: except Exception as e:
logger.error(f"Error merging armatures:", exception=e) logger.error(f"Error merging armatures: {str(e)}\n{traceback.format_exc()}")
self.report({'ERROR'}, traceback.format_exc()) self.report({'ERROR'}, traceback.format_exc())
return {'CANCELLED'} return {'CANCELLED'}
+33 -29
View File
@@ -186,7 +186,6 @@ class AvatarToolKit_OT_RemoveZeroWeightBones(Operator):
if not armature: if not armature:
return {'CANCELLED'} return {'CANCELLED'}
# Store initial transforms
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
initial_transforms: Dict[str, Dict[str, Any]] = {} initial_transforms: Dict[str, Dict[str, Any]] = {}
data_breaking = store_breaking_settings_armature(armature) data_breaking = store_breaking_settings_armature(armature)
@@ -200,56 +199,61 @@ class AvatarToolKit_OT_RemoveZeroWeightBones(Operator):
'parent': bone.parent.name if bone.parent else None 'parent': bone.parent.name if bone.parent else None
} }
# Get weighted bones # Get bones with any weight
weighted_bones: List[str] = [] weighted_bones: List[str] = []
meshes = get_all_meshes(context) meshes = get_all_meshes(context)
zero_weight_bones: List[str] = []
for mesh in meshes: for mesh in meshes:
mesh_data: Mesh = mesh.data for vertex in mesh.data.vertices:
for vertex in mesh_data.vertices:
for group in vertex.groups: for group in vertex.groups:
if group.weight > context.scene.avatar_toolkit.merge_weights_threshold: if group.weight > context.scene.avatar_toolkit.merge_weights_threshold:
weighted_bones.append(mesh.vertex_groups[group.group].name) vg = mesh.vertex_groups[group.group]
if vg.name not in weighted_bones:
weighted_bones.append(vg.name)
# Process bone removal armature_data = armature.data
bpy.ops.object.mode_set(mode='EDIT')
armature_data: Armature = armature.data
removed_count = 0 removed_count = 0
zero_weight_bones: List[str] = []
for bone in armature_data.edit_bones[:]: # Create a copy of the list def is_zero_weight_chain(bone, weighted_bones, preserve_check_fn):
if (bone.name not in weighted_bones and if bone.name in weighted_bones or preserve_check_fn(bone.name, context):
not self.should_preserve_bone(bone.name, context)): return False
return all(is_zero_weight_chain(child, weighted_bones, preserve_check_fn) for child in bone.children)
for bone in armature_data.edit_bones[:]:
if bone.name in weighted_bones or self.should_preserve_bone(bone.name, context):
continue
if not is_zero_weight_chain(bone, weighted_bones, self.should_preserve_bone):
continue
if context.scene.avatar_toolkit.list_only_mode: if context.scene.avatar_toolkit.list_only_mode:
zero_weight_bones.append(bone.name) zero_weight_bones.append(bone.name)
continue continue
# Store children data # Traverse and collect the full empty chain
children = bone.children stack = [bone]
children_data = {child.name: initial_transforms[child.name] for child in children} chain = []
# Reparent children while stack:
for child in children: b = stack.pop()
chain.append(b)
stack.extend(b.children)
for b in reversed(chain): # Remove children before parents
for child in b.children:
child.use_connect = False child.use_connect = False
if bone.parent: if b.parent:
child.parent = bone.parent child.parent = b.parent
if b.name in armature_data.edit_bones:
# Remove bone armature_data.edit_bones.remove(b)
armature_data.edit_bones.remove(bone)
removed_count += 1 removed_count += 1
# Restore children positions
for child_name, data in children_data.items():
if child_name in armature_data.edit_bones:
child = armature_data.edit_bones[child_name]
restore_bone_transforms(child, data)
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
if context.scene.avatar_toolkit.list_only_mode: if context.scene.avatar_toolkit.list_only_mode:
self.populate_bone_list(context, zero_weight_bones) self.populate_bone_list(context, zero_weight_bones)
return {'FINISHED'} return {'FINISHED'}
restore_breaking_settings_armature(armature, data_breaking) restore_breaking_settings_armature(armature, data_breaking)
self.report({'INFO'}, t("Tools.clean_weights_success", count=removed_count)) self.report({'INFO'}, t("Tools.clean_weights_success", count=removed_count))
return {'FINISHED'} return {'FINISHED'}
+62 -3
View File
@@ -2,7 +2,7 @@ import traceback
import bpy import bpy
from typing import Dict, List, Set, Optional, Tuple, Any from typing import Dict, List, Set, Optional, Tuple, Any
from bpy.types import Operator, Context, Object, PoseBone, EditBone, Bone, Constraint from bpy.types import Operator, Context, Object, PoseBone, EditBone, Bone, Constraint
from ...core.common import get_active_armature from ...core.common import get_active_armature, transfer_vertex_weights, get_all_meshes
from ...core.logging_setup import logger from ...core.logging_setup import logger
from ...core.translations import t from ...core.translations import t
from ...core.dictionaries import rigify_unity_names, rigify_basic_unity_names, rigify_unnecessary_bones from ...core.dictionaries import rigify_unity_names, rigify_basic_unity_names, rigify_unnecessary_bones
@@ -69,19 +69,50 @@ class AvatarToolkit_OT_ConvertRigifyToUnity(Operator):
# Set armature as active object before mode switch # Set armature as active object before mode switch
bpy.context.view_layer.objects.active = armature bpy.context.view_layer.objects.active = armature
# Get all meshes for weight transfer
meshes = get_all_meshes(bpy.context)
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
bones_to_remove: List[str] = [] bones_to_remove: List[str] = []
for bone in armature.data.edit_bones: for bone in armature.data.edit_bones:
if any(pattern in bone.name.lower() for pattern in rigify_unnecessary_bones): bone_name_lower = bone.name.lower()
if any(bone_name_lower.startswith(pattern) or bone_name_lower == pattern
for pattern in rigify_unnecessary_bones):
bones_to_remove.append(bone.name) bones_to_remove.append(bone.name)
# Check for neck bones that need merging
merge_neck_bones = 'spine.004' in armature.data.edit_bones and 'spine.005' in armature.data.edit_bones
bpy.ops.object.mode_set(mode='OBJECT')
# Transfer weights from bones being removed
for bone_name in bones_to_remove:
if bone_name in armature.data.bones:
logger.debug(f"Transferring weights from bone: {bone_name}")
for mesh in meshes:
if bone_name in mesh.vertex_groups:
# Remove the vertex group since we don't need the weights
mesh.vertex_groups.remove(mesh.vertex_groups[bone_name])
# Transfer weights for neck bone merging
if merge_neck_bones:
logger.debug("Transferring weights from spine.005 to spine.004")
for mesh in meshes:
if 'spine.005' in mesh.vertex_groups:
transfer_vertex_weights(mesh, 'spine.005', 'spine.004')
bpy.ops.object.mode_set(mode='EDIT')
# Remove unnecessary bones
for bone_name in bones_to_remove: for bone_name in bones_to_remove:
if bone_name in armature.data.edit_bones: if bone_name in armature.data.edit_bones:
logger.debug(f"Removing bone: {bone_name}") logger.debug(f"Removing bone: {bone_name}")
armature.data.edit_bones.remove(armature.data.edit_bones[bone_name]) armature.data.edit_bones.remove(armature.data.edit_bones[bone_name])
if 'spine.004' in armature.data.edit_bones and 'spine.005' in armature.data.edit_bones: # Merge neck bones
if merge_neck_bones:
logger.debug("Merging neck bones") logger.debug("Merging neck bones")
neck_start = armature.data.edit_bones['spine.004'] neck_start = armature.data.edit_bones['spine.004']
neck_end = armature.data.edit_bones['spine.005'] neck_end = armature.data.edit_bones['spine.005']
@@ -89,6 +120,7 @@ class AvatarToolkit_OT_ConvertRigifyToUnity(Operator):
armature.data.edit_bones.remove(neck_end) armature.data.edit_bones.remove(neck_end)
neck_start.name = "Neck" neck_start.name = "Neck"
# Rename head bone
if 'spine.006' in armature.data.edit_bones: if 'spine.006' in armature.data.edit_bones:
logger.debug("Renaming head bone") logger.debug("Renaming head bone")
head_bone = armature.data.edit_bones['spine.006'] head_bone = armature.data.edit_bones['spine.006']
@@ -137,6 +169,22 @@ class AvatarToolkit_OT_ConvertRigifyToUnity(Operator):
if bone_name in armature.data.bones: if bone_name in armature.data.bones:
armature.data.bones[bone_name].use_deform = False armature.data.bones[bone_name].use_deform = False
# Get all meshes for weight transfer
meshes = get_all_meshes(bpy.context)
bpy.ops.object.mode_set(mode='OBJECT')
for bone_name in remove_bones_in_chain:
if bone_name in armature.data.bones:
parent_name = armature.data.bones[bone_name].parent.name if armature.data.bones[bone_name].parent else None
if parent_name:
logger.debug(f"Transferring weights from {bone_name} to {parent_name}")
for mesh in meshes:
if bone_name in mesh.vertex_groups and parent_name in mesh.vertex_groups:
transfer_vertex_weights(mesh, bone_name, parent_name)
elif bone_name in mesh.vertex_groups:
# Remove weights if no parent to merge to
mesh.vertex_groups.remove(mesh.vertex_groups[bone_name])
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
for bone_name in remove_bones_in_chain: for bone_name in remove_bones_in_chain:
if bone_name in armature.data.bones: if bone_name in armature.data.bones:
@@ -190,6 +238,17 @@ class AvatarToolkit_OT_ConvertRigifyToUnity(Operator):
("DEF-thigh_twist.R", "DEF-thigh.R") ("DEF-thigh_twist.R", "DEF-thigh.R")
] ]
# Get all meshes for weight transfer
meshes = get_all_meshes(bpy.context)
bpy.ops.object.mode_set(mode='OBJECT')
for twist_bone, parent_bone in twist_bones:
if twist_bone in armature.data.bones and parent_bone in armature.data.bones:
logger.debug(f"Transferring weights from {twist_bone} to {parent_bone}")
for mesh in meshes:
if twist_bone in mesh.vertex_groups:
transfer_vertex_weights(mesh, twist_bone, parent_bone)
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
for twist_bone, parent_bone in twist_bones: for twist_bone, parent_bone in twist_bones:
if twist_bone in armature.data.edit_bones and parent_bone in armature.data.edit_bones: if twist_bone in armature.data.edit_bones and parent_bone in armature.data.edit_bones:
+13 -20
View File
@@ -13,7 +13,9 @@ from ...core.dictionaries import (
bone_hierarchy, bone_hierarchy,
acceptable_bone_names, acceptable_bone_names,
acceptable_bone_hierarchy, acceptable_bone_hierarchy,
non_standard_mappings non_standard_mappings,
reverse_bone_lookup,
simplify_bonename
) )
class AvatarToolkit_OT_StandardizeArmature(Operator): class AvatarToolkit_OT_StandardizeArmature(Operator):
@@ -134,17 +136,14 @@ class AvatarToolkit_OT_StandardizeArmature(Operator):
existing_standard_bones.add(bone.name) existing_standard_bones.add(bone.name)
logger.debug(f"Found existing standard bone: {bone.name}") logger.debug(f"Found existing standard bone: {bone.name}")
# Build a mapping of non-standard bone names to standard names # Use the reverse bone lookup that's already built and simplified
name_mapping: Dict[str, str] = {} name_mapping: Dict[str, str] = {}
for category, standard_name in standard_bones.items(): for simplified_name, category in reverse_bone_lookup.items():
if category in standard_bones:
standard_name = standard_bones[category]
# Skip if this standard bone already exists # Skip if this standard bone already exists
if standard_name in existing_standard_bones: if standard_name not in existing_standard_bones:
continue name_mapping[simplified_name] = standard_name
# Get all variants for this category
if category in non_standard_mappings:
for variant in non_standard_mappings[category]:
name_mapping[variant.lower()] = standard_name
# First pass: identify bones to rename # First pass: identify bones to rename
bones_to_rename: Dict[str, str] = {} bones_to_rename: Dict[str, str] = {}
@@ -155,20 +154,14 @@ class AvatarToolkit_OT_StandardizeArmature(Operator):
if original_name in standard_bones.values(): if original_name in standard_bones.values():
continue continue
simplified_name: str = original_name.lower().replace(' ', '').replace('_', '').replace('.', '') simplified_name: str = simplify_bonename(original_name)
# Check if this bone matches any known pattern
for variant, standard_name in name_mapping.items():
# More precise matching - exact match or with common separators
if (variant == simplified_name or
variant == original_name.lower() or
f"{variant}_" in simplified_name or
f"{variant}." in simplified_name):
# Check if this simplified bone name has a standard mapping
if simplified_name in name_mapping:
standard_name = name_mapping[simplified_name]
if original_name != standard_name: if original_name != standard_name:
bones_to_rename[original_name] = standard_name bones_to_rename[original_name] = standard_name
logger.debug(f"Identified bone to rename: {original_name} -> {standard_name}") logger.debug(f"Identified bone to rename: {original_name} -> {standard_name}")
break
# Special case for spine/chest hierarchy # Special case for spine/chest hierarchy
# If we don't have an upper chest, don't rename chest to upper chest because it will break hierarchy # If we don't have an upper chest, don't rename chest to upper chest because it will break hierarchy
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"authors": ["Avatar Toolkit Team"], "authors": ["Avatar Toolkit Team"],
"messages": { "messages": {
"AvatarToolkit.label": "Avatar Toolkit (Alpha 0.3.0)", "AvatarToolkit.label": "Avatar Toolkit (Alpha 0.4.0)",
"AvatarToolkit.desc1": "Avatar Toolkit is in Early Access there", "AvatarToolkit.desc1": "Avatar Toolkit is in Early Access there",
"AvatarToolkit.desc2": "will be issues, if you find any issues,", "AvatarToolkit.desc2": "will be issues, if you find any issues,",
"AvatarToolkit.desc3": "please report it on our Github.", "AvatarToolkit.desc3": "please report it on our Github.",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"authors": ["Avatar Toolkit Team"], "authors": ["Avatar Toolkit Team"],
"messages": { "messages": {
"AvatarToolkit.label": "アバターツールキット (アルファ 0.3.0)", "AvatarToolkit.label": "アバターツールキット (アルファ 0.4.0)",
"AvatarToolkit.desc1": "アバターツールキットは早期アクセス中であり、", "AvatarToolkit.desc1": "アバターツールキットは早期アクセス中であり、",
"AvatarToolkit.desc2": "問題が発生する可能性があります。問題を見つけた場合は、", "AvatarToolkit.desc2": "問題が発生する可能性があります。問題を見つけた場合は、",
"AvatarToolkit.desc3": "GitHubで報告してください。", "AvatarToolkit.desc3": "GitHubで報告してください。",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"authors": ["Avatar Toolkit Team"], "authors": ["Avatar Toolkit Team"],
"messages": { "messages": {
"AvatarToolkit.label": "아바타 툴킷 (알파 0.3.0)", "AvatarToolkit.label": "아바타 툴킷 (알파 0.4.0)",
"AvatarToolkit.desc1": "아바타 툴킷은 초기 액세스 단계에 있으므로", "AvatarToolkit.desc1": "아바타 툴킷은 초기 액세스 단계에 있으므로",
"AvatarToolkit.desc2": "문제가 있을 수 있습니다. 문제를 발견하시면", "AvatarToolkit.desc2": "문제가 있을 수 있습니다. 문제를 발견하시면",
"AvatarToolkit.desc3": "Github에 보고해 주세요.", "AvatarToolkit.desc3": "Github에 보고해 주세요.",