Better checks
- Added standard list. - Added bone_hierarchy list - Added bone_hierarchy - Better checks. - Better UI. This is the first part, still needs alot of work, but this is better then before. Need to add some more standards and then we will be golden.
This commit is contained in:
+58
-35
@@ -1,11 +1,14 @@
|
||||
import bpy
|
||||
from typing import Tuple, List, Dict, Set
|
||||
from typing import Tuple, List, Dict, Set, Optional
|
||||
from bpy.types import Object, Bone
|
||||
from ..core.translations import t
|
||||
from ..core.dictionaries import bone_names
|
||||
from ..core.dictionaries import (
|
||||
standard_bones,
|
||||
bone_hierarchy,
|
||||
finger_hierarchy
|
||||
)
|
||||
|
||||
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] = []
|
||||
|
||||
@@ -15,28 +18,48 @@ def validate_armature(armature: Object) -> Tuple[bool, List[str]]:
|
||||
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'}
|
||||
found_bones: Dict[str, Bone] = {bone.name: bone for bone in armature.data.bones}
|
||||
|
||||
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)
|
||||
# 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))
|
||||
|
||||
# Check each bone against our standard
|
||||
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):
|
||||
if bone_name not in standard_bones.values():
|
||||
non_standard_bones.append(bone_name)
|
||||
|
||||
if non_standard_bones:
|
||||
non_standard_list = "\n".join([f"- {bone}" for bone in non_standard_bones])
|
||||
messages.append(t("Armature.validation.non_standard_bones", bones=non_standard_list))
|
||||
|
||||
# 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:
|
||||
messages.append(t("Armature.validation.missing_bones", bones=", ".join(missing_bones)))
|
||||
missing_list = "\n".join([f"- {bone}" for bone in missing_bones])
|
||||
messages.append(t("Armature.validation.missing_bones", bones=missing_list))
|
||||
|
||||
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))
|
||||
# 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):
|
||||
messages.append(t("Armature.validation.invalid_hierarchy",
|
||||
parent=parent, child=child))
|
||||
|
||||
symmetry_pairs: List[Tuple[str, str, str]] = [('arm', 'l', 'r'), ('leg', 'l', 'r')]
|
||||
# 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):
|
||||
messages.append(t("Armature.validation.asymmetric_bones", bone=base))
|
||||
@@ -45,28 +68,21 @@ def validate_armature(armature: Object) -> Tuple[bool, List[str]]:
|
||||
not validate_symmetry(found_bones, 'wrist', 'l', 'r')):
|
||||
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):
|
||||
messages.append(t("Armature.validation.invalid_finger", finger=finger_chain[0]))
|
||||
|
||||
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:
|
||||
if parent_name not in bones or child_name not in bones:
|
||||
return False
|
||||
|
||||
return child_bone.parent == parent_bone
|
||||
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"""
|
||||
@@ -86,3 +102,10 @@ def validate_symmetry(bones: Dict[str, Bone], base: str, left: str, right: str)
|
||||
right_exists: bool = any(pattern in bones for pattern in right_patterns)
|
||||
|
||||
return left_exists and 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
|
||||
|
||||
@@ -354,3 +354,121 @@ resonite_translations = {
|
||||
'thumb_2_r': "thumb2.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'
|
||||
}
|
||||
|
||||
# Define standard bone hierarchies
|
||||
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')
|
||||
]
|
||||
|
||||
# Define finger hierarchies
|
||||
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')
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -463,6 +463,19 @@ class AvatarToolkitSceneProperties(PropertyGroup):
|
||||
type=SceneMatClass
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
|
||||
def register() -> None:
|
||||
|
||||
@@ -69,6 +69,15 @@
|
||||
"Armature.validation.invalid_hierarchy": "Invalid bone hierarchy between {parent} and {child}",
|
||||
"Armature.validation.asymmetric_bones": "Missing symmetric bones for {bone}",
|
||||
"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_vertex_groups": "No vertex groups found",
|
||||
|
||||
+52
-10
@@ -70,6 +70,7 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
|
||||
def draw(self, context: Context) -> None:
|
||||
"""Draw the panel layout"""
|
||||
layout: UILayout = self.layout
|
||||
props = context.scene.avatar_toolkit
|
||||
|
||||
# Armature Selection Box
|
||||
armature_box: UILayout = layout.box()
|
||||
@@ -83,26 +84,67 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
|
||||
# Armature Validation
|
||||
active_armature: Optional[Object] = get_active_armature(context)
|
||||
if active_armature:
|
||||
is_valid: bool
|
||||
messages: List[str]
|
||||
is_valid, messages = validate_armature(active_armature)
|
||||
|
||||
# Create info box for all validation information
|
||||
info_box: UILayout = col.box()
|
||||
info_box = col.box()
|
||||
|
||||
if is_valid:
|
||||
row: UILayout = info_box.row()
|
||||
split: UILayout = row.split(factor=0.6)
|
||||
row = info_box.row()
|
||||
split = row.split(factor=0.6)
|
||||
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']))
|
||||
|
||||
if stats['has_pose']:
|
||||
info_box.label(text=t("QuickAccess.pose_bones_available"), icon='POSE_HLT')
|
||||
else:
|
||||
# Display validation failure messages
|
||||
for message in messages:
|
||||
info_box.label(text=message, icon='ERROR')
|
||||
# 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)
|
||||
|
||||
# Validation Mode Warnings - always show in info box
|
||||
validation_mode = context.scene.avatar_toolkit.validation_mode
|
||||
|
||||
Reference in New Issue
Block a user