refactor: overhaul armature validation system to be opt-in by default
- Change default validation mode from STRICT to NONE (disabled) - Move validation from automatic panel draw to explicit "Validate Now" button - Hide validation results when mode is changed to NONE - Fix PMX/MMD model detection to check mmd_type value, not just attribute existence - Add new validation result collapsible sections - Improve UI presentation with better visual hierarchy - Add translation strings for new validation UI elements
This commit is contained in:
+150
-177
@@ -34,7 +34,7 @@ from ..functions.pose_mode import (
|
||||
AvatarToolkit_OT_ApplyPoseAsShapekey,
|
||||
AvatarToolkit_OT_ApplyPoseAsRest
|
||||
)
|
||||
from ..core.armature_validation import validate_armature, AvatarToolkit_OT_ValidateTPose
|
||||
from ..core.armature_validation import validate_armature, AvatarToolkit_OT_ValidateTPose, is_pmx_model
|
||||
from ..core.importers.importer import AvatarToolKit_OT_Import
|
||||
from ..core.resonite_utils import AvatarToolKit_OT_ExportResonite
|
||||
from ..functions.tools.standardize_armature import AvatarToolkit_OT_StandardizeArmature
|
||||
@@ -95,206 +95,179 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
|
||||
# Armature Selection
|
||||
col.prop(context.scene.avatar_toolkit, "active_armature", text="")
|
||||
|
||||
# Armature Validation (cached to improve performance)
|
||||
# Get active armature
|
||||
active_armature: Optional[Object] = get_active_armature(context)
|
||||
if active_armature:
|
||||
# Cache validation results to avoid expensive recalculations on every draw
|
||||
cache_key = f"validation_{active_armature.name}_{active_armature.data.name}_{len(active_armature.data.bones)}"
|
||||
# Validation Button Box - Always visible
|
||||
validation_box: UILayout = layout.box()
|
||||
col = validation_box.column(align=True)
|
||||
col.label(text=t("Validation.label", "Armature Validation"), icon='CHECKMARK')
|
||||
col.separator(factor=0.5)
|
||||
|
||||
if cache_key not in _validation_cache:
|
||||
_validation_cache[cache_key] = validate_armature(active_armature, detailed_messages=True)
|
||||
# Main validate button with prominent styling
|
||||
validate_row = col.row(align=True)
|
||||
validate_row.scale_y = 1.3
|
||||
validate_row.operator("avatar_toolkit.validate_armature_manual",
|
||||
text=t("Validation.validate_now", "Validate Armature Now"),
|
||||
icon='CHECKMARK')
|
||||
|
||||
is_valid, messages, is_acceptable, hierarchy_messages, scale_messages, non_standard_messages = _validation_cache[cache_key]
|
||||
# Validation mode selector
|
||||
col.prop(props, "validation_mode", text=t("Settings.validation_mode", "Mode"))
|
||||
|
||||
# Check if this is a PMX model
|
||||
is_pmx_model = False
|
||||
if hasattr(active_armature, 'mmd_type') or (hasattr(active_armature, 'parent') and active_armature.parent and hasattr(active_armature.parent, 'mmd_type')):
|
||||
is_pmx_model = True
|
||||
|
||||
info_box = col.box()
|
||||
|
||||
# If it's a PMX model, display a prominent notice
|
||||
if is_pmx_model:
|
||||
pmx_box = info_box.box()
|
||||
pmx_box.label(text=t("Armature.validation.pmx_model_detected"), icon='INFO')
|
||||
# Show validation results if flag is set
|
||||
if props.show_validation_results:
|
||||
# Cache validation results
|
||||
cache_key = f"validation_{active_armature.name}_{active_armature.data.name}_{len(active_armature.data.bones)}"
|
||||
|
||||
validation_mode = context.scene.avatar_toolkit.validation_mode
|
||||
if validation_mode == 'STRICT':
|
||||
pmx_box.label(text=t("Armature.validation.pmx_model_strict"))
|
||||
pmx_box.label(text=t("Armature.validation.pmx_model_standardize"))
|
||||
else:
|
||||
pmx_box.label(text=t("Armature.validation.pmx_model_basic"))
|
||||
|
||||
if not is_valid:
|
||||
# Display non-standard bones and hierarchy issues
|
||||
if messages and len(messages) > 0:
|
||||
# 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 and len(messages) > 0:
|
||||
for line in messages[0].split('\n'):
|
||||
validation_box.label(text=line)
|
||||
if cache_key not in _validation_cache:
|
||||
_validation_cache[cache_key] = validate_armature(active_armature, detailed_messages=True)
|
||||
|
||||
is_valid, messages, is_acceptable, hierarchy_messages, scale_messages, non_standard_messages = _validation_cache[cache_key]
|
||||
|
||||
# Check if this is a PMX model
|
||||
pmx_detected = is_pmx_model(active_armature)
|
||||
|
||||
results_box = validation_box.box()
|
||||
row = results_box.row()
|
||||
row.prop(props, "show_validation_results", text=t("Validation.results", "Validation Results"),
|
||||
icon='TRIA_DOWN' if props.show_validation_results else 'TRIA_RIGHT', emboss=False)
|
||||
|
||||
# PMX Model Notice
|
||||
if pmx_detected:
|
||||
pmx_box = results_box.box()
|
||||
pmx_box.label(text=t("Armature.validation.pmx_model_detected"), icon='INFO')
|
||||
|
||||
# Main validation status
|
||||
validation_box = info_box.box()
|
||||
row = validation_box.row()
|
||||
row.alert = True
|
||||
row.label(text=t("Validation.status.failed"))
|
||||
validation_mode = context.scene.avatar_toolkit.validation_mode
|
||||
if validation_mode == 'STRICT':
|
||||
pmx_box.label(text=t("Armature.validation.pmx_model_strict"))
|
||||
pmx_box.label(text=t("Armature.validation.pmx_model_standardize"))
|
||||
else:
|
||||
pmx_box.label(text=t("Armature.validation.pmx_model_basic"))
|
||||
|
||||
# Validation Results
|
||||
if not is_valid:
|
||||
# Display found bones
|
||||
if messages and len(messages) > 0:
|
||||
bones_section = results_box.box()
|
||||
row = bones_section.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'):
|
||||
bones_section.label(text=line)
|
||||
|
||||
# Detailed validation message
|
||||
validation_box = info_box.box()
|
||||
row = validation_box.row()
|
||||
# Status message
|
||||
status_box = results_box.box()
|
||||
row = status_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"))
|
||||
row.label(text=t("Validation.status.failed"), icon='ERROR')
|
||||
|
||||
# Error explanation
|
||||
error_box = results_box.box()
|
||||
error_box.alert = True
|
||||
error_box.label(text=t("Validation.message.failed.line1"))
|
||||
error_box.label(text=t("Validation.message.failed.line2"))
|
||||
error_box.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 and len(non_standard_messages) > 0:
|
||||
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:
|
||||
# For PMX models, if no non-standard messages but it's a PMX model,
|
||||
# we should still indicate there might be non-standard bones
|
||||
if is_pmx_model:
|
||||
sub_row = validation_box.row()
|
||||
sub_row.alert = True
|
||||
sub_row.label(text=t("Armature.validation.pmx_model_basic"))
|
||||
|
||||
sub_row = validation_box.row()
|
||||
sub_row.alert = True
|
||||
sub_row.label(text=t("Armature.validation.pmx_model_strict"))
|
||||
|
||||
sub_row = validation_box.row()
|
||||
sub_row.alert = True
|
||||
sub_row.label(text=t("Armature.validation.pmx_model_standardize"))
|
||||
|
||||
if non_standard_messages or pmx_detected:
|
||||
ns_section = results_box.box()
|
||||
row = ns_section.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 and len(non_standard_messages) > 0:
|
||||
for message in non_standard_messages:
|
||||
for line in message.split('\n'):
|
||||
sub_row = ns_section.row()
|
||||
sub_row.alert = True
|
||||
sub_row.label(text=line)
|
||||
elif pmx_detected:
|
||||
ns_section.alert = True
|
||||
ns_section.label(text=t("Armature.validation.pmx_model_basic"))
|
||||
ns_section.label(text=t("Armature.validation.pmx_model_strict"))
|
||||
ns_section.label(text=t("Armature.validation.pmx_model_standardize"))
|
||||
else:
|
||||
sub_row = validation_box.row()
|
||||
sub_row.label(text=t("Validation.no_non_standard_issues"))
|
||||
|
||||
ns_section.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:
|
||||
if hierarchy_messages:
|
||||
hier_section = results_box.box()
|
||||
row = hier_section.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 hierarchy_messages:
|
||||
sub_row = validation_box.row()
|
||||
sub_row = hier_section.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:
|
||||
if scale_messages:
|
||||
scale_section = results_box.box()
|
||||
row = scale_section.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:
|
||||
for scale_msg in scale_messages:
|
||||
sub_row = validation_box.row()
|
||||
sub_row = scale_section.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(AvatarToolkit_OT_ValidateTPose.bl_idname, 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
|
||||
if messages and len(messages) > 0:
|
||||
info_box.label(text=messages[0], icon='INFO')
|
||||
if len(messages) > 1:
|
||||
info_box.label(text=messages[1])
|
||||
if len(messages) > 2:
|
||||
info_box.label(text=messages[2])
|
||||
else:
|
||||
info_box.label(text=t("Validation.no_messages"), icon='INFO')
|
||||
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')
|
||||
|
||||
# Cache armature stats to avoid expensive recalculations
|
||||
stats_cache_key = f"stats_{active_armature.name}_{active_armature.data.name}_{len(active_armature.data.bones)}"
|
||||
|
||||
if stats_cache_key not in _stats_cache:
|
||||
_stats_cache[stats_cache_key] = get_armature_stats(active_armature)
|
||||
|
||||
stats = _stats_cache[stats_cache_key]
|
||||
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
|
||||
if messages and len(messages) > 0:
|
||||
info_box.label(text=messages[0], icon='INFO')
|
||||
elif is_valid and not is_acceptable:
|
||||
# Valid armature - show stats
|
||||
stats_cache_key = f"stats_{active_armature.name}_{active_armature.data.name}_{len(active_armature.data.bones)}"
|
||||
|
||||
# Only try to access additional messages if they exist
|
||||
if len(messages) > 1:
|
||||
info_box.label(text=messages[1])
|
||||
if len(messages) > 2:
|
||||
info_box.label(text=messages[2])
|
||||
else:
|
||||
info_box.label(text=t("Validation.no_messages"), icon='INFO')
|
||||
if stats_cache_key not in _stats_cache:
|
||||
_stats_cache[stats_cache_key] = get_armature_stats(active_armature)
|
||||
|
||||
stats = _stats_cache[stats_cache_key]
|
||||
|
||||
status_box = results_box.box()
|
||||
row = status_box.row()
|
||||
row.label(text=t("QuickAccess.valid_armature"), icon='CHECKMARK')
|
||||
split = row.split(factor=0.4)
|
||||
split.label(text=t("QuickAccess.bones_count", count=stats['bone_count']))
|
||||
|
||||
if stats['has_pose']:
|
||||
results_box.label(text=t("QuickAccess.pose_bones_available"), icon='POSE_HLT')
|
||||
|
||||
# Add standardize button
|
||||
standardize_box = info_box.box()
|
||||
standardize_box.operator(AvatarToolkit_OT_StandardizeArmature.bl_idname,
|
||||
text=t("QuickAccess.standardize_armature"),
|
||||
icon='MODIFIER')
|
||||
elif is_valid and is_acceptable:
|
||||
# Acceptable standard
|
||||
status_box = results_box.box()
|
||||
status_box.label(text=t("Armature.validation.acceptable_standard.success"), icon='INFO')
|
||||
status_box.label(text=t("Armature.validation.acceptable_standard.note"))
|
||||
status_box.label(text=t("Armature.validation.acceptable_standard.option"))
|
||||
|
||||
# Add standardize button
|
||||
standardize_box = results_box.box()
|
||||
standardize_box.operator(AvatarToolkit_OT_StandardizeArmature.bl_idname,
|
||||
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"))
|
||||
# T-Pose Validation Box
|
||||
tpose_box: UILayout = layout.box()
|
||||
col = tpose_box.column(align=True)
|
||||
col.label(text=t("Validation.tpose.label"), icon='ARMATURE_DATA')
|
||||
col.separator(factor=0.5)
|
||||
col.operator(AvatarToolkit_OT_ValidateTPose.bl_idname, text=t("Validation.tpose.validate_now"), 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)
|
||||
|
||||
# Pose Mode Controls
|
||||
pose_box: UILayout = layout.box()
|
||||
|
||||
Reference in New Issue
Block a user