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
|
||||
#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.
|
||||
|
||||
#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]] = {}
|
||||
bool_material_list_expand: dict[str,bool] = {}
|
||||
@@ -46,7 +45,6 @@ class MaterialListBool:
|
||||
if mat_slot.material:
|
||||
if mat_slot.material not in newlist:
|
||||
newlist.append(mat_slot.material)
|
||||
|
||||
still_the_same: bool = True
|
||||
if bpy.context.scene.name in MaterialListBool.old_list:
|
||||
for item in newlist:
|
||||
@@ -60,7 +58,6 @@ class MaterialListBool:
|
||||
else:
|
||||
still_the_same = False
|
||||
MaterialListBool.bool_material_list_expand[bpy.context.scene.name] = still_the_same
|
||||
|
||||
return MaterialListBool.bool_material_list_expand[bpy.context.scene.name]
|
||||
|
||||
class ProgressTracker:
|
||||
@@ -110,89 +107,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"""
|
||||
|
||||
+173
-1
@@ -355,6 +355,177 @@ resonite_translations = {
|
||||
'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 = {
|
||||
"DEF-spine": "Hips",
|
||||
"DEF-spine.001": "Spine",
|
||||
@@ -422,4 +593,5 @@ rigify_unnecessary_bones = [
|
||||
'lid',
|
||||
'heel',
|
||||
'pelvis.'
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
+117
-103
@@ -44,6 +44,117 @@ def update_shape_intensity(self: PropertyGroup, context: Context) -> None:
|
||||
|
||||
class AvatarToolkitSceneProperties(PropertyGroup):
|
||||
"""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(
|
||||
items=get_version_list,
|
||||
@@ -396,12 +507,6 @@ class AvatarToolkitSceneProperties(PropertyGroup):
|
||||
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(
|
||||
name=t("Tools.list_only_mode"),
|
||||
description=t("Tools.list_only_mode_desc"),
|
||||
@@ -413,104 +518,13 @@ class AvatarToolkitSceneProperties(PropertyGroup):
|
||||
description=t('MergeArmature.cleanup_shape_keys_desc'),
|
||||
default=True
|
||||
)
|
||||
|
||||
material_search_filter: StringProperty(
|
||||
name=t("TextureAtlas.search_materials"),
|
||||
description=t("TextureAtlas.search_materials_desc"),
|
||||
default=""
|
||||
|
||||
merge_twist_bones: BoolProperty(
|
||||
name=t("Tools.merge_twist_bones"),
|
||||
description=t("Tools.merge_twist_bones_desc"),
|
||||
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:
|
||||
"""Register the Avatar Toolkit property group"""
|
||||
logger.info("Registering Avatar Toolkit properties")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,12 +7,12 @@ from ...core.logging_setup import logger
|
||||
from ...core.translations import t
|
||||
from ...core.common import (
|
||||
get_active_armature,
|
||||
validate_armature,
|
||||
get_all_meshes,
|
||||
ProgressTracker,
|
||||
calculate_bone_orientation,
|
||||
add_armature_modifier
|
||||
)
|
||||
from ...core.armature_validation import validate_armature
|
||||
|
||||
class AvatarToolkit_OT_AttachMesh(Operator):
|
||||
"""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)
|
||||
if not armature:
|
||||
return False
|
||||
is_valid, _ = validate_armature(armature)
|
||||
return is_valid
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid
|
||||
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
try:
|
||||
|
||||
@@ -18,11 +18,11 @@ from ..core.common import (
|
||||
get_active_armature,
|
||||
get_all_meshes,
|
||||
get_armature_list,
|
||||
validate_armature,
|
||||
validate_mesh_for_pose,
|
||||
cache_vertex_positions,
|
||||
apply_vertex_positions
|
||||
)
|
||||
from ..core.armature_validation import validate_armature
|
||||
|
||||
VALID_EYE_NAMES: Dict[str, List[str]] = {
|
||||
'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 (
|
||||
ProgressTracker,
|
||||
get_active_armature,
|
||||
validate_armature,
|
||||
get_vertex_weights,
|
||||
transfer_vertex_weights,
|
||||
get_all_meshes
|
||||
)
|
||||
from ..core.translations import t
|
||||
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):
|
||||
"""MMD Bone standardization system"""
|
||||
|
||||
@@ -14,10 +14,10 @@ from ...core.translations import t
|
||||
from ...core.common import (
|
||||
get_active_armature,
|
||||
get_all_meshes,
|
||||
validate_armature,
|
||||
clear_unused_data_blocks,
|
||||
ProgressTracker
|
||||
)
|
||||
from ...core.armature_validation import validate_armature
|
||||
|
||||
def textures_match(tex1: ShaderNodeTexImage, tex2: ShaderNodeTexImage) -> bool:
|
||||
"""Compare two texture nodes for matching properties and image data"""
|
||||
@@ -92,7 +92,7 @@ class AvatarToolkit_OT_CombineMaterials(Operator):
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
valid, _ = validate_armature(armature)
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid
|
||||
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
|
||||
@@ -6,11 +6,11 @@ from ...core.translations import t
|
||||
from ...core.common import (
|
||||
get_active_armature,
|
||||
get_all_meshes,
|
||||
validate_armature,
|
||||
validate_meshes,
|
||||
join_mesh_objects,
|
||||
ProgressTracker
|
||||
)
|
||||
from ...core.armature_validation import validate_armature
|
||||
|
||||
class AvatarToolkit_OT_JoinAllMeshes(Operator):
|
||||
"""Operator to join all meshes in the scene"""
|
||||
@@ -25,7 +25,7 @@ class AvatarToolkit_OT_JoinAllMeshes(Operator):
|
||||
if not armature:
|
||||
return False
|
||||
valid: bool
|
||||
valid, _ = validate_armature(armature)
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid
|
||||
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
@@ -69,7 +69,7 @@ class AvatarToolkit_OT_JoinSelectedMeshes(Operator):
|
||||
if not armature:
|
||||
return False
|
||||
valid: bool
|
||||
valid, _ = validate_armature(armature)
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return (valid and
|
||||
context.mode == 'OBJECT' and
|
||||
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 (
|
||||
get_active_armature,
|
||||
get_all_meshes,
|
||||
validate_armature
|
||||
)
|
||||
from ...core.armature_validation import validate_armature
|
||||
|
||||
# Constants
|
||||
MERGE_ITERATION_COUNT = 20
|
||||
@@ -88,7 +88,7 @@ class AvatarToolkit_OT_RemoveDoublesAdvanced(Operator):
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
valid, _ = validate_armature(armature)
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid
|
||||
|
||||
def execute(self, context: Context) -> set[str]:
|
||||
@@ -111,7 +111,7 @@ class AvatarToolkit_OT_RemoveDoubles(Operator):
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
valid, _ = validate_armature(armature)
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid
|
||||
|
||||
def draw(self, context: Context) -> None:
|
||||
|
||||
@@ -8,13 +8,13 @@ from ..core.common import (
|
||||
get_active_armature,
|
||||
get_all_meshes,
|
||||
apply_pose_as_rest,
|
||||
validate_armature,
|
||||
cache_vertex_positions,
|
||||
apply_vertex_positions,
|
||||
validate_mesh_for_pose,
|
||||
process_armature_modifiers,
|
||||
ProgressTracker
|
||||
)
|
||||
from ..core.armature_validation import validate_armature
|
||||
|
||||
class BatchPoseOperationMixin:
|
||||
"""Base class for batch pose operations"""
|
||||
@@ -23,7 +23,7 @@ class BatchPoseOperationMixin:
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
valid, _ = validate_armature(armature)
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid and context.mode == 'POSE'
|
||||
|
||||
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)
|
||||
if not armature or context.mode == "POSE":
|
||||
return False
|
||||
valid, _ = validate_armature(armature)
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid
|
||||
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
|
||||
@@ -4,7 +4,8 @@ from bpy.types import Operator, Context
|
||||
from typing import Set
|
||||
from ...core.translations import t
|
||||
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):
|
||||
"""Apply all transformations to armature and associated meshes"""
|
||||
@@ -18,8 +19,8 @@ class AvatarToolkit_OT_ApplyTransforms(Operator):
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
is_valid, _ = validate_armature(armature)
|
||||
return is_valid and context.mode == 'OBJECT'
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid and context.mode == 'OBJECT'
|
||||
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
try:
|
||||
@@ -66,8 +67,8 @@ class AvatarToolkit_OT_CleanShapekeys(Operator):
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
is_valid, _ = validate_armature(armature)
|
||||
return is_valid and context.mode == 'OBJECT' and len(get_all_meshes(context)) > 0
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid and context.mode == 'OBJECT' and len(get_all_meshes(context)) > 0
|
||||
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
try:
|
||||
|
||||
@@ -5,12 +5,11 @@ from typing import Optional, Dict, Any, List, Tuple
|
||||
from ...core.translations import t
|
||||
from ...core.common import (
|
||||
get_active_armature,
|
||||
validate_armature,
|
||||
get_all_meshes,
|
||||
ProgressTracker,
|
||||
validate_bone_hierarchy,
|
||||
restore_bone_transforms
|
||||
)
|
||||
from ...core.armature_validation import validate_armature, validate_bone_hierarchy
|
||||
|
||||
def duplicate_bone(bone: EditBone) -> EditBone:
|
||||
"""Create a duplicate of the given bone"""
|
||||
@@ -35,8 +34,8 @@ class AvatarToolKit_OT_CreateDigitigradeLegs(Operator):
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
is_valid, _ = validate_armature(armature)
|
||||
return (is_valid and
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return (valid and
|
||||
context.mode == 'EDIT_ARMATURE' and
|
||||
context.selected_editable_bones is not None and
|
||||
len(context.selected_editable_bones) == 2)
|
||||
@@ -129,8 +128,8 @@ class AvatarToolKit_OT_DeleteBoneConstraints(Operator):
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
is_valid, _ = validate_armature(armature)
|
||||
return is_valid
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid
|
||||
|
||||
def execute(self, context: Context) -> set[str]:
|
||||
"""Execute the constraint removal operation"""
|
||||
|
||||
@@ -4,8 +4,9 @@ from typing import Set, Dict, Optional
|
||||
from bpy.types import Operator, Context
|
||||
from ...core.translations import t
|
||||
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.armature_validation import validate_armature
|
||||
|
||||
class AvatarToolkit_OT_ConvertResonite(Operator):
|
||||
"""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)
|
||||
if not armature:
|
||||
return False
|
||||
is_valid, _ = validate_armature(armature)
|
||||
return is_valid
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid
|
||||
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
armature = get_active_armature(context)
|
||||
|
||||
@@ -4,7 +4,8 @@ from typing import Set, List
|
||||
from bpy.types import Operator, Context, Armature, EditBone
|
||||
from ...core.translations import t
|
||||
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):
|
||||
"""Connect disconnected bones in chain"""
|
||||
@@ -18,8 +19,8 @@ class AvatarToolkit_OT_ConnectBones(Operator):
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
is_valid, _ = validate_armature(armature)
|
||||
return is_valid
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid
|
||||
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
try:
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import bpy
|
||||
from bpy.types import Operator, Context
|
||||
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):
|
||||
"""Operator to separate mesh by materials"""
|
||||
@@ -16,10 +17,10 @@ class AvatarToolKit_OT_SeparateByMaterials(Operator):
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
is_valid, _ = validate_armature(armature)
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return (context.active_object and
|
||||
context.active_object.type == 'MESH' and
|
||||
is_valid)
|
||||
valid)
|
||||
|
||||
def execute(self, context: Context) -> set[str]:
|
||||
"""Execute the separation operation"""
|
||||
@@ -48,10 +49,10 @@ class AvatarToolKit_OT_SeparateByLooseParts(Operator):
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
is_valid, _ = validate_armature(armature)
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return (context.active_object and
|
||||
context.active_object.type == 'MESH' and
|
||||
is_valid)
|
||||
valid)
|
||||
|
||||
def execute(self, context: Context) -> set[str]:
|
||||
"""Execute the separation operation"""
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import bpy
|
||||
from typing import Dict, List, Set, Optional, Tuple, Any
|
||||
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.translations import t
|
||||
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):
|
||||
"""Convert Rigify armature to Unity-compatible format"""
|
||||
|
||||
@@ -9,10 +9,10 @@ from ..core.logging_setup import logger
|
||||
from ..core.translations import t
|
||||
from ..core.common import (
|
||||
get_active_armature,
|
||||
validate_armature,
|
||||
get_all_meshes,
|
||||
validate_mesh_for_pose
|
||||
)
|
||||
from ..core.armature_validation import validate_armature
|
||||
|
||||
class VisemeCache:
|
||||
"""Manages caching of generated viseme shape data for performance optimization"""
|
||||
@@ -138,9 +138,8 @@ class ATOOLKIT_OT_preview_visemes(Operator):
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
valid, _ = validate_armature(armature)
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid and mesh_obj and mesh_obj.type == 'MESH'
|
||||
|
||||
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
props = context.scene.avatar_toolkit
|
||||
@@ -197,7 +196,7 @@ class ATOOLKIT_OT_create_visemes(Operator):
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
valid, _ = validate_armature(armature)
|
||||
valid, _, _ = validate_armature(armature)
|
||||
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.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",
|
||||
|
||||
@@ -6,9 +6,9 @@ from ..core.translations import t
|
||||
from ..core.common import (
|
||||
get_active_armature,
|
||||
get_all_meshes,
|
||||
validate_armature,
|
||||
get_armature_list
|
||||
)
|
||||
from ..core.armature_validation import validate_armature
|
||||
|
||||
class AvatarToolkit_OT_SearchMergeArmatureInto(Operator):
|
||||
"""Search operator for selecting target armature to merge into"""
|
||||
|
||||
+75
-15
@@ -14,7 +14,6 @@ from ..core.translations import t
|
||||
from ..core.common import (
|
||||
get_active_armature,
|
||||
clear_default_objects,
|
||||
validate_armature,
|
||||
get_armature_list,
|
||||
get_armature_stats
|
||||
)
|
||||
@@ -24,6 +23,7 @@ from ..functions.pose_mode import (
|
||||
AvatarToolkit_OT_ApplyPoseAsShapekey,
|
||||
AvatarToolkit_OT_ApplyPoseAsRest
|
||||
)
|
||||
from ..core.armature_validation import validate_armature
|
||||
|
||||
class AvatarToolKit_OT_ExportFBX(Operator):
|
||||
"""Export selected objects as FBX"""
|
||||
@@ -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,28 +84,87 @@ 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)
|
||||
is_valid, messages, is_acceptable = 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)
|
||||
if not is_valid:
|
||||
# Display non-standard bones and hierarchy issues
|
||||
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')
|
||||
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')
|
||||
elif is_valid and is_acceptable:
|
||||
# Show acceptable standard message
|
||||
info_box.label(text=messages[0], icon='INFO')
|
||||
info_box.label(text=messages[1])
|
||||
info_box.label(text=messages[2])
|
||||
|
||||
# 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 - always show in info box
|
||||
# Validation Mode Warnings
|
||||
validation_mode = context.scene.avatar_toolkit.validation_mode
|
||||
if validation_mode == 'BASIC':
|
||||
warning_row = info_box.box()
|
||||
|
||||
Reference in New Issue
Block a user