Merge pull request #117 from Yusarina/Armature_Validation_Version2
Armature validation version 2 Part 1 (Don't merge yet)
This commit is contained in:
@@ -0,0 +1,167 @@
|
|||||||
|
import bpy
|
||||||
|
from typing import Tuple, List, Dict, Set, Optional
|
||||||
|
from bpy.types import Object, Bone
|
||||||
|
from ..core.translations import t
|
||||||
|
from ..core.dictionaries import (
|
||||||
|
standard_bones,
|
||||||
|
bone_hierarchy,
|
||||||
|
finger_hierarchy,
|
||||||
|
acceptable_bone_hierarchy,
|
||||||
|
acceptable_bone_names
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_armature(armature: Object) -> Tuple[bool, List[str], bool]:
|
||||||
|
"""
|
||||||
|
Validates armature and returns (is_valid, messages, is_acceptable_standard)
|
||||||
|
"""
|
||||||
|
validation_mode = bpy.context.scene.avatar_toolkit.validation_mode
|
||||||
|
messages: List[str] = []
|
||||||
|
hierarchy_messages: List[str] = []
|
||||||
|
non_standard_messages: List[str] = []
|
||||||
|
|
||||||
|
if validation_mode == 'NONE':
|
||||||
|
return True, [], False
|
||||||
|
|
||||||
|
if not armature or armature.type != 'ARMATURE' or not armature.data.bones:
|
||||||
|
return False, [t("Armature.validation.basic_check_failed")], False
|
||||||
|
|
||||||
|
found_bones: Dict[str, Bone] = {bone.name: bone for bone in armature.data.bones}
|
||||||
|
is_acceptable = check_acceptable_standards(found_bones)
|
||||||
|
|
||||||
|
# List all bones in armature
|
||||||
|
bone_list = "\n".join([f"- {bone}" for bone in found_bones.keys()])
|
||||||
|
messages.append(t("Armature.validation.found_bones", bones=bone_list))
|
||||||
|
|
||||||
|
# Basic validation for both STRICT and LIMITED modes
|
||||||
|
# Check for missing required bones
|
||||||
|
essential_bones = {standard_bones[key] for key in ['hips', 'spine', 'chest', 'neck', 'head']}
|
||||||
|
missing_bones = [bone for bone in essential_bones if bone not in found_bones]
|
||||||
|
|
||||||
|
if missing_bones:
|
||||||
|
missing_list = "\n".join([f"- {bone}" for bone in missing_bones])
|
||||||
|
hierarchy_messages.append(t("Armature.validation.missing_bones", bones=missing_list))
|
||||||
|
|
||||||
|
if validation_mode == 'STRICT':
|
||||||
|
# Validate bone hierarchy
|
||||||
|
for parent, child in bone_hierarchy:
|
||||||
|
if parent in found_bones and child in found_bones:
|
||||||
|
if not validate_bone_hierarchy(found_bones, parent, child):
|
||||||
|
hierarchy_messages.append(t("Armature.validation.invalid_hierarchy",
|
||||||
|
parent=parent, child=child))
|
||||||
|
|
||||||
|
# Validate symmetry
|
||||||
|
symmetry_pairs = [('arm', 'L', 'R'), ('leg', 'L', 'R')]
|
||||||
|
for base, left, right in symmetry_pairs:
|
||||||
|
if not validate_symmetry(found_bones, base, left, right):
|
||||||
|
hierarchy_messages.append(t("Armature.validation.asymmetric_bones", bone=base))
|
||||||
|
|
||||||
|
if (not validate_symmetry(found_bones, 'hand', 'L', 'R') and
|
||||||
|
not validate_symmetry(found_bones, 'wrist', 'L', 'R')):
|
||||||
|
hierarchy_messages.append(t("Armature.validation.asymmetric_hand_wrist"))
|
||||||
|
|
||||||
|
# Validate finger hierarchies
|
||||||
|
for side in ['left', 'right']:
|
||||||
|
for finger_chain in finger_hierarchy[side]:
|
||||||
|
if all(bone in found_bones for bone in finger_chain):
|
||||||
|
if not validate_finger_chain(found_bones, finger_chain):
|
||||||
|
hierarchy_messages.append(t("Armature.validation.invalid_finger", finger=finger_chain[0]))
|
||||||
|
|
||||||
|
# Non-standard bones check
|
||||||
|
non_standard_bones = []
|
||||||
|
required_patterns = [
|
||||||
|
'Hips', 'Spine', 'Chest', 'Neck', 'Head',
|
||||||
|
'Upper', 'Lower', 'Hand', 'Foot', 'Toe',
|
||||||
|
'Thumb', 'Index', 'Middle', 'Ring', 'Pinky',
|
||||||
|
'Eye'
|
||||||
|
]
|
||||||
|
|
||||||
|
for bone_name in found_bones:
|
||||||
|
if any(pattern in bone_name for pattern in required_patterns):
|
||||||
|
is_standard = bone_name in standard_bones.values()
|
||||||
|
is_acceptable_bone = any(bone_name in names for names in acceptable_bone_names.values())
|
||||||
|
if not (is_standard or is_acceptable_bone):
|
||||||
|
non_standard_bones.append(bone_name)
|
||||||
|
|
||||||
|
if non_standard_bones:
|
||||||
|
non_standard_list = "\n".join([f"- {bone}" for bone in non_standard_bones])
|
||||||
|
non_standard_messages.append(t("Armature.validation.non_standard_bones", bones=non_standard_list))
|
||||||
|
|
||||||
|
# Combine messages in correct order
|
||||||
|
messages.extend(non_standard_messages)
|
||||||
|
messages.extend(hierarchy_messages)
|
||||||
|
|
||||||
|
is_valid = len(non_standard_messages) == 0 and len(hierarchy_messages) == 0
|
||||||
|
|
||||||
|
if not is_valid and is_acceptable:
|
||||||
|
if non_standard_bones:
|
||||||
|
return False, messages, False
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
t("Armature.validation.acceptable_standard.success"),
|
||||||
|
t("Armature.validation.acceptable_standard.note"),
|
||||||
|
t("Armature.validation.acceptable_standard.option")
|
||||||
|
]
|
||||||
|
return True, messages, True
|
||||||
|
|
||||||
|
return is_valid, messages, False
|
||||||
|
|
||||||
|
def validate_bone_hierarchy(bones: Dict[str, Bone], parent_name: str, child_name: str) -> bool:
|
||||||
|
"""Validate if there is a valid parent-child relationship between bones"""
|
||||||
|
if parent_name not in bones or child_name not in bones:
|
||||||
|
return False
|
||||||
|
return bones[child_name].parent == bones[parent_name]
|
||||||
|
|
||||||
|
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"""
|
||||||
|
# Extract left and right bone names from both hierarchies
|
||||||
|
left_bone_names = set()
|
||||||
|
right_bone_names = set()
|
||||||
|
|
||||||
|
# Add standard bones
|
||||||
|
for key, value in standard_bones.items():
|
||||||
|
if base in key.lower():
|
||||||
|
if '_l' in key.lower():
|
||||||
|
left_bone_names.add(value)
|
||||||
|
elif '_r' in key.lower():
|
||||||
|
right_bone_names.add(value)
|
||||||
|
|
||||||
|
# Add acceptable bones
|
||||||
|
for key, names in acceptable_bone_names.items():
|
||||||
|
if base in key.lower():
|
||||||
|
if '_l' in key.lower():
|
||||||
|
left_bone_names.update(names)
|
||||||
|
elif '_r' in key.lower():
|
||||||
|
right_bone_names.update(names)
|
||||||
|
|
||||||
|
# Check if at least one pair exists and matches
|
||||||
|
left_exists = any(name in bones for name in left_bone_names)
|
||||||
|
right_exists = any(name in bones for name in right_bone_names)
|
||||||
|
|
||||||
|
return left_exists == right_exists
|
||||||
|
|
||||||
|
def validate_finger_chain(bones: Dict[str, Bone], chain: Tuple[str, ...]) -> bool:
|
||||||
|
"""Validate if a finger bone chain has correct hierarchy"""
|
||||||
|
for i in range(len(chain) - 1):
|
||||||
|
if not validate_bone_hierarchy(bones, chain[i], chain[i + 1]):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_acceptable_standards(bones: Dict[str, Bone]) -> bool:
|
||||||
|
"""Check if armature matches acceptable non-standard hierarchy"""
|
||||||
|
# Check if bones exist in acceptable list
|
||||||
|
for bone_category, acceptable_names in acceptable_bone_names.items():
|
||||||
|
found = False
|
||||||
|
for name in acceptable_names:
|
||||||
|
if name in bones:
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Validate acceptable hierarchy
|
||||||
|
for parent, child in acceptable_bone_hierarchy:
|
||||||
|
if parent in bones and child in bones:
|
||||||
|
if not validate_bone_hierarchy(bones, parent, child):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
@@ -28,7 +28,6 @@ class MaterialListBool:
|
|||||||
#For the love that is holy do not ever touch these. If this was java I would make these private
|
#For the love that is holy do not ever touch these. If this was java I would make these private
|
||||||
#They should only be accessed via context.scene.texture_atlas_Has_Mat_List_Shown
|
#They should only be accessed via context.scene.texture_atlas_Has_Mat_List_Shown
|
||||||
#This is so we know if the materials are up to date. messing with these variables directly will make the thing blow up.
|
#This is so we know if the materials are up to date. messing with these variables directly will make the thing blow up.
|
||||||
|
|
||||||
#The only exception to this is the ExpandSection_Materials operator which populates this with new data once the materials have changed and need reloading.
|
#The only exception to this is the ExpandSection_Materials operator which populates this with new data once the materials have changed and need reloading.
|
||||||
old_list: dict[str,list[Material]] = {}
|
old_list: dict[str,list[Material]] = {}
|
||||||
bool_material_list_expand: dict[str,bool] = {}
|
bool_material_list_expand: dict[str,bool] = {}
|
||||||
@@ -46,7 +45,6 @@ class MaterialListBool:
|
|||||||
if mat_slot.material:
|
if mat_slot.material:
|
||||||
if mat_slot.material not in newlist:
|
if mat_slot.material not in newlist:
|
||||||
newlist.append(mat_slot.material)
|
newlist.append(mat_slot.material)
|
||||||
|
|
||||||
still_the_same: bool = True
|
still_the_same: bool = True
|
||||||
if bpy.context.scene.name in MaterialListBool.old_list:
|
if bpy.context.scene.name in MaterialListBool.old_list:
|
||||||
for item in newlist:
|
for item in newlist:
|
||||||
@@ -60,7 +58,6 @@ class MaterialListBool:
|
|||||||
else:
|
else:
|
||||||
still_the_same = False
|
still_the_same = False
|
||||||
MaterialListBool.bool_material_list_expand[bpy.context.scene.name] = still_the_same
|
MaterialListBool.bool_material_list_expand[bpy.context.scene.name] = still_the_same
|
||||||
|
|
||||||
return MaterialListBool.bool_material_list_expand[bpy.context.scene.name]
|
return MaterialListBool.bool_material_list_expand[bpy.context.scene.name]
|
||||||
|
|
||||||
class ProgressTracker:
|
class ProgressTracker:
|
||||||
@@ -111,89 +108,6 @@ def get_armature_list(self: Optional[Any] = None, context: Optional[Context] = N
|
|||||||
return [('NONE', t("Armature.validation.no_armature"), '')]
|
return [('NONE', t("Armature.validation.no_armature"), '')]
|
||||||
return armatures
|
return armatures
|
||||||
|
|
||||||
def validate_armature(armature: Object) -> Tuple[bool, List[str]]:
|
|
||||||
"""Enhanced armature validation with multiple validation modes"""
|
|
||||||
validation_mode = bpy.context.scene.avatar_toolkit.validation_mode
|
|
||||||
messages: List[str] = []
|
|
||||||
|
|
||||||
if validation_mode == 'NONE':
|
|
||||||
return True, []
|
|
||||||
|
|
||||||
if not armature or armature.type != 'ARMATURE' or not armature.data.bones:
|
|
||||||
return False, [t("Armature.validation.basic_check_failed")]
|
|
||||||
|
|
||||||
found_bones: Dict[str, Bone] = {bone.name.lower(): bone for bone in armature.data.bones}
|
|
||||||
essential_bones: Set[str] = {'hips', 'spine', 'chest', 'neck', 'head'}
|
|
||||||
|
|
||||||
missing_bones: List[str] = []
|
|
||||||
for bone in essential_bones:
|
|
||||||
if not any(alt_name in found_bones for alt_name in bone_names[bone]):
|
|
||||||
missing_bones.append(bone)
|
|
||||||
|
|
||||||
if missing_bones:
|
|
||||||
messages.append(t("Armature.validation.missing_bones", bones=", ".join(missing_bones)))
|
|
||||||
|
|
||||||
if validation_mode == 'STRICT':
|
|
||||||
hierarchy: List[Tuple[str, str]] = [
|
|
||||||
('hips', 'spine'), ('spine', 'chest'),
|
|
||||||
('chest', 'neck'), ('neck', 'head')
|
|
||||||
]
|
|
||||||
for parent, child in hierarchy:
|
|
||||||
if not validate_bone_hierarchy(found_bones, parent, child):
|
|
||||||
messages.append(t("Armature.validation.invalid_hierarchy",
|
|
||||||
parent=parent, child=child))
|
|
||||||
|
|
||||||
symmetry_pairs: List[Tuple[str, str, str]] = [('arm', 'l', 'r'), ('leg', 'l', 'r')]
|
|
||||||
for base, left, right in symmetry_pairs:
|
|
||||||
if not validate_symmetry(found_bones, base, left, right):
|
|
||||||
messages.append(t("Armature.validation.asymmetric_bones", bone=base))
|
|
||||||
|
|
||||||
if (not validate_symmetry(found_bones, 'hand', 'l', 'r') and
|
|
||||||
not validate_symmetry(found_bones, 'wrist', 'l', 'r')):
|
|
||||||
messages.append(t("Armature.validation.asymmetric_hand_wrist"))
|
|
||||||
|
|
||||||
is_valid: bool = len(messages) == 0
|
|
||||||
return is_valid, messages
|
|
||||||
|
|
||||||
def validate_bone_hierarchy(bones: Dict[str, Bone], parent_name: str, child_name: str) -> bool:
|
|
||||||
"""Validate if there is a valid parent-child relationship between bones"""
|
|
||||||
parent_bone: Optional[Bone] = None
|
|
||||||
child_bone: Optional[Bone] = None
|
|
||||||
|
|
||||||
for alt_name in bone_names[parent_name]:
|
|
||||||
if alt_name in bones:
|
|
||||||
parent_bone = bones[alt_name]
|
|
||||||
break
|
|
||||||
|
|
||||||
for alt_name in bone_names[child_name]:
|
|
||||||
if alt_name in bones:
|
|
||||||
child_bone = bones[alt_name]
|
|
||||||
break
|
|
||||||
|
|
||||||
if not parent_bone or not child_bone:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return child_bone.parent == parent_bone
|
|
||||||
|
|
||||||
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"""
|
|
||||||
left_patterns: List[str] = [
|
|
||||||
f"{base}.{left}",
|
|
||||||
f"{base}_{left}",
|
|
||||||
f"{left}_{base}"
|
|
||||||
]
|
|
||||||
|
|
||||||
right_patterns: List[str] = [
|
|
||||||
f"{base}.{right}",
|
|
||||||
f"{base}_{right}",
|
|
||||||
f"{right}_{base}"
|
|
||||||
]
|
|
||||||
|
|
||||||
left_exists: bool = any(pattern in bones for pattern in left_patterns)
|
|
||||||
right_exists: bool = any(pattern in bones for pattern in right_patterns)
|
|
||||||
|
|
||||||
return left_exists and right_exists
|
|
||||||
|
|
||||||
def auto_select_single_armature(context: Context) -> None:
|
def auto_select_single_armature(context: Context) -> None:
|
||||||
"""Automatically select armature if only one exists in scene"""
|
"""Automatically select armature if only one exists in scene"""
|
||||||
armatures: List[Tuple[str, str, str]] = get_armature_list(context)
|
armatures: List[Tuple[str, str, str]] = get_armature_list(context)
|
||||||
|
|||||||
@@ -355,6 +355,177 @@ resonite_translations = {
|
|||||||
'thumb_3_r': "thumb3.R"
|
'thumb_3_r': "thumb3.R"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
standard_bones = {
|
||||||
|
# Core Structure
|
||||||
|
'hips': 'Hips',
|
||||||
|
'spine': 'Spine',
|
||||||
|
'chest': 'Chest',
|
||||||
|
'upper_chest': 'Chest.Up',
|
||||||
|
'neck': 'Neck',
|
||||||
|
'head': 'Head',
|
||||||
|
|
||||||
|
# Arms
|
||||||
|
'left_arm': 'UpperArm.L',
|
||||||
|
'left_elbow': 'LowerArm.L',
|
||||||
|
'left_wrist': 'Hand.L',
|
||||||
|
'right_arm': 'UpperArm.R',
|
||||||
|
'right_elbow': 'LowerArm.R',
|
||||||
|
'right_wrist': 'Hand.R',
|
||||||
|
|
||||||
|
# Legs
|
||||||
|
'left_leg': 'UpperLeg.L',
|
||||||
|
'left_knee': 'LowerLeg.L',
|
||||||
|
'left_ankle': 'Foot.L',
|
||||||
|
'left_toe': 'Toes.L',
|
||||||
|
'right_leg': 'UpperLeg.R',
|
||||||
|
'right_knee': 'LowerLeg.R',
|
||||||
|
'right_ankle': 'Foot.R',
|
||||||
|
'right_toe': 'Toes.R',
|
||||||
|
|
||||||
|
# Fingers Left
|
||||||
|
'thumb_1_l': 'Thumb1.L',
|
||||||
|
'thumb_2_l': 'Thumb2.L',
|
||||||
|
'thumb_3_l': 'Thumb3.L',
|
||||||
|
'index_1_l': 'Index1.L',
|
||||||
|
'index_2_l': 'Index2.L',
|
||||||
|
'index_3_l': 'Index3.L',
|
||||||
|
'middle_1_l': 'Middle1.L',
|
||||||
|
'middle_2_l': 'Middle2.L',
|
||||||
|
'middle_3_l': 'Middle3.L',
|
||||||
|
'ring_1_l': 'Ring1.L',
|
||||||
|
'ring_2_l': 'Ring2.L',
|
||||||
|
'ring_3_l': 'Ring3.L',
|
||||||
|
'pinkie_1_l': 'Pinky1.L',
|
||||||
|
'pinkie_2_l': 'Pinky2.L',
|
||||||
|
'pinkie_3_l': 'Pinky3.L',
|
||||||
|
|
||||||
|
# Fingers Right
|
||||||
|
'thumb_1_r': 'Thumb1.R',
|
||||||
|
'thumb_2_r': 'Thumb2.R',
|
||||||
|
'thumb_3_r': 'Thumb3.R',
|
||||||
|
'index_1_r': 'Index1.R',
|
||||||
|
'index_2_r': 'Index2.R',
|
||||||
|
'index_3_r': 'Index3.R',
|
||||||
|
'middle_1_r': 'Middle1.R',
|
||||||
|
'middle_2_r': 'Middle2.R',
|
||||||
|
'middle_3_r': 'Middle3.R',
|
||||||
|
'ring_1_r': 'Ring1.R',
|
||||||
|
'ring_2_r': 'Ring2.R',
|
||||||
|
'ring_3_r': 'Ring3.R',
|
||||||
|
'pinkie_1_r': 'Pinky1.R',
|
||||||
|
'pinkie_2_r': 'Pinky2.R',
|
||||||
|
'pinkie_3_r': 'Pinky3.R',
|
||||||
|
|
||||||
|
# Eyes
|
||||||
|
'left_eye': 'Eye.L',
|
||||||
|
'right_eye': 'Eye.R'
|
||||||
|
}
|
||||||
|
|
||||||
|
bone_hierarchy = [
|
||||||
|
('Hips', 'Spine'),
|
||||||
|
('Spine', 'Chest'),
|
||||||
|
('Chest', 'Chest.Up'),
|
||||||
|
('Chest.Up', 'Neck'),
|
||||||
|
('Neck', 'Head'),
|
||||||
|
('Head', 'Eye.L'),
|
||||||
|
('Head', 'Eye.R'),
|
||||||
|
|
||||||
|
# Left Arm Chain
|
||||||
|
('Chest.Up', 'UpperArm.L'),
|
||||||
|
('UpperArm.L', 'LowerArm.L'),
|
||||||
|
('LowerArm.L', 'Hand.L'),
|
||||||
|
|
||||||
|
# Right Arm Chain
|
||||||
|
('Chest.Up', 'UpperArm.R'),
|
||||||
|
('UpperArm.R', 'LowerArm.R'),
|
||||||
|
('LowerArm.R', 'Hand.R'),
|
||||||
|
|
||||||
|
# Left Leg Chain
|
||||||
|
('Hips', 'UpperLeg.L'),
|
||||||
|
('UpperLeg.L', 'LowerLeg.L'),
|
||||||
|
('LowerLeg.L', 'Foot.L'),
|
||||||
|
('Foot.L', 'Toes.L'),
|
||||||
|
|
||||||
|
# Right Leg Chain
|
||||||
|
('Hips', 'UpperLeg.R'),
|
||||||
|
('UpperLeg.R', 'LowerLeg.R'),
|
||||||
|
('LowerLeg.R', 'Foot.R'),
|
||||||
|
('Foot.R', 'Toes.R')
|
||||||
|
]
|
||||||
|
|
||||||
|
finger_hierarchy = {
|
||||||
|
'left': [
|
||||||
|
('Hand.L', 'Thumb1.L', 'Thumb2.L', 'Thumb3.L'),
|
||||||
|
('Hand.L', 'Index1.L', 'Index2.L', 'Index3.L'),
|
||||||
|
('Hand.L', 'Middle1.L', 'Middle2.L', 'Middle3.L'),
|
||||||
|
('Hand.L', 'Ring1.L', 'Ring2.L', 'Ring3.L'),
|
||||||
|
('Hand.L', 'Pinky1.L', 'Pinky2.L', 'Pinky3.L')
|
||||||
|
],
|
||||||
|
'right': [
|
||||||
|
('Hand.R', 'Thumb1.R', 'Thumb2.R', 'Thumb3.R'),
|
||||||
|
('Hand.R', 'Index1.R', 'Index2.R', 'Index3.R'),
|
||||||
|
('Hand.R', 'Middle1.R', 'Middle2.R', 'Middle3.R'),
|
||||||
|
('Hand.R', 'Ring1.R', 'Ring2.R', 'Ring3.R'),
|
||||||
|
('Hand.R', 'Pinky1.R', 'Pinky2.R', 'Pinky3.R')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptable_bone_hierarchy = [
|
||||||
|
# Right side chain
|
||||||
|
('Hips', 'Chest'),
|
||||||
|
('Chest', 'Shoulder.R'),
|
||||||
|
('Shoulder.R', 'Arm.R'),
|
||||||
|
('Arm.R', 'Elbow.R'),
|
||||||
|
('Elbow.R', 'Wrist.R'),
|
||||||
|
('Hips', 'Leg.R'),
|
||||||
|
('Leg.R', 'Knee.R'),
|
||||||
|
('Knee.R', 'Foot.R'),
|
||||||
|
('Foot.R', 'Toes.R'),
|
||||||
|
|
||||||
|
# Left side chain
|
||||||
|
('Chest', 'Shoulder.L'),
|
||||||
|
('Shoulder.L', 'Arm.L'),
|
||||||
|
('Arm.L', 'Elbow.L'),
|
||||||
|
('Elbow.L', 'Wrist.L'),
|
||||||
|
('Hips', 'Leg.L'),
|
||||||
|
('Leg.L', 'Knee.L'),
|
||||||
|
('Knee.L', 'Foot.L'),
|
||||||
|
('Foot.L', 'Toes.L'),
|
||||||
|
|
||||||
|
# Head and Eyes
|
||||||
|
('Chest', 'Neck'),
|
||||||
|
('Neck', 'Head'),
|
||||||
|
('Head', 'Eye_L'),
|
||||||
|
('Head', 'Eye_R'),
|
||||||
|
('Head', 'LeftEye'),
|
||||||
|
('Head', 'RightEye')
|
||||||
|
]
|
||||||
|
|
||||||
|
acceptable_bone_names = {
|
||||||
|
'hips': ['Hips'],
|
||||||
|
'chest': ['Chest'],
|
||||||
|
'neck': ['Neck'],
|
||||||
|
'head': ['Head'],
|
||||||
|
'eye_l': ['Eye_L', 'LeftEye'],
|
||||||
|
'eye_r': ['Eye_R', 'RightEye'],
|
||||||
|
'shoulder_r': ['Shoulder.R'],
|
||||||
|
'arm_r': ['Arm.R'],
|
||||||
|
'elbow_r': ['Elbow.R'],
|
||||||
|
'wrist_r': ['Wrist.R'],
|
||||||
|
'leg_r': ['Leg.R'],
|
||||||
|
'knee_r': ['Knee.R'],
|
||||||
|
'foot_r': ['Foot.R'],
|
||||||
|
'toes_r': ['Toes.R'],
|
||||||
|
'shoulder_l': ['Shoulder.L'],
|
||||||
|
'arm_l': ['Arm.L'],
|
||||||
|
'elbow_l': ['Elbow.L'],
|
||||||
|
'wrist_l': ['Wrist.L'],
|
||||||
|
'leg_l': ['Leg.L'],
|
||||||
|
'knee_l': ['Knee.L'],
|
||||||
|
'foot_l': ['Foot.L'],
|
||||||
|
'toes_l': ['Toes.L']
|
||||||
|
}
|
||||||
|
|
||||||
rigify_unity_names = {
|
rigify_unity_names = {
|
||||||
"DEF-spine": "Hips",
|
"DEF-spine": "Hips",
|
||||||
"DEF-spine.001": "Spine",
|
"DEF-spine.001": "Spine",
|
||||||
@@ -423,3 +594,4 @@ rigify_unnecessary_bones = [
|
|||||||
'heel',
|
'heel',
|
||||||
'pelvis.'
|
'pelvis.'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
+115
-101
@@ -45,6 +45,117 @@ def update_shape_intensity(self: PropertyGroup, context: Context) -> None:
|
|||||||
class AvatarToolkitSceneProperties(PropertyGroup):
|
class AvatarToolkitSceneProperties(PropertyGroup):
|
||||||
"""Property group containing Avatar Toolkit scene-level settings and properties"""
|
"""Property group containing Avatar Toolkit scene-level settings and properties"""
|
||||||
|
|
||||||
|
show_found_bones: BoolProperty(
|
||||||
|
name="Show Found Bones",
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
show_non_standard: BoolProperty(
|
||||||
|
name="Show Non-Standard Bones",
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
show_hierarchy: BoolProperty(
|
||||||
|
name="Show Hierarchy Issues",
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
material_search_filter: StringProperty(
|
||||||
|
name=t("TextureAtlas.search_materials"),
|
||||||
|
description=t("TextureAtlas.search_materials_desc"),
|
||||||
|
default=""
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_texture_node_list(self: Material, context: Context) -> list[tuple]:
|
||||||
|
if self.use_nodes:
|
||||||
|
Object.Enum = [((i.image.name if i.image else i.name+"_image"),
|
||||||
|
(i.image.name if i.image else "node with no image..."),
|
||||||
|
(i.image.name if i.image else i.name),index+1)
|
||||||
|
for index,i in enumerate(self.node_tree.nodes)
|
||||||
|
if i.bl_idname == "ShaderNodeTexImage"]
|
||||||
|
if not len(Object.Enum):
|
||||||
|
Object.Enum = [(t("TextureAtlas.error.label"),
|
||||||
|
t("TextureAtlas.no_images_error.desc"),
|
||||||
|
t("TextureAtlas.error.label"), 0)]
|
||||||
|
else:
|
||||||
|
Object.Enum = [(t("TextureAtlas.error.label"),
|
||||||
|
t("TextureAtlas.no_nodes_error.desc"),
|
||||||
|
t("TextureAtlas.error.label"), 0)]
|
||||||
|
Object.Enum.append((t("TextureAtlas.none.label"),
|
||||||
|
t("TextureAtlas.none.label"),
|
||||||
|
t("TextureAtlas.none.label"), 0))
|
||||||
|
return Object.Enum
|
||||||
|
|
||||||
|
Material.texture_atlas_albedo = EnumProperty(
|
||||||
|
name=t("TextureAtlas.albedo"),
|
||||||
|
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.albedo").lower()),
|
||||||
|
default=0,
|
||||||
|
items=get_texture_node_list
|
||||||
|
)
|
||||||
|
|
||||||
|
Material.texture_atlas_normal = EnumProperty(
|
||||||
|
name=t("TextureAtlas.normal"),
|
||||||
|
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.normal").lower()),
|
||||||
|
default=0,
|
||||||
|
items=get_texture_node_list
|
||||||
|
)
|
||||||
|
|
||||||
|
Material.texture_atlas_emission = EnumProperty(
|
||||||
|
name=t("TextureAtlas.emission"),
|
||||||
|
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.emission").lower()),
|
||||||
|
default=0,
|
||||||
|
items=get_texture_node_list
|
||||||
|
)
|
||||||
|
|
||||||
|
Material.texture_atlas_ambient_occlusion = EnumProperty(
|
||||||
|
name=t("TextureAtlas.ambient_occlusion"),
|
||||||
|
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.ambient_occlusion").lower()),
|
||||||
|
default=0,
|
||||||
|
items=get_texture_node_list
|
||||||
|
)
|
||||||
|
|
||||||
|
Material.texture_atlas_height = EnumProperty(
|
||||||
|
name=t("TextureAtlas.height"),
|
||||||
|
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.height").lower()),
|
||||||
|
default=0,
|
||||||
|
items=get_texture_node_list
|
||||||
|
)
|
||||||
|
|
||||||
|
Material.texture_atlas_roughness = EnumProperty(
|
||||||
|
name=t("TextureAtlas.roughness"),
|
||||||
|
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.roughness").lower()),
|
||||||
|
default=0,
|
||||||
|
items=get_texture_node_list
|
||||||
|
)
|
||||||
|
|
||||||
|
Material.include_in_atlas = BoolProperty(
|
||||||
|
name=t("TextureAtlas.include_in_atlas"),
|
||||||
|
description=t("TextureAtlas.include_in_atlas_desc"),
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
Material.material_expanded = BoolProperty(
|
||||||
|
name=t("TextureAtlas.material_expanded"),
|
||||||
|
description=t("TextureAtlas.material_expanded_desc"),
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
texture_atlas_Has_Mat_List_Shown: BoolProperty(
|
||||||
|
name=t("TextureAtlas.list_shown"),
|
||||||
|
description=t("TextureAtlas.list_shown_desc"),
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
|
texture_atlas_material_index: IntProperty(
|
||||||
|
default=-1,
|
||||||
|
get=lambda self: -1,
|
||||||
|
set=lambda self, context: None
|
||||||
|
)
|
||||||
|
|
||||||
|
materials: CollectionProperty(
|
||||||
|
type=SceneMatClass
|
||||||
|
)
|
||||||
|
|
||||||
avatar_toolkit_updater_version_list: EnumProperty(
|
avatar_toolkit_updater_version_list: EnumProperty(
|
||||||
items=get_version_list,
|
items=get_version_list,
|
||||||
name=t("Scene.avatar_toolkit_updater_version_list.name"),
|
name=t("Scene.avatar_toolkit_updater_version_list.name"),
|
||||||
@@ -396,12 +507,6 @@ class AvatarToolkitSceneProperties(PropertyGroup):
|
|||||||
default=0
|
default=0
|
||||||
)
|
)
|
||||||
|
|
||||||
merge_twist_bones: BoolProperty(
|
|
||||||
name=t("Tools.merge_twist_bones"),
|
|
||||||
description=t("Tools.merge_twist_bones_desc"),
|
|
||||||
default=True
|
|
||||||
)
|
|
||||||
|
|
||||||
list_only_mode: BoolProperty(
|
list_only_mode: BoolProperty(
|
||||||
name=t("Tools.list_only_mode"),
|
name=t("Tools.list_only_mode"),
|
||||||
description=t("Tools.list_only_mode_desc"),
|
description=t("Tools.list_only_mode_desc"),
|
||||||
@@ -414,103 +519,12 @@ class AvatarToolkitSceneProperties(PropertyGroup):
|
|||||||
default=True
|
default=True
|
||||||
)
|
)
|
||||||
|
|
||||||
material_search_filter: StringProperty(
|
merge_twist_bones: BoolProperty(
|
||||||
name=t("TextureAtlas.search_materials"),
|
name=t("Tools.merge_twist_bones"),
|
||||||
description=t("TextureAtlas.search_materials_desc"),
|
description=t("Tools.merge_twist_bones_desc"),
|
||||||
default=""
|
default=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_texture_node_list(self: Material, context: Context) -> list[tuple]:
|
|
||||||
if self.use_nodes:
|
|
||||||
Object.Enum = [((i.image.name if i.image else i.name+"_image"),
|
|
||||||
(i.image.name if i.image else "node with no image..."),
|
|
||||||
(i.image.name if i.image else i.name),index+1)
|
|
||||||
for index,i in enumerate(self.node_tree.nodes)
|
|
||||||
if i.bl_idname == "ShaderNodeTexImage"]
|
|
||||||
if not len(Object.Enum):
|
|
||||||
Object.Enum = [(t("TextureAtlas.error.label"),
|
|
||||||
t("TextureAtlas.no_images_error.desc"),
|
|
||||||
t("TextureAtlas.error.label"), 0)]
|
|
||||||
else:
|
|
||||||
Object.Enum = [(t("TextureAtlas.error.label"),
|
|
||||||
t("TextureAtlas.no_nodes_error.desc"),
|
|
||||||
t("TextureAtlas.error.label"), 0)]
|
|
||||||
Object.Enum.append((t("TextureAtlas.none.label"),
|
|
||||||
t("TextureAtlas.none.label"),
|
|
||||||
t("TextureAtlas.none.label"), 0))
|
|
||||||
return Object.Enum
|
|
||||||
|
|
||||||
Material.texture_atlas_albedo = EnumProperty(
|
|
||||||
name=t("TextureAtlas.albedo"),
|
|
||||||
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.albedo").lower()),
|
|
||||||
default=0,
|
|
||||||
items=get_texture_node_list
|
|
||||||
)
|
|
||||||
|
|
||||||
Material.texture_atlas_normal = EnumProperty(
|
|
||||||
name=t("TextureAtlas.normal"),
|
|
||||||
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.normal").lower()),
|
|
||||||
default=0,
|
|
||||||
items=get_texture_node_list
|
|
||||||
)
|
|
||||||
|
|
||||||
Material.texture_atlas_emission = EnumProperty(
|
|
||||||
name=t("TextureAtlas.emission"),
|
|
||||||
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.emission").lower()),
|
|
||||||
default=0,
|
|
||||||
items=get_texture_node_list
|
|
||||||
)
|
|
||||||
|
|
||||||
Material.texture_atlas_ambient_occlusion = EnumProperty(
|
|
||||||
name=t("TextureAtlas.ambient_occlusion"),
|
|
||||||
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.ambient_occlusion").lower()),
|
|
||||||
default=0,
|
|
||||||
items=get_texture_node_list
|
|
||||||
)
|
|
||||||
|
|
||||||
Material.texture_atlas_height = EnumProperty(
|
|
||||||
name=t("TextureAtlas.height"),
|
|
||||||
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.height").lower()),
|
|
||||||
default=0,
|
|
||||||
items=get_texture_node_list
|
|
||||||
)
|
|
||||||
|
|
||||||
Material.texture_atlas_roughness = EnumProperty(
|
|
||||||
name=t("TextureAtlas.roughness"),
|
|
||||||
description=t("TextureAtlas.texture_use_atlas.desc").format(name=t("TextureAtlas.roughness").lower()),
|
|
||||||
default=0,
|
|
||||||
items=get_texture_node_list
|
|
||||||
)
|
|
||||||
|
|
||||||
Material.include_in_atlas = BoolProperty(
|
|
||||||
name=t("TextureAtlas.include_in_atlas"),
|
|
||||||
description=t("TextureAtlas.include_in_atlas_desc"),
|
|
||||||
default=False
|
|
||||||
)
|
|
||||||
|
|
||||||
Material.material_expanded = BoolProperty(
|
|
||||||
name=t("TextureAtlas.material_expanded"),
|
|
||||||
description=t("TextureAtlas.material_expanded_desc"),
|
|
||||||
default=False
|
|
||||||
)
|
|
||||||
|
|
||||||
texture_atlas_Has_Mat_List_Shown: BoolProperty(
|
|
||||||
name=t("TextureAtlas.list_shown"),
|
|
||||||
description=t("TextureAtlas.list_shown_desc"),
|
|
||||||
default=False
|
|
||||||
)
|
|
||||||
|
|
||||||
texture_atlas_material_index: IntProperty(
|
|
||||||
default=-1,
|
|
||||||
get=lambda self: -1,
|
|
||||||
set=lambda self, context: None
|
|
||||||
)
|
|
||||||
|
|
||||||
materials: CollectionProperty(
|
|
||||||
type=SceneMatClass
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def register() -> None:
|
def register() -> None:
|
||||||
"""Register the Avatar Toolkit property group"""
|
"""Register the Avatar Toolkit property group"""
|
||||||
logger.info("Registering Avatar Toolkit properties")
|
logger.info("Registering Avatar Toolkit properties")
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import bpy_extras
|
|||||||
from numpy import double
|
from numpy import double
|
||||||
from typing import Set, Dict
|
from typing import Set, Dict
|
||||||
|
|
||||||
from .common import get_active_armature, simplify_bonename, validate_armature, ProgressTracker
|
from .common import get_active_armature, simplify_bonename, ProgressTracker
|
||||||
from bpy.types import Context, Operator
|
from bpy.types import Context, Operator
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
from ..core.dictionaries import bone_names, resonite_translations
|
from ..core.dictionaries import bone_names, resonite_translations
|
||||||
from ..core.logging_setup import logger
|
from ..core.logging_setup import logger
|
||||||
|
from ..core.armature_validation import validate_armature
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from .resonite_loader import resonite_animx, resonite_types
|
from .resonite_loader import resonite_animx, resonite_types
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ from ...core.logging_setup import logger
|
|||||||
from ...core.translations import t
|
from ...core.translations import t
|
||||||
from ...core.common import (
|
from ...core.common import (
|
||||||
get_active_armature,
|
get_active_armature,
|
||||||
validate_armature,
|
|
||||||
get_all_meshes,
|
get_all_meshes,
|
||||||
ProgressTracker,
|
ProgressTracker,
|
||||||
calculate_bone_orientation,
|
calculate_bone_orientation,
|
||||||
add_armature_modifier
|
add_armature_modifier
|
||||||
)
|
)
|
||||||
|
from ...core.armature_validation import validate_armature
|
||||||
|
|
||||||
class AvatarToolkit_OT_AttachMesh(Operator):
|
class AvatarToolkit_OT_AttachMesh(Operator):
|
||||||
"""Operator to attach a mesh to an armature bone with automatic weight setup"""
|
"""Operator to attach a mesh to an armature bone with automatic weight setup"""
|
||||||
@@ -27,8 +27,8 @@ class AvatarToolkit_OT_AttachMesh(Operator):
|
|||||||
armature: Optional[Object] = get_active_armature(context)
|
armature: Optional[Object] = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
is_valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return is_valid
|
return valid
|
||||||
|
|
||||||
def execute(self, context: Context) -> Set[str]:
|
def execute(self, context: Context) -> Set[str]:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ from ..core.common import (
|
|||||||
get_active_armature,
|
get_active_armature,
|
||||||
get_all_meshes,
|
get_all_meshes,
|
||||||
get_armature_list,
|
get_armature_list,
|
||||||
validate_armature,
|
|
||||||
validate_mesh_for_pose,
|
validate_mesh_for_pose,
|
||||||
cache_vertex_positions,
|
cache_vertex_positions,
|
||||||
apply_vertex_positions
|
apply_vertex_positions
|
||||||
)
|
)
|
||||||
|
from ..core.armature_validation import validate_armature
|
||||||
|
|
||||||
VALID_EYE_NAMES: Dict[str, List[str]] = {
|
VALID_EYE_NAMES: Dict[str, List[str]] = {
|
||||||
'left': ['LeftEye', 'Eye_L', 'eye_L', 'eye.L', 'EyeLeft', 'left_eye', 'l_eye'],
|
'left': ['LeftEye', 'Eye_L', 'eye_L', 'eye.L', 'EyeLeft', 'left_eye', 'l_eye'],
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ from ..core.logging_setup import logger
|
|||||||
from ..core.common import (
|
from ..core.common import (
|
||||||
ProgressTracker,
|
ProgressTracker,
|
||||||
get_active_armature,
|
get_active_armature,
|
||||||
validate_armature,
|
|
||||||
get_vertex_weights,
|
get_vertex_weights,
|
||||||
transfer_vertex_weights,
|
transfer_vertex_weights,
|
||||||
get_all_meshes
|
get_all_meshes
|
||||||
)
|
)
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
from ..core.dictionaries import bone_names, dont_delete_these_main_bones
|
from ..core.dictionaries import bone_names, dont_delete_these_main_bones
|
||||||
|
from ..core.armature_validation import validate_armature, validate_bone_hierarchy
|
||||||
|
|
||||||
class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
|
class AVATAR_TOOLKIT_OT_StandardizeMmd(Operator):
|
||||||
"""MMD Bone standardization system"""
|
"""MMD Bone standardization system"""
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ from ...core.translations import t
|
|||||||
from ...core.common import (
|
from ...core.common import (
|
||||||
get_active_armature,
|
get_active_armature,
|
||||||
get_all_meshes,
|
get_all_meshes,
|
||||||
validate_armature,
|
|
||||||
clear_unused_data_blocks,
|
clear_unused_data_blocks,
|
||||||
ProgressTracker
|
ProgressTracker
|
||||||
)
|
)
|
||||||
|
from ...core.armature_validation import validate_armature
|
||||||
|
|
||||||
def textures_match(tex1: ShaderNodeTexImage, tex2: ShaderNodeTexImage) -> bool:
|
def textures_match(tex1: ShaderNodeTexImage, tex2: ShaderNodeTexImage) -> bool:
|
||||||
"""Compare two texture nodes for matching properties and image data"""
|
"""Compare two texture nodes for matching properties and image data"""
|
||||||
@@ -92,7 +92,7 @@ class AvatarToolkit_OT_CombineMaterials(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
def execute(self, context: Context) -> Set[str]:
|
def execute(self, context: Context) -> Set[str]:
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ from ...core.translations import t
|
|||||||
from ...core.common import (
|
from ...core.common import (
|
||||||
get_active_armature,
|
get_active_armature,
|
||||||
get_all_meshes,
|
get_all_meshes,
|
||||||
validate_armature,
|
|
||||||
validate_meshes,
|
validate_meshes,
|
||||||
join_mesh_objects,
|
join_mesh_objects,
|
||||||
ProgressTracker
|
ProgressTracker
|
||||||
)
|
)
|
||||||
|
from ...core.armature_validation import validate_armature
|
||||||
|
|
||||||
class AvatarToolkit_OT_JoinAllMeshes(Operator):
|
class AvatarToolkit_OT_JoinAllMeshes(Operator):
|
||||||
"""Operator to join all meshes in the scene"""
|
"""Operator to join all meshes in the scene"""
|
||||||
@@ -25,7 +25,7 @@ class AvatarToolkit_OT_JoinAllMeshes(Operator):
|
|||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
valid: bool
|
valid: bool
|
||||||
valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
def execute(self, context: Context) -> Set[str]:
|
def execute(self, context: Context) -> Set[str]:
|
||||||
@@ -69,7 +69,7 @@ class AvatarToolkit_OT_JoinSelectedMeshes(Operator):
|
|||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
valid: bool
|
valid: bool
|
||||||
valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return (valid and
|
return (valid and
|
||||||
context.mode == 'OBJECT' and
|
context.mode == 'OBJECT' and
|
||||||
len([obj for obj in context.selected_objects if obj.type == 'MESH']) > 1)
|
len([obj for obj in context.selected_objects if obj.type == 'MESH']) > 1)
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from ...core.translations import t
|
|||||||
from ...core.common import (
|
from ...core.common import (
|
||||||
get_active_armature,
|
get_active_armature,
|
||||||
get_all_meshes,
|
get_all_meshes,
|
||||||
validate_armature
|
|
||||||
)
|
)
|
||||||
|
from ...core.armature_validation import validate_armature
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
MERGE_ITERATION_COUNT = 20
|
MERGE_ITERATION_COUNT = 20
|
||||||
@@ -88,7 +88,7 @@ class AvatarToolkit_OT_RemoveDoublesAdvanced(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
def execute(self, context: Context) -> set[str]:
|
def execute(self, context: Context) -> set[str]:
|
||||||
@@ -111,7 +111,7 @@ class AvatarToolkit_OT_RemoveDoubles(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ from ..core.common import (
|
|||||||
get_active_armature,
|
get_active_armature,
|
||||||
get_all_meshes,
|
get_all_meshes,
|
||||||
apply_pose_as_rest,
|
apply_pose_as_rest,
|
||||||
validate_armature,
|
|
||||||
cache_vertex_positions,
|
cache_vertex_positions,
|
||||||
apply_vertex_positions,
|
apply_vertex_positions,
|
||||||
validate_mesh_for_pose,
|
validate_mesh_for_pose,
|
||||||
process_armature_modifiers,
|
process_armature_modifiers,
|
||||||
ProgressTracker
|
ProgressTracker
|
||||||
)
|
)
|
||||||
|
from ..core.armature_validation import validate_armature
|
||||||
|
|
||||||
class BatchPoseOperationMixin:
|
class BatchPoseOperationMixin:
|
||||||
"""Base class for batch pose operations"""
|
"""Base class for batch pose operations"""
|
||||||
@@ -23,7 +23,7 @@ class BatchPoseOperationMixin:
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return valid and context.mode == 'POSE'
|
return valid and context.mode == 'POSE'
|
||||||
|
|
||||||
def validate_meshes(self, meshes: List[Object]) -> List[Tuple[Object, str]]:
|
def validate_meshes(self, meshes: List[Object]) -> List[Tuple[Object, str]]:
|
||||||
@@ -46,7 +46,7 @@ class AvatarToolkit_OT_StartPoseMode(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature or context.mode == "POSE":
|
if not armature or context.mode == "POSE":
|
||||||
return False
|
return False
|
||||||
valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
def execute(self, context: Context) -> Set[str]:
|
def execute(self, context: Context) -> Set[str]:
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ from bpy.types import Operator, Context
|
|||||||
from typing import Set
|
from typing import Set
|
||||||
from ...core.translations import t
|
from ...core.translations import t
|
||||||
from ...core.logging_setup import logger
|
from ...core.logging_setup import logger
|
||||||
from ...core.common import get_active_armature, get_all_meshes, validate_armature, remove_unused_shapekeys
|
from ...core.common import get_active_armature, get_all_meshes, remove_unused_shapekeys
|
||||||
|
from ...core.armature_validation import validate_armature
|
||||||
|
|
||||||
class AvatarToolkit_OT_ApplyTransforms(Operator):
|
class AvatarToolkit_OT_ApplyTransforms(Operator):
|
||||||
"""Apply all transformations to armature and associated meshes"""
|
"""Apply all transformations to armature and associated meshes"""
|
||||||
@@ -18,8 +19,8 @@ class AvatarToolkit_OT_ApplyTransforms(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
is_valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return is_valid and context.mode == 'OBJECT'
|
return valid and context.mode == 'OBJECT'
|
||||||
|
|
||||||
def execute(self, context: Context) -> Set[str]:
|
def execute(self, context: Context) -> Set[str]:
|
||||||
try:
|
try:
|
||||||
@@ -66,8 +67,8 @@ class AvatarToolkit_OT_CleanShapekeys(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
is_valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return is_valid and context.mode == 'OBJECT' and len(get_all_meshes(context)) > 0
|
return valid and context.mode == 'OBJECT' and len(get_all_meshes(context)) > 0
|
||||||
|
|
||||||
def execute(self, context: Context) -> Set[str]:
|
def execute(self, context: Context) -> Set[str]:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -5,12 +5,11 @@ from typing import Optional, Dict, Any, List, Tuple
|
|||||||
from ...core.translations import t
|
from ...core.translations import t
|
||||||
from ...core.common import (
|
from ...core.common import (
|
||||||
get_active_armature,
|
get_active_armature,
|
||||||
validate_armature,
|
|
||||||
get_all_meshes,
|
get_all_meshes,
|
||||||
ProgressTracker,
|
ProgressTracker,
|
||||||
validate_bone_hierarchy,
|
|
||||||
restore_bone_transforms
|
restore_bone_transforms
|
||||||
)
|
)
|
||||||
|
from ...core.armature_validation import validate_armature, validate_bone_hierarchy
|
||||||
|
|
||||||
def duplicate_bone(bone: EditBone) -> EditBone:
|
def duplicate_bone(bone: EditBone) -> EditBone:
|
||||||
"""Create a duplicate of the given bone"""
|
"""Create a duplicate of the given bone"""
|
||||||
@@ -35,8 +34,8 @@ class AvatarToolKit_OT_CreateDigitigradeLegs(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
is_valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return (is_valid and
|
return (valid and
|
||||||
context.mode == 'EDIT_ARMATURE' and
|
context.mode == 'EDIT_ARMATURE' and
|
||||||
context.selected_editable_bones is not None and
|
context.selected_editable_bones is not None and
|
||||||
len(context.selected_editable_bones) == 2)
|
len(context.selected_editable_bones) == 2)
|
||||||
@@ -129,8 +128,8 @@ class AvatarToolKit_OT_DeleteBoneConstraints(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
is_valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return is_valid
|
return valid
|
||||||
|
|
||||||
def execute(self, context: Context) -> set[str]:
|
def execute(self, context: Context) -> set[str]:
|
||||||
"""Execute the constraint removal operation"""
|
"""Execute the constraint removal operation"""
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ from typing import Set, Dict, Optional
|
|||||||
from bpy.types import Operator, Context
|
from bpy.types import Operator, Context
|
||||||
from ...core.translations import t
|
from ...core.translations import t
|
||||||
from ...core.logging_setup import logger
|
from ...core.logging_setup import logger
|
||||||
from ...core.common import get_active_armature, simplify_bonename, validate_armature, ProgressTracker
|
from ...core.common import get_active_armature, simplify_bonename, ProgressTracker
|
||||||
from ...core.dictionaries import bone_names, resonite_translations
|
from ...core.dictionaries import bone_names, resonite_translations
|
||||||
|
from ...core.armature_validation import validate_armature
|
||||||
|
|
||||||
class AvatarToolkit_OT_ConvertResonite(Operator):
|
class AvatarToolkit_OT_ConvertResonite(Operator):
|
||||||
"""Convert armature bone names to Resonite format with progress tracking and validation"""
|
"""Convert armature bone names to Resonite format with progress tracking and validation"""
|
||||||
@@ -19,8 +20,8 @@ class AvatarToolkit_OT_ConvertResonite(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
is_valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return is_valid
|
return valid
|
||||||
|
|
||||||
def execute(self, context: Context) -> Set[str]:
|
def execute(self, context: Context) -> Set[str]:
|
||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ from typing import Set, List
|
|||||||
from bpy.types import Operator, Context, Armature, EditBone
|
from bpy.types import Operator, Context, Armature, EditBone
|
||||||
from ...core.translations import t
|
from ...core.translations import t
|
||||||
from ...core.logging_setup import logger
|
from ...core.logging_setup import logger
|
||||||
from ...core.common import get_active_armature, get_all_meshes, get_vertex_weights, transfer_vertex_weights, validate_armature
|
from ...core.common import get_active_armature, get_all_meshes, get_vertex_weights, transfer_vertex_weights
|
||||||
|
from ...core.armature_validation import validate_armature
|
||||||
|
|
||||||
class AvatarToolkit_OT_ConnectBones(Operator):
|
class AvatarToolkit_OT_ConnectBones(Operator):
|
||||||
"""Connect disconnected bones in chain"""
|
"""Connect disconnected bones in chain"""
|
||||||
@@ -18,8 +19,8 @@ class AvatarToolkit_OT_ConnectBones(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
is_valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return is_valid
|
return valid
|
||||||
|
|
||||||
def execute(self, context: Context) -> Set[str]:
|
def execute(self, context: Context) -> Set[str]:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Operator, Context
|
from bpy.types import Operator, Context
|
||||||
from ...core.translations import t
|
from ...core.translations import t
|
||||||
from ...core.common import get_active_armature, validate_armature
|
from ...core.common import get_active_armature
|
||||||
|
from ...core.armature_validation import validate_armature
|
||||||
|
|
||||||
class AvatarToolKit_OT_SeparateByMaterials(Operator):
|
class AvatarToolKit_OT_SeparateByMaterials(Operator):
|
||||||
"""Operator to separate mesh by materials"""
|
"""Operator to separate mesh by materials"""
|
||||||
@@ -16,10 +17,10 @@ class AvatarToolKit_OT_SeparateByMaterials(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
is_valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return (context.active_object and
|
return (context.active_object and
|
||||||
context.active_object.type == 'MESH' and
|
context.active_object.type == 'MESH' and
|
||||||
is_valid)
|
valid)
|
||||||
|
|
||||||
def execute(self, context: Context) -> set[str]:
|
def execute(self, context: Context) -> set[str]:
|
||||||
"""Execute the separation operation"""
|
"""Execute the separation operation"""
|
||||||
@@ -48,10 +49,10 @@ class AvatarToolKit_OT_SeparateByLooseParts(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
is_valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return (context.active_object and
|
return (context.active_object and
|
||||||
context.active_object.type == 'MESH' and
|
context.active_object.type == 'MESH' and
|
||||||
is_valid)
|
valid)
|
||||||
|
|
||||||
def execute(self, context: Context) -> set[str]:
|
def execute(self, context: Context) -> set[str]:
|
||||||
"""Execute the separation operation"""
|
"""Execute the separation operation"""
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
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, validate_armature
|
from ...core.common import get_active_armature
|
||||||
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
|
||||||
|
from ...core.armature_validation import validate_armature
|
||||||
|
|
||||||
class AvatarToolkit_OT_ConvertRigifyToUnity(Operator):
|
class AvatarToolkit_OT_ConvertRigifyToUnity(Operator):
|
||||||
"""Convert Rigify armature to Unity-compatible format"""
|
"""Convert Rigify armature to Unity-compatible format"""
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ from ..core.logging_setup import logger
|
|||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
from ..core.common import (
|
from ..core.common import (
|
||||||
get_active_armature,
|
get_active_armature,
|
||||||
validate_armature,
|
|
||||||
get_all_meshes,
|
get_all_meshes,
|
||||||
validate_mesh_for_pose
|
validate_mesh_for_pose
|
||||||
)
|
)
|
||||||
|
from ..core.armature_validation import validate_armature
|
||||||
|
|
||||||
class VisemeCache:
|
class VisemeCache:
|
||||||
"""Manages caching of generated viseme shape data for performance optimization"""
|
"""Manages caching of generated viseme shape data for performance optimization"""
|
||||||
@@ -138,10 +138,9 @@ class ATOOLKIT_OT_preview_visemes(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return valid and mesh_obj and mesh_obj.type == 'MESH'
|
return valid and mesh_obj and mesh_obj.type == 'MESH'
|
||||||
|
|
||||||
|
|
||||||
def execute(self, context: Context) -> Set[str]:
|
def execute(self, context: Context) -> Set[str]:
|
||||||
props = context.scene.avatar_toolkit
|
props = context.scene.avatar_toolkit
|
||||||
mesh = context.active_object
|
mesh = context.active_object
|
||||||
@@ -197,7 +196,7 @@ class ATOOLKIT_OT_create_visemes(Operator):
|
|||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if not armature:
|
if not armature:
|
||||||
return False
|
return False
|
||||||
valid, _ = validate_armature(armature)
|
valid, _, _ = validate_armature(armature)
|
||||||
return valid and mesh_obj and mesh_obj.type == 'MESH'
|
return valid and mesh_obj and mesh_obj.type == 'MESH'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,15 @@
|
|||||||
"Armature.validation.invalid_hierarchy": "Invalid bone hierarchy between {parent} and {child}",
|
"Armature.validation.invalid_hierarchy": "Invalid bone hierarchy between {parent} and {child}",
|
||||||
"Armature.validation.asymmetric_bones": "Missing symmetric bones for {bone}",
|
"Armature.validation.asymmetric_bones": "Missing symmetric bones for {bone}",
|
||||||
"Armature.validation.asymmetric_hand_wrist": "Missing symmetric bones for hands/wrists",
|
"Armature.validation.asymmetric_hand_wrist": "Missing symmetric bones for hands/wrists",
|
||||||
|
"Armature.validation.found_bones": "Found bones in armature:\n- {bones}",
|
||||||
|
"Armature.validation.non_standard_bones": "Non-standard bones found:\n- {bones}",
|
||||||
|
"Validation.section.found_bones": "Found Bones",
|
||||||
|
"Validation.section.non_standard": "Non-Standard Bones",
|
||||||
|
"Validation.section.hierarchy": "Hierarchy Issues",
|
||||||
|
"Validation.status.failed": "Validation has failed",
|
||||||
|
"Validation.message.failed.line1": "Armature validation has failed",
|
||||||
|
"Validation.message.failed.line2": "Please check below what the",
|
||||||
|
"Validation.message.failed.line3": "issues are",
|
||||||
|
|
||||||
"Mesh.validation.no_data": "No mesh data",
|
"Mesh.validation.no_data": "No mesh data",
|
||||||
"Mesh.validation.no_vertex_groups": "No vertex groups found",
|
"Mesh.validation.no_vertex_groups": "No vertex groups found",
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ from ..core.translations import t
|
|||||||
from ..core.common import (
|
from ..core.common import (
|
||||||
get_active_armature,
|
get_active_armature,
|
||||||
get_all_meshes,
|
get_all_meshes,
|
||||||
validate_armature,
|
|
||||||
get_armature_list
|
get_armature_list
|
||||||
)
|
)
|
||||||
|
from ..core.armature_validation import validate_armature
|
||||||
|
|
||||||
class AvatarToolkit_OT_SearchMergeArmatureInto(Operator):
|
class AvatarToolkit_OT_SearchMergeArmatureInto(Operator):
|
||||||
"""Search operator for selecting target armature to merge into"""
|
"""Search operator for selecting target armature to merge into"""
|
||||||
|
|||||||
+75
-15
@@ -14,7 +14,6 @@ from ..core.translations import t
|
|||||||
from ..core.common import (
|
from ..core.common import (
|
||||||
get_active_armature,
|
get_active_armature,
|
||||||
clear_default_objects,
|
clear_default_objects,
|
||||||
validate_armature,
|
|
||||||
get_armature_list,
|
get_armature_list,
|
||||||
get_armature_stats
|
get_armature_stats
|
||||||
)
|
)
|
||||||
@@ -24,6 +23,7 @@ from ..functions.pose_mode import (
|
|||||||
AvatarToolkit_OT_ApplyPoseAsShapekey,
|
AvatarToolkit_OT_ApplyPoseAsShapekey,
|
||||||
AvatarToolkit_OT_ApplyPoseAsRest
|
AvatarToolkit_OT_ApplyPoseAsRest
|
||||||
)
|
)
|
||||||
|
from ..core.armature_validation import validate_armature
|
||||||
|
|
||||||
class AvatarToolKit_OT_ExportFBX(Operator):
|
class AvatarToolKit_OT_ExportFBX(Operator):
|
||||||
"""Export selected objects as FBX"""
|
"""Export selected objects as FBX"""
|
||||||
@@ -70,6 +70,7 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
|
|||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
"""Draw the panel layout"""
|
"""Draw the panel layout"""
|
||||||
layout: UILayout = self.layout
|
layout: UILayout = self.layout
|
||||||
|
props = context.scene.avatar_toolkit
|
||||||
|
|
||||||
# Armature Selection Box
|
# Armature Selection Box
|
||||||
armature_box: UILayout = layout.box()
|
armature_box: UILayout = layout.box()
|
||||||
@@ -83,28 +84,87 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
|
|||||||
# Armature Validation
|
# Armature Validation
|
||||||
active_armature: Optional[Object] = get_active_armature(context)
|
active_armature: Optional[Object] = get_active_armature(context)
|
||||||
if active_armature:
|
if active_armature:
|
||||||
is_valid: bool
|
is_valid, messages, is_acceptable = validate_armature(active_armature)
|
||||||
messages: List[str]
|
|
||||||
is_valid, messages = validate_armature(active_armature)
|
|
||||||
|
|
||||||
# Create info box for all validation information
|
info_box = col.box()
|
||||||
info_box: UILayout = col.box()
|
|
||||||
|
|
||||||
if is_valid:
|
if not is_valid:
|
||||||
row: UILayout = info_box.row()
|
# Display non-standard bones and hierarchy issues
|
||||||
split: UILayout = row.split(factor=0.6)
|
if len(messages) > 1:
|
||||||
|
# Found Bones section
|
||||||
|
validation_box = info_box.box()
|
||||||
|
row = validation_box.row()
|
||||||
|
row.prop(props, "show_found_bones", text=t("Validation.section.found_bones"), icon='TRIA_DOWN' if props.show_found_bones else 'TRIA_RIGHT', emboss=False)
|
||||||
|
if props.show_found_bones:
|
||||||
|
for line in messages[0].split('\n'):
|
||||||
|
validation_box.label(text=line)
|
||||||
|
|
||||||
|
# Main validation status
|
||||||
|
validation_box = info_box.box()
|
||||||
|
row = validation_box.row()
|
||||||
|
row.alert = True
|
||||||
|
row.label(text=t("Validation.status.failed"))
|
||||||
|
|
||||||
|
# Detailed validation message
|
||||||
|
validation_box = info_box.box()
|
||||||
|
row = validation_box.row()
|
||||||
|
row.alert = True
|
||||||
|
row.label(text=t("Validation.message.failed.line1"))
|
||||||
|
row = validation_box.row()
|
||||||
|
row.alert = True
|
||||||
|
row.label(text=t("Validation.message.failed.line2"))
|
||||||
|
row = validation_box.row()
|
||||||
|
row.alert = True
|
||||||
|
row.label(text=t("Validation.message.failed.line3"))
|
||||||
|
|
||||||
|
# Non-Standard Bones section
|
||||||
|
validation_box = info_box.box()
|
||||||
|
row = validation_box.row()
|
||||||
|
row.alert = True
|
||||||
|
row.prop(props, "show_non_standard", text=t("Validation.section.non_standard"), icon='TRIA_DOWN' if props.show_non_standard else 'TRIA_RIGHT', emboss=False)
|
||||||
|
if props.show_non_standard:
|
||||||
|
for line in messages[1].split('\n'):
|
||||||
|
sub_row = validation_box.row()
|
||||||
|
sub_row.alert = True
|
||||||
|
sub_row.label(text=line)
|
||||||
|
|
||||||
|
# Hierarchy Issues section
|
||||||
|
validation_box = info_box.box()
|
||||||
|
row = validation_box.row()
|
||||||
|
row.alert = True
|
||||||
|
row.prop(props, "show_hierarchy", text=t("Validation.section.hierarchy"), icon='TRIA_DOWN' if props.show_hierarchy else 'TRIA_RIGHT', emboss=False)
|
||||||
|
if props.show_hierarchy:
|
||||||
|
for message in messages[2:]:
|
||||||
|
sub_row = validation_box.row()
|
||||||
|
sub_row.alert = True
|
||||||
|
sub_row.label(text=message)
|
||||||
|
else:
|
||||||
|
# If no specific issues, show acceptable message
|
||||||
|
info_box.label(text=messages[0], icon='INFO')
|
||||||
|
info_box.label(text=messages[1])
|
||||||
|
info_box.label(text=messages[2])
|
||||||
|
elif is_valid and not is_acceptable:
|
||||||
|
row = info_box.row()
|
||||||
|
split = row.split(factor=0.6)
|
||||||
split.label(text=t("QuickAccess.valid_armature"), icon='CHECKMARK')
|
split.label(text=t("QuickAccess.valid_armature"), icon='CHECKMARK')
|
||||||
stats: Dict[str, int] = get_armature_stats(active_armature)
|
stats = get_armature_stats(active_armature)
|
||||||
split.label(text=t("QuickAccess.bones_count", count=stats['bone_count']))
|
split.label(text=t("QuickAccess.bones_count", count=stats['bone_count']))
|
||||||
|
|
||||||
if stats['has_pose']:
|
if stats['has_pose']:
|
||||||
info_box.label(text=t("QuickAccess.pose_bones_available"), icon='POSE_HLT')
|
info_box.label(text=t("QuickAccess.pose_bones_available"), icon='POSE_HLT')
|
||||||
else:
|
elif is_valid and is_acceptable:
|
||||||
# Display validation failure messages
|
# Show acceptable standard message
|
||||||
for message in messages:
|
info_box.label(text=messages[0], icon='INFO')
|
||||||
info_box.label(text=message, icon='ERROR')
|
info_box.label(text=messages[1])
|
||||||
|
info_box.label(text=messages[2])
|
||||||
|
|
||||||
# Validation Mode Warnings - always show in info box
|
# Add standardize button
|
||||||
|
standardize_box = info_box.box()
|
||||||
|
standardize_box.operator("avatar_toolkit.standardize_armature",
|
||||||
|
text=t("QuickAccess.standardize_armature"),
|
||||||
|
icon='MODIFIER')
|
||||||
|
|
||||||
|
# Validation Mode Warnings
|
||||||
validation_mode = context.scene.avatar_toolkit.validation_mode
|
validation_mode = context.scene.avatar_toolkit.validation_mode
|
||||||
if validation_mode == 'BASIC':
|
if validation_mode == 'BASIC':
|
||||||
warning_row = info_box.box()
|
warning_row = info_box.box()
|
||||||
|
|||||||
Reference in New Issue
Block a user