Moved Armature Validation to it's own file

This commit is contained in:
Yusarina
2025-02-07 16:04:54 +00:00
parent 3eb0029b5e
commit 4b59147649
18 changed files with 109 additions and 100 deletions
+88
View File
@@ -0,0 +1,88 @@
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
-83
View File
@@ -110,89 +110,6 @@ def get_armature_list(self: Optional[Any] = None, context: Optional[Context] = N
if not armatures:
return [('NONE', t("Armature.validation.no_armature"), '')]
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:
"""Automatically select armature if only one exists in scene"""
+2 -1
View File
@@ -4,11 +4,12 @@ import bpy_extras
from numpy import double
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 ..core.translations import t
from ..core.dictionaries import bone_names, resonite_translations
from ..core.logging_setup import logger
from ..core.armature_validation import validate_armature
import re
from .resonite_loader import resonite_animx, resonite_types