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:
@@ -3,7 +3,7 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "avatar_toolkit"
|
id = "avatar_toolkit"
|
||||||
version = "0.5.0"
|
version = "0.6.0"
|
||||||
name = "Avatar Toolkit"
|
name = "Avatar Toolkit"
|
||||||
tagline = "A modern tool for importing and optimizing models for VRChat, Resonite, and other similar games."
|
tagline = "A modern tool for importing and optimizing models for VRChat, Resonite, and other similar games."
|
||||||
maintainer = "Team NekoNeo"
|
maintainer = "Team NekoNeo"
|
||||||
|
|||||||
@@ -63,6 +63,6 @@ def get_addon_preferences(context):
|
|||||||
# Initialize preferences if the file doesn't exist
|
# Initialize preferences if the file doesn't exist
|
||||||
if not os.path.exists(PREFERENCES_FILE):
|
if not os.path.exists(PREFERENCES_FILE):
|
||||||
save_preference("language", 0) # Set default language to 0 (auto)
|
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("enable_logging", False) # Set default logging mode
|
||||||
save_preference("highlight_problem_bones", True) # Set default bone highlighting
|
save_preference("highlight_problem_bones", True) # Set default bone highlighting
|
||||||
|
|||||||
@@ -15,6 +15,26 @@ from ..core.dictionaries import (
|
|||||||
)
|
)
|
||||||
from ..core.logging_setup import logger
|
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]]]:
|
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
|
Validates armature and returns validation results
|
||||||
@@ -27,9 +47,8 @@ def validate_armature(armature: Object, detailed_messages: bool = False, overrid
|
|||||||
scale_messages: List[str] = []
|
scale_messages: List[str] = []
|
||||||
|
|
||||||
# Check if this is a PMX model
|
# Check if this is a PMX model
|
||||||
is_pmx_model = False
|
pmx_model = is_pmx_model(armature)
|
||||||
if armature and hasattr(armature, 'mmd_type') or (hasattr(armature, 'parent') and armature.parent and hasattr(armature.parent, 'mmd_type')):
|
if pmx_model:
|
||||||
is_pmx_model = True
|
|
||||||
logger.debug("Detected PMX model, using specialized validation")
|
logger.debug("Detected PMX model, using specialized validation")
|
||||||
|
|
||||||
if validation_mode == 'NONE':
|
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"))
|
non_standard_messages.append(t("Armature.validation.standardize_note.line3"))
|
||||||
|
|
||||||
# Special handling for PMX models
|
# Special handling for PMX models
|
||||||
if is_pmx_model:
|
if pmx_model:
|
||||||
logger.info("PMX model detected, applying specialized validation")
|
logger.info("PMX model detected, applying specialized validation")
|
||||||
# For PMX models, we'll be more lenient with validation
|
# For PMX models, we'll be more lenient with validation
|
||||||
# and provide specific guidance for these models
|
# and provide specific guidance for these models
|
||||||
@@ -783,3 +802,44 @@ class AvatarToolkit_OT_ClearBoneHighlighting(Operator):
|
|||||||
logger.info("Bone highlighting cleared")
|
logger.info("Bone highlighting cleared")
|
||||||
self.report({'INFO'}, t("Validation.highlighting_cleared"))
|
self.report({'INFO'}, t("Validation.highlighting_cleared"))
|
||||||
return {'FINISHED'}
|
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'}
|
||||||
|
|||||||
+12
-1
@@ -34,6 +34,11 @@ def update_validation_mode(self: PropertyGroup, context: Context) -> None:
|
|||||||
"""Updates validation mode and saves preference"""
|
"""Updates validation mode and saves preference"""
|
||||||
logger.info(f"Updating validation mode to: {self.validation_mode}")
|
logger.info(f"Updating validation mode to: {self.validation_mode}")
|
||||||
save_preference("validation_mode", 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:
|
def update_logging_state(self: PropertyGroup, context: Context) -> None:
|
||||||
@@ -153,6 +158,12 @@ class AvatarToolkitSceneProperties(PropertyGroup):
|
|||||||
default=False
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
show_validation_results: BoolProperty(
|
||||||
|
name="Show Validation Results",
|
||||||
|
default=False,
|
||||||
|
description="Show the validation results section"
|
||||||
|
)
|
||||||
|
|
||||||
material_search_filter: StringProperty(
|
material_search_filter: StringProperty(
|
||||||
name=t("TextureAtlas.search_materials"),
|
name=t("TextureAtlas.search_materials"),
|
||||||
description=t("TextureAtlas.search_materials_desc"),
|
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")),
|
('BASIC', t("Settings.validation_mode.basic"), t("Settings.validation_mode.basic_desc")),
|
||||||
('NONE', t("Settings.validation_mode.none"), t("Settings.validation_mode.none_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
|
update=update_validation_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ GITHUB_REPO = "teamneoneko/Avatar-Toolkit"
|
|||||||
# Define which version series this installation can update to
|
# Define which version series this installation can update to
|
||||||
# For example: ["0.1"] means only look for 0.1.x updates
|
# 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
|
# ["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
|
is_checking_for_update: bool = False
|
||||||
update_needed: bool = False
|
update_needed: bool = False
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"authors": ["Avatar Toolkit Team"],
|
"authors": ["Avatar Toolkit Team"],
|
||||||
"messages": {
|
"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.desc1": "Avatar Toolkit is in Early Access there",
|
||||||
"AvatarToolkit.desc2": "will be issues, if you find any issues,",
|
"AvatarToolkit.desc2": "will be issues, if you find any issues,",
|
||||||
"AvatarToolkit.desc3": "please report it on our Github.",
|
"AvatarToolkit.desc3": "please report it on our Github.",
|
||||||
@@ -117,6 +117,15 @@
|
|||||||
"Validation.clear_bone_highlighting": "Clear Bone Highlighting",
|
"Validation.clear_bone_highlighting": "Clear Bone Highlighting",
|
||||||
"Validation.clear_bone_highlighting_desc": "Remove bone highlighting and reset bone colors to default",
|
"Validation.clear_bone_highlighting_desc": "Remove bone highlighting and reset bone colors to default",
|
||||||
"Validation.highlighting_cleared": "Bone highlighting cleared successfully",
|
"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_data": "No mesh data",
|
||||||
"Mesh.validation.no_vertex_groups": "No vertex groups found",
|
"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.hierarchy_issues": "Conversion completed but hierarchy validation found issues:",
|
||||||
"VRM.validation.armature_passed": "Armature passes standard validation",
|
"VRM.validation.armature_passed": "Armature passes standard validation",
|
||||||
"VRM.validation.failed": "Conversion completed but validation failed: {error}",
|
"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_colliders_desc": "Remove VRM collider bones during conversion",
|
||||||
"VRM.remove_root": "Remove Root Bone",
|
"VRM.remove_root": "Remove Root Bone",
|
||||||
"VRM.remove_root_desc": "Remove unnecessary VRM root bone and make Hips the root bone",
|
"VRM.remove_root_desc": "Remove unnecessary VRM root bone and make Hips the root bone",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"authors": ["Avatar Toolkit Team"],
|
"authors": ["Avatar Toolkit Team"],
|
||||||
"messages": {
|
"messages": {
|
||||||
"AvatarToolkit.label": "アバターツールキット (アルファ 0.5.0)",
|
"AvatarToolkit.label": "アバターツールキット (アルファ 0.6.0)",
|
||||||
"AvatarToolkit.desc1": "アバターツールキットは早期アクセス中であり、",
|
"AvatarToolkit.desc1": "アバターツールキットは早期アクセス中であり、",
|
||||||
"AvatarToolkit.desc2": "問題が発生する可能性があります。問題を見つけた場合は、",
|
"AvatarToolkit.desc2": "問題が発生する可能性があります。問題を見つけた場合は、",
|
||||||
"AvatarToolkit.desc3": "GitHubで報告してください。",
|
"AvatarToolkit.desc3": "GitHubで報告してください。",
|
||||||
@@ -117,6 +117,15 @@
|
|||||||
"Validation.clear_bone_highlighting": "ボーンの強調表示をクリア",
|
"Validation.clear_bone_highlighting": "ボーンの強調表示をクリア",
|
||||||
"Validation.clear_bone_highlighting_desc": "ボーンの強調表示を削除し、ボーンの色をデフォルトにリセット",
|
"Validation.clear_bone_highlighting_desc": "ボーンの強調表示を削除し、ボーンの色をデフォルトにリセット",
|
||||||
"Validation.highlighting_cleared": "ボーンの強調表示が正常にクリアされました",
|
"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_data": "メッシュデータがありません",
|
||||||
"Mesh.validation.no_vertex_groups": "頂点グループが見つかりません",
|
"Mesh.validation.no_vertex_groups": "頂点グループが見つかりません",
|
||||||
@@ -549,7 +558,6 @@
|
|||||||
"VRM.armature_name": "アーマチュア: {name}",
|
"VRM.armature_name": "アーマチュア: {name}",
|
||||||
"VRM.armature_detected": "VRMアーマチュアが検出されました",
|
"VRM.armature_detected": "VRMアーマチュアが検出されました",
|
||||||
"VRM.no_vrm_bones_detected": "VRMボーンが検出されませんでした",
|
"VRM.no_vrm_bones_detected": "VRMボーンが検出されませんでした",
|
||||||
"VRM.remove_colliders": "コライダーを削除",
|
|
||||||
"VRM.remove_root_bone": "ルートボーンを削除",
|
"VRM.remove_root_bone": "ルートボーンを削除",
|
||||||
"VRM.convert_to_unity_format": "Unity形式に変換",
|
"VRM.convert_to_unity_format": "Unity形式に変換",
|
||||||
"VRM.convert_to_unity.label": "VRMをUnityに変換",
|
"VRM.convert_to_unity.label": "VRMをUnityに変換",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"authors": ["Avatar Toolkit Team"],
|
"authors": ["Avatar Toolkit Team"],
|
||||||
"messages": {
|
"messages": {
|
||||||
"AvatarToolkit.label": "아바타 툴킷 (알파 0.5.0)",
|
"AvatarToolkit.label": "아바타 툴킷 (알파 0.6.0)",
|
||||||
"AvatarToolkit.desc1": "아바타 툴킷은 초기 액세스 단계에 있으므로",
|
"AvatarToolkit.desc1": "아바타 툴킷은 초기 액세스 단계에 있으므로",
|
||||||
"AvatarToolkit.desc2": "문제가 있을 수 있습니다. 문제를 발견하시면",
|
"AvatarToolkit.desc2": "문제가 있을 수 있습니다. 문제를 발견하시면",
|
||||||
"AvatarToolkit.desc3": "Github에 보고해 주세요.",
|
"AvatarToolkit.desc3": "Github에 보고해 주세요.",
|
||||||
@@ -117,6 +117,15 @@
|
|||||||
"Validation.clear_bone_highlighting": "본 강조 표시 지우기",
|
"Validation.clear_bone_highlighting": "본 강조 표시 지우기",
|
||||||
"Validation.clear_bone_highlighting_desc": "본 강조 표시를 제거하고 본 색상을 기본값으로 재설정",
|
"Validation.clear_bone_highlighting_desc": "본 강조 표시를 제거하고 본 색상을 기본값으로 재설정",
|
||||||
"Validation.highlighting_cleared": "본 강조 표시 지우기 성공",
|
"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_data": "메시 데이터 없음",
|
||||||
"Mesh.validation.no_vertex_groups": "버텍스 그룹을 찾을 수 없음",
|
"Mesh.validation.no_vertex_groups": "버텍스 그룹을 찾을 수 없음",
|
||||||
@@ -549,7 +558,6 @@
|
|||||||
"VRM.armature_name": "아마추어: {name}",
|
"VRM.armature_name": "아마추어: {name}",
|
||||||
"VRM.armature_detected": "VRM 아마추어 감지됨",
|
"VRM.armature_detected": "VRM 아마추어 감지됨",
|
||||||
"VRM.no_vrm_bones_detected": "VRM 본이 감지되지 않음",
|
"VRM.no_vrm_bones_detected": "VRM 본이 감지되지 않음",
|
||||||
"VRM.remove_colliders": "콜라이더 제거",
|
|
||||||
"VRM.remove_root_bone": "루트 본 제거",
|
"VRM.remove_root_bone": "루트 본 제거",
|
||||||
"VRM.convert_to_unity_format": "Unity 형식으로 변환",
|
"VRM.convert_to_unity_format": "Unity 형식으로 변환",
|
||||||
"VRM.convert_to_unity.label": "VRM을 Unity로 변환",
|
"VRM.convert_to_unity.label": "VRM을 Unity로 변환",
|
||||||
|
|||||||
+150
-177
@@ -34,7 +34,7 @@ from ..functions.pose_mode import (
|
|||||||
AvatarToolkit_OT_ApplyPoseAsShapekey,
|
AvatarToolkit_OT_ApplyPoseAsShapekey,
|
||||||
AvatarToolkit_OT_ApplyPoseAsRest
|
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.importers.importer import AvatarToolKit_OT_Import
|
||||||
from ..core.resonite_utils import AvatarToolKit_OT_ExportResonite
|
from ..core.resonite_utils import AvatarToolKit_OT_ExportResonite
|
||||||
from ..functions.tools.standardize_armature import AvatarToolkit_OT_StandardizeArmature
|
from ..functions.tools.standardize_armature import AvatarToolkit_OT_StandardizeArmature
|
||||||
@@ -95,206 +95,179 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
|
|||||||
# Armature Selection
|
# Armature Selection
|
||||||
col.prop(context.scene.avatar_toolkit, "active_armature", text="")
|
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)
|
active_armature: Optional[Object] = get_active_armature(context)
|
||||||
if active_armature:
|
if active_armature:
|
||||||
# Cache validation results to avoid expensive recalculations on every draw
|
# Validation Button Box - Always visible
|
||||||
cache_key = f"validation_{active_armature.name}_{active_armature.data.name}_{len(active_armature.data.bones)}"
|
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:
|
# Main validate button with prominent styling
|
||||||
_validation_cache[cache_key] = validate_armature(active_armature, detailed_messages=True)
|
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
|
# Show validation results if flag is set
|
||||||
is_pmx_model = False
|
if props.show_validation_results:
|
||||||
if hasattr(active_armature, 'mmd_type') or (hasattr(active_armature, 'parent') and active_armature.parent and hasattr(active_armature.parent, 'mmd_type')):
|
# Cache validation results
|
||||||
is_pmx_model = True
|
cache_key = f"validation_{active_armature.name}_{active_armature.data.name}_{len(active_armature.data.bones)}"
|
||||||
|
|
||||||
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')
|
|
||||||
|
|
||||||
validation_mode = context.scene.avatar_toolkit.validation_mode
|
if cache_key not in _validation_cache:
|
||||||
if validation_mode == 'STRICT':
|
_validation_cache[cache_key] = validate_armature(active_armature, detailed_messages=True)
|
||||||
pmx_box.label(text=t("Armature.validation.pmx_model_strict"))
|
|
||||||
pmx_box.label(text=t("Armature.validation.pmx_model_standardize"))
|
is_valid, messages, is_acceptable, hierarchy_messages, scale_messages, non_standard_messages = _validation_cache[cache_key]
|
||||||
else:
|
|
||||||
pmx_box.label(text=t("Armature.validation.pmx_model_basic"))
|
# Check if this is a PMX model
|
||||||
|
pmx_detected = is_pmx_model(active_armature)
|
||||||
if not is_valid:
|
|
||||||
# Display non-standard bones and hierarchy issues
|
results_box = validation_box.box()
|
||||||
if messages and len(messages) > 0:
|
row = results_box.row()
|
||||||
# Found Bones section
|
row.prop(props, "show_validation_results", text=t("Validation.results", "Validation Results"),
|
||||||
validation_box = info_box.box()
|
icon='TRIA_DOWN' if props.show_validation_results else 'TRIA_RIGHT', emboss=False)
|
||||||
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)
|
# PMX Model Notice
|
||||||
if props.show_found_bones and len(messages) > 0:
|
if pmx_detected:
|
||||||
for line in messages[0].split('\n'):
|
pmx_box = results_box.box()
|
||||||
validation_box.label(text=line)
|
pmx_box.label(text=t("Armature.validation.pmx_model_detected"), icon='INFO')
|
||||||
|
|
||||||
# Main validation status
|
validation_mode = context.scene.avatar_toolkit.validation_mode
|
||||||
validation_box = info_box.box()
|
if validation_mode == 'STRICT':
|
||||||
row = validation_box.row()
|
pmx_box.label(text=t("Armature.validation.pmx_model_strict"))
|
||||||
row.alert = True
|
pmx_box.label(text=t("Armature.validation.pmx_model_standardize"))
|
||||||
row.label(text=t("Validation.status.failed"))
|
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
|
# Status message
|
||||||
validation_box = info_box.box()
|
status_box = results_box.box()
|
||||||
row = validation_box.row()
|
row = status_box.row()
|
||||||
row.alert = True
|
row.alert = True
|
||||||
row.label(text=t("Validation.message.failed.line1"))
|
row.label(text=t("Validation.status.failed"), icon='ERROR')
|
||||||
row = validation_box.row()
|
|
||||||
row.alert = True
|
# Error explanation
|
||||||
row.label(text=t("Validation.message.failed.line2"))
|
error_box = results_box.box()
|
||||||
row = validation_box.row()
|
error_box.alert = True
|
||||||
row.alert = True
|
error_box.label(text=t("Validation.message.failed.line1"))
|
||||||
row.label(text=t("Validation.message.failed.line3"))
|
error_box.label(text=t("Validation.message.failed.line2"))
|
||||||
|
error_box.label(text=t("Validation.message.failed.line3"))
|
||||||
|
|
||||||
# Non-Standard Bones section
|
# Non-Standard Bones section
|
||||||
validation_box = info_box.box()
|
if non_standard_messages or pmx_detected:
|
||||||
row = validation_box.row()
|
ns_section = results_box.box()
|
||||||
row.alert = True
|
row = ns_section.row()
|
||||||
row.prop(props, "show_non_standard", text=t("Validation.section.non_standard"),
|
row.alert = True
|
||||||
icon='TRIA_DOWN' if props.show_non_standard else 'TRIA_RIGHT', emboss=False)
|
row.prop(props, "show_non_standard", text=t("Validation.section.non_standard"),
|
||||||
if props.show_non_standard:
|
icon='TRIA_DOWN' if props.show_non_standard else 'TRIA_RIGHT', emboss=False)
|
||||||
if non_standard_messages and len(non_standard_messages) > 0:
|
if props.show_non_standard:
|
||||||
for message in non_standard_messages:
|
if non_standard_messages and len(non_standard_messages) > 0:
|
||||||
for line in message.split('\n'):
|
for message in non_standard_messages:
|
||||||
sub_row = validation_box.row()
|
for line in message.split('\n'):
|
||||||
sub_row.alert = True
|
sub_row = ns_section.row()
|
||||||
sub_row.label(text=line)
|
sub_row.alert = True
|
||||||
else:
|
sub_row.label(text=line)
|
||||||
# For PMX models, if no non-standard messages but it's a PMX model,
|
elif pmx_detected:
|
||||||
# we should still indicate there might be non-standard bones
|
ns_section.alert = True
|
||||||
if is_pmx_model:
|
ns_section.label(text=t("Armature.validation.pmx_model_basic"))
|
||||||
sub_row = validation_box.row()
|
ns_section.label(text=t("Armature.validation.pmx_model_strict"))
|
||||||
sub_row.alert = True
|
ns_section.label(text=t("Armature.validation.pmx_model_standardize"))
|
||||||
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"))
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
sub_row = validation_box.row()
|
ns_section.label(text=t("Validation.no_non_standard_issues"))
|
||||||
sub_row.label(text=t("Validation.no_non_standard_issues"))
|
|
||||||
|
|
||||||
# Hierarchy Issues section
|
# Hierarchy Issues section
|
||||||
validation_box = info_box.box()
|
if hierarchy_messages:
|
||||||
row = validation_box.row()
|
hier_section = results_box.box()
|
||||||
row.alert = True
|
row = hier_section.row()
|
||||||
row.prop(props, "show_hierarchy", text=t("Validation.section.hierarchy"),
|
row.alert = True
|
||||||
icon='TRIA_DOWN' if props.show_hierarchy else 'TRIA_RIGHT', emboss=False)
|
row.prop(props, "show_hierarchy", text=t("Validation.section.hierarchy"),
|
||||||
if props.show_hierarchy:
|
icon='TRIA_DOWN' if props.show_hierarchy else 'TRIA_RIGHT', emboss=False)
|
||||||
if hierarchy_messages:
|
if props.show_hierarchy:
|
||||||
for message in hierarchy_messages:
|
for message in hierarchy_messages:
|
||||||
sub_row = validation_box.row()
|
sub_row = hier_section.row()
|
||||||
sub_row.alert = True
|
sub_row.alert = True
|
||||||
sub_row.label(text=message)
|
sub_row.label(text=message)
|
||||||
else:
|
|
||||||
sub_row = validation_box.row()
|
|
||||||
sub_row.label(text=t("Validation.no_hierarchy_issues"))
|
|
||||||
|
|
||||||
# Scale Issues section
|
# Scale Issues section
|
||||||
validation_box = info_box.box()
|
if scale_messages:
|
||||||
row = validation_box.row()
|
scale_section = results_box.box()
|
||||||
row.alert = True
|
row = scale_section.row()
|
||||||
row.prop(props, "show_scale_issues", text=t("Validation.section.scale_issues"),
|
row.alert = True
|
||||||
icon='TRIA_DOWN' if props.show_scale_issues else 'TRIA_RIGHT', emboss=False)
|
row.prop(props, "show_scale_issues", text=t("Validation.section.scale_issues"),
|
||||||
if props.show_scale_issues:
|
icon='TRIA_DOWN' if props.show_scale_issues else 'TRIA_RIGHT', emboss=False)
|
||||||
if scale_messages:
|
if props.show_scale_issues:
|
||||||
for scale_msg in scale_messages:
|
for scale_msg in scale_messages:
|
||||||
sub_row = validation_box.row()
|
sub_row = scale_section.row()
|
||||||
sub_row.alert = True
|
sub_row.alert = True
|
||||||
sub_row.label(text=scale_msg)
|
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
|
elif is_valid and not is_acceptable:
|
||||||
stats_cache_key = f"stats_{active_armature.name}_{active_armature.data.name}_{len(active_armature.data.bones)}"
|
# Valid armature - show stats
|
||||||
|
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')
|
|
||||||
|
|
||||||
# Only try to access additional messages if they exist
|
if stats_cache_key not in _stats_cache:
|
||||||
if len(messages) > 1:
|
_stats_cache[stats_cache_key] = get_armature_stats(active_armature)
|
||||||
info_box.label(text=messages[1])
|
|
||||||
if len(messages) > 2:
|
stats = _stats_cache[stats_cache_key]
|
||||||
info_box.label(text=messages[2])
|
|
||||||
else:
|
status_box = results_box.box()
|
||||||
info_box.label(text=t("Validation.no_messages"), icon='INFO')
|
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
|
elif is_valid and is_acceptable:
|
||||||
standardize_box = info_box.box()
|
# Acceptable standard
|
||||||
standardize_box.operator(AvatarToolkit_OT_StandardizeArmature.bl_idname,
|
status_box = results_box.box()
|
||||||
text=t("QuickAccess.standardize_armature"),
|
status_box.label(text=t("Armature.validation.acceptable_standard.success"), icon='INFO')
|
||||||
icon='MODIFIER')
|
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
|
# T-Pose Validation Box
|
||||||
validation_mode = context.scene.avatar_toolkit.validation_mode
|
tpose_box: UILayout = layout.box()
|
||||||
if validation_mode == 'BASIC':
|
col = tpose_box.column(align=True)
|
||||||
warning_row = info_box.box()
|
col.label(text=t("Validation.tpose.label"), icon='ARMATURE_DATA')
|
||||||
warning_row.alert = True
|
col.separator(factor=0.5)
|
||||||
warning_row.label(text=t("QuickAccess.validation_basic_warning"), icon='INFO')
|
col.operator(AvatarToolkit_OT_ValidateTPose.bl_idname, text=t("Validation.tpose.validate_now"), icon='CHECKMARK')
|
||||||
warning_row.label(text=t("QuickAccess.validation_basic_details"))
|
|
||||||
elif validation_mode == 'NONE':
|
if props.show_tpose_validation:
|
||||||
warning_row = info_box.box()
|
validation_box = col.box()
|
||||||
warning_row.alert = True
|
if props.tpose_validation_result:
|
||||||
warning_row.label(text=t("QuickAccess.validation_none_warning"), icon='ERROR')
|
validation_box.label(text=t("Validation.tpose.valid"), icon='CHECKMARK')
|
||||||
warning_row.label(text=t("QuickAccess.validation_none_details"))
|
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 Mode Controls
|
||||||
pose_box: UILayout = layout.box()
|
pose_box: UILayout = layout.box()
|
||||||
|
|||||||
Reference in New Issue
Block a user