Merge branch 'atk-next' into Current
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"
|
||||||
@@ -16,10 +16,10 @@ license = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
wheels = [
|
wheels = [
|
||||||
"./wheels/lz4-4.4.3-cp311-cp311-macosx_11_0_arm64.whl",
|
"./wheels/lz4-4.4.5-cp311-cp311-macosx_11_0_arm64.whl",
|
||||||
"./wheels/lz4-4.3.3-cp311-cp311-macosx_10_9_x86_64.whl",
|
"./wheels/lz4-4.4.5-cp311-cp311-macosx_10_9_x86_64.whl",
|
||||||
"./wheels/lz4-4.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
|
"./wheels/lz4-4.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl",
|
||||||
"./wheels/lz4-4.4.3-cp311-cp311-win_amd64.whl"
|
"./wheels/lz4-4.4.5-cp311-cp311-win_amd64.whl"
|
||||||
]
|
]
|
||||||
|
|
||||||
[permissions]
|
[permissions]
|
||||||
|
|||||||
@@ -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
@@ -35,6 +35,11 @@ def update_validation_mode(self: PropertyGroup, context: Context) -> None:
|
|||||||
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:
|
||||||
"""Updates logging state and configures logging"""
|
"""Updates logging state and configures logging"""
|
||||||
@@ -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,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",
|
||||||
@@ -191,6 +200,7 @@
|
|||||||
"Tools.digitigrade_error": "Failed to create digitigrade legs: {error}",
|
"Tools.digitigrade_error": "Failed to create digitigrade legs: {error}",
|
||||||
"Tools.digitigrade_success": "Successfully created digitigrade leg setup",
|
"Tools.digitigrade_success": "Successfully created digitigrade leg setup",
|
||||||
"Tools.processing_leg": "Processing leg bone: {bone}",
|
"Tools.processing_leg": "Processing leg bone: {bone}",
|
||||||
|
"Tools.weight_title": "Weight Tools",
|
||||||
"Tools.merge_twist_bones": "Keep Twist Bones",
|
"Tools.merge_twist_bones": "Keep Twist Bones",
|
||||||
"Tools.merge_twist_bones_desc": "When checked, twist bones will be kept, even if there are zero-weight",
|
"Tools.merge_twist_bones_desc": "When checked, twist bones will be kept, even if there are zero-weight",
|
||||||
"Tools.clean_weights": "Remove Zero Weight Bones",
|
"Tools.clean_weights": "Remove Zero Weight Bones",
|
||||||
@@ -587,7 +597,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": "頂点グループが見つかりません",
|
||||||
@@ -194,6 +203,7 @@
|
|||||||
"Tools.digitigrade_error": "デジティグレード脚の作成に失敗: {error}",
|
"Tools.digitigrade_error": "デジティグレード脚の作成に失敗: {error}",
|
||||||
"Tools.digitigrade_success": "デジティグレード脚の設定が正常に作成されました",
|
"Tools.digitigrade_success": "デジティグレード脚の設定が正常に作成されました",
|
||||||
"Tools.processing_leg": "脚のボーンを処理中: {bone}",
|
"Tools.processing_leg": "脚のボーンを処理中: {bone}",
|
||||||
|
"Tools.weight_title": "ウェイトツール",
|
||||||
"Tools.merge_twist_bones": "ツイストボーンを保持",
|
"Tools.merge_twist_bones": "ツイストボーンを保持",
|
||||||
"Tools.merge_twist_bones_desc": "チェックすると、ウェイトがゼロでもツイストボーンが保持されます",
|
"Tools.merge_twist_bones_desc": "チェックすると、ウェイトがゼロでもツイストボーンが保持されます",
|
||||||
"Tools.clean_weights": "ゼロウェイトボーンを削除",
|
"Tools.clean_weights": "ゼロウェイトボーンを削除",
|
||||||
@@ -549,7 +559,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": "버텍스 그룹을 찾을 수 없음",
|
||||||
@@ -194,6 +203,7 @@
|
|||||||
"Tools.digitigrade_error": "디지티그레이드 다리 생성 실패: {error}",
|
"Tools.digitigrade_error": "디지티그레이드 다리 생성 실패: {error}",
|
||||||
"Tools.digitigrade_success": "디지티그레이드 다리 설정 생성 성공",
|
"Tools.digitigrade_success": "디지티그레이드 다리 설정 생성 성공",
|
||||||
"Tools.processing_leg": "다리 본 처리 중: {bone}",
|
"Tools.processing_leg": "다리 본 처리 중: {bone}",
|
||||||
|
"Tools.weight_title": "가중치 도구",
|
||||||
"Tools.merge_twist_bones": "트위스트 본 유지",
|
"Tools.merge_twist_bones": "트위스트 본 유지",
|
||||||
"Tools.merge_twist_bones_desc": "체크하면 가중치가 0이더라도 트위스트 본이 유지됩니다",
|
"Tools.merge_twist_bones_desc": "체크하면 가중치가 0이더라도 트위스트 본이 유지됩니다",
|
||||||
"Tools.clean_weights": "가중치 0인 본 제거",
|
"Tools.clean_weights": "가중치 0인 본 제거",
|
||||||
@@ -549,7 +559,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로 변환",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from bpy.types import UIList, Panel, UILayout, Object, Context, Material, Operat
|
|||||||
import bpy
|
import bpy
|
||||||
from math import sqrt
|
from math import sqrt
|
||||||
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||||
|
from .panel_layout import get_panel_order, should_open_by_default
|
||||||
from ..core.common import SceneMatClass, MaterialListBool, get_active_armature
|
from ..core.common import SceneMatClass, MaterialListBool, get_active_armature
|
||||||
from ..functions.atlas_materials import AvatarToolKit_OT_AtlasMaterials
|
from ..functions.atlas_materials import AvatarToolKit_OT_AtlasMaterials
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
@@ -214,7 +215,8 @@ class AvatarToolKit_PT_TextureAtlasPanel(Panel):
|
|||||||
bl_region_type = 'UI'
|
bl_region_type = 'UI'
|
||||||
bl_category = CATEGORY_NAME
|
bl_category = CATEGORY_NAME
|
||||||
bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
||||||
bl_order = 7
|
bl_order = get_panel_order('texture_atlas')
|
||||||
|
bl_options = set() if not should_open_by_default('TEXTURE_ATLAS') else {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
def draw(self, context: Context):
|
def draw(self, context: Context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import bpy
|
|||||||
from typing import Set, List, Tuple, Any
|
from typing import Set, List, Tuple, Any
|
||||||
from bpy.types import Panel, Context, UILayout, Operator, Event, WindowManager
|
from bpy.types import Panel, Context, UILayout, Operator, Event, WindowManager
|
||||||
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||||
|
from .panel_layout import get_panel_order, should_open_by_default
|
||||||
from ..functions.custom_tools.mesh_attachment import AvatarToolkit_OT_AttachMesh
|
from ..functions.custom_tools.mesh_attachment import AvatarToolkit_OT_AttachMesh
|
||||||
from ..functions.custom_tools.armature_merging import AvatarToolkit_OT_MergeArmature
|
from ..functions.custom_tools.armature_merging import AvatarToolkit_OT_MergeArmature
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
@@ -112,8 +113,8 @@ class AvatarToolKit_PT_CustomPanel(Panel):
|
|||||||
bl_region_type: str = 'UI'
|
bl_region_type: str = 'UI'
|
||||||
bl_category: str = CATEGORY_NAME
|
bl_category: str = CATEGORY_NAME
|
||||||
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
||||||
bl_order: int = 4
|
bl_order: int = get_panel_order('custom_avatar')
|
||||||
bl_options: Set[str] = {'DEFAULT_CLOSED'}
|
bl_options: Set[str] = set() if not should_open_by_default('CUSTOM_AVATAR') else {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
"""Draw the custom avatar panel UI"""
|
"""Draw the custom avatar panel UI"""
|
||||||
|
|||||||
+43
-72
@@ -2,6 +2,8 @@ import bpy
|
|||||||
from typing import Set
|
from typing import Set
|
||||||
from bpy.types import Panel, Context, UILayout, Operator, Event, WindowManager
|
from bpy.types import Panel, Context, UILayout, Operator, Event, WindowManager
|
||||||
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||||
|
from .ui_utils import UIStyle, draw_section_header, wrap_text_label
|
||||||
|
from .panel_layout import get_panel_order, should_open_by_default
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
from ..core.common import get_active_armature, get_all_meshes
|
from ..core.common import get_active_armature, get_all_meshes
|
||||||
from ..functions.eye_tracking import (
|
from ..functions.eye_tracking import (
|
||||||
@@ -26,38 +28,37 @@ class AvatarToolKit_PT_EyeTrackingPanel(Panel):
|
|||||||
bl_region_type: str = 'UI'
|
bl_region_type: str = 'UI'
|
||||||
bl_category: str = CATEGORY_NAME
|
bl_category: str = CATEGORY_NAME
|
||||||
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
||||||
bl_order: int = 6
|
bl_order: int = get_panel_order('eye_tracking')
|
||||||
bl_options: Set[str] = {'DEFAULT_CLOSED'}
|
bl_options: Set[str] = set() if not should_open_by_default('EYE_TRACKING') else {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
"""Draw the eye tracking panel interface"""
|
"""Draw the eye tracking panel interface"""
|
||||||
layout: UILayout = self.layout
|
layout: UILayout = self.layout
|
||||||
toolkit = context.scene.avatar_toolkit
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
|
||||||
# SDK Version Selection Box
|
# SDK Version Selection
|
||||||
sdk_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("EyeTracking.sdk_version"), icon='PRESET')
|
||||||
col: UILayout = sdk_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.sdk_version"), icon='PRESET')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
row: UILayout = col.row(align=True)
|
row: UILayout = col.row(align=True)
|
||||||
row.prop(toolkit, "eye_tracking_type", expand=True)
|
row.prop(toolkit, "eye_tracking_type", expand=True)
|
||||||
|
|
||||||
if toolkit.eye_tracking_type == 'SDK2':
|
if toolkit.eye_tracking_type == 'SDK2':
|
||||||
# SDK2 Warning Box
|
# SDK2 Warning
|
||||||
warning_box: UILayout = layout.box()
|
warning_box: UILayout = layout.box()
|
||||||
col: UILayout = warning_box.column(align=True)
|
col: UILayout = warning_box.column(align=True)
|
||||||
col.label(text=t("EyeTracking.sdk2_warning"), icon='INFO')
|
col.alert = True
|
||||||
col.separator(factor=0.5)
|
col.label(text=t("EyeTracking.sdk2_warning"), icon='ERROR')
|
||||||
col.label(text=t("EyeTracking.sdk2_warning_detail1"))
|
col.separator(factor=UIStyle.SUBSECTION_SEPARATOR_FACTOR)
|
||||||
col.label(text=t("EyeTracking.sdk2_warning_detail2"))
|
|
||||||
col.label(text=t("EyeTracking.sdk2_warning_detail3"))
|
|
||||||
col.label(text=t("EyeTracking.sdk2_warning_detail4"))
|
|
||||||
|
|
||||||
# Mode Selection Box
|
warning_text = "\n".join([
|
||||||
mode_box: UILayout = layout.box()
|
t("EyeTracking.sdk2_warning_detail1"),
|
||||||
col: UILayout = mode_box.column(align=True)
|
t("EyeTracking.sdk2_warning_detail2"),
|
||||||
col.label(text=t("EyeTracking.setup"), icon='TOOL_SETTINGS')
|
t("EyeTracking.sdk2_warning_detail3"),
|
||||||
col.separator(factor=0.5)
|
t("EyeTracking.sdk2_warning_detail4")
|
||||||
|
])
|
||||||
|
wrap_text_label(col, warning_text, max_length=45)
|
||||||
|
|
||||||
|
# Mode Selection
|
||||||
|
col = draw_section_header(layout, t("EyeTracking.setup"), icon='TOOL_SETTINGS')
|
||||||
col.prop(toolkit, "eye_mode", expand=True)
|
col.prop(toolkit, "eye_mode", expand=True)
|
||||||
|
|
||||||
if toolkit.eye_mode == 'CREATION':
|
if toolkit.eye_mode == 'CREATION':
|
||||||
@@ -72,11 +73,9 @@ class AvatarToolKit_PT_EyeTrackingPanel(Panel):
|
|||||||
"""Draw the AV3 eye tracking setup interface"""
|
"""Draw the AV3 eye tracking setup interface"""
|
||||||
toolkit = context.scene.avatar_toolkit
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
|
||||||
# Bone Setup Box
|
# Bone Setup
|
||||||
bone_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("EyeTracking.bone_setup"), icon='BONE_DATA')
|
||||||
col: UILayout = bone_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.bone_setup"), icon='BONE_DATA')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
|
|
||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if armature:
|
if armature:
|
||||||
@@ -86,21 +85,16 @@ class AvatarToolKit_PT_EyeTrackingPanel(Panel):
|
|||||||
else:
|
else:
|
||||||
col.label(text=t("EyeTracking.no_armature"), icon='ERROR')
|
col.label(text=t("EyeTracking.no_armature"), icon='ERROR')
|
||||||
|
|
||||||
# Create Button
|
|
||||||
row: UILayout = layout.row(align=True)
|
row: UILayout = layout.row(align=True)
|
||||||
row.scale_y = 1.5
|
row.scale_y = UIStyle.PRIMARY_BUTTON_SCALE
|
||||||
row.operator(CreateEyesAV3Button.bl_idname, icon='PLAY')
|
row.operator(CreateEyesAV3Button.bl_idname, icon='PLAY')
|
||||||
|
|
||||||
def draw_creation_mode(self, context: Context, layout: UILayout) -> None:
|
def draw_creation_mode(self, context: Context, layout: UILayout) -> None:
|
||||||
"""Draw the eye tracking creation mode interface"""
|
"""Draw the eye tracking creation mode interface"""
|
||||||
toolkit = context.scene.avatar_toolkit
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
|
||||||
# Bone Setup Box
|
# Bone Setup
|
||||||
bone_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("EyeTracking.bone_setup"), icon='BONE_DATA')
|
||||||
col: UILayout = bone_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.bone_setup"), icon='BONE_DATA')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
|
|
||||||
armature = get_active_armature(context)
|
armature = get_active_armature(context)
|
||||||
if armature:
|
if armature:
|
||||||
col.prop_search(toolkit, "head", armature.data, "bones", text=t("EyeTracking.head_bone"))
|
col.prop_search(toolkit, "head", armature.data, "bones", text=t("EyeTracking.head_bone"))
|
||||||
@@ -109,19 +103,12 @@ class AvatarToolKit_PT_EyeTrackingPanel(Panel):
|
|||||||
else:
|
else:
|
||||||
col.label(text=t("EyeTracking.no_armature"), icon='ERROR')
|
col.label(text=t("EyeTracking.no_armature"), icon='ERROR')
|
||||||
|
|
||||||
# Mesh Setup Box
|
# Mesh Setup
|
||||||
mesh_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("EyeTracking.mesh_setup"), icon='MESH_DATA')
|
||||||
col: UILayout = mesh_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.mesh_setup"), icon='MESH_DATA')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
col.prop_search(toolkit, "mesh_name_eye", bpy.data, "objects", text="")
|
col.prop_search(toolkit, "mesh_name_eye", bpy.data, "objects", text="")
|
||||||
|
|
||||||
# Shape Key Setup Box
|
# Shape Key Setup
|
||||||
shape_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("EyeTracking.shapekey_setup"), icon='SHAPEKEY_DATA')
|
||||||
col: UILayout = shape_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.shapekey_setup"), icon='SHAPEKEY_DATA')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
|
|
||||||
mesh = bpy.data.objects.get(toolkit.mesh_name_eye)
|
mesh = bpy.data.objects.get(toolkit.mesh_name_eye)
|
||||||
if mesh and mesh.data.shape_keys:
|
if mesh and mesh.data.shape_keys:
|
||||||
col.prop_search(toolkit, "wink_left", mesh.data.shape_keys, "key_blocks", text=t("EyeTracking.wink_left"))
|
col.prop_search(toolkit, "wink_left", mesh.data.shape_keys, "key_blocks", text=t("EyeTracking.wink_left"))
|
||||||
@@ -131,19 +118,15 @@ class AvatarToolKit_PT_EyeTrackingPanel(Panel):
|
|||||||
else:
|
else:
|
||||||
col.label(text=t("EyeTracking.no_shapekeys"), icon='ERROR')
|
col.label(text=t("EyeTracking.no_shapekeys"), icon='ERROR')
|
||||||
|
|
||||||
# Options Box
|
# Options
|
||||||
options_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("EyeTracking.options"), icon='SETTINGS')
|
||||||
col: UILayout = options_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.options"), icon='SETTINGS')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
col.prop(toolkit, "disable_eye_blinking")
|
col.prop(toolkit, "disable_eye_blinking")
|
||||||
col.prop(toolkit, "disable_eye_movement")
|
col.prop(toolkit, "disable_eye_movement")
|
||||||
if not toolkit.disable_eye_movement:
|
if not toolkit.disable_eye_movement:
|
||||||
col.prop(toolkit, "eye_distance")
|
col.prop(toolkit, "eye_distance")
|
||||||
|
|
||||||
# Create Button
|
|
||||||
row: UILayout = layout.row(align=True)
|
row: UILayout = layout.row(align=True)
|
||||||
row.scale_y = 1.5
|
row.scale_y = UIStyle.PRIMARY_BUTTON_SCALE
|
||||||
row.operator(CreateEyesSDK2Button.bl_idname, icon='PLAY')
|
row.operator(CreateEyesSDK2Button.bl_idname, icon='PLAY')
|
||||||
|
|
||||||
def draw_testing_mode(self, context: Context, layout: UILayout) -> None:
|
def draw_testing_mode(self, context: Context, layout: UILayout) -> None:
|
||||||
@@ -151,37 +134,25 @@ class AvatarToolKit_PT_EyeTrackingPanel(Panel):
|
|||||||
toolkit = context.scene.avatar_toolkit
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
|
||||||
if context.mode != 'POSE':
|
if context.mode != 'POSE':
|
||||||
# Testing Start Box
|
# Testing Start
|
||||||
test_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("EyeTracking.testing"), icon='PLAY')
|
||||||
col: UILayout = test_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.testing"), icon='PLAY')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
row: UILayout = col.row(align=True)
|
row: UILayout = col.row(align=True)
|
||||||
row.scale_y = 1.5
|
row.scale_y = UIStyle.PRIMARY_BUTTON_SCALE
|
||||||
row.operator(StartTestingButton.bl_idname, icon='PLAY')
|
row.operator(StartTestingButton.bl_idname, icon='PLAY')
|
||||||
else:
|
else:
|
||||||
# Eye Rotation Box
|
# Eye Rotation
|
||||||
rotation_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("EyeTracking.rotation_controls"), icon='DRIVER_ROTATIONAL_DIFFERENCE')
|
||||||
col: UILayout = rotation_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.rotation_controls"), icon='DRIVER_ROTATIONAL_DIFFERENCE')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
col.prop(toolkit, "eye_rotation_x", text=t("EyeTracking.rotation.x"))
|
col.prop(toolkit, "eye_rotation_x", text=t("EyeTracking.rotation.x"))
|
||||||
col.prop(toolkit, "eye_rotation_y", text=t("EyeTracking.rotation.y"))
|
col.prop(toolkit, "eye_rotation_y", text=t("EyeTracking.rotation.y"))
|
||||||
col.operator(ResetRotationButton.bl_idname, icon='LOOP_BACK')
|
col.operator(ResetRotationButton.bl_idname, icon='LOOP_BACK')
|
||||||
|
|
||||||
# Eye Adjustment Box
|
# Eye Adjustment
|
||||||
adjust_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("EyeTracking.adjustments"), icon='MODIFIER')
|
||||||
col: UILayout = adjust_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.adjustments"), icon='MODIFIER')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
col.prop(toolkit, "eye_distance")
|
col.prop(toolkit, "eye_distance")
|
||||||
col.operator(AdjustEyesButton.bl_idname, icon='CON_TRACKTO')
|
col.operator(AdjustEyesButton.bl_idname, icon='CON_TRACKTO')
|
||||||
|
|
||||||
# Blinking Test Box
|
# Blinking Test
|
||||||
blink_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("EyeTracking.blink_testing"), icon='HIDE_OFF')
|
||||||
col: UILayout = blink_box.column(align=True)
|
|
||||||
col.label(text=t("EyeTracking.blink_testing"), icon='HIDE_OFF')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
row: UILayout = col.row(align=True)
|
row: UILayout = col.row(align=True)
|
||||||
row.prop(toolkit, "eye_blink_shape")
|
row.prop(toolkit, "eye_blink_shape")
|
||||||
row.operator(TestBlinking.bl_idname, icon='RESTRICT_VIEW_OFF')
|
row.operator(TestBlinking.bl_idname, icon='RESTRICT_VIEW_OFF')
|
||||||
@@ -192,7 +163,7 @@ class AvatarToolKit_PT_EyeTrackingPanel(Panel):
|
|||||||
|
|
||||||
# Stop Testing Button
|
# Stop Testing Button
|
||||||
row: UILayout = layout.row(align=True)
|
row: UILayout = layout.row(align=True)
|
||||||
row.scale_y = 1.5
|
row.scale_y = UIStyle.PRIMARY_BUTTON_SCALE
|
||||||
row.operator(StopTestingButton.bl_idname, icon='PAUSE')
|
row.operator(StopTestingButton.bl_idname, icon='PAUSE')
|
||||||
|
|
||||||
# Reset Button
|
# Reset Button
|
||||||
|
|||||||
+9
-7
@@ -1,6 +1,7 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from typing import Optional, Set
|
from typing import Optional, Set
|
||||||
from bpy.types import Panel, Context, UILayout
|
from bpy.types import Panel, Context, UILayout
|
||||||
|
from .ui_utils import UIStyle, wrap_text_label
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
|
|
||||||
CATEGORY_NAME: str = "Avatar Toolkit"
|
CATEGORY_NAME: str = "Avatar Toolkit"
|
||||||
@@ -16,13 +17,14 @@ def draw_title(self: Panel) -> None:
|
|||||||
row.scale_y: float = 1.2
|
row.scale_y: float = 1.2
|
||||||
row.label(text=t("AvatarToolkit.label"), icon='ARMATURE_DATA')
|
row.label(text=t("AvatarToolkit.label"), icon='ARMATURE_DATA')
|
||||||
|
|
||||||
# Description as a flowing paragraph
|
# Description
|
||||||
desc_col: UILayout = col.column()
|
col.separator(factor=UIStyle.SECTION_SEPARATOR_FACTOR)
|
||||||
desc_col.scale_y: float = 0.6
|
description = " ".join([
|
||||||
desc_col.label(text=t("AvatarToolkit.desc1"))
|
t("AvatarToolkit.desc1"),
|
||||||
desc_col.label(text=t("AvatarToolkit.desc2"))
|
t("AvatarToolkit.desc2"),
|
||||||
desc_col.label(text=t("AvatarToolkit.desc3"))
|
t("AvatarToolkit.desc3")
|
||||||
col.separator()
|
])
|
||||||
|
wrap_text_label(col, description, max_length=50)
|
||||||
|
|
||||||
class AvatarToolKit_PT_AvatarToolkitPanel(Panel):
|
class AvatarToolKit_PT_AvatarToolkitPanel(Panel):
|
||||||
"""Main panel for Avatar Toolkit containing general information and settings"""
|
"""Main panel for Avatar Toolkit containing general information and settings"""
|
||||||
|
|||||||
+15
-28
@@ -2,6 +2,8 @@ import bpy
|
|||||||
from typing import Set
|
from typing import Set
|
||||||
from bpy.types import Panel, Context, UILayout, Operator
|
from bpy.types import Panel, Context, UILayout, Operator
|
||||||
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||||
|
from .ui_utils import UIStyle, draw_section_header, draw_operator_row
|
||||||
|
from .panel_layout import get_panel_order, should_open_by_default
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
from ..functions.optimization.materials_tools import AvatarToolkit_OT_CombineMaterials
|
from ..functions.optimization.materials_tools import AvatarToolkit_OT_CombineMaterials
|
||||||
from ..functions.optimization.remove_doubles import AvatarToolkit_OT_RemoveDoubles
|
from ..functions.optimization.remove_doubles import AvatarToolkit_OT_RemoveDoubles
|
||||||
@@ -15,39 +17,24 @@ class AvatarToolKit_PT_OptimizationPanel(Panel):
|
|||||||
bl_region_type: str = 'UI'
|
bl_region_type: str = 'UI'
|
||||||
bl_category: str = CATEGORY_NAME
|
bl_category: str = CATEGORY_NAME
|
||||||
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
||||||
bl_order: int = 1
|
bl_order: int = get_panel_order('optimization')
|
||||||
bl_options = {'DEFAULT_CLOSED'}
|
bl_options = set() if not should_open_by_default('OPTIMIZATION') else {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
"""Draws the optimization panel interface with material, mesh cleanup and join mesh tools"""
|
"""Draws the optimization panel interface with material, mesh cleanup and join mesh tools"""
|
||||||
layout: UILayout = self.layout
|
layout: UILayout = self.layout
|
||||||
|
|
||||||
# Materials Box
|
# Materials section
|
||||||
materials_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("Optimization.materials_title"), icon='MATERIAL')
|
||||||
col: UILayout = materials_box.column(align=True)
|
|
||||||
col.label(text=t("Optimization.materials_title"), icon='MATERIAL')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
|
|
||||||
# Material Operations
|
|
||||||
col.operator(AvatarToolkit_OT_CombineMaterials.bl_idname, icon='MATERIAL')
|
col.operator(AvatarToolkit_OT_CombineMaterials.bl_idname, icon='MATERIAL')
|
||||||
|
|
||||||
# Mesh Cleanup Box
|
# Mesh Cleanup section
|
||||||
cleanup_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("Optimization.cleanup_title"), icon='MESH_DATA')
|
||||||
col: UILayout = cleanup_box.column(align=True)
|
col.operator(AvatarToolkit_OT_RemoveDoubles.bl_idname, icon='MESH_DATA')
|
||||||
col.label(text=t("Optimization.cleanup_title"), icon='MESH_DATA')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
|
|
||||||
# Remove Doubles Row
|
# Join Meshes section
|
||||||
row: UILayout = col.row(align=True)
|
col = draw_section_header(layout, t("Optimization.join_meshes_title"), icon='OBJECT_DATA')
|
||||||
row.operator(AvatarToolkit_OT_RemoveDoubles.bl_idname, icon='MESH_DATA')
|
draw_operator_row(col, [
|
||||||
|
(AvatarToolkit_OT_JoinAllMeshes.bl_idname, t("Optimization.join_all_meshes"), 'OBJECT_DATA'),
|
||||||
# Join Meshes Box
|
(AvatarToolkit_OT_JoinSelectedMeshes.bl_idname, t("Optimization.join_selected_meshes"), 'RESTRICT_SELECT_OFF')
|
||||||
join_box: UILayout = layout.box()
|
])
|
||||||
col: UILayout = join_box.column(align=True)
|
|
||||||
col.label(text=t("Optimization.join_meshes_title"), icon='OBJECT_DATA')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
|
|
||||||
# Join Meshes Row
|
|
||||||
row: UILayout = col.row(align=True)
|
|
||||||
row.operator(AvatarToolkit_OT_JoinAllMeshes.bl_idname, icon='OBJECT_DATA')
|
|
||||||
row.operator(AvatarToolkit_OT_JoinSelectedMeshes.bl_idname, icon='RESTRICT_SELECT_OFF')
|
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"""Panel ordering and organization guide for Avatar Toolkit UI
|
||||||
|
This module defines the standard panel order and grouping for the Avatar Toolkit.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Main Panel
|
||||||
|
MAIN_PANEL_ORDER = -1 # Always first (parent panel)
|
||||||
|
QUICK_ACCESS_ORDER = 0
|
||||||
|
OPTIMIZATION_ORDER = 1
|
||||||
|
TOOLS_ORDER = 2
|
||||||
|
CUSTOM_TOOLS_ORDER = 3
|
||||||
|
CUSTOM_AVATAR_ORDER = 4
|
||||||
|
TRANSLATION_ORDER = 5
|
||||||
|
VISEMES_ORDER = 6
|
||||||
|
EYE_TRACKING_ORDER = 7
|
||||||
|
TEXTURE_ATLAS_ORDER = 8
|
||||||
|
VRM_UNITY_ORDER = 9
|
||||||
|
SETTINGS_ORDER = 10
|
||||||
|
|
||||||
|
# Panel open/closed by default
|
||||||
|
PANELS_OPEN_BY_DEFAULT = {
|
||||||
|
'QUICK_ACCESS': False,
|
||||||
|
'OPTIMIZATION': True,
|
||||||
|
'TOOLS': True,
|
||||||
|
'CUSTOM_TOOLS': True,
|
||||||
|
'CUSTOM_AVATAR': True,
|
||||||
|
'VISEMES': True,
|
||||||
|
'EYE_TRACKING': True,
|
||||||
|
'TEXTURE_ATLAS': True,
|
||||||
|
'VRM_UNITY': True,
|
||||||
|
'SETTINGS': True,
|
||||||
|
'TRANSLATION': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_panel_order(panel_name: str) -> int:
|
||||||
|
"""Get the recommended bl_order value for a panel"""
|
||||||
|
order_map = {
|
||||||
|
'quick_access': QUICK_ACCESS_ORDER,
|
||||||
|
'optimization': OPTIMIZATION_ORDER,
|
||||||
|
'tools': TOOLS_ORDER,
|
||||||
|
'custom_tools': CUSTOM_TOOLS_ORDER,
|
||||||
|
'custom_avatar': CUSTOM_AVATAR_ORDER,
|
||||||
|
'translation': TRANSLATION_ORDER,
|
||||||
|
'visemes': VISEMES_ORDER,
|
||||||
|
'eye_tracking': EYE_TRACKING_ORDER,
|
||||||
|
'texture_atlas': TEXTURE_ATLAS_ORDER,
|
||||||
|
'vrm_unity': VRM_UNITY_ORDER,
|
||||||
|
'settings': SETTINGS_ORDER,
|
||||||
|
}
|
||||||
|
return order_map.get(panel_name.lower(), 99)
|
||||||
|
|
||||||
|
def should_open_by_default(panel_name: str) -> bool:
|
||||||
|
"""Check if a panel should be open by default"""
|
||||||
|
return PANELS_OPEN_BY_DEFAULT.get(panel_name.upper(), True)
|
||||||
+107
-149
@@ -10,6 +10,8 @@ from bpy.types import (
|
|||||||
Object
|
Object
|
||||||
)
|
)
|
||||||
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||||
|
from .ui_utils import UIStyle, draw_section_header, draw_operator_row
|
||||||
|
from .panel_layout import get_panel_order, should_open_by_default
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
from ..core.common import (
|
from ..core.common import (
|
||||||
get_active_armature,
|
get_active_armature,
|
||||||
@@ -34,7 +36,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
|
||||||
@@ -79,26 +81,37 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
|
|||||||
bl_region_type: str = 'UI'
|
bl_region_type: str = 'UI'
|
||||||
bl_category: str = CATEGORY_NAME
|
bl_category: str = CATEGORY_NAME
|
||||||
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
||||||
bl_order: int = 0
|
bl_order: int = get_panel_order('quick_access')
|
||||||
|
bl_options = {'DEFAULT_CLOSED'} if should_open_by_default('QUICK_ACCESS') else set()
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
"""Draw the panel layout"""
|
"""Draw the panel layout"""
|
||||||
layout: UILayout = self.layout
|
layout: UILayout = self.layout
|
||||||
props = context.scene.avatar_toolkit
|
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
|
# Armature Selection
|
||||||
|
col = draw_section_header(layout, t("QuickAccess.select_armature"), icon='ARMATURE_DATA')
|
||||||
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 Section
|
||||||
|
col = draw_section_header(layout, t("Validation.label", "Armature Validation"), icon='CHECKMARK')
|
||||||
|
|
||||||
|
# Main validate button with prominent styling
|
||||||
|
validate_row = col.row(align=True)
|
||||||
|
validate_row.scale_y = UIStyle.PRIMARY_BUTTON_SCALE
|
||||||
|
validate_row.operator("avatar_toolkit.validate_armature_manual",
|
||||||
|
text=t("Validation.validate_now", "Validate Armature Now"),
|
||||||
|
icon='CHECKMARK')
|
||||||
|
|
||||||
|
# Validation mode selector
|
||||||
|
col.prop(props, "validation_mode", text=t("Settings.validation_mode", "Mode"))
|
||||||
|
|
||||||
|
# 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)}"
|
cache_key = f"validation_{active_armature.name}_{active_armature.data.name}_{len(active_armature.data.bones)}"
|
||||||
|
|
||||||
if cache_key not in _validation_cache:
|
if cache_key not in _validation_cache:
|
||||||
@@ -107,15 +120,16 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
|
|||||||
is_valid, messages, is_acceptable, hierarchy_messages, scale_messages, non_standard_messages = _validation_cache[cache_key]
|
is_valid, messages, is_acceptable, hierarchy_messages, scale_messages, non_standard_messages = _validation_cache[cache_key]
|
||||||
|
|
||||||
# Check if this is a PMX model
|
# Check if this is a PMX model
|
||||||
is_pmx_model = False
|
pmx_detected = is_pmx_model(active_armature)
|
||||||
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()
|
results_box = col.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)
|
||||||
|
|
||||||
# If it's a PMX model, display a prominent notice
|
# PMX Model Notice
|
||||||
if is_pmx_model:
|
if pmx_detected:
|
||||||
pmx_box = info_box.box()
|
pmx_box = results_box.box()
|
||||||
pmx_box.label(text=t("Armature.validation.pmx_model_detected"), icon='INFO')
|
pmx_box.label(text=t("Armature.validation.pmx_model_detected"), icon='INFO')
|
||||||
|
|
||||||
validation_mode = context.scene.avatar_toolkit.validation_mode
|
validation_mode = context.scene.avatar_toolkit.validation_mode
|
||||||
@@ -125,38 +139,35 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
|
|||||||
else:
|
else:
|
||||||
pmx_box.label(text=t("Armature.validation.pmx_model_basic"))
|
pmx_box.label(text=t("Armature.validation.pmx_model_basic"))
|
||||||
|
|
||||||
|
# Validation Results
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
# Display non-standard bones and hierarchy issues
|
# Display found bones
|
||||||
if messages and len(messages) > 0:
|
if messages and len(messages) > 0:
|
||||||
# Found Bones section
|
bones_section = results_box.box()
|
||||||
validation_box = info_box.box()
|
row = bones_section.row()
|
||||||
row = validation_box.row()
|
row.prop(props, "show_found_bones", text=t("Validation.section.found_bones"),
|
||||||
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)
|
icon='TRIA_DOWN' if props.show_found_bones else 'TRIA_RIGHT', emboss=False)
|
||||||
if props.show_found_bones and len(messages) > 0:
|
if props.show_found_bones:
|
||||||
for line in messages[0].split('\n'):
|
for line in messages[0].split('\n'):
|
||||||
validation_box.label(text=line)
|
bones_section.label(text=line)
|
||||||
|
|
||||||
# Main validation status
|
# 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.status.failed"))
|
row.label(text=t("Validation.status.failed"), icon='ERROR')
|
||||||
|
|
||||||
# Detailed validation message
|
# Error explanation
|
||||||
validation_box = info_box.box()
|
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.line1"))
|
error_box.label(text=t("Validation.message.failed.line2"))
|
||||||
row = validation_box.row()
|
error_box.label(text=t("Validation.message.failed.line3"))
|
||||||
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
|
# 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 = ns_section.row()
|
||||||
row.alert = True
|
row.alert = True
|
||||||
row.prop(props, "show_non_standard", text=t("Validation.section.non_standard"),
|
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)
|
icon='TRIA_DOWN' if props.show_non_standard else 'TRIA_RIGHT', emboss=False)
|
||||||
@@ -164,161 +175,108 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
|
|||||||
if non_standard_messages and len(non_standard_messages) > 0:
|
if non_standard_messages and len(non_standard_messages) > 0:
|
||||||
for message in non_standard_messages:
|
for message in non_standard_messages:
|
||||||
for line in message.split('\n'):
|
for line in message.split('\n'):
|
||||||
sub_row = validation_box.row()
|
sub_row = ns_section.row()
|
||||||
sub_row.alert = True
|
sub_row.alert = True
|
||||||
sub_row.label(text=line)
|
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:
|
else:
|
||||||
# For PMX models, if no non-standard messages but it's a PMX model,
|
ns_section.label(text=t("Validation.no_non_standard_issues"))
|
||||||
# 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"))
|
|
||||||
|
|
||||||
else:
|
|
||||||
sub_row = validation_box.row()
|
|
||||||
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 = hier_section.row()
|
||||||
row.alert = True
|
row.alert = True
|
||||||
row.prop(props, "show_hierarchy", text=t("Validation.section.hierarchy"),
|
row.prop(props, "show_hierarchy", text=t("Validation.section.hierarchy"),
|
||||||
icon='TRIA_DOWN' if props.show_hierarchy else 'TRIA_RIGHT', emboss=False)
|
icon='TRIA_DOWN' if props.show_hierarchy else 'TRIA_RIGHT', emboss=False)
|
||||||
if props.show_hierarchy:
|
if props.show_hierarchy:
|
||||||
if hierarchy_messages:
|
|
||||||
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 = scale_section.row()
|
||||||
row.alert = True
|
row.alert = True
|
||||||
row.prop(props, "show_scale_issues", text=t("Validation.section.scale_issues"),
|
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)
|
icon='TRIA_DOWN' if props.show_scale_issues else 'TRIA_RIGHT', emboss=False)
|
||||||
if props.show_scale_issues:
|
if props.show_scale_issues:
|
||||||
if scale_messages:
|
|
||||||
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:
|
elif is_valid and not is_acceptable:
|
||||||
row = info_box.row()
|
# Valid armature - show stats
|
||||||
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)}"
|
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:
|
if stats_cache_key not in _stats_cache:
|
||||||
_stats_cache[stats_cache_key] = get_armature_stats(active_armature)
|
_stats_cache[stats_cache_key] = get_armature_stats(active_armature)
|
||||||
|
|
||||||
stats = _stats_cache[stats_cache_key]
|
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']))
|
split.label(text=t("QuickAccess.bones_count", count=stats['bone_count']))
|
||||||
|
|
||||||
if stats['has_pose']:
|
if stats['has_pose']:
|
||||||
info_box.label(text=t("QuickAccess.pose_bones_available"), icon='POSE_HLT')
|
results_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
|
elif is_valid and is_acceptable:
|
||||||
if len(messages) > 1:
|
# Acceptable standard
|
||||||
info_box.label(text=messages[1])
|
status_box = results_box.box()
|
||||||
if len(messages) > 2:
|
status_box.label(text=t("Armature.validation.acceptable_standard.success"), icon='INFO')
|
||||||
info_box.label(text=messages[2])
|
status_box.label(text=t("Armature.validation.acceptable_standard.note"))
|
||||||
else:
|
status_box.label(text=t("Armature.validation.acceptable_standard.option"))
|
||||||
info_box.label(text=t("Validation.no_messages"), icon='INFO')
|
|
||||||
|
|
||||||
# Add standardize button
|
# Add standardize button
|
||||||
standardize_box = info_box.box()
|
standardize_box = results_box.box()
|
||||||
standardize_box.operator(AvatarToolkit_OT_StandardizeArmature.bl_idname,
|
standardize_box.operator(AvatarToolkit_OT_StandardizeArmature.bl_idname,
|
||||||
text=t("QuickAccess.standardize_armature"),
|
text=t("QuickAccess.standardize_armature"),
|
||||||
icon='MODIFIER')
|
icon='MODIFIER')
|
||||||
|
|
||||||
# Validation Mode Warnings
|
# T-Pose Validation
|
||||||
validation_mode = context.scene.avatar_toolkit.validation_mode
|
col = draw_section_header(layout, t("Validation.tpose.label"), icon='ARMATURE_DATA')
|
||||||
if validation_mode == 'BASIC':
|
col.operator(AvatarToolkit_OT_ValidateTPose.bl_idname, text=t("Validation.tpose.validate_now"), icon='CHECKMARK')
|
||||||
warning_row = info_box.box()
|
|
||||||
warning_row.alert = True
|
if props.show_tpose_validation:
|
||||||
warning_row.label(text=t("QuickAccess.validation_basic_warning"), icon='INFO')
|
validation_result_col = col.column(align=True)
|
||||||
warning_row.label(text=t("QuickAccess.validation_basic_details"))
|
if props.tpose_validation_result:
|
||||||
elif validation_mode == 'NONE':
|
validation_result_col.label(text=t("Validation.tpose.valid"), icon='CHECKMARK')
|
||||||
warning_row = info_box.box()
|
else:
|
||||||
warning_row.alert = True
|
validation_result_col.alert = True
|
||||||
warning_row.label(text=t("QuickAccess.validation_none_warning"), icon='ERROR')
|
validation_result_col.label(text=t("Validation.tpose.warning"), icon='ERROR')
|
||||||
warning_row.label(text=t("QuickAccess.validation_none_details"))
|
|
||||||
|
for msg in props.tpose_validation_messages:
|
||||||
|
validation_result_col.label(text=msg.name)
|
||||||
|
|
||||||
# Pose Mode Controls
|
# Pose Mode Controls
|
||||||
pose_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("QuickAccess.pose_controls"), icon='ARMATURE_DATA')
|
||||||
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":
|
if context.mode == "POSE":
|
||||||
col.operator(AvatarToolkit_OT_StopPoseMode.bl_idname, icon='POSE_HLT')
|
col.operator(AvatarToolkit_OT_StopPoseMode.bl_idname, icon='POSE_HLT')
|
||||||
col.separator(factor=0.5)
|
col.separator(factor=UIStyle.SUBSECTION_SEPARATOR_FACTOR)
|
||||||
col.operator(AvatarToolkit_OT_ApplyPoseAsRest.bl_idname, icon='MOD_ARMATURE')
|
draw_operator_row(col, [
|
||||||
col.operator(AvatarToolkit_OT_ApplyPoseAsShapekey.bl_idname, icon='MOD_ARMATURE')
|
(AvatarToolkit_OT_ApplyPoseAsRest.bl_idname, t("QuickAccess.apply_pose_as_rest.label"), 'MOD_ARMATURE'),
|
||||||
|
(AvatarToolkit_OT_ApplyPoseAsShapekey.bl_idname, t("QuickAccess.apply_pose_as_shapekey.label"), 'MOD_ARMATURE')
|
||||||
|
])
|
||||||
else:
|
else:
|
||||||
col.operator(AvatarToolkit_OT_StartPoseMode.bl_idname, icon='POSE_HLT')
|
col.operator(AvatarToolkit_OT_StartPoseMode.bl_idname, icon='POSE_HLT')
|
||||||
|
|
||||||
# Import/Export Box
|
# Import/Export Section
|
||||||
import_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("QuickAccess.import_export"), icon='IMPORT')
|
||||||
col = import_box.column(align=True)
|
|
||||||
col.label(text=t("QuickAccess.import_export"), icon='IMPORT')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
|
|
||||||
# Import/Export Buttons
|
# Import/Export Buttons
|
||||||
button_row: UILayout = col.row(align=True)
|
draw_operator_row(col, [
|
||||||
button_row.scale_y = 1.5
|
(AvatarToolKit_OT_Import.bl_idname, t("QuickAccess.import"), 'IMPORT'),
|
||||||
button_row.operator(AvatarToolKit_OT_Import.bl_idname, text=t("QuickAccess.import"), icon='IMPORT')
|
(AvatarToolKit_OT_ExportMenu.bl_idname, t("QuickAccess.export"), 'EXPORT')
|
||||||
button_row.operator(AvatarToolKit_OT_ExportMenu.bl_idname, text=t("QuickAccess.export"), icon='EXPORT')
|
], scale_y=UIStyle.PRIMARY_BUTTON_SCALE)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
"""Base classes for reusable search operators"""
|
||||||
|
|
||||||
|
from typing import Set, Callable, Optional
|
||||||
|
from bpy.types import Operator, Context, Event, WindowManager
|
||||||
|
|
||||||
|
|
||||||
|
class SearchOperatorBase(Operator):
|
||||||
|
"""
|
||||||
|
Reusable base class for search/selection operators.
|
||||||
|
|
||||||
|
This is an abstract base class - do not use directly.
|
||||||
|
Subclass and implement your specific search operator instead.
|
||||||
|
|
||||||
|
Subclasses should:
|
||||||
|
1. Define bl_idname, bl_label, bl_description
|
||||||
|
2. Define search_property_name (name of EnumProperty)
|
||||||
|
3. Define target_property_name (name of property to set on scene)
|
||||||
|
4. Define get_items_func (function to get enum items)
|
||||||
|
5. Optionally override get_enum_property() to customize the enum
|
||||||
|
|
||||||
|
This was created because search in ATK was all over the place and inconsistent, this way we have a standard way to do it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Mark this as abstract by setting a non-Blender-compatible idname
|
||||||
|
bl_idname = "wm.search_operator_base" # Will be overridden in subclasses
|
||||||
|
bl_label = "Search and Select"
|
||||||
|
bl_options = {'REGISTER', 'INTERNAL'}
|
||||||
|
|
||||||
|
# These should be overridden in subclasses
|
||||||
|
search_property_name: str = "search_enum"
|
||||||
|
target_property_name: str = "target_property"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_items_func(scene, context) -> list:
|
||||||
|
"""Override this to provide enum items. Return list of (id, name, description) tuples"""
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_enum_property(self) -> None:
|
||||||
|
"""
|
||||||
|
Create the enum property dynamically. Override if you need custom behavior.
|
||||||
|
This is called during class creation.
|
||||||
|
"""
|
||||||
|
import bpy
|
||||||
|
setattr(
|
||||||
|
type(self),
|
||||||
|
self.search_property_name,
|
||||||
|
bpy.props.EnumProperty(
|
||||||
|
name="Search",
|
||||||
|
description="Select item",
|
||||||
|
items=self.get_items_func
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def execute(self, context: Context) -> Set[str]:
|
||||||
|
"""Set the target property from the search selection"""
|
||||||
|
search_value = getattr(self, self.search_property_name, None)
|
||||||
|
if search_value:
|
||||||
|
setattr(context.scene.avatar_toolkit, self.target_property_name, search_value)
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def invoke(self, context: Context, event: Event) -> Set[str]:
|
||||||
|
"""Open search popup"""
|
||||||
|
wm: WindowManager = context.window_manager
|
||||||
|
wm.invoke_search_popup(self)
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
class ArmatureSearchOperator(SearchOperatorBase):
|
||||||
|
"""Specialized search operator for selecting armatures"""
|
||||||
|
|
||||||
|
bl_label = "Search Armatures"
|
||||||
|
search_property_name: str = "search_armature_enum"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_items_func(scene, context) -> list:
|
||||||
|
"""Get list of all armature objects in scene"""
|
||||||
|
import bpy
|
||||||
|
return [
|
||||||
|
(obj.name, obj.name, "")
|
||||||
|
for obj in bpy.data.objects
|
||||||
|
if obj.type == 'ARMATURE'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class MeshSearchOperator(SearchOperatorBase):
|
||||||
|
"""Specialized search operator for selecting meshes"""
|
||||||
|
|
||||||
|
bl_label = "Search Meshes"
|
||||||
|
search_property_name: str = "search_mesh_enum"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_items_func(scene, context) -> list:
|
||||||
|
"""Get list of all mesh objects without armature modifiers"""
|
||||||
|
import bpy
|
||||||
|
return [
|
||||||
|
(obj.name, obj.name, "")
|
||||||
|
for obj in bpy.data.objects
|
||||||
|
if obj.type == 'MESH'
|
||||||
|
and not any(mod.type == 'ARMATURE' for mod in obj.modifiers)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class BoneSearchOperator(SearchOperatorBase):
|
||||||
|
"""Specialized search operator for selecting bones from active armature"""
|
||||||
|
|
||||||
|
bl_label = "Search Bones"
|
||||||
|
search_property_name: str = "search_bone_enum"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_items_func(scene, context) -> list:
|
||||||
|
"""Get list of all bones from active armature"""
|
||||||
|
from ..core.common import get_active_armature
|
||||||
|
|
||||||
|
armature = get_active_armature(context)
|
||||||
|
if not armature:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return [
|
||||||
|
(bone.name, bone.name, "")
|
||||||
|
for bone in armature.data.bones
|
||||||
|
]
|
||||||
+15
-23
@@ -9,6 +9,8 @@ from bpy.types import (
|
|||||||
Event
|
Event
|
||||||
)
|
)
|
||||||
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||||
|
from .ui_utils import UIStyle, draw_section_header, wrap_text_label
|
||||||
|
from .panel_layout import get_panel_order, should_open_by_default
|
||||||
from ..core.translations import t, get_languages_list
|
from ..core.translations import t, get_languages_list
|
||||||
from ..core.armature_validation import AvatarToolkit_OT_HighlightProblemBones, AvatarToolkit_OT_ClearBoneHighlighting
|
from ..core.armature_validation import AvatarToolkit_OT_HighlightProblemBones, AvatarToolkit_OT_ClearBoneHighlighting
|
||||||
|
|
||||||
@@ -26,8 +28,10 @@ class AvatarToolkit_OT_TranslationRestartPopup(Operator):
|
|||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
layout: UILayout = self.layout
|
layout: UILayout = self.layout
|
||||||
layout.label(text=t("Language.changed.success"))
|
col = layout.column(align=True)
|
||||||
layout.label(text=t("Language.changed.restart"))
|
col.label(text=t("Language.changed.success"))
|
||||||
|
col.separator(factor=UIStyle.SUBSECTION_SEPARATOR_FACTOR)
|
||||||
|
wrap_text_label(col, t("Language.changed.restart"), max_length=50)
|
||||||
|
|
||||||
class AvatarToolKit_PT_SettingsPanel(Panel):
|
class AvatarToolKit_PT_SettingsPanel(Panel):
|
||||||
"""Settings panel for Avatar Toolkit containing language preferences"""
|
"""Settings panel for Avatar Toolkit containing language preferences"""
|
||||||
@@ -37,8 +41,8 @@ class AvatarToolKit_PT_SettingsPanel(Panel):
|
|||||||
bl_region_type: str = 'UI'
|
bl_region_type: str = 'UI'
|
||||||
bl_category: str = CATEGORY_NAME
|
bl_category: str = CATEGORY_NAME
|
||||||
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
||||||
bl_order: int = 8
|
bl_order: int = get_panel_order('settings')
|
||||||
bl_options = {'DEFAULT_CLOSED'}
|
bl_options = set() if not should_open_by_default('SETTINGS') else {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
"""Draw the settings panel layout with language selection"""
|
"""Draw the settings panel layout with language selection"""
|
||||||
@@ -46,30 +50,18 @@ class AvatarToolKit_PT_SettingsPanel(Panel):
|
|||||||
props = context.scene.avatar_toolkit
|
props = context.scene.avatar_toolkit
|
||||||
|
|
||||||
# Language Settings
|
# Language Settings
|
||||||
lang_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("Settings.language"), icon='WORLD')
|
||||||
col: UILayout = lang_box.column(align=True)
|
|
||||||
row: UILayout = col.row()
|
|
||||||
row.scale_y = 1.2
|
|
||||||
row.label(text=t("Settings.language"), icon='WORLD')
|
|
||||||
col.separator()
|
|
||||||
col.prop(props, "language", text="")
|
col.prop(props, "language", text="")
|
||||||
|
|
||||||
# Validation Settings
|
# Validation Settings with help text
|
||||||
val_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("Settings.validation_mode"), icon='CHECKMARK')
|
||||||
col = val_box.column(align=True)
|
|
||||||
row = col.row()
|
|
||||||
row.scale_y = 1.2
|
|
||||||
row.label(text=t("Settings.validation_mode"), icon='CHECKMARK')
|
|
||||||
col.separator()
|
|
||||||
col.prop(props, "validation_mode", text="")
|
col.prop(props, "validation_mode", text="")
|
||||||
|
# Help text for validation mode
|
||||||
|
col.separator(factor=UIStyle.SUBSECTION_SEPARATOR_FACTOR)
|
||||||
|
wrap_text_label(col, "Select how strictly to validate armature bone structure and naming conventions.", max_length=40)
|
||||||
|
|
||||||
# Bone Highlighting Settings
|
# Bone Highlighting Settings
|
||||||
bone_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("Settings.bone_highlighting"), icon='BONE_DATA')
|
||||||
col = bone_box.column(align=True)
|
|
||||||
row = col.row()
|
|
||||||
row.scale_y = 1.2
|
|
||||||
row.label(text=t("Settings.bone_highlighting"), icon='BONE_DATA')
|
|
||||||
col.separator()
|
|
||||||
col.prop(props, "highlight_problem_bones")
|
col.prop(props, "highlight_problem_bones")
|
||||||
if props.highlight_problem_bones:
|
if props.highlight_problem_bones:
|
||||||
col.operator(AvatarToolkit_OT_HighlightProblemBones.bl_idname, icon='COLOR')
|
col.operator(AvatarToolkit_OT_HighlightProblemBones.bl_idname, icon='COLOR')
|
||||||
|
|||||||
+34
-56
@@ -2,6 +2,8 @@ import bpy
|
|||||||
from typing import Set
|
from typing import Set
|
||||||
from bpy.types import Panel, Context, UILayout, Operator, UIList
|
from bpy.types import Panel, Context, UILayout, Operator, UIList
|
||||||
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||||
|
from .ui_utils import UIStyle, draw_section_header, draw_operator_row
|
||||||
|
from .panel_layout import get_panel_order, should_open_by_default
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
|
|
||||||
from ..core.resonite_utils import AvatarToolkit_OT_ConvertResonite
|
from ..core.resonite_utils import AvatarToolkit_OT_ConvertResonite
|
||||||
@@ -29,8 +31,8 @@ class AvatarToolKit_PT_ToolsPanel(Panel):
|
|||||||
bl_region_type: str = 'UI'
|
bl_region_type: str = 'UI'
|
||||||
bl_category: str = CATEGORY_NAME
|
bl_category: str = CATEGORY_NAME
|
||||||
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
||||||
bl_order: int = 2
|
bl_order: int = get_panel_order('tools')
|
||||||
bl_options = {'DEFAULT_CLOSED'}
|
bl_options = set() if not should_open_by_default('TOOLS') else {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
"""Draw the tools panel interface"""
|
"""Draw the tools panel interface"""
|
||||||
@@ -38,94 +40,70 @@ class AvatarToolKit_PT_ToolsPanel(Panel):
|
|||||||
toolkit = context.scene.avatar_toolkit
|
toolkit = context.scene.avatar_toolkit
|
||||||
|
|
||||||
# General Tools
|
# General Tools
|
||||||
tools_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("Tools.general_title"), icon='TOOL_SETTINGS')
|
||||||
col: UILayout = tools_box.column(align=True)
|
|
||||||
col.label(text=t("Tools.general_title"), icon='TOOL_SETTINGS')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
col.operator(AvatarToolkit_OT_ConvertResonite.bl_idname, text=t("Tools.convert_resonite"), icon='EXPORT')
|
col.operator(AvatarToolkit_OT_ConvertResonite.bl_idname, text=t("Tools.convert_resonite"), icon='EXPORT')
|
||||||
|
|
||||||
# Separation Tools
|
# Separation Tools
|
||||||
sep_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("Tools.separate_title"), icon='MOD_EXPLODE')
|
||||||
col = sep_box.column(align=True)
|
draw_operator_row(col, [
|
||||||
col.label(text=t("Tools.separate_title"), icon='MOD_EXPLODE')
|
(AvatarToolKit_OT_SeparateByMaterials.bl_idname, t("Tools.separate_materials"), 'MATERIAL'),
|
||||||
col.separator(factor=0.5)
|
(AvatarToolKit_OT_SeparateByLooseParts.bl_idname, t("Tools.separate_loose"), 'MESH_DATA')
|
||||||
row: UILayout = col.row(align=True)
|
])
|
||||||
row.operator(AvatarToolKit_OT_SeparateByMaterials.bl_idname, text=t("Tools.separate_materials"), icon='MATERIAL')
|
|
||||||
row.operator(AvatarToolKit_OT_SeparateByLooseParts.bl_idname, text=t("Tools.separate_loose"), icon='MESH_DATA')
|
|
||||||
|
|
||||||
# Bone Tools
|
# Bone Tools
|
||||||
bone_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("Tools.bone_title"), icon='BONE_DATA')
|
||||||
col = bone_box.column(align=True)
|
|
||||||
col.label(text=t("Tools.bone_title"), icon='BONE_DATA')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
col.operator(AvatarToolKit_OT_CreateDigitigradeLegs.bl_idname, text=t("Tools.create_digitigrade"), icon='BONE_DATA')
|
col.operator(AvatarToolKit_OT_CreateDigitigradeLegs.bl_idname, text=t("Tools.create_digitigrade"), icon='BONE_DATA')
|
||||||
col.operator(AvatarToolKit_OT_FlipCurrentKeyFrames.bl_idname,text=t("Tools.flip_pose_frames"),icon="ACTION")
|
col.operator(AvatarToolKit_OT_FlipCurrentKeyFrames.bl_idname, text=t("Tools.flip_pose_frames"), icon="ACTION")
|
||||||
|
|
||||||
# Mesh Tools
|
# Mesh Tools
|
||||||
mesh_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("Tools.mesh_title"), icon='MESH_DATA')
|
||||||
col = mesh_box.column(align=True)
|
col.operator(AvatarToolkit_OT_SelectShortestSeamPath.bl_idname, text=t("Tools.find_shortest_seam_path"), icon="MESH_DATA")
|
||||||
col.label(text=t("Tools.mesh_title"), icon='MESH_DATA')
|
col.operator(AvatarToolkit_OT_ApplyModifierForShapkeyObj.bl_idname, text=t("Tools.apply_modifier_on_shapekey_obj"), icon="SHAPEKEY_DATA")
|
||||||
col.separator(factor=0.5)
|
col.operator(AvatarToolkit_OT_ExplodeMesh.bl_idname, text=t("Tools.explode_mesh"), icon="MOD_EXPLODE")
|
||||||
col.operator(AvatarToolkit_OT_SelectShortestSeamPath.bl_idname,text=t("Tools.find_shortest_seam_path"),icon="MESH_DATA")
|
|
||||||
col.operator(AvatarToolkit_OT_ApplyModifierForShapkeyObj.bl_idname,text=t("Tools.apply_modifier_on_shapekey_obj"),icon="SHAPEKEY_DATA")
|
|
||||||
col.operator(AvatarToolkit_OT_ExplodeMesh.bl_idname,text=t("Tools.explode_mesh"),icon="MOD_EXPLODE")
|
|
||||||
|
|
||||||
|
|
||||||
# Standardization Tools
|
# Standardization Tools
|
||||||
standardize_box: UILayout = bone_box.box()
|
col = draw_section_header(layout, t("Tools.standardize_title"), icon='OUTLINER_OB_ARMATURE')
|
||||||
col = standardize_box.column(align=True)
|
|
||||||
col.label(text=t("Tools.standardize_title"), icon='OUTLINER_OB_ARMATURE')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
col.operator(AvatarToolkit_OT_StandardizeArmature.bl_idname, icon='CHECKMARK')
|
col.operator(AvatarToolkit_OT_StandardizeArmature.bl_idname, icon='CHECKMARK')
|
||||||
|
|
||||||
# Weight Tools
|
# Weight Tools
|
||||||
weight_box: UILayout = bone_box.box()
|
col = draw_section_header(layout, t("Tools.weight_title"), icon='GROUP_BONE')
|
||||||
col = weight_box.column(align=True)
|
|
||||||
col.prop(toolkit, "merge_twist_bones", text=t("Tools.merge_twist_bones"))
|
col.prop(toolkit, "merge_twist_bones", text=t("Tools.merge_twist_bones"))
|
||||||
col.prop(toolkit, "preserve_parent_bones")
|
col.prop(toolkit, "preserve_parent_bones")
|
||||||
col.prop(toolkit, "target_bone_type")
|
col.prop(toolkit, "target_bone_type")
|
||||||
col.prop(toolkit, "list_only_mode")
|
col.prop(toolkit, "list_only_mode")
|
||||||
|
|
||||||
if toolkit.list_only_mode and len(toolkit.zero_weight_bones) > 0:
|
if toolkit.list_only_mode and len(toolkit.zero_weight_bones) > 0:
|
||||||
box = weight_box.box()
|
sub_col = col.box()
|
||||||
row = box.row()
|
row = sub_col.row()
|
||||||
row.template_list("AVATAR_TOOLKIT_UL_ZeroWeightBones", "",
|
row.template_list("AVATAR_TOOLKIT_UL_ZeroWeightBones", "",
|
||||||
toolkit, "zero_weight_bones",
|
toolkit, "zero_weight_bones",
|
||||||
toolkit, "zero_weight_bones_index")
|
toolkit, "zero_weight_bones_index")
|
||||||
|
|
||||||
col = box.column(align=True)
|
sub_col.operator(AvatarToolKit_OT_RemoveSelectedBones.bl_idname,
|
||||||
col.operator(AvatarToolKit_OT_RemoveSelectedBones.bl_idname,
|
|
||||||
text=t("Tools.remove_selected_bones"))
|
text=t("Tools.remove_selected_bones"))
|
||||||
|
|
||||||
row = col.row(align=True)
|
# Combine weight
|
||||||
row.operator(AvatarToolKit_OT_RemoveZeroWeightBones.bl_idname, text=t("Tools.clean_weights"), icon='GROUP_BONE')
|
draw_operator_row(col, [
|
||||||
row.operator(AvatarToolKit_OT_DeleteBoneConstraints.bl_idname, text=t("Tools.clean_constraints"), icon='CONSTRAINT_BONE')
|
(AvatarToolKit_OT_RemoveZeroWeightBones.bl_idname, t("Tools.clean_weights"), 'GROUP_BONE'),
|
||||||
row = col.row(align=True)
|
(AvatarToolKit_OT_DeleteBoneConstraints.bl_idname, t("Tools.clean_constraints"), 'CONSTRAINT_BONE')
|
||||||
row.operator(AvatarToolKit_OT_RemoveZeroWeightVertexGroups.bl_idname, text=t("Tools.clean_vertex_groups"), icon='CONSTRAINT_BONE')
|
])
|
||||||
|
col.operator(AvatarToolKit_OT_RemoveZeroWeightVertexGroups.bl_idname, text=t("Tools.clean_vertex_groups"), icon='CONSTRAINT_BONE')
|
||||||
|
|
||||||
# Merge Tools
|
# Merge Tools
|
||||||
merge_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("Tools.merge_title"), icon='AUTOMERGE_ON')
|
||||||
col = merge_box.column(align=True)
|
draw_operator_row(col, [
|
||||||
col.label(text=t("Tools.merge_title"), icon='AUTOMERGE_ON')
|
(AvatarToolkit_OT_MergeToActive.bl_idname, t("Tools.merge_to_active"), 'BONE_DATA'),
|
||||||
col.separator(factor=0.5)
|
(AvatarToolkit_OT_MergeToParent.bl_idname, t("Tools.merge_to_parent"), 'BONE_DATA')
|
||||||
row = col.row(align=True)
|
])
|
||||||
row.operator(AvatarToolkit_OT_MergeToActive.bl_idname, text=t("Tools.merge_to_active"), icon='BONE_DATA')
|
|
||||||
row.operator(AvatarToolkit_OT_MergeToParent.bl_idname, text=t("Tools.merge_to_parent"), icon='BONE_DATA')
|
|
||||||
col.operator(AvatarToolkit_OT_ConnectBones.bl_idname, text=t("Tools.connect_bones"), icon='BONE_DATA')
|
col.operator(AvatarToolkit_OT_ConnectBones.bl_idname, text=t("Tools.connect_bones"), icon='BONE_DATA')
|
||||||
|
|
||||||
# Additional Tools
|
# Additional Tools
|
||||||
extra_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("Tools.additional_title"), icon='TOOL_SETTINGS')
|
||||||
col = extra_box.column(align=True)
|
|
||||||
col.label(text=t("Tools.additional_title"), icon='TOOL_SETTINGS')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
col.operator(AvatarToolkit_OT_ApplyTransforms.bl_idname, text=t("Tools.apply_transforms"), icon='OBJECT_DATA')
|
col.operator(AvatarToolkit_OT_ApplyTransforms.bl_idname, text=t("Tools.apply_transforms"), icon='OBJECT_DATA')
|
||||||
col.operator(AvatarToolkit_OT_CleanShapekeys.bl_idname, text=t("Tools.clean_shapekeys"), icon='SHAPEKEY_DATA')
|
col.operator(AvatarToolkit_OT_CleanShapekeys.bl_idname, text=t("Tools.clean_shapekeys"), icon='SHAPEKEY_DATA')
|
||||||
|
|
||||||
# Rigify Tools
|
# Rigify Tools
|
||||||
rigify_box: UILayout = layout.box()
|
col = draw_section_header(layout, t("Tools.rigify_title"), icon='ARMATURE_DATA')
|
||||||
col = rigify_box.column(align=True)
|
|
||||||
col.label(text=t("Tools.rigify_title"), icon='ARMATURE_DATA')
|
|
||||||
col.separator(factor=0.5)
|
|
||||||
col.operator(AvatarToolkit_OT_ConvertRigifyToUnity.bl_idname, icon='ARMATURE_DATA')
|
col.operator(AvatarToolkit_OT_ConvertRigifyToUnity.bl_idname, icon='ARMATURE_DATA')
|
||||||
col.prop(context.scene.avatar_toolkit, "merge_twist_bones")
|
col.prop(context.scene.avatar_toolkit, "merge_twist_bones")
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from bpy.types import (
|
|||||||
Object
|
Object
|
||||||
)
|
)
|
||||||
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||||
|
from .panel_layout import get_panel_order, should_open_by_default
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
from ..core.logging_setup import logger
|
from ..core.logging_setup import logger
|
||||||
from ..core.common import get_active_armature, ProgressTracker
|
from ..core.common import get_active_armature, ProgressTracker
|
||||||
@@ -465,8 +466,8 @@ class AvatarToolKit_PT_TranslationPanel(Panel):
|
|||||||
bl_region_type: str = 'UI'
|
bl_region_type: str = 'UI'
|
||||||
bl_category: str = CATEGORY_NAME
|
bl_category: str = CATEGORY_NAME
|
||||||
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
||||||
bl_order: int = 9
|
bl_order: int = get_panel_order('translation')
|
||||||
bl_options = {'DEFAULT_CLOSED'}
|
bl_options = set() if not should_open_by_default('TRANSLATION') else {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
"""Draw the translation panel layout"""
|
"""Draw the translation panel layout"""
|
||||||
|
|||||||
+137
@@ -0,0 +1,137 @@
|
|||||||
|
"""UI utilities and styling helpers for consistent Avatar Toolkit panel design"""
|
||||||
|
|
||||||
|
from typing import Callable, Optional
|
||||||
|
from bpy.types import UILayout, Context, Operator
|
||||||
|
|
||||||
|
|
||||||
|
class UIStyle:
|
||||||
|
"""Centralized UI styling constants for consistent appearance"""
|
||||||
|
|
||||||
|
SECTION_SEPARATOR_FACTOR: float = 0.5
|
||||||
|
SUBSECTION_SEPARATOR_FACTOR: float = 0.3
|
||||||
|
PRIMARY_BUTTON_SCALE: float = 1.5
|
||||||
|
STANDARD_BUTTON_SCALE: float = 1.0
|
||||||
|
COMPACT_BUTTON_SCALE: float = 0.9
|
||||||
|
DEFAULT_PADDING: float = 1.0
|
||||||
|
COMPACT_PADDING: float = 0.5
|
||||||
|
|
||||||
|
CATEGORY_ICONS = {
|
||||||
|
'optimization': 'MOD_SMOOTH',
|
||||||
|
'tools': 'TOOL_SETTINGS',
|
||||||
|
'custom': 'TOOL_OPTIONS',
|
||||||
|
'eye_tracking': 'OBJECT_CAMERA',
|
||||||
|
'settings': 'PREFERENCES',
|
||||||
|
'import_export': 'EXPORT',
|
||||||
|
'pose': 'POSE_HLT',
|
||||||
|
'materials': 'MATERIAL',
|
||||||
|
'mesh': 'MESH_DATA',
|
||||||
|
'bones': 'BONE_DATA',
|
||||||
|
'vfx': 'MOD_DISPLACE'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def draw_section_header(layout: UILayout, title: str, icon: str = 'NONE', separator: bool = True) -> UILayout:
|
||||||
|
"""Draw a consistent section header with optional icon and separator"""
|
||||||
|
header_box = layout.box()
|
||||||
|
col = header_box.column(align=True)
|
||||||
|
row = col.row()
|
||||||
|
row.scale_y = 1.2
|
||||||
|
row.label(text=title, icon=icon)
|
||||||
|
|
||||||
|
if separator:
|
||||||
|
col.separator(factor=UIStyle.SECTION_SEPARATOR_FACTOR)
|
||||||
|
|
||||||
|
return col
|
||||||
|
|
||||||
|
|
||||||
|
def draw_subsection(layout: UILayout, title: str, icon: str = 'NONE') -> UILayout:
|
||||||
|
"""Draw a subsection with reduced visual weight (no box)"""
|
||||||
|
col = layout.column(align=True)
|
||||||
|
row = col.row()
|
||||||
|
row.label(text=title, icon=icon)
|
||||||
|
col.separator(factor=UIStyle.SUBSECTION_SEPARATOR_FACTOR)
|
||||||
|
return col
|
||||||
|
|
||||||
|
|
||||||
|
def draw_info_text(layout: UILayout, text: str, icon: str = 'INFO') -> None:
|
||||||
|
"""Draw informational text that can wrap (replaces multiple labels)"""
|
||||||
|
col = layout.column()
|
||||||
|
col.alert = False
|
||||||
|
# Split long text for wrapping
|
||||||
|
row = col.row()
|
||||||
|
row.label(text=text, icon=icon)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_warning_text(layout: UILayout, text: str) -> None:
|
||||||
|
"""Draw warning-styled text"""
|
||||||
|
col = layout.column()
|
||||||
|
col.alert = True
|
||||||
|
row = col.row()
|
||||||
|
row.label(text=text, icon='ERROR')
|
||||||
|
|
||||||
|
|
||||||
|
def draw_primary_button(layout: UILayout, operator_idname: str, text: str = "",
|
||||||
|
icon: str = 'NONE', **kwargs) -> None:
|
||||||
|
"""Draw a primary action button with standard scaling"""
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.scale_y = UIStyle.PRIMARY_BUTTON_SCALE
|
||||||
|
row.operator(operator_idname, text=text, icon=icon, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_operator_row(layout: UILayout, operators: list[tuple[str, str, str]],
|
||||||
|
scale_y: float = 1.0, equal_width: bool = True) -> None:
|
||||||
|
"""Draw multiple operators in a single row with consistent sizing"""
|
||||||
|
if not operators:
|
||||||
|
return
|
||||||
|
|
||||||
|
row = layout.row(align=equal_width)
|
||||||
|
row.scale_y = scale_y
|
||||||
|
|
||||||
|
for op_id, text, icon in operators:
|
||||||
|
row.operator(op_id, text=text, icon=icon)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_collapsible_section(layout: UILayout, title: str, icon: str,
|
||||||
|
draw_func: Callable[[UILayout], None],
|
||||||
|
context: Context, storage_attr: str) -> None:
|
||||||
|
"""Draw a collapsible section (using context scene properties for state)"""
|
||||||
|
col = layout.column(align=True)
|
||||||
|
row = col.row()
|
||||||
|
|
||||||
|
scene = context.scene
|
||||||
|
attr_name = f"_ui_expand_{storage_attr}"
|
||||||
|
is_expanded = getattr(scene, attr_name, False)
|
||||||
|
icon_name = 'DISCLOSURE_TRI_DOWN' if is_expanded else 'DISCLOSURE_TRI_RIGHT'
|
||||||
|
row.prop(scene, attr_name, text="", icon=icon_name, emboss=False)
|
||||||
|
row.label(text=title, icon=icon)
|
||||||
|
|
||||||
|
if is_expanded:
|
||||||
|
col.separator(factor=UIStyle.SUBSECTION_SEPARATOR_FACTOR)
|
||||||
|
draw_func(col)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_operator_disable_feedback(operator: Operator, layout: UILayout,
|
||||||
|
is_disabled: bool, reason: str = "") -> UILayout:
|
||||||
|
"""Prepare layout for disabled operator with visual feedback"""
|
||||||
|
if is_disabled:
|
||||||
|
layout.enabled = False
|
||||||
|
return layout
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_text_label(layout: UILayout, text: str, max_length: int = 50) -> None:
|
||||||
|
"""Draw a label that wraps long text across multiple lines"""
|
||||||
|
words = text.split()
|
||||||
|
current_line = ""
|
||||||
|
|
||||||
|
col = layout.column()
|
||||||
|
|
||||||
|
for word in words:
|
||||||
|
test_line = (current_line + " " + word).strip()
|
||||||
|
if len(test_line) > max_length and current_line:
|
||||||
|
col.label(text=current_line)
|
||||||
|
current_line = word
|
||||||
|
else:
|
||||||
|
current_line = test_line
|
||||||
|
|
||||||
|
if current_line:
|
||||||
|
col.label(text=current_line)
|
||||||
+3
-2
@@ -2,6 +2,7 @@ import bpy
|
|||||||
from bpy.types import Panel, Context, UILayout, Object, ShapeKey
|
from bpy.types import Panel, Context, UILayout, Object, ShapeKey
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||||
|
from .panel_layout import get_panel_order, should_open_by_default
|
||||||
from ..core.common import get_active_armature
|
from ..core.common import get_active_armature
|
||||||
from ..functions.visemes import AvatarToolkit_OT_PreviewVisemes, AvatarToolkit_OT_CreateVisemes
|
from ..functions.visemes import AvatarToolkit_OT_PreviewVisemes, AvatarToolkit_OT_CreateVisemes
|
||||||
|
|
||||||
@@ -13,8 +14,8 @@ class AvatarToolKit_PT_VisemesPanel(Panel):
|
|||||||
bl_region_type: str = 'UI'
|
bl_region_type: str = 'UI'
|
||||||
bl_category: str = CATEGORY_NAME
|
bl_category: str = CATEGORY_NAME
|
||||||
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
bl_parent_id: str = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
||||||
bl_order: int = 5
|
bl_order: int = get_panel_order('visemes')
|
||||||
bl_options: set[str] = {'DEFAULT_CLOSED'}
|
bl_options: set[str] = set() if not should_open_by_default('VISEMES') else {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
"""Draw the visemes panel interface with shape key selection and preview controls"""
|
"""Draw the visemes panel interface with shape key selection and preview controls"""
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Panel, Context, UILayout
|
from bpy.types import Panel, Context, UILayout
|
||||||
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||||
|
from .panel_layout import get_panel_order, should_open_by_default
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
from ..core.common import get_active_armature
|
from ..core.common import get_active_armature
|
||||||
from ..core.vrm_unity_converter import detect_vrm_armature
|
from ..core.vrm_unity_converter import detect_vrm_armature
|
||||||
@@ -15,8 +16,8 @@ class AvatarToolKit_PT_VRMUnityPanel(Panel):
|
|||||||
bl_region_type = 'UI'
|
bl_region_type = 'UI'
|
||||||
bl_category = CATEGORY_NAME
|
bl_category = CATEGORY_NAME
|
||||||
bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
bl_parent_id = AvatarToolKit_PT_AvatarToolkitPanel.bl_idname
|
||||||
bl_order = 3
|
bl_order = get_panel_order('vrm_unity')
|
||||||
bl_options = {'DEFAULT_CLOSED'}
|
bl_options = set() if not should_open_by_default('VRM_UNITY') else {'DEFAULT_CLOSED'}
|
||||||
|
|
||||||
def draw(self, context: Context) -> None:
|
def draw(self, context: Context) -> None:
|
||||||
"""Draw the VRM to Unity conversion panel interface"""
|
"""Draw the VRM to Unity conversion panel interface"""
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user