From 3545951faebc7d247bb863a3ea25bed5bb3c7257 Mon Sep 17 00:00:00 2001 From: Yusarina Date: Sun, 16 Nov 2025 01:47:21 +0000 Subject: [PATCH] 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 --- blender_manifest.toml | 2 +- core/addon_preferences.py | 2 +- core/armature_validation.py | 68 ++++++- core/properties.py | 13 +- core/updater.py | 2 +- resources/translations/en_US.json | 12 +- resources/translations/ja_JP.json | 12 +- resources/translations/ko_KR.json | 12 +- ui/quick_access_panel.py | 327 ++++++++++++++---------------- 9 files changed, 259 insertions(+), 191 deletions(-) diff --git a/blender_manifest.toml b/blender_manifest.toml index 9578ecd..1fcf32e 100644 --- a/blender_manifest.toml +++ b/blender_manifest.toml @@ -3,7 +3,7 @@ schema_version = "1.0.0" id = "avatar_toolkit" -version = "0.5.0" +version = "0.6.0" name = "Avatar Toolkit" tagline = "A modern tool for importing and optimizing models for VRChat, Resonite, and other similar games." maintainer = "Team NekoNeo" diff --git a/core/addon_preferences.py b/core/addon_preferences.py index 31f580d..2234447 100644 --- a/core/addon_preferences.py +++ b/core/addon_preferences.py @@ -63,6 +63,6 @@ def get_addon_preferences(context): # Initialize preferences if the file doesn't exist if not os.path.exists(PREFERENCES_FILE): save_preference("language", 0) # Set default language to 0 (auto) - save_preference("validation_mode", "STRICT") # Set default validation mode + save_preference("validation_mode", "NONE") # Set default validation mode to NONE (off by default) save_preference("enable_logging", False) # Set default logging mode save_preference("highlight_problem_bones", True) # Set default bone highlighting diff --git a/core/armature_validation.py b/core/armature_validation.py index 570c098..a7a82a7 100644 --- a/core/armature_validation.py +++ b/core/armature_validation.py @@ -15,6 +15,26 @@ from ..core.dictionaries import ( ) from ..core.logging_setup import logger +def is_pmx_model(armature: Object) -> bool: + """ + Check if the armature is a PMX/MMD model. + PMX models have an mmd_type attribute set to 'ROOT' on the root object. + """ + if not armature: + return False + + # Check if armature itself has mmd_type set to ROOT + if hasattr(armature, 'mmd_type') and armature.mmd_type == 'ROOT': + return True + + # Check if parent has mmd_type set to ROOT (parent container model) + if hasattr(armature, 'parent') and armature.parent: + parent = armature.parent + if hasattr(parent, 'mmd_type') and parent.mmd_type == 'ROOT': + return True + + return False + def validate_armature(armature: Object, detailed_messages: bool = False, override_mode: Optional[str] = None) -> Union[Tuple[bool, List[str], bool], Tuple[bool, List[str], bool, List[str], List[str], List[str]]]: """ Validates armature and returns validation results @@ -27,9 +47,8 @@ def validate_armature(armature: Object, detailed_messages: bool = False, overrid scale_messages: List[str] = [] # Check if this is a PMX model - is_pmx_model = False - if armature and hasattr(armature, 'mmd_type') or (hasattr(armature, 'parent') and armature.parent and hasattr(armature.parent, 'mmd_type')): - is_pmx_model = True + pmx_model = is_pmx_model(armature) + if pmx_model: logger.debug("Detected PMX model, using specialized validation") if validation_mode == 'NONE': @@ -157,7 +176,7 @@ def validate_armature(armature: Object, detailed_messages: bool = False, overrid non_standard_messages.append(t("Armature.validation.standardize_note.line3")) # Special handling for PMX models - if is_pmx_model: + if pmx_model: logger.info("PMX model detected, applying specialized validation") # For PMX models, we'll be more lenient with validation # and provide specific guidance for these models @@ -783,3 +802,44 @@ class AvatarToolkit_OT_ClearBoneHighlighting(Operator): logger.info("Bone highlighting cleared") self.report({'INFO'}, t("Validation.highlighting_cleared")) return {'FINISHED'} + +class AvatarToolkit_OT_ValidateArmatureManual(Operator): + """Manually validate armature and show results""" + bl_idname = "avatar_toolkit.validate_armature_manual" + bl_label = t("Validation.validate_now", "Validate Armature Now") + bl_description = t("Validation.validate_now_desc", "Run armature validation and display detailed results") + + @classmethod + def poll(cls, context): + return get_active_armature(context) is not None + + def execute(self, context): + armature = get_active_armature(context) + if not armature: + logger.warning("No active armature found for validation") + self.report({'ERROR'}, t("Validation.no_armature")) + return {'CANCELLED'} + + logger.info(f"Running manual validation for armature: {armature.name}") + + # Clear the validation cache to force a refresh + from ..ui.quick_access_panel import clear_armature_caches + clear_armature_caches() + + # Toggle the show_validation_results flag to display results + props = context.scene.avatar_toolkit + props.show_validation_results = True + + # Run validation + is_valid, messages, is_acceptable = validate_armature(armature, detailed_messages=False) + + if is_valid: + if is_acceptable: + self.report({'INFO'}, t("Armature.validation.acceptable_standard.success")) + else: + self.report({'INFO'}, t("QuickAccess.valid_armature")) + else: + self.report({'WARNING'}, t("Validation.status.failed")) + + logger.info("Manual validation complete") + return {'FINISHED'} diff --git a/core/properties.py b/core/properties.py index 4e6ea22..4db9744 100644 --- a/core/properties.py +++ b/core/properties.py @@ -34,6 +34,11 @@ def update_validation_mode(self: PropertyGroup, context: Context) -> None: """Updates validation mode and saves preference""" logger.info(f"Updating validation mode to: {self.validation_mode}") save_preference("validation_mode", self.validation_mode) + + # Hide validation results if mode is set to NONE + if self.validation_mode == 'NONE': + self.show_validation_results = False + logger.debug("Validation mode set to NONE, hiding validation results") def update_logging_state(self: PropertyGroup, context: Context) -> None: @@ -153,6 +158,12 @@ class AvatarToolkitSceneProperties(PropertyGroup): default=False ) + show_validation_results: BoolProperty( + name="Show Validation Results", + default=False, + description="Show the validation results section" + ) + material_search_filter: StringProperty( name=t("TextureAtlas.search_materials"), description=t("TextureAtlas.search_materials_desc"), @@ -283,7 +294,7 @@ class AvatarToolkitSceneProperties(PropertyGroup): ('BASIC', t("Settings.validation_mode.basic"), t("Settings.validation_mode.basic_desc")), ('NONE', t("Settings.validation_mode.none"), t("Settings.validation_mode.none_desc")) ], - default=get_preference("validation_mode", "STRICT"), + default=get_preference("validation_mode", "NONE"), update=update_validation_mode ) diff --git a/core/updater.py b/core/updater.py index caf2c43..eab1575 100644 --- a/core/updater.py +++ b/core/updater.py @@ -20,7 +20,7 @@ GITHUB_REPO = "teamneoneko/Avatar-Toolkit" # Define which version series this installation can update to # For example: ["0.1"] means only look for 0.1.x updates # ["0.2", "0.3"] would look for both 0.2.x and 0.3.x -ALLOWED_ = ["0.5"] +ALLOWED_ = ["0.6"] is_checking_for_update: bool = False update_needed: bool = False diff --git a/resources/translations/en_US.json b/resources/translations/en_US.json index 1fdef90..a6d3ed2 100644 --- a/resources/translations/en_US.json +++ b/resources/translations/en_US.json @@ -1,7 +1,7 @@ { "authors": ["Avatar Toolkit Team"], "messages": { - "AvatarToolkit.label": "Avatar Toolkit (Alpha 0.5.0)", + "AvatarToolkit.label": "Avatar Toolkit (Alpha 0.6.0)", "AvatarToolkit.desc1": "Avatar Toolkit is in Early Access there", "AvatarToolkit.desc2": "will be issues, if you find any issues,", "AvatarToolkit.desc3": "please report it on our Github.", @@ -117,6 +117,15 @@ "Validation.clear_bone_highlighting": "Clear Bone Highlighting", "Validation.clear_bone_highlighting_desc": "Remove bone highlighting and reset bone colors to default", "Validation.highlighting_cleared": "Bone highlighting cleared successfully", + "Validation.label": "Armature Validation", + "Validation.validate_now": "Validate Armature Now", + "Validation.validate_now_desc": "Run armature validation and display detailed results", + "Validation.results": "Validation Results", + "Validation.tpose.validate_now": "Validate T-Pose Now", + + "Armature.validation.acceptable_standard.success": "Armature meets acceptable standards", + "Armature.validation.acceptable_standard.note": "This is a valid armature format that is compatible with most avatar systems", + "Armature.validation.acceptable_standard.option": "You can standardize the armature if desired", "Mesh.validation.no_data": "No mesh data", "Mesh.validation.no_vertex_groups": "No vertex groups found", @@ -587,7 +596,6 @@ "VRM.validation.hierarchy_issues": "Conversion completed but hierarchy validation found issues:", "VRM.validation.armature_passed": "Armature passes standard validation", "VRM.validation.failed": "Conversion completed but validation failed: {error}", - "VRM.remove_colliders": "Remove Colliders", "VRM.remove_colliders_desc": "Remove VRM collider bones during conversion", "VRM.remove_root": "Remove Root Bone", "VRM.remove_root_desc": "Remove unnecessary VRM root bone and make Hips the root bone", diff --git a/resources/translations/ja_JP.json b/resources/translations/ja_JP.json index 9761092..6e42fc4 100644 --- a/resources/translations/ja_JP.json +++ b/resources/translations/ja_JP.json @@ -1,7 +1,7 @@ { "authors": ["Avatar Toolkit Team"], "messages": { - "AvatarToolkit.label": "アバターツールキット (アルファ 0.5.0)", + "AvatarToolkit.label": "アバターツールキット (アルファ 0.6.0)", "AvatarToolkit.desc1": "アバターツールキットは早期アクセス中であり、", "AvatarToolkit.desc2": "問題が発生する可能性があります。問題を見つけた場合は、", "AvatarToolkit.desc3": "GitHubで報告してください。", @@ -117,6 +117,15 @@ "Validation.clear_bone_highlighting": "ボーンの強調表示をクリア", "Validation.clear_bone_highlighting_desc": "ボーンの強調表示を削除し、ボーンの色をデフォルトにリセット", "Validation.highlighting_cleared": "ボーンの強調表示が正常にクリアされました", + "Validation.label": "アーマチュア検証", + "Validation.validate_now": "アーマチュアを検証する", + "Validation.validate_now_desc": "アーマチュア検証を実行し、詳細な結果を表示", + "Validation.results": "検証結果", + "Validation.tpose.validate_now": "T-ポーズを検証する", + + "Armature.validation.acceptable_standard.success": "アーマチュアが許容可能な標準を満たしています", + "Armature.validation.acceptable_standard.note": "これは、ほとんどのアバターシステムと互換性のある有効なアーマチュア形式です", + "Armature.validation.acceptable_standard.option": "必要に応じてアーマチュアを標準化できます", "Mesh.validation.no_data": "メッシュデータがありません", "Mesh.validation.no_vertex_groups": "頂点グループが見つかりません", @@ -549,7 +558,6 @@ "VRM.armature_name": "アーマチュア: {name}", "VRM.armature_detected": "VRMアーマチュアが検出されました", "VRM.no_vrm_bones_detected": "VRMボーンが検出されませんでした", - "VRM.remove_colliders": "コライダーを削除", "VRM.remove_root_bone": "ルートボーンを削除", "VRM.convert_to_unity_format": "Unity形式に変換", "VRM.convert_to_unity.label": "VRMをUnityに変換", diff --git a/resources/translations/ko_KR.json b/resources/translations/ko_KR.json index 2e000c7..1d3cc89 100644 --- a/resources/translations/ko_KR.json +++ b/resources/translations/ko_KR.json @@ -1,7 +1,7 @@ { "authors": ["Avatar Toolkit Team"], "messages": { - "AvatarToolkit.label": "아바타 툴킷 (알파 0.5.0)", + "AvatarToolkit.label": "아바타 툴킷 (알파 0.6.0)", "AvatarToolkit.desc1": "아바타 툴킷은 초기 액세스 단계에 있으므로", "AvatarToolkit.desc2": "문제가 있을 수 있습니다. 문제를 발견하시면", "AvatarToolkit.desc3": "Github에 보고해 주세요.", @@ -117,6 +117,15 @@ "Validation.clear_bone_highlighting": "본 강조 표시 지우기", "Validation.clear_bone_highlighting_desc": "본 강조 표시를 제거하고 본 색상을 기본값으로 재설정", "Validation.highlighting_cleared": "본 강조 표시 지우기 성공", + "Validation.label": "아마추어 검증", + "Validation.validate_now": "지금 아마추어 검증", + "Validation.validate_now_desc": "아마추어 검증을 실행하고 자세한 결과 표시", + "Validation.results": "검증 결과", + "Validation.tpose.validate_now": "지금 T-포즈 검증", + + "Armature.validation.acceptable_standard.success": "아마추어가 허용 가능한 표준을 충족합니다", + "Armature.validation.acceptable_standard.note": "이것은 대부분의 아바타 시스템과 호환되는 유효한 아마추어 형식입니다", + "Armature.validation.acceptable_standard.option": "필요한 경우 아마추어를 표준화할 수 있습니다", "Mesh.validation.no_data": "메시 데이터 없음", "Mesh.validation.no_vertex_groups": "버텍스 그룹을 찾을 수 없음", @@ -549,7 +558,6 @@ "VRM.armature_name": "아마추어: {name}", "VRM.armature_detected": "VRM 아마추어 감지됨", "VRM.no_vrm_bones_detected": "VRM 본이 감지되지 않음", - "VRM.remove_colliders": "콜라이더 제거", "VRM.remove_root_bone": "루트 본 제거", "VRM.convert_to_unity_format": "Unity 형식으로 변환", "VRM.convert_to_unity.label": "VRM을 Unity로 변환", diff --git a/ui/quick_access_panel.py b/ui/quick_access_panel.py index 6e43e69..f41242a 100644 --- a/ui/quick_access_panel.py +++ b/ui/quick_access_panel.py @@ -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()