Files
Avatar-Toolkit/ui/quick_access_panel.py
T
Yusarina c65bed3ff4 Armature Validation P2
- Added Highlight Bone System in the 3D View, can be turned off in settings.
- Added more bones to the acceptable bone lists.
- Fixed issue with properties registrations and unregistration, the system is more rebust now.
- Added a validate t-pose system
- Added a detect bone scales system.
- Fixed some translation strings
- Armature validation now uses logger system.
2025-03-24 02:12:03 +00:00

252 lines
12 KiB
Python

import bpy
from typing import Set, Dict, List, Optional, Tuple
from bpy.types import (
Operator,
Panel,
Menu,
Context,
UILayout,
WindowManager,
Object
)
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
from ..core.translations import t
from ..core.common import (
get_active_armature,
clear_default_objects,
get_armature_list,
get_armature_stats
)
from ..functions.pose_mode import (
AvatarToolkit_OT_StartPoseMode,
AvatarToolkit_OT_StopPoseMode,
AvatarToolkit_OT_ApplyPoseAsShapekey,
AvatarToolkit_OT_ApplyPoseAsRest
)
from ..core.armature_validation import validate_armature
class AvatarToolKit_OT_ExportFBX(Operator):
"""Export selected objects as FBX"""
bl_idname: str = "avatar_toolkit.export_fbx"
bl_label: str = t("QuickAccess.export_fbx")
def execute(self, context: Context) -> Set[str]:
bpy.ops.export_scene.fbx('INVOKE_DEFAULT')
return {'FINISHED'}
class AvatarToolKit_MT_ExportMenu(Menu):
"""Export menu containing various export options"""
bl_idname: str = "AVATAR_TOOLKIT_MT_export_menu"
bl_label: str = t("QuickAccess.export")
def draw(self, context: Context) -> None:
layout: UILayout = self.layout
layout.operator("avatar_toolkit.export_fbx", text=t("QuickAccess.export_fbx"))
layout.operator("avatar_toolkit.export_resonite", text=t("QuickAccess.export_resonite"))
class AvatarToolKit_OT_ExportMenu(Operator):
"""Open the export menu"""
bl_idname: str = "avatar_toolkit.export"
bl_label: str = t("QuickAccess.export")
@classmethod
def poll(cls, context: Context) -> bool:
return get_active_armature(context) is not None
def execute(self, context: Context) -> Set[str]:
bpy.context.window_manager.popup_menu(AvatarToolKit_MT_ExportMenu.draw)
return {'FINISHED'}
class AvatarToolKit_PT_QuickAccessPanel(Panel):
"""Quick access panel for common Avatar Toolkit operations"""
bl_label: str = t("QuickAccess.label")
bl_idname: str = "OBJECT_PT_avatar_toolkit_quick_access"
bl_space_type: str = 'VIEW_3D'
bl_region_type: str = 'UI'
bl_category: str = CATEGORY_NAME
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
bl_order: int = 0
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()
col: UILayout = armature_box.column(align=True)
col.label(text=t("QuickAccess.select_armature"), icon='ARMATURE_DATA')
col.separator(factor=0.5)
# Armature Selection
col.prop(context.scene.avatar_toolkit, "active_armature", text="")
# Armature Validation
active_armature: Optional[Object] = get_active_armature(context)
if active_armature:
is_valid, messages, is_acceptable, hierarchy_messages, scale_messages, non_standard_messages = validate_armature(active_armature, detailed_messages=True)
info_box = col.box()
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:
if non_standard_messages:
for message in non_standard_messages:
for line in message.split('\n'):
sub_row = validation_box.row()
sub_row.alert = True
sub_row.label(text=line)
else:
sub_row = validation_box.row()
sub_row.label(text=t("Validation.no_non_standard_issues"))
# 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:
if hierarchy_messages:
for message in hierarchy_messages:
sub_row = validation_box.row()
sub_row.alert = True
sub_row.label(text=message)
else:
sub_row = validation_box.row()
sub_row.label(text=t("Validation.no_hierarchy_issues"))
# Scale Issues section
validation_box = info_box.box()
row = validation_box.row()
row.alert = True
row.prop(props, "show_scale_issues", text=t("Validation.section.scale_issues"),
icon='TRIA_DOWN' if props.show_scale_issues else 'TRIA_RIGHT', emboss=False)
if props.show_scale_issues:
if scale_messages:
for scale_msg in scale_messages:
sub_row = validation_box.row()
sub_row.alert = True
sub_row.label(text=scale_msg)
else:
sub_row = validation_box.row()
sub_row.label(text=t("Validation.no_scale_issues"))
pose_box = layout.box()
col = pose_box.column(align=True)
col.label(text=t("Validation.tpose.label"), icon='ARMATURE_DATA')
col.separator(factor=0.5)
col.operator("avatar_toolkit.validate_tpose", icon='CHECKMARK')
if props.show_tpose_validation:
validation_box = col.box()
if props.tpose_validation_result:
validation_box.label(text=t("Validation.tpose.valid"), icon='CHECKMARK')
else:
row = validation_box.row()
row.alert = True
row.label(text=t("Validation.tpose.warning"), icon='ERROR')
for msg in props.tpose_validation_messages:
row = validation_box.row()
row.alert = True
row.label(text=msg.name)
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 = 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')
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
validation_mode = context.scene.avatar_toolkit.validation_mode
if validation_mode == 'BASIC':
warning_row = info_box.box()
warning_row.alert = True
warning_row.label(text=t("QuickAccess.validation_basic_warning"), icon='INFO')
warning_row.label(text=t("QuickAccess.validation_basic_details"))
elif validation_mode == 'NONE':
warning_row = info_box.box()
warning_row.alert = True
warning_row.label(text=t("QuickAccess.validation_none_warning"), icon='ERROR')
warning_row.label(text=t("QuickAccess.validation_none_details"))
# Pose Mode Controls
pose_box: UILayout = layout.box()
col = pose_box.column(align=True)
col.label(text=t("QuickAccess.pose_controls"), icon='ARMATURE_DATA')
col.separator(factor=0.5)
if context.mode == "POSE":
col.operator(AvatarToolkit_OT_StopPoseMode.bl_idname, icon='POSE_HLT')
col.separator(factor=0.5)
col.operator(AvatarToolkit_OT_ApplyPoseAsRest.bl_idname, icon='MOD_ARMATURE')
col.operator(AvatarToolkit_OT_ApplyPoseAsShapekey.bl_idname, icon='MOD_ARMATURE')
else:
col.operator(AvatarToolkit_OT_StartPoseMode.bl_idname, icon='POSE_HLT')
# Import/Export Box
import_box: UILayout = layout.box()
col = import_box.column(align=True)
col.label(text=t("QuickAccess.import_export"), icon='IMPORT')
col.separator(factor=0.5)
# Import/Export Buttons
button_row: UILayout = col.row(align=True)
button_row.scale_y = 1.5
button_row.operator("avatar_toolkit.import", text=t("QuickAccess.import"), icon='IMPORT')
button_row.operator("avatar_toolkit.export", text=t("QuickAccess.export"), icon='EXPORT')