89 lines
3.4 KiB
Python
89 lines
3.4 KiB
Python
import bpy
|
|
from typing import Tuple, List, Dict, Set
|
|
from bpy.types import Object, Bone
|
|
from ..core.translations import t
|
|
from ..core.dictionaries import bone_names
|
|
|
|
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
|