Armature Validation P2

- Added Highlight Bone System in the 3D View, can be turned off in settings.
- Added more bones to the acceptable bone lists.
- Fixed issue with properties registrations and unregistration, the system is more rebust now.
- Added a validate t-pose system
- Added a detect bone scales system.
- Fixed some translation strings
- Armature validation now uses logger system.
This commit is contained in:
Yusarina
2025-03-24 02:12:03 +00:00
parent b946041ec1
commit c65bed3ff4
9 changed files with 731 additions and 103 deletions
+128 -48
View File
@@ -18,6 +18,10 @@ from .common import get_armature_list, get_active_armature, get_all_meshes, Scen
from ..functions.visemes import VisemePreview
from ..functions.eye_tracking import set_rotation
class ValidationMessageItem(PropertyGroup):
"""Property group for validation message items"""
name: StringProperty(name="Message")
class ZeroWeightBoneItem(PropertyGroup):
"""Property group for zero weight bone list items"""
name: StringProperty(name="Bone Name")
@@ -25,11 +29,13 @@ class ZeroWeightBoneItem(PropertyGroup):
has_children: BoolProperty(name="Has Children", default=False)
is_deform: BoolProperty(name="Is Deform Bone", default=False)
def update_validation_mode(self: PropertyGroup, context: Context) -> None:
"""Updates validation mode and saves preference"""
logger.info(f"Updating validation mode to: {self.validation_mode}")
save_preference("validation_mode", self.validation_mode)
def update_logging_state(self: PropertyGroup, context: Context) -> None:
"""Updates logging state and configures logging"""
logger.info(f"Updating logging state to: {self.enable_logging}")
@@ -37,11 +43,19 @@ def update_logging_state(self: PropertyGroup, context: Context) -> None:
from .logging_setup import configure_logging
configure_logging(self.enable_logging)
def update_shape_intensity(self: PropertyGroup, context: Context) -> None:
"""Updates shape key intensity and refreshes preview"""
if self.viseme_preview_mode:
VisemePreview.update_preview(context)
def highlight_problem_bones(self: PropertyGroup, context: Context) -> None:
"""Updates problem bone highlighting state and saves preference"""
logger.info(f"Updating problem bone highlighting to: {self.highlight_problem_bones}")
save_preference("highlight_problem_bones", self.highlight_problem_bones)
class AvatarToolkitSceneProperties(PropertyGroup):
"""Property group containing Avatar Toolkit scene-level settings and properties"""
@@ -70,8 +84,8 @@ class AvatarToolkitSceneProperties(PropertyGroup):
if self.use_nodes:
Object.Enum = [((i.image.name if i.image else i.name+"_image"),
(i.image.name if i.image else "node with no image..."),
(i.image.name if i.image else i.name),index+1)
for index,i in enumerate(self.node_tree.nodes)
(i.image.name if i.image else i.name), index+1)
for index, i in enumerate(self.node_tree.nodes)
if i.bl_idname == "ShaderNodeTexImage"]
if not len(Object.Enum):
Object.Enum = [(t("TextureAtlas.error.label"),
@@ -285,38 +299,37 @@ class AvatarToolkitSceneProperties(PropertyGroup):
)
viseme_preview_selection: EnumProperty(
name=t("Visemes.preview_selection"),
description=t("Visemes.preview_selection_desc"),
items=[
('vrc.v_aa', 'AA', 'A as in "bat"'),
('vrc.v_ch', 'CH', 'Ch as in "choose"'),
('vrc.v_dd', 'DD', 'D as in "dog"'),
('vrc.v_ih', 'IH', 'I as in "bit"'),
('vrc.v_ff', 'FF', 'F as in "fox"'),
('vrc.v_e', 'E', 'E as in "bet"'),
('vrc.v_kk', 'KK', 'K as in "cat"'),
('vrc.v_nn', 'NN', 'N as in "net"'),
('vrc.v_oh', 'OH', 'O as in "hot"'),
('vrc.v_ou', 'OU', 'O as in "go"'),
('vrc.v_pp', 'PP', 'P as in "pat"'),
('vrc.v_rr', 'RR', 'R as in "red"'),
('vrc.v_sil', 'SIL', 'Silence'),
('vrc.v_ss', 'SS', 'S as in "sit"'),
('vrc.v_th', 'TH', 'Th as in "think"')
],
update=lambda s, c: VisemePreview.update_preview(c)
)
name=t("Visemes.preview_selection"),
description=t("Visemes.preview_selection_desc"),
items=[
('vrc.v_aa', 'AA', 'A as in "bat"'),
('vrc.v_ch', 'CH', 'Ch as in "choose"'),
('vrc.v_dd', 'DD', 'D as in "dog"'),
('vrc.v_ih', 'IH', 'I as in "bit"'),
('vrc.v_ff', 'FF', 'F as in "fox"'),
('vrc.v_e', 'E', 'E as in "bet"'),
('vrc.v_kk', 'KK', 'K as in "cat"'),
('vrc.v_nn', 'NN', 'N as in "net"'),
('vrc.v_oh', 'OH', 'O as in "hot"'),
('vrc.v_ou', 'OU', 'O as in "go"'),
('vrc.v_pp', 'PP', 'P as in "pat"'),
('vrc.v_rr', 'RR', 'R as in "red"'),
('vrc.v_sil', 'SIL', 'Silence'),
('vrc.v_ss', 'SS', 'S as in "sit"'),
('vrc.v_th', 'TH', 'Th as in "think"')
],
update=lambda s, c: VisemePreview.update_preview(c)
)
eye_tracking_type: EnumProperty(
name=t("EyeTracking.type"),
description=t("EyeTracking.type_desc"),
items=[
('AV3', t("EyeTracking.type.av3"), t("EyeTracking.type.av3_desc")),
('SDK2', t("EyeTracking.type.sdk2"), t("EyeTracking.type.sdk2_desc"))
],
default='AV3'
)
name=t("EyeTracking.type"),
description=t("EyeTracking.type_desc"),
items=[
('AV3', t("EyeTracking.type.av3"), t("EyeTracking.type.av3_desc")),
('SDK2', t("EyeTracking.type.sdk2"), t("EyeTracking.type.sdk2_desc"))
],
default='AV3'
)
eye_mode: EnumProperty(
name=t("EyeTracking.mode"),
@@ -518,33 +531,100 @@ class AvatarToolkitSceneProperties(PropertyGroup):
description=t("Tools.merge_twist_bones_desc"),
default=True
)
highlight_problem_bones: BoolProperty(
name=t("Settings.highlight_problem_bones"),
description=t("Settings.highlight_problem_bones_desc"),
default=get_preference("highlight_problem_bones", True),
update=highlight_problem_bones
)
show_scale_issues: BoolProperty(
name="Show Scale Issues",
default=False
)
tpose_validation_result: BoolProperty(
name="T-Pose Validation Result",
default=True
)
tpose_validation_messages: CollectionProperty(
type=bpy.types.PropertyGroup,
name="T-Pose Validation Messages"
)
show_tpose_validation: BoolProperty(
name="Show T-Pose Validation Results",
default=False
)
def register() -> None:
"""Register the Avatar Toolkit property group"""
logger.info("Registering Avatar Toolkit properties")
# Clear any existing registrations to prevent conflicts
if hasattr(bpy.types.Scene, "avatar_toolkit"):
try:
del bpy.types.Scene.avatar_toolkit
except:
logger.warning("Failed to remove existing avatar_toolkit property")
# Register classes
try:
# Try to register all classes at once
bpy.utils.register_class(ZeroWeightBoneItem)
bpy.utils.register_class(ValidationMessageItem)
bpy.utils.register_class(AvatarToolkitSceneProperties)
except ValueError:
# Class already registered, we can continue
pass
except ValueError as e:
logger.warning(f"Class registration issue: {e}")
# Try to unregister first in case they're already registered
try:
# Try to unregister in reverse order
try:
bpy.utils.unregister_class(AvatarToolkitSceneProperties)
except:
pass
try:
bpy.utils.unregister_class(ValidationMessageItem)
except:
pass
try:
bpy.utils.unregister_class(ZeroWeightBoneItem)
except:
pass
# Then register again
bpy.utils.register_class(ZeroWeightBoneItem)
bpy.utils.register_class(ValidationMessageItem)
bpy.utils.register_class(AvatarToolkitSceneProperties)
except Exception as e:
logger.error(f"Failed to recover from registration error: {e}")
raise
# Register the property
bpy.types.Scene.avatar_toolkit = PointerProperty(type=AvatarToolkitSceneProperties)
logger.debug("Properties registered successfully")
def unregister() -> None:
"""Unregister the Avatar Toolkit property group"""
logger.info("Unregistering Avatar Toolkit properties")
# Remove the property first
if hasattr(bpy.types.Scene, "avatar_toolkit"):
try:
del bpy.types.Scene.avatar_toolkit
logger.debug("Removed avatar_toolkit property")
except Exception as e:
logger.warning(f"Failed to remove avatar_toolkit property: {e}")
# Then unregister the classes
try:
del bpy.types.Scene.avatar_toolkit
except:
pass
try:
bpy.utils.unregister_class(ZeroWeightBoneItem)
bpy.utils.unregister_class(AvatarToolkitSceneProperties)
except RuntimeError:
pass
logger.debug("Properties unregistered successfully")
bpy.utils.unregister_class(ValidationMessageItem)
bpy.utils.unregister_class(ZeroWeightBoneItem)
logger.debug("Unregistered property classes")
except (RuntimeError, ValueError) as e:
logger.warning(f"Error during property class unregistration: {e}")
# Not fatal - continue