fix flip animation
add to menu fix resonite animx importer bug add flip animations add flip animation keyframes to help users rekey and remake animations as if they were mirrored.
This commit is contained in:
+4
-5
@@ -15,10 +15,9 @@ from bpy.types import (Context, Object, Modifier, EditBone, Operator, Material,
|
||||
from functools import lru_cache
|
||||
from bpy.props import PointerProperty, IntProperty, StringProperty
|
||||
from bpy.utils import register_class
|
||||
from ..core.logging_setup import logger
|
||||
from ..core.translations import t
|
||||
from ..core.dictionaries import bone_names
|
||||
from .dictionaries import reverse_bone_lookup, bone_names
|
||||
from .logging_setup import logger
|
||||
from .translations import t
|
||||
from .dictionaries import reverse_bone_lookup, simplify_bonename
|
||||
|
||||
class SceneMatClass(PropertyGroup):
|
||||
mat: PointerProperty(type=Material)
|
||||
@@ -383,7 +382,7 @@ def clear_unused_data_blocks() -> int:
|
||||
if isinstance(getattr(bpy.data, attr), bpy.types.bpy_prop_collection))
|
||||
return initial_count - final_count
|
||||
|
||||
def identify_bones(arm_data: bpy.types.Armature, context: bpy.types.Context) -> Dict[str,str]:
|
||||
def identify_bones(arm_data: bpy.types.Armature) -> Dict[str,str]:
|
||||
"""Identify bone names in an armature based on our reverse dictionary, so there is no confusion to what a bone is.
|
||||
Essentially makes a dictionary of keys from dictionaries.bone_names like "hips", and the corosponding value is the bone that can be mapped to that key."""
|
||||
returned: Dict[str,str] = {}
|
||||
|
||||
@@ -3,7 +3,6 @@ from os import replace
|
||||
from re import S
|
||||
from types import FrameType
|
||||
|
||||
import lz4.block
|
||||
from . import resonite_types
|
||||
from . import common
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ class AvatarToolkit_OT_ConvertResonite(Operator):
|
||||
|
||||
total_bones = len(arm_data.bones)
|
||||
with ProgressTracker(context, total_bones, t("Tools.convert_resonite.operation")) as progress:
|
||||
for key_simple,bone_name in identify_bones(arm_data,context).items():
|
||||
for key_simple,bone_name in identify_bones(arm_data).items():
|
||||
|
||||
if key_simple in resonite_translations:
|
||||
new_name = resonite_translations[key_simple]
|
||||
|
||||
@@ -9,6 +9,7 @@ from ...core.common import (
|
||||
ProgressTracker,
|
||||
restore_bone_transforms,
|
||||
remove_unused_vertex_groups,
|
||||
identify_bones,
|
||||
)
|
||||
from ...core.armature_validation import validate_armature, validate_bone_hierarchy
|
||||
|
||||
@@ -304,3 +305,110 @@ class AvatarToolKit_OT_RemoveSelectedBones(Operator):
|
||||
|
||||
self.report({'INFO'}, t("Tools.bones_removed", count=len(selected_bones)))
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class AvatarToolKit_OT_FlipCurrentKeyFrames(Operator):
|
||||
"""Operator to flip the selected bone keyframes using blender's flip pose."""
|
||||
bl_idname = "avatar_toolkit.flip_pose_frames"
|
||||
bl_label = t("Tools.flip_pose_frames")
|
||||
bl_description = t("Tools.flip_pose_frames_desc")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: Context) -> bool:
|
||||
"""Check if operator can be executed"""
|
||||
armature = get_active_armature(context)
|
||||
if not armature:
|
||||
return False
|
||||
if context.mode != 'POSE':
|
||||
return False
|
||||
if not armature.animation_data:
|
||||
return False
|
||||
valid, _, _ = validate_armature(armature)
|
||||
return valid
|
||||
|
||||
def execute(self, context: Context) -> set[str]:
|
||||
armature = get_active_armature(context)
|
||||
|
||||
|
||||
|
||||
armature_data: bpy.types.Armature = armature.data
|
||||
|
||||
standard_mappings: Dict[str,str] = identify_bones(armature_data)
|
||||
|
||||
|
||||
|
||||
|
||||
# Do we need this? If flipping in the future has issues, then uncommenting this may help - @989onan
|
||||
#To make sure our flip pose is extremely reliable, we're gonna temp rename all bones to standard names to make the posing work.
|
||||
#for standard,bone_name in standard_mappings.items():
|
||||
# armature_data.bones[bone_name].name = standard
|
||||
|
||||
#save our selection
|
||||
selected: list[bool] = [False] * len(armature_data.bones)
|
||||
armature_data.bones.foreach_get("select", selected)
|
||||
#select everything
|
||||
armature_data.bones.foreach_set("select", [False] * len(armature_data.bones))
|
||||
|
||||
|
||||
|
||||
#create a set for every frame time where we need to key a keyframe for the flipped pose
|
||||
times: Dict[float,list[bpy.types.FCurve]] = {}
|
||||
for curve in armature.animation_data.action.fcurves:
|
||||
if not curve.data_path.startswith("pose"):
|
||||
continue
|
||||
for point in curve.keyframe_points:
|
||||
if point.select_control_point:
|
||||
if point.co.x not in times:
|
||||
times[point.co.x] = []
|
||||
|
||||
times[point.co.x].append(curve)
|
||||
|
||||
for time,curves in times.items():
|
||||
context.scene.frame_set(frame=int(time), subframe=float(time-float(int(time))))
|
||||
armature_data.bones.foreach_set("select", [True] * len(armature_data.bones))
|
||||
bpy.ops.pose.copy()
|
||||
armature_data.bones.foreach_set("select", [False] * len(armature_data.bones))
|
||||
bpy.ops.pose.paste(flipped=True,selected_mask=False)
|
||||
|
||||
|
||||
|
||||
|
||||
for curve in curves:
|
||||
|
||||
bone_name: str = curve.data_path.replace("pose.bones[\"","")
|
||||
bone_name = bone_name[:bone_name.index("\"")]
|
||||
|
||||
armature_data.bones[bone_name].select = True
|
||||
|
||||
bpy.ops.pose.select_mirror(extend=False)
|
||||
|
||||
#this can get the opposite side bone's data path and key it, if it is ever needed - @989onan
|
||||
#for bone in armature_data.bones:
|
||||
# if bone.select == True:
|
||||
# bone_name = bone.name
|
||||
# break
|
||||
#new_path = curve.data_path[:curve.data_path.index("[")+1]+"\""+bone_name+"\""+curve.data_path[curve.data_path.index("]"):]
|
||||
|
||||
if armature.keyframe_insert(data_path=curve.data_path, index=curve.array_index, frame=time):
|
||||
#if armature.keyframe_insert(data_path=new_path, index=curve.array_index, frame=time):
|
||||
continue
|
||||
self.report({'ERROR'}, f"Keyframe insertion for key with data path \"{curve.data_path}\" and frame {time} failed!")
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Do we need this? If flipping in the future has issues, then uncommenting this may help - @989onan
|
||||
#bring our names back as to not break their model.
|
||||
#for standard,bone_name in standard_mappings.items():
|
||||
# armature_data.bones[standard].name = bone_name
|
||||
|
||||
# restore selection
|
||||
armature_data.bones.foreach_set("select", selected)
|
||||
return {'FINISHED'}
|
||||
|
||||
@@ -205,6 +205,8 @@
|
||||
"Tools.zero_weight_bones_found": "Zero weight bones found: {bones}",
|
||||
"Tools.remove_selected_bones": "Remove Selected Bones",
|
||||
"Tools.remove_selected_bones_desc": "Remove selected zero weight bones from armature",
|
||||
"Tools.flip_pose_frames": "Flip Selected Armature Key Frames",
|
||||
"Tools.flip_pose_frames_desc": "Takes the selected keyframes and sets them to a mirrored pose, gotten from the opposite side of the armature on that frame.\nSelecting the entire animation's keyframes will flip the entire animation.",
|
||||
"Tools.bones_removed": "Removed {count} bones",
|
||||
"Tools.vertex_groups_removed": "Removed {count} vertex groups.",
|
||||
"Tools.clean_constraints": "Delete Bone Constraints",
|
||||
|
||||
+23
-14
@@ -7,24 +7,18 @@ from ..core.translations import t
|
||||
from ..core.resonite_utils import AvatarToolkit_OT_ConvertResonite
|
||||
from ..functions.tools.mesh_separation import AvatarToolKit_OT_SeparateByLooseParts, AvatarToolKit_OT_SeparateByMaterials
|
||||
from ..functions.tools.additional_tools import AvatarToolkit_OT_ApplyTransforms, AvatarToolkit_OT_CleanShapekeys
|
||||
from ..functions.tools.bone_tools import AvatarToolKit_OT_CreateDigitigradeLegs, AvatarToolKit_OT_DeleteBoneConstraints, AvatarToolKit_OT_RemoveSelectedBones, AvatarToolKit_OT_RemoveZeroWeightBones, AvatarToolKit_OT_RemoveZeroWeightVertexGroups
|
||||
from ..functions.tools.bone_tools import (
|
||||
AvatarToolKit_OT_CreateDigitigradeLegs,
|
||||
AvatarToolKit_OT_DeleteBoneConstraints,
|
||||
AvatarToolKit_OT_RemoveSelectedBones,
|
||||
AvatarToolKit_OT_RemoveZeroWeightBones,
|
||||
AvatarToolKit_OT_RemoveZeroWeightVertexGroups,
|
||||
AvatarToolKit_OT_FlipCurrentKeyFrames
|
||||
)
|
||||
from ..functions.tools.standardize_armature import AvatarToolkit_OT_StandardizeArmature
|
||||
from ..functions.tools.merge_tools import AvatarToolkit_OT_MergeToActive, AvatarToolkit_OT_MergeToParent, AvatarToolkit_OT_ConnectBones
|
||||
from ..functions.tools.rigify_converter import AvatarToolkit_OT_ConvertRigifyToUnity
|
||||
|
||||
|
||||
class AVATAR_TOOLKIT_UL_ZeroWeightBones(UIList):
|
||||
"""UI List for displaying zero weight bones with selection options"""
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||
row = layout.row(align=True)
|
||||
row.prop(item, "selected", text="")
|
||||
row.label(text=item.name)
|
||||
if item.has_children:
|
||||
row.label(text="", icon='OUTLINER_OB_ARMATURE')
|
||||
if item.is_deform:
|
||||
row.label(text="", icon='MOD_ARMATURE')
|
||||
|
||||
class AvatarToolKit_PT_ToolsPanel(Panel):
|
||||
"""Panel containing various tools for avatar customization and optimization"""
|
||||
bl_label: str = t("Tools.label")
|
||||
@@ -63,6 +57,8 @@ class AvatarToolKit_PT_ToolsPanel(Panel):
|
||||
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_FlipCurrentKeyFrames.bl_idname,text=t("Tools.flip_pose_frames"),icon="ACTION")
|
||||
|
||||
|
||||
# Standardization Tools
|
||||
standardize_box: UILayout = bone_box.box()
|
||||
@@ -121,3 +117,16 @@ class AvatarToolKit_PT_ToolsPanel(Panel):
|
||||
col.separator(factor=0.5)
|
||||
col.operator(AvatarToolkit_OT_ConvertRigifyToUnity.bl_idname, icon='ARMATURE_DATA')
|
||||
col.prop(context.scene.avatar_toolkit, "merge_twist_bones")
|
||||
|
||||
|
||||
class AVATAR_TOOLKIT_UL_ZeroWeightBones(UIList):
|
||||
"""UI List for displaying zero weight bones with selection options"""
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||
row = layout.row(align=True)
|
||||
row.prop(item, "selected", text="")
|
||||
row.label(text=item.name)
|
||||
if item.has_children:
|
||||
row.label(text="", icon='OUTLINER_OB_ARMATURE')
|
||||
if item.is_deform:
|
||||
row.label(text="", icon='MOD_ARMATURE')
|
||||
+1
-1
@@ -12,7 +12,7 @@ class AvatarToolKit_PT_UVTools(Panel):
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "UV Tools"
|
||||
bl_parent_id = AvatarToolKit_PT_UVPanel.bl_idname
|
||||
bl_order = 3
|
||||
bl_order = 0
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context: Context) -> None:
|
||||
|
||||
Reference in New Issue
Block a user