2025-12-01
This commit is contained in:
@@ -20,11 +20,10 @@
|
||||
bl_info = {
|
||||
"name": "Animation Layers",
|
||||
"author": "Tal Hershkovich",
|
||||
"version" : (2, 1, 8, 8),
|
||||
"version" : (2, 3, 4),
|
||||
"blender" : (3, 2, 0),
|
||||
"location": "View3D - Properties - Animation Panel",
|
||||
"description": "Simplifying the NLA editor into an animation layers UI and workflow",
|
||||
#"warning": "New Branch of Animation Layers, Do not use below Blender v3.2",
|
||||
"wiki_url": "",
|
||||
"category": "Animation"}
|
||||
|
||||
@@ -66,7 +65,11 @@ class AnimLayersSceneSettings(bpy.types.PropertyGroup):
|
||||
]
|
||||
)
|
||||
|
||||
viewlayer_objects: bpy.props.IntProperty(name='View Layer Objects', description='checking if objects were turned on or off from the view Layers', default=0)
|
||||
influence: bpy.props.FloatProperty(name="Layer Influence", description="Layer Influence", min = 0.0, options={'ANIMATABLE'}, max = 1.0, default = 1.0, precision = 3, update = anim_layers.influence_update)
|
||||
influence_settings: bpy.props.BoolProperty(name="Influence Settings", description="Opens Influence settings menu", default=False)
|
||||
influence_global: bpy.props.BoolProperty(name="Influence Global/Local", description="Influence options affect current layer or all layers", default=False)
|
||||
edit_all_layers_op: bpy.props.BoolProperty(name="Edit All Layers Check Property", description="Operator to check if edit all layers is running", default=False)
|
||||
|
||||
|
||||
class AnimLayersSettings(bpy.types.PropertyGroup):
|
||||
turn_on: bpy.props.BoolProperty(name="Turn Animation Layers On", description="Turn on and start Animation Layers", default=False, options={'HIDDEN'}, update = anim_layers.turn_animlayers_on, override = {'LIBRARY_OVERRIDABLE'})
|
||||
@@ -75,7 +78,7 @@ class AnimLayersSettings(bpy.types.PropertyGroup):
|
||||
|
||||
#Bake settings
|
||||
smartbake: bpy.props.BoolProperty(name="Smart Bake", description="Stay with the same amount of keyframes after merging and baking", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
onlyselected: bpy.props.BoolProperty(name="Only selected Bones", description="Bake only selected Armature controls", default=True, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
onlyselected: bpy.props.BoolProperty(name="Only selected Bones", description="Bake only selected Armature controls", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
clearconstraints: bpy.props.BoolProperty(name="Clear constraints", description="Clear constraints during bake", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mergefcurves: bpy.props.BoolProperty(name="Merge Cyclic & Fcurve modifiers", description="Include Fcurve modifiers in the bake", default = True, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
@@ -102,22 +105,27 @@ class AnimLayersSettings(bpy.types.PropertyGroup):
|
||||
auto_blend: bpy.props.BoolProperty(name="Auto Blend", description="Apply blend type automatically based on scale and rotation values", default = False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
fcurves: bpy.props.IntProperty(name='fcurves', description='helper to check if fcurves are changed', default=0, override = {'LIBRARY_OVERRIDABLE'})
|
||||
upper_stack : bpy.props.BoolProperty(name="Upper Stack Evaluation", description="Checks if tweak mode uses upper stack", default=False, override = {'LIBRARY_OVERRIDABLE'})
|
||||
viewlayer : bpy.props.BoolProperty(name="View Layer Exclusion", description="Check if the object was added or removed from the view layer", default=True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
#tools
|
||||
|
||||
#Tools
|
||||
inbetweener : bpy.props.FloatProperty(name='Inbetween Keyframe', description="Adds an inbetween Keyframe between the Layer's neighbor keyframes", soft_min = 0, soft_max = 1, default=0.5, options = set(), override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.add_inbetween_key)
|
||||
share_layer_keys: bpy.props.EnumProperty(name = 'Share Layer Keys', description="Share keyframes positions between layers", items = anim_layers.share_layerkeys_items, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
influence_hide: bpy.props.BoolProperty(name="Hide Influence", description="Hide Influence Fcurves", default=False, update = anim_layers.influence_hide_keyframes, override = {'LIBRARY_OVERRIDABLE'})
|
||||
influence_lock: bpy.props.BoolProperty(name="Lock Influence", description="Lock Influence Fcurves", default=False, update = anim_layers.influence_lock_keyframes, override = {'LIBRARY_OVERRIDABLE'})
|
||||
influence_mute: bpy.props.BoolProperty(name="Mute Influence", description="Mute Influence Fcurves", default=False, update = anim_layers.influence_mute_fcurves, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
class AnimLayersItems(bpy.types.PropertyGroup):
|
||||
name: bpy.props.StringProperty(name="AnimLayer", override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_name_update)
|
||||
mute: bpy.props.BoolProperty(name="Mute", description="Mute Animation Layer", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_mute)
|
||||
lock: bpy.props.BoolProperty(name="Lock", description="Lock Animation Layer", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_lock)
|
||||
solo: bpy.props.BoolProperty(name="Solo", description="Solo Animation Layer", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_solo)
|
||||
influence: bpy.props.FloatProperty(name="Layer Influence", description="Layer Influence", min = 0.0, options={'ANIMATABLE'}, max = 1.0, default = 1.0, precision = 3, update = anim_layers.influence_update, override = {'LIBRARY_OVERRIDABLE'})
|
||||
influence_mute: bpy.props.BoolProperty(name="Animated Influence", description="Turn Animated influence On/Off", default=False, options={'HIDDEN'}, update = anim_layers.influence_mute_update, override = {'LIBRARY_OVERRIDABLE'})
|
||||
#action_list: bpy.props.EnumProperty(name = 'Actions', description = "Select action", update = anim_layers.load_action, items = anim_layers.action_items, override = {'LIBRARY_OVERRIDABLE'})
|
||||
influence: bpy.props.FloatProperty(name="Layer Influence", description="Layer Influence", min = 0.0, options={'ANIMATABLE'}, max = 1.0, default = 1.0, precision = 3, update = anim_layers.influence_update) #
|
||||
|
||||
action: bpy.props.PointerProperty(name = 'action', description = "Select action", type=bpy.types.Action, update = anim_layers.load_action, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
action_range: bpy.props.FloatVectorProperty(name='action range', description="used to check if layer needs to update frame range", override = {'LIBRARY_OVERRIDABLE'}, size = 2)
|
||||
frame_range: bpy.props.BoolProperty(name="Custom Frame Range", description="Use a custom frame range per layer instead of the scene frame range", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_frame_range)
|
||||
custom_frame_range: bpy.props.BoolProperty(name="Custom Frame Range", description="Use a custom frame range per layer instead of the scene frame range", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_frame_range)
|
||||
|
||||
frame_start: bpy.props.FloatProperty(name='Action Start Frame', description="First frame of the layer's action",min = 0, default=0, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_frame_start)
|
||||
frame_end: bpy.props.FloatProperty(name='Action End Frame', description="End frame of the layer's action", default=0, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_frame_end)
|
||||
speed: bpy.props.FloatProperty(name='Speed of the action', description="Speed of the action strip", default = 1, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_speed)
|
||||
@@ -129,12 +137,15 @@ class AnimLayersObjects(bpy.types.PropertyGroup):
|
||||
|
||||
object: bpy.props.PointerProperty(name = "object", description = "objects with animation layers turned on", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
|
||||
|
||||
# Add-ons Preferences Update Panel
|
||||
# Define Panel classes for updating
|
||||
panels = (anim_layers.ANIMLAYERS_PT_List, anim_layers.ANIMLAYERS_PT_Ops, anim_layers.ANIMLAYERS_PT_Tools, anim_layers.ANIMLAYERS_PT_Settings) #anim_layers.ANIMLAYERS_PT_Panel, anim_layers.ANIMLAYERS_PT_Multikey,
|
||||
|
||||
panels = anim_layers.panel_classes
|
||||
def update_panel(self, context):
|
||||
anim_layers.unregister_panels()
|
||||
anim_layers.register_panels()
|
||||
|
||||
message = "AnimationLayers: Updating Panel locations has failed"
|
||||
try:
|
||||
for panel in panels:
|
||||
@@ -165,6 +176,8 @@ class AnimLayersAddonPreferences(bpy.types.AddonPreferences):
|
||||
items = [('ANIMLAYERS', 'Anim Layers Settings', 'Use Anim Layers properties to adjust custom frame range'),
|
||||
('NLA', 'NLA Settings', 'Use the nla properties to adjust custom frame range')])
|
||||
|
||||
lock_nlatracks: bpy.props.BoolProperty(name="Automatically lock the nla tracks for safety measures", description="Automatically lock nla tracks when creating layers for safety", default = True)
|
||||
|
||||
#Property for ClearActiveAction
|
||||
proceed: bpy.props.EnumProperty(name="Choose how to proceed", description="Select an option how to proceed with Anim Layers", override = {'LIBRARY_OVERRIDABLE'},
|
||||
items = [
|
||||
@@ -172,6 +185,15 @@ class AnimLayersAddonPreferences(bpy.types.AddonPreferences):
|
||||
( 'REMOVE_ACTION', 'Remove current action and reload older Layers', 'Remove current action and continue with the previous layers', 1),
|
||||
('ADD_ACTION', 'Add the current action as a new Layer', 'Keep previous Anim Layers and Add the active action as a new layer', 2),])
|
||||
|
||||
enabled_editors: bpy.props.EnumProperty(
|
||||
name="Enabled Editors",
|
||||
description="Select which editors should show animation layers panels",
|
||||
items=[('VIEW_3D', "3D View", ""), ('GRAPH_EDITOR', "Graph Editor", ""), ('DOPESHEET_EDITOR', "Dope Sheet", ""),('NLA_EDITOR', "NLA Editor", ""),],
|
||||
options={'ENUM_FLAG'},
|
||||
default={'VIEW_3D', 'NLA_EDITOR', 'DOPESHEET_EDITOR', 'GRAPH_EDITOR'},
|
||||
update=update_panel
|
||||
)
|
||||
|
||||
category: bpy.props.StringProperty(
|
||||
name="Tab Category",
|
||||
description="Choose a name for the category of the panel",
|
||||
@@ -183,7 +205,7 @@ class AnimLayersAddonPreferences(bpy.types.AddonPreferences):
|
||||
auto_check_update: bpy.props.BoolProperty(
|
||||
name = "Auto-check for Update",
|
||||
description = "If enabled, auto-check for updates using an interval",
|
||||
default = False,
|
||||
default = True,
|
||||
)
|
||||
|
||||
updater_interval_months: bpy.props.IntProperty(
|
||||
@@ -222,14 +244,14 @@ class AnimLayersAddonPreferences(bpy.types.AddonPreferences):
|
||||
|
||||
col.label(text="Tab Category:")
|
||||
col.prop(self, "category", text="")
|
||||
row = layout.row()
|
||||
row.prop(self, "enabled_editors")
|
||||
col = layout.column()
|
||||
col.separator(factor = 1)
|
||||
col = col.box()
|
||||
col.label(text="Defaults:")
|
||||
#row = layout.row()
|
||||
split = col.split(factor=0.5, align = True)
|
||||
split.prop(self, "auto_rename")
|
||||
#row.prop(self, "blend_type")
|
||||
#split = row.split(factor=0.7, align = True)
|
||||
|
||||
split.label(text="Default Blend Type: ")
|
||||
split.prop(self,'blend_type', text = '')
|
||||
@@ -237,11 +259,12 @@ class AnimLayersAddonPreferences(bpy.types.AddonPreferences):
|
||||
row = col.row()
|
||||
row.label(text = "Custom Frame Range Settings")
|
||||
row.prop(self, "frame_range_settings", text = '')
|
||||
#row = layout.row(align = True)
|
||||
#col = layout.row(bpy.context.scene.als,'blend_type', text = '')
|
||||
|
||||
col.prop(self, "lock_nlatracks")
|
||||
|
||||
|
||||
classes = (AnimLayersSettings, AnimLayersSceneSettings, AnimLayersItems, AnimLayersObjects)
|
||||
|
||||
|
||||
addon_keymaps = []
|
||||
|
||||
def register():
|
||||
|
||||
@@ -1374,9 +1374,9 @@ def register(bl_info):
|
||||
# **WARNING** Depending on the engine, this token can act like a password!!
|
||||
# Only provide a token if the project is *non-public*, see readme for
|
||||
# other considerations and suggestions from a security standpoint
|
||||
# updater.private_token = "kgsE5RsxTzDvQRs9P_Yg" # "tokenstring"
|
||||
updater.private_token = "glpat-K6GjFFka_J6o3GTbe3JK"
|
||||
|
||||
# "tokenstring" Need read api and read repo permission
|
||||
# updater.private_token = "glpat-K6GjFFka_J6o3GTbe3JK"
|
||||
updater.private_token = "glpat-js__ikigVSQ_tKWgjVn1"
|
||||
# choose your own username, must match website (not needed for GitLab)
|
||||
updater.user = ""
|
||||
|
||||
@@ -1508,7 +1508,7 @@ def register(bl_info):
|
||||
|
||||
# max install (<) will install strictly anything lower
|
||||
# updater.version_max_update = (9,9,9)
|
||||
updater.version_max_update = (2,1,8,7) # set to None if not wanting to set max
|
||||
updater.version_max_update = None # set to None if not wanting to set max
|
||||
|
||||
# Function defined above, customize as appropriate per repository
|
||||
updater.skip_tag = skip_tag_function # min and max used in this function
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"last_check": "2025-05-28 13:56:15.908215",
|
||||
"last_check": "2025-11-26 10:56:41.294043",
|
||||
"backup_date": "",
|
||||
"update_ready": false,
|
||||
"ignore": false,
|
||||
|
||||
@@ -1,311 +0,0 @@
|
||||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ***** END GPL LICENCE BLOCK *****
|
||||
|
||||
bl_info = {
|
||||
"name": "Animation Layers",
|
||||
"author": "Tal Hershkovich",
|
||||
"version" : (2, 1, 9, 7),
|
||||
"blender" : (3, 2, 0),
|
||||
"location": "View3D - Properties - Animation Panel",
|
||||
"description": "Simplifying the NLA editor into an animation layers UI and workflow",
|
||||
"wiki_url": "",
|
||||
"category": "Animation"}
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
if "bake_ops" in locals():
|
||||
importlib.reload(bake_ops)
|
||||
if "anim_layers" in locals():
|
||||
print('reloading anim layers')
|
||||
importlib.reload(anim_layers)
|
||||
if "subscriptions" in locals():
|
||||
importlib.reload(subscriptions)
|
||||
if "multikey" in locals():
|
||||
importlib.reload(multikey)
|
||||
if "addon_updater_ops" in locals():
|
||||
importlib.reload(addon_updater_ops)
|
||||
|
||||
import bpy
|
||||
from . import addon_updater_ops
|
||||
from . import anim_layers
|
||||
from . import bake_ops
|
||||
from . import subscriptions
|
||||
from . import multikey
|
||||
from bpy.utils import register_class
|
||||
from bpy.utils import unregister_class
|
||||
|
||||
class AnimLayersSceneSettings(bpy.types.PropertyGroup):
|
||||
bake_range_type: bpy.props.EnumProperty(name = 'Bake Range', description="Use either scene, actions length or custom frame range", default = 'SCENE', update = bake_ops.bake_range_type,
|
||||
items = [('SCENE', 'Scene Range', 'Bake to the scene range'), ('KEYFRAMES', 'Keyframes Range', 'Bake all the keyframes in the layers'), ('CUSTOM', 'Custom', 'Enter a custom frame range')], override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
bake_range: bpy.props.IntVectorProperty(name='Frame Range', description='Bake to a custom frame range', size = 2)
|
||||
handles_type: bpy.props.EnumProperty(name="Handle Types", description="Select handle type before recalculating the handle values", override = {'LIBRARY_OVERRIDABLE'}, default = 'FREE',
|
||||
items = [
|
||||
('PRESERVE', 'Preserve', 'Preserves previous Bezier handlers types before recalculation', 0),
|
||||
( 'FREE', 'Free', 'Bezier handlers get Free handle types before recalculation', 1),
|
||||
('ALIGNED', 'Aligned', 'Bezier handlers get Aligned handle types before recalculation', 2),
|
||||
('VECTOR','Vector', 'Bezier handlers get Vector handle types', 3),
|
||||
('AUTO', 'Auto', 'Bezier handlers get Auto handle types before recalculation',4),
|
||||
('AUTO_CLAMPED', 'Auto Clamped', 'Bezier handlers get Free handle types before recalculation', 5)
|
||||
]
|
||||
)
|
||||
|
||||
class AnimLayersSettings(bpy.types.PropertyGroup):
|
||||
turn_on: bpy.props.BoolProperty(name="Turn Animation Layers On", description="Turn on and start Animation Layers", default=False, options={'HIDDEN'}, update = anim_layers.turn_animlayers_on, override = {'LIBRARY_OVERRIDABLE'})
|
||||
layer_index: bpy.props.IntProperty(update = anim_layers.update_layer_index, options={'LIBRARY_EDITABLE'}, default = 0, override = {'LIBRARY_OVERRIDABLE'})
|
||||
linked: bpy.props.BoolProperty(name="Linked", description="Duplicate a layer with a linked action", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
#Bake settings
|
||||
smartbake: bpy.props.BoolProperty(name="Smart Bake", description="Stay with the same amount of keyframes after merging and baking", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
onlyselected: bpy.props.BoolProperty(name="Only selected Bones", description="Bake only selected Armature controls", default=True, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
clearconstraints: bpy.props.BoolProperty(name="Clear constraints", description="Clear constraints during bake", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mergefcurves: bpy.props.BoolProperty(name="Merge Cyclic & Fcurve modifiers", description="Include Fcurve modifiers in the bake", default = True, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
view_all_keyframes: bpy.props.BoolProperty(name="View", description="View keyframes from multiple layers, use lock and mute to exclude layers", default=False, update = anim_layers.view_all_keyframes, override = {'LIBRARY_OVERRIDABLE'})
|
||||
edit_all_keyframes: bpy.props.BoolProperty(name="Edit", description="Edit keyframes from multiple layers", default=False, update = anim_layers.unlock_edit_keyframes, override = {'LIBRARY_OVERRIDABLE'})
|
||||
only_selected_bones: bpy.props.BoolProperty(name="Only Selected Bones", description="Edit and view only selected bones", default=True, update = anim_layers.only_selected_bones, override = {'LIBRARY_OVERRIDABLE'})
|
||||
view_all_type: bpy.props.EnumProperty(name="Type", description="Select visibiltiy type of keyframes", update = anim_layers.view_all_keyframes, override = {'LIBRARY_OVERRIDABLE'}, default = '2',
|
||||
items = [
|
||||
('0', 'Breakdown', 'select Breakdown visibility'),
|
||||
('1', 'Jitter', 'select Jitter visibility'),
|
||||
('2', 'Moving Hold', 'select Moving Hold visibility'),
|
||||
('3', 'Extreme', 'select Extreme visibility'),
|
||||
('4', 'Keyframe', 'select Keyframe visibility')
|
||||
]
|
||||
)
|
||||
baketype : bpy.props.EnumProperty(name = '', description="Type of Bake", items = [('AL', 'Anim Layers','Use Animation Layers Bake',0), ('NLA', 'NLA Bake', 'Use Blender internal NLA Bake',1)], override = {'LIBRARY_OVERRIDABLE'})
|
||||
direction: bpy.props.EnumProperty(name = '', description="Select direction of merge", items = [('UP', 'Up','Merge upwards','TRIA_UP',1), ('DOWN', 'Down', 'Merge downwards','TRIA_DOWN',0), ('ALL', 'All', 'Merge all layers')], override = {'LIBRARY_OVERRIDABLE'})
|
||||
operator : bpy.props.EnumProperty(name = '', description="Type of bake", items = [('NEW', 'New Baked Layer','Bake into a New Layer','NLA',1), ('MERGE', 'Merge', 'Merge Layers','NLA_PUSHDOWN',0)], override = {'LIBRARY_OVERRIDABLE'})
|
||||
blend_type : bpy.props.EnumProperty(name = 'Blend Type', description="Blend Type",
|
||||
items = [('REPLACE', 'Replace', 'Use as a Base Layer'), ('ADD', 'Add', 'Use as an Additive Layer'), ('SUBTRACT', 'Subtract', 'Use as an Subtract Layer')], update = anim_layers.blend_type_update , override = {'LIBRARY_OVERRIDABLE'})
|
||||
data_type : bpy.props.EnumProperty(name = 'Data Type', description="Select type of action data", default = 'OBJECT', update = anim_layers.data_type_update,
|
||||
items = [('KEY', 'Shapekey', 'Switch to shapekey animation layers'), ('OBJECT', 'Object', 'Switch to object animation')], override = {'LIBRARY_OVERRIDABLE'})#, update = anim_layers.blend_type_update
|
||||
auto_rename: bpy.props.BoolProperty(name="Auto Rename Layer", description="Rename layer to match to selected action", default = True, update = anim_layers.auto_rename, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
auto_blend: bpy.props.BoolProperty(name="Auto Blend", description="Apply blend type automatically based on scale and rotation values", default = False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
fcurves: bpy.props.IntProperty(name='fcurves', description='helper to check if fcurves are changed', default=0, override = {'LIBRARY_OVERRIDABLE'})
|
||||
upper_stack : bpy.props.BoolProperty(name="Upper Stack Evaluation", description="Checks if tweak mode uses upper stack", default=False, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
#Tools
|
||||
inbetweener : bpy.props.FloatProperty(name='Inbetween Keyframe', description="Adds an inbetween Keyframe between the Layer's neighbor keyframes", soft_min = 0, soft_max = 1, default=0.5, options = set(), override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.add_inbetween_key)
|
||||
share_layer_keys: bpy.props.EnumProperty(name = 'Share Layer Keys', description="Share keyframes positions between layers", items = anim_layers.share_layerkeys_items, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
class AnimLayersItems(bpy.types.PropertyGroup):
|
||||
name: bpy.props.StringProperty(name="AnimLayer", override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_name_update)
|
||||
mute: bpy.props.BoolProperty(name="Mute", description="Mute Animation Layer", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_mute)
|
||||
lock: bpy.props.BoolProperty(name="Lock", description="Lock Animation Layer", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_lock)
|
||||
solo: bpy.props.BoolProperty(name="Solo", description="Solo Animation Layer", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_solo)
|
||||
influence: bpy.props.FloatProperty(name="Layer Influence", description="Layer Influence", min = 0.0, options={'ANIMATABLE'}, max = 1.0, default = 1.0, precision = 3, update = anim_layers.influence_update, override = {'LIBRARY_OVERRIDABLE'})
|
||||
influence_mute: bpy.props.BoolProperty(name="Animated Influence", description="Turn Animated influence On/Off", default=False, options={'HIDDEN'}, update = anim_layers.influence_mute_update, override = {'LIBRARY_OVERRIDABLE'})
|
||||
#action_list: bpy.props.EnumProperty(name = 'Actions', description = "Select action", update = anim_layers.load_action, items = anim_layers.action_items, override = {'LIBRARY_OVERRIDABLE'})
|
||||
action: bpy.props.PointerProperty(name = 'action', description = "Select action", type=bpy.types.Action, update = anim_layers.load_action, override = {'LIBRARY_OVERRIDABLE'})
|
||||
action_range: bpy.props.FloatVectorProperty(name='action range', description="used to check if layer needs to update frame range", override = {'LIBRARY_OVERRIDABLE'}, size = 2)
|
||||
frame_range: bpy.props.BoolProperty(name="Custom Frame Range", description="Use a custom frame range per layer instead of the scene frame range", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_frame_range)
|
||||
frame_start: bpy.props.FloatProperty(name='Action Start Frame', description="First frame of the layer's action",min = 0, default=0, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_frame_start)
|
||||
frame_end: bpy.props.FloatProperty(name='Action End Frame', description="End frame of the layer's action", default=0, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_frame_end)
|
||||
speed: bpy.props.FloatProperty(name='Speed of the action', description="Speed of the action strip", default = 1, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_speed)
|
||||
offset: bpy.props.FloatProperty(name='Offset when the action starts', description="Offseting the whole layer animation", default = 0, precision = 2, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_offset)
|
||||
repeat: bpy.props.FloatProperty(name="Repeat", description="Repeat the action", min = 0.1, default = 1, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.layer_repeat)
|
||||
|
||||
|
||||
class AnimLayersObjects(bpy.types.PropertyGroup):
|
||||
|
||||
object: bpy.props.PointerProperty(name = "object", description = "objects with animation layers turned on", type=bpy.types.Object, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
|
||||
# Add-ons Preferences Update Panel
|
||||
# Define Panel classes for updating
|
||||
panels = anim_layers.panel_classes
|
||||
def update_panel(self, context):
|
||||
anim_layers.unregister_panels()
|
||||
anim_layers.register_panels()
|
||||
|
||||
message = "AnimationLayers: Updating Panel locations has failed"
|
||||
try:
|
||||
for panel in panels:
|
||||
if "bl_rna" in panel.__dict__:
|
||||
bpy.utils.unregister_class(panel)
|
||||
|
||||
for panel in panels:
|
||||
#print (panel.bl_category)
|
||||
panel.bl_category = context.preferences.addons[__name__].preferences.category
|
||||
bpy.utils.register_class(panel)
|
||||
|
||||
except Exception as e:
|
||||
print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
|
||||
pass
|
||||
|
||||
@addon_updater_ops.make_annotations
|
||||
class AnimLayersAddonPreferences(bpy.types.AddonPreferences):
|
||||
# this must match the addon name, use '__package__'
|
||||
# when defining this in a submodule of a python package.
|
||||
bl_idname = __package__
|
||||
|
||||
auto_rename: bpy.props.BoolProperty(name="Sync Layer/Action Names", description="Rename layer to match to selected action", default = True)
|
||||
blend_type : bpy.props.EnumProperty(name = 'Default Blend Type', description="Default Blend Type when adding layers", default = 'COMBINE',
|
||||
items = [('COMBINE', 'Combine', 'Use Combine as the default blend type'), ('ADD', 'Add', 'Use Add as the default blend type'), ('REPLACE', 'Replace', 'Use Replace as the default blend type'),
|
||||
('SUBTRACT', 'Subtract', 'Use as an Subtract Layer')])
|
||||
|
||||
frame_range_settings : bpy.props.EnumProperty(name = 'Frame Range Settings', description="Use Either Anim Layers Custom Frame Range Settings or NLA Settings", default = 'ANIMLAYERS',
|
||||
items = [('ANIMLAYERS', 'Anim Layers Settings', 'Use Anim Layers properties to adjust custom frame range'),
|
||||
('NLA', 'NLA Settings', 'Use the nla properties to adjust custom frame range')])
|
||||
|
||||
#Property for ClearActiveAction
|
||||
proceed: bpy.props.EnumProperty(name="Choose how to proceed", description="Select an option how to proceed with Anim Layers", override = {'LIBRARY_OVERRIDABLE'},
|
||||
items = [
|
||||
('REMOVE_LAYERS', 'Remove old layers and continue with the current action', 'Remove previous Layers and continue with current action in the base layer', 0),
|
||||
( 'REMOVE_ACTION', 'Remove current action and reload older Layers', 'Remove current action and continue with the previous layers', 1),
|
||||
('ADD_ACTION', 'Add the current action as a new Layer', 'Keep previous Anim Layers and Add the active action as a new layer', 2),])
|
||||
|
||||
enabled_editors: bpy.props.EnumProperty(
|
||||
name="Enabled Editors",
|
||||
description="Select which editors should show animation layers panels",
|
||||
items=[('VIEW_3D', "3D View", ""), ('GRAPH_EDITOR', "Graph Editor", ""), ('DOPESHEET_EDITOR', "Dope Sheet", ""),('NLA_EDITOR', "NLA Editor", ""),],
|
||||
options={'ENUM_FLAG'},
|
||||
default={'VIEW_3D', 'NLA_EDITOR'},
|
||||
update=update_panel
|
||||
)
|
||||
|
||||
category: bpy.props.StringProperty(
|
||||
name="Tab Category",
|
||||
description="Choose a name for the category of the panel",
|
||||
default="Animation",
|
||||
update=update_panel
|
||||
)
|
||||
|
||||
# addon updater preferences from `__init__`, be sure to copy all of them
|
||||
auto_check_update: bpy.props.BoolProperty(
|
||||
name = "Auto-check for Update",
|
||||
description = "If enabled, auto-check for updates using an interval",
|
||||
default = False,
|
||||
)
|
||||
|
||||
updater_interval_months: bpy.props.IntProperty(
|
||||
name='Months',
|
||||
description = "Number of months between checking for updates",
|
||||
default=0,
|
||||
min=0
|
||||
)
|
||||
updater_interval_days: bpy.props.IntProperty(
|
||||
name='Days',
|
||||
description = "Number of days between checking for updates",
|
||||
default=7,
|
||||
min=0,
|
||||
|
||||
)
|
||||
updater_interval_hours: bpy.props.IntProperty(
|
||||
name='Hours',
|
||||
description = "Number of hours between checking for updates",
|
||||
default=0,
|
||||
min=0,
|
||||
max=23
|
||||
)
|
||||
updater_interval_minutes: bpy.props.IntProperty(
|
||||
name='Minutes',
|
||||
description = "Number of minutes between checking for updates",
|
||||
default=0,
|
||||
min=0,
|
||||
max=59
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
addon_updater_ops.update_settings_ui(self, context)
|
||||
|
||||
col = layout.column()
|
||||
|
||||
col.label(text="Tab Category:")
|
||||
col.prop(self, "category", text="")
|
||||
row = layout.row()
|
||||
row.prop(self, "enabled_editors")
|
||||
col = layout.column()
|
||||
col.separator(factor = 1)
|
||||
col = col.box()
|
||||
col.label(text="Defaults:")
|
||||
#row = layout.row()
|
||||
split = col.split(factor=0.5, align = True)
|
||||
split.prop(self, "auto_rename")
|
||||
#row.prop(self, "blend_type")
|
||||
#split = row.split(factor=0.7, align = True)
|
||||
|
||||
split.label(text="Default Blend Type: ")
|
||||
split.prop(self,'blend_type', text = '')
|
||||
|
||||
row = col.row()
|
||||
row.label(text = "Custom Frame Range Settings")
|
||||
row.prop(self, "frame_range_settings", text = '')
|
||||
#row = layout.row(align = True)
|
||||
#col = layout.row(bpy.context.scene.als,'blend_type', text = '')
|
||||
|
||||
classes = (AnimLayersSettings, AnimLayersSceneSettings, AnimLayersItems, AnimLayersObjects)
|
||||
|
||||
addon_keymaps = []
|
||||
|
||||
def register():
|
||||
addon_updater_ops.register(bl_info)
|
||||
register_class(AnimLayersAddonPreferences)
|
||||
addon_updater_ops.make_annotations(AnimLayersAddonPreferences) # to avoid blender 2.8 warnings
|
||||
if bpy.app.version < (3, 2, 0):
|
||||
return
|
||||
|
||||
bake_ops.register()
|
||||
anim_layers.register()
|
||||
multikey.register()
|
||||
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
|
||||
bpy.types.Object.als = bpy.props.PointerProperty(type = AnimLayersSettings, options={'LIBRARY_EDITABLE'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Scene.als = bpy.props.PointerProperty(type = AnimLayersSceneSettings, options={'LIBRARY_EDITABLE'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
bpy.types.Object.Anim_Layers = bpy.props.CollectionProperty(type = AnimLayersItems, override = {'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})
|
||||
bpy.types.Scene.AL_objects = bpy.props.CollectionProperty(type = AnimLayersObjects, options={'LIBRARY_EDITABLE'}, override = {'LIBRARY_OVERRIDABLE', 'USE_INSERTION'})
|
||||
|
||||
update_panel(None, bpy.context)
|
||||
#update_tweak_keymap()
|
||||
|
||||
#Make sure TAB hotkey in the NLA goes into full stack mode
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.addon
|
||||
km = kc.keymaps.new(name= 'NLA Generic', space_type= 'NLA_EDITOR')
|
||||
if 'nla.tweakmode_enter' not in km.keymap_items:
|
||||
kmi = km.keymap_items.new('nla.tweakmode_enter', type= 'TAB', value= 'PRESS')
|
||||
kmi.properties.use_upper_stack_evaluation = True
|
||||
addon_keymaps.append((km, kmi))
|
||||
|
||||
|
||||
def unregister():
|
||||
addon_updater_ops.unregister()
|
||||
unregister_class(AnimLayersAddonPreferences)
|
||||
if bpy.app.version < (3, 2, 0):
|
||||
return
|
||||
|
||||
for cls in classes:
|
||||
unregister_class(cls)
|
||||
bake_ops.unregister()
|
||||
anim_layers.unregister()
|
||||
multikey.unregister()
|
||||
del bpy.types.Object.als
|
||||
del bpy.types.Object.Anim_Layers
|
||||
del bpy.types.Scene.AL_objects
|
||||
#removing keymaps
|
||||
for km, kmi in addon_keymaps:
|
||||
km.keymap_items.remove(kmi)
|
||||
addon_keymaps.clear()
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
File diff suppressed because it is too large
Load Diff
-1560
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
-17
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"last_check": "2025-06-04 16:35:52.082677",
|
||||
"backup_date": "May-6-2025",
|
||||
"update_ready": true,
|
||||
"ignore": false,
|
||||
"just_restored": false,
|
||||
"just_updated": false,
|
||||
"version_text": {
|
||||
"link": "https://gitlab.com/api/v4/projects/22294607/repository/archive.zip?sha=d8a2bbe2878233bf807b592e59fd534918d2dabf",
|
||||
"version": [
|
||||
2,
|
||||
1,
|
||||
9,
|
||||
8
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,510 +0,0 @@
|
||||
# if "bpy" in locals():
|
||||
# import importlib
|
||||
# if "multikey" in locals():
|
||||
# importlib.reload()
|
||||
|
||||
import bpy
|
||||
import random
|
||||
import numpy as np
|
||||
from mathutils import Quaternion
|
||||
from . import bake_ops
|
||||
from . import anim_layers
|
||||
|
||||
def attr_default(obj, fcu_key):
|
||||
#check if the fcurve source belongs to a bone or obj
|
||||
if fcu_key[0][:10] == 'pose.bones':
|
||||
transform = fcu_key[0].split('.')[-1]
|
||||
attr = fcu_key[0].split('"')[-2]
|
||||
bone = fcu_key[0].split('"')[1]
|
||||
source = obj.pose.bones[bone]
|
||||
|
||||
#in case of shapekey animation
|
||||
elif fcu_key[0][:10] == 'key_blocks':
|
||||
attr = fcu_key[0].split('"')[1]
|
||||
shapekey = obj.data.shape_keys.key_blocks[attr]
|
||||
return 0 if shapekey.slider_min <= 0 else shapekey.slider_min
|
||||
#in case of transforms in object mode
|
||||
else:# fcu_key[0] in transform_types:
|
||||
source = obj
|
||||
transform = fcu_key[0]
|
||||
|
||||
#check when it's transform property of Blender
|
||||
if transform in source.bl_rna.properties.keys():
|
||||
if hasattr(source.bl_rna.properties[transform], 'default_array'):
|
||||
if len(source.bl_rna.properties[transform].default_array) > fcu_key[1]:
|
||||
attrvalue = source.bl_rna.properties[transform].default_array[fcu_key[1]]
|
||||
return attrvalue
|
||||
|
||||
#in case of property on object
|
||||
elif fcu_key[0].split('"')[1] in obj.keys():
|
||||
attr = fcu_key[0].split('"')[1]
|
||||
|
||||
if 'attr' not in locals():
|
||||
return 0
|
||||
|
||||
#since blender 3 access to custom property settings changed
|
||||
if attr in source:
|
||||
id_attr = source.id_properties_ui(attr).as_dict()
|
||||
attrvalue = id_attr['default']
|
||||
return attrvalue
|
||||
|
||||
return 0
|
||||
|
||||
def store_handles(key):
|
||||
#storing the distance between the handles bezier to the key value
|
||||
handle_r = key.handle_right[1] - key.co[1]
|
||||
handle_l = key.handle_left[1] - key.co[1]
|
||||
|
||||
return handle_r, handle_l
|
||||
|
||||
def apply_handles(key, handle_r, handle_l):
|
||||
key.handle_right[1] = key.co[1] + handle_r
|
||||
key.handle_left[1] = key.co[1] + handle_l
|
||||
|
||||
def filter_properties(obj, fcu):
|
||||
'Filter the W X Y Z attributes of the transform properties'
|
||||
|
||||
transformations = ["rotation_quaternion","rotation_euler", "location", "scale"]
|
||||
#check if the fcurve data path ends with any of the transformations
|
||||
if not any(fcu.data_path.endswith(transform) for transform in transformations):
|
||||
return True
|
||||
transform = fcu.data_path.split('"].')[1] if obj.mode == 'POSE' else fcu.data_path
|
||||
index = fcu.array_index
|
||||
if 'rotation' in transform:
|
||||
if transform == 'rotation_euler':
|
||||
index -= 1
|
||||
transform = 'rotation'
|
||||
transform = 'filter_' + transform
|
||||
#in case of channels like bbone_scalein that are no included then return
|
||||
if not hasattr(bpy.context.scene.multikey, transform):
|
||||
return True
|
||||
attr = getattr(bpy.context.scene.multikey, transform)
|
||||
return True if attr[index] else False
|
||||
|
||||
def add_value(key, value):
|
||||
if key.select_control_point:
|
||||
#store handle values in relative to the keyframe value
|
||||
handle_r, handle_l = store_handles(key)
|
||||
|
||||
key.co[1] += value
|
||||
apply_handles(key, handle_r, handle_l)
|
||||
|
||||
#calculate the difference between current value and the fcurve value
|
||||
def add_diff(obj, fcurves, path, current_value, eval_array):
|
||||
'''Get the difference value and add it to all selected keyframes'''
|
||||
array_value = current_value - eval_array
|
||||
|
||||
if not any(array_value):
|
||||
return
|
||||
for i, value in enumerate(array_value):
|
||||
fcu = fcurves.find(path, index = i)
|
||||
if fcu is None or not filter_properties(obj, fcu):
|
||||
continue
|
||||
for key in fcu.keyframe_points:
|
||||
add_value(key, value)
|
||||
fcu.update()
|
||||
|
||||
class ScaleValuesOp(bpy.types.Operator):
|
||||
"""Modal operator used while scale value is running before release"""
|
||||
bl_idname = "anim.multikey_scale_value"
|
||||
bl_label = "Scale Values"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
#reset the values for dragging
|
||||
self.stop = False
|
||||
scene = context.scene
|
||||
global is_dragging
|
||||
is_dragging = True
|
||||
|
||||
self.avg_value = dict()
|
||||
#dictionary of the keyframes and their original INITIAL values
|
||||
self.keyframes_values = dict()
|
||||
self.keyframes_handle_right = dict()
|
||||
self.keyframes_handle_left = dict()
|
||||
|
||||
#the average value for each fcurve
|
||||
self.keyframes_avg_value = dict()
|
||||
for obj in context.selected_objects:
|
||||
if obj.animation_data.action is None:
|
||||
continue
|
||||
action = obj.animation_data.action
|
||||
|
||||
fcurves = anim_layers.get_fcurves_channelbag(obj, action)
|
||||
for fcu in fcurves:
|
||||
if anim_layers.selected_bones_filter(obj, fcu):
|
||||
continue
|
||||
if not filter_properties(obj, fcu):
|
||||
continue
|
||||
|
||||
#avg and value list per fcurve
|
||||
avg_value = []
|
||||
value_list = []
|
||||
for key in fcu.keyframe_points:
|
||||
if key.select_control_point:
|
||||
value_list.append(key.co[1])
|
||||
self.keyframes_values.update({key : key.co[1]})
|
||||
self.keyframes_handle_right.update({key : key.handle_right[1]})
|
||||
self.keyframes_handle_left.update({key : key.handle_left[1]})
|
||||
|
||||
if len(value_list)>1:
|
||||
#the average value with the scale property added to it
|
||||
avg_value = sum(value_list) / len(value_list)
|
||||
|
||||
for key in fcu.keyframe_points:
|
||||
if key.select_control_point:
|
||||
self.keyframes_avg_value.update({key : avg_value})
|
||||
|
||||
if not self.keyframes_avg_value:
|
||||
if 'is_dragging' in globals():
|
||||
del is_dragging
|
||||
scene.multikey['scale'] = 1
|
||||
anim_layers.redraw_areas(['VIEW_3D'])
|
||||
return('CANCELLED')
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def modal(self, context, event):
|
||||
|
||||
global is_dragging
|
||||
|
||||
try:
|
||||
scene = context.scene
|
||||
scale = scene.multikey.scale
|
||||
#Quit the modal operator when the slider is released
|
||||
if self.stop:
|
||||
del is_dragging
|
||||
scene.multikey['scale'] = 1
|
||||
anim_layers.redraw_areas(['VIEW_3D'])
|
||||
#modal is being cancelled because of undo issue with the modal running through the property
|
||||
return {'FINISHED'}
|
||||
if event.value == 'RELEASE': # Stop the modal on next frame. Don't block the event since we want to exit the field dragging
|
||||
self.stop = True
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
for key, key_value in self.keyframes_values.items():
|
||||
if not key.select_control_point:
|
||||
continue
|
||||
if key not in self.keyframes_avg_value:
|
||||
continue
|
||||
avg_value = self.keyframes_avg_value[key]
|
||||
handle_right_value = self.keyframes_handle_right[key]
|
||||
handle_left_value = self.keyframes_handle_left[key]
|
||||
|
||||
#add the value of the distance from the average * scale factor
|
||||
key.co[1] = avg_value + ((key_value - avg_value)*scale)
|
||||
key.handle_right[1] = avg_value + ((handle_right_value - avg_value)*scale)
|
||||
key.handle_left[1] = avg_value + ((handle_left_value - avg_value)*scale)
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
except Exception as e:
|
||||
# Log the error
|
||||
print("Error:", e)
|
||||
self['scale'] = 1
|
||||
self.stop = True
|
||||
del is_dragging
|
||||
return {'CANCELLED'}
|
||||
|
||||
def scale_value(self, context):
|
||||
|
||||
if 'is_dragging' in globals():
|
||||
if is_dragging:
|
||||
return
|
||||
|
||||
obj = context.object
|
||||
|
||||
if obj is None:
|
||||
self['scale'] = 1
|
||||
return
|
||||
action = obj.animation_data.action
|
||||
|
||||
if action is None:
|
||||
self['scale'] = 1
|
||||
return
|
||||
|
||||
if context.mode == 'POSE' and not context.selected_pose_bones:
|
||||
self['scale'] = 1
|
||||
return
|
||||
bpy.ops.anim.multikey_scale_value('INVOKE_DEFAULT')
|
||||
|
||||
def random_value(self, context):
|
||||
|
||||
for obj in context.selected_objects:
|
||||
if obj.animation_data.action is None:
|
||||
continue
|
||||
action = obj.animation_data.action
|
||||
fcurves = anim_layers.get_fcurves_channelbag(obj, action)
|
||||
for fcu in fcurves:
|
||||
# if obj.mode == 'POSE':
|
||||
if anim_layers.selected_bones_filter(obj, fcu):
|
||||
continue
|
||||
if not filter_properties(obj, fcu):
|
||||
continue
|
||||
value_list = []
|
||||
threshold = bpy.context.scene.multikey.randomness
|
||||
for key in fcu.keyframe_points:
|
||||
if key.select_control_point == True:
|
||||
value_list.append(key.co[1])
|
||||
|
||||
if len(value_list) > 0:
|
||||
value = max(value_list)- min(value_list)
|
||||
for key in fcu.keyframe_points:
|
||||
add_value(key, value * random.uniform(-threshold, threshold))
|
||||
fcu.update()
|
||||
|
||||
self['randomness'] = 0.1
|
||||
|
||||
def evaluate_combine(data_path, added_array, eval_array, array_default, influence):
|
||||
|
||||
if 'scale' in data_path:
|
||||
eval_array = eval_array * (added_array / array_default) ** influence
|
||||
elif 'rotation_quaternion' in data_path:
|
||||
#multiply first the influence with the w separatly
|
||||
added_array[0] = added_array[0] + (1- added_array[0])*(1 - influence)
|
||||
added_array[1:] *= influence
|
||||
eval_array = np.array(Quaternion(eval_array) @ Quaternion(added_array))# ** influence
|
||||
#if it's a custom property
|
||||
elif 'rotation_euler' not in data_path and 'location' not in data_path:
|
||||
eval_array = eval_array + (added_array - array_default) * influence
|
||||
|
||||
return eval_array
|
||||
|
||||
def evaluate_array(fcurves, fcu_path, frame, array_default = [0, 0, 0]):
|
||||
'''Create an array from all the indexes'''
|
||||
|
||||
array_len = len(array_default)
|
||||
|
||||
# fcu_array = array_default if array_len == 4 else [0, 0, 0]
|
||||
fcu_array = []
|
||||
#get the missing arrays in case quaternion is not complete
|
||||
missing_arrays = []
|
||||
for i in range(array_len):
|
||||
fcu = fcurves.find(fcu_path, index = i)
|
||||
if fcu is None:
|
||||
missing_arrays.append(i)
|
||||
continue
|
||||
|
||||
fcu_array.append(fcu.evaluate(frame))
|
||||
|
||||
#In case it's a quaternion and missing attributes, then adding from default value
|
||||
if fcu_array and array_len == 4 and missing_arrays:
|
||||
for i in missing_arrays:
|
||||
fcu_array.insert(i, array_default[i])
|
||||
|
||||
if not len(fcu_array):
|
||||
return None
|
||||
return np.array(fcu_array)
|
||||
|
||||
def evaluate_layers(context, obj, anim_data, fcu, array_default):
|
||||
'''Calculate the evaluation of all the layers when using the nla'''
|
||||
|
||||
if not hasattr(anim_data, 'nla_tracks') or not anim_data.use_nla:
|
||||
return None
|
||||
nla_tracks = anim_data.nla_tracks
|
||||
if not len(nla_tracks):
|
||||
return None
|
||||
frame = context.scene.frame_current
|
||||
blend_types = {'ADD' : '+', 'SUBTRACT' : '-', 'MULTIPLY' : '*'}
|
||||
fcu_path = fcu.data_path
|
||||
|
||||
eval_array = array_default
|
||||
|
||||
for track in nla_tracks:
|
||||
if track.mute:
|
||||
continue
|
||||
if not len(track.strips):
|
||||
continue
|
||||
for strip in track.strips:
|
||||
if not strip.frame_start < frame < strip.frame_end:
|
||||
continue
|
||||
action = strip.action
|
||||
if action is None:
|
||||
continue
|
||||
blend_type = strip.blend_type
|
||||
|
||||
#get the influence value either from the attribute or the fcurve. function coming from bake
|
||||
influence = strip.influence
|
||||
if len(strip.fcurves):
|
||||
if not strip.fcurves[0].mute and len(strip.fcurves[0].keyframe_points):
|
||||
influence = strip.fcurves[0].evaluate(frame)
|
||||
|
||||
#evaluate the frame according to the strip settings
|
||||
frame_eval = frame
|
||||
#change the frame if the strip is on hold
|
||||
if frame < strip.frame_start:
|
||||
if strip.extrapolation == 'HOLD':
|
||||
frame_eval = strip.frame_start
|
||||
elif frame >= strip.frame_end:
|
||||
if strip.extrapolation == 'HOLD' or strip.extrapolation == 'HOLD_FORWARD':
|
||||
frame_eval = strip.frame_end
|
||||
|
||||
last_frame = strip.frame_start + (strip.frame_end - strip.frame_start) / strip.repeat
|
||||
|
||||
if strip.repeat > 1 and (frame) >= last_frame:
|
||||
action_range = (strip.action_frame_end * strip.scale - strip.action_frame_start * strip.scale)
|
||||
frame_eval = (((frame_eval - strip.frame_start) % (action_range)) + strip.frame_start)
|
||||
|
||||
if strip.use_reverse:
|
||||
frame_eval = last_frame - (frame_eval - strip.frame_start)
|
||||
offset = (strip.frame_start * 1/strip.scale - strip.action_frame_start) * strip.scale
|
||||
frame_eval = strip.frame_start * 1/strip.scale + (frame_eval - strip.frame_start) * 1/strip.scale - offset * 1/strip.scale
|
||||
fcurves = anim_layers.get_fcurves_channelbag(obj, action)
|
||||
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame_eval, influence, array_default, blend_type, blend_types)
|
||||
|
||||
#Adding an extra layer from the action outside and on top of the nla
|
||||
tweak_mode = anim_data.use_tweak_mode
|
||||
if tweak_mode:
|
||||
anim_data.use_tweak_mode = False
|
||||
action = anim_data.action
|
||||
if action:
|
||||
influence = anim_data.action_influence
|
||||
blend_type = anim_data.action_blend_type
|
||||
|
||||
fcurves = anim_layers.get_fcurves_channelbag(obj, action)
|
||||
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame, influence, array_default, blend_type, blend_types)
|
||||
anim_data.use_tweak_mode = tweak_mode
|
||||
|
||||
return eval_array
|
||||
|
||||
|
||||
def evaluate_blend_type(fcurves, eval_array, fcu_path, frame, influence,
|
||||
array_default, blend_type, blend_types):
|
||||
'''Calculate the value based on the blend type'''
|
||||
|
||||
fcu_array = evaluate_array(fcurves, fcu_path, frame, array_default)
|
||||
if fcu_array is None:# or len(fcu_array) != len(eval_array):
|
||||
return eval_array
|
||||
###EVALUATION###
|
||||
if blend_type =='COMBINE':
|
||||
if 'location' in fcu_path or 'rotation_euler' in fcu_path:
|
||||
blend_type = 'ADD'
|
||||
if blend_type =='REPLACE':
|
||||
eval_array = eval_array * (1 - influence) + fcu_array * influence
|
||||
elif blend_type =='COMBINE':
|
||||
eval_array = evaluate_combine(fcu_path, fcu_array, eval_array, array_default, influence)
|
||||
else:
|
||||
eval_array = eval('eval_array' + blend_types[blend_type] + 'fcu_array' + '*' + str(influence))
|
||||
|
||||
return eval_array
|
||||
|
||||
def evaluate_value(self, context):
|
||||
for obj in context.selected_objects:
|
||||
|
||||
anim_data = obj.animation_data
|
||||
if anim_data is None:
|
||||
return
|
||||
if anim_data.action is None:
|
||||
return
|
||||
|
||||
action = obj.animation_data.action
|
||||
fcu_paths = []
|
||||
transformations = ["rotation_quaternion","rotation_euler", "location", "scale"]
|
||||
if obj.mode == 'POSE':
|
||||
bonelist = context.selected_pose_bones if obj.als.onlyselected else obj.pose.bones
|
||||
|
||||
fcurves = anim_layers.get_fcurves_channelbag(obj, action)
|
||||
for fcu in fcurves:
|
||||
if fcu in fcu_paths:
|
||||
continue
|
||||
if obj.mode == 'POSE':
|
||||
if anim_layers.selected_bones_filter(obj, fcu):
|
||||
continue
|
||||
if not filter_properties(obj, fcu):
|
||||
continue
|
||||
|
||||
for bone in bonelist:
|
||||
#find the fcurve of the bone
|
||||
if fcu.data_path.rfind(bone.name) != 12 or fcu.data_path[12 + len(bone.name)] != '"':
|
||||
continue
|
||||
path_split = fcu.data_path.split('"].')
|
||||
|
||||
if len(path_split) <= 1:
|
||||
continue
|
||||
else:
|
||||
transform = fcu.data_path.split('"].')[1]
|
||||
if transform not in transformations:
|
||||
continue
|
||||
|
||||
current_value = getattr(obj.pose.bones[bone.name], transform)
|
||||
else:
|
||||
transform = fcu.data_path
|
||||
current_value = getattr(obj, transform)
|
||||
|
||||
array_default = np.array(bake_ops.attr_default(obj, (fcu.data_path, fcu.array_index)))
|
||||
eval_array = evaluate_layers(context, obj, anim_data, fcu, array_default)
|
||||
if eval_array is None:
|
||||
fcurves = anim_layers.get_fcurves_channelbag(obj, action)
|
||||
eval_array = evaluate_array(fcurves, fcu.data_path, context.scene.frame_current, array_default)
|
||||
|
||||
#calculate the difference between current value and the fcurve value
|
||||
add_diff(obj, fcurves, fcu.data_path, np.array(current_value), eval_array)
|
||||
|
||||
class MULTIKEY_OT_Multikey(bpy.types.Operator):
|
||||
"""Edit all selected keyframes"""
|
||||
bl_label = "Edit Selected Keyframes"
|
||||
bl_idname = "fcurves.multikey"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
# bl_description = ('Select keyframes, move your bone or objecet and press the operator. Does not work with Autokey')
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object and context.active_object.animation_data and bpy.context.scene.tool_settings.use_keyframe_insert_auto == False
|
||||
|
||||
def execute(self, context):
|
||||
evaluate_value(self, context)
|
||||
return {'FINISHED'}
|
||||
|
||||
class MultikeyProperties(bpy.types.PropertyGroup):
|
||||
|
||||
#selectedbones: bpy.props.BoolProperty(name="Affect only selected bones", description="Affect only selected bones", default=True, options={'HIDDEN'})
|
||||
#handletype: bpy.props.BoolProperty(name="Keep handle types", description="Keep handle types", default=False, options={'HIDDEN'})
|
||||
scale: bpy.props.FloatProperty(name="Scale Values Factor", description="Scale percentage from the average value", default=1.0, soft_max = 10, soft_min = -10, step=0.1, precision = 3, update = scale_value)
|
||||
randomness: bpy.props.FloatProperty(name="Randomness", description="Random Threshold of keyframes", default=0.1, min=0.0, max = 1.0, update = random_value)
|
||||
# is_dragging: bpy.props.BoolProperty(default = False)
|
||||
|
||||
#filters
|
||||
filter_location: bpy.props.BoolVectorProperty(name="Location", description="Filter Location properties", default=(True, True, True), size = 3, options={'HIDDEN'})
|
||||
filter_rotation: bpy.props.BoolVectorProperty(name="Rotation", description="Filter Rotation properties", default=(True, True, True, True), size = 4, options={'HIDDEN'})
|
||||
filter_scale: bpy.props.BoolVectorProperty(name="Scale", description="Filter Scale properties", default=(True, True, True), size = 3, options={'HIDDEN'})
|
||||
|
||||
class FilterProperties(bpy.types.Operator):
|
||||
"""Filter Location Rotation and Scale Properties"""
|
||||
bl_idname = "fcurves.filter"
|
||||
bl_label = "Filter Properties W X Y Z"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self, width = 200)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
row = layout.row()
|
||||
row.label(text = 'Location')
|
||||
row.prop(context.scene.multikey, 'filter_location', text = '')
|
||||
row = layout.row()
|
||||
row.label(text = 'Rotation')
|
||||
row.prop(context.scene.multikey, 'filter_rotation', text = '')
|
||||
row = layout.row()
|
||||
row.label(text = 'Scale')
|
||||
row.prop(context.scene.multikey, 'filter_scale', text = '')
|
||||
|
||||
def execute(self, context):
|
||||
return {'CANCELLED'}
|
||||
|
||||
classes = (MultikeyProperties, FilterProperties, MULTIKEY_OT_Multikey, ScaleValuesOp)
|
||||
|
||||
|
||||
def register():
|
||||
from bpy.utils import register_class
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
bpy.types.Scene.multikey = bpy.props.PointerProperty(type = MultikeyProperties, options={'LIBRARY_EDITABLE'}, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
def unregister():
|
||||
from bpy.utils import unregister_class
|
||||
for cls in classes:
|
||||
unregister_class(cls)
|
||||
del bpy.types.Scene.multikey
|
||||
@@ -1,649 +0,0 @@
|
||||
import bpy
|
||||
|
||||
from . import anim_layers
|
||||
from . import bake_ops
|
||||
|
||||
def subscriptions_remove(handler = True):
|
||||
#clear all handlers and subsciptions
|
||||
bpy.msgbus.clear_by_owner(bpy.context.scene)
|
||||
|
||||
global influence_keys, selected_bones
|
||||
|
||||
if 'influence_keys' in globals():
|
||||
del influence_keys
|
||||
if 'selected_bones' in globals():
|
||||
del selected_bones
|
||||
|
||||
if not handler:
|
||||
return
|
||||
if check_handler in bpy.app.handlers.depsgraph_update_pre:
|
||||
bpy.app.handlers.depsgraph_update_pre.remove(check_handler)
|
||||
if animlayers_frame in bpy.app.handlers.frame_change_post:
|
||||
bpy.app.handlers.frame_change_post.remove(animlayers_frame)
|
||||
|
||||
def subscriptions_add(scene, handler = True):
|
||||
bpy.msgbus.clear_by_owner(scene)
|
||||
|
||||
subscribe_to_frame_end(scene)
|
||||
subscribe_to_track_name(scene)
|
||||
subscribe_to_action_name(scene)
|
||||
subscribe_to_strip_settings(scene)
|
||||
subscribe_to_influence(scene)
|
||||
|
||||
if not handler:
|
||||
return
|
||||
|
||||
if check_handler not in bpy.app.handlers.depsgraph_update_pre:
|
||||
bpy.app.handlers.depsgraph_update_pre.append(check_handler)
|
||||
if animlayers_frame not in bpy.app.handlers.frame_change_post:
|
||||
bpy.app.handlers.frame_change_post.append(animlayers_frame)
|
||||
|
||||
def animlayers_frame(self, context):
|
||||
scene = bpy.context.scene
|
||||
current = scene.frame_current_final
|
||||
|
||||
#Make sure the animation is playing and not just running a motion path
|
||||
if not bpy.context.screen.is_animation_playing:
|
||||
return
|
||||
|
||||
#Checking if preview range was turned on or off, when using hotkey P it doesn't recognize
|
||||
#only during the frame handler
|
||||
if scene.get('framerange_preview') != scene.use_preview_range:
|
||||
scene['framerange_preview'] = scene.use_preview_range
|
||||
frameend_update_callback()
|
||||
return
|
||||
|
||||
frame_start, frame_end = bake_ops.frame_start_end(scene)
|
||||
reset_subscription = False
|
||||
if 'outofrange' not in globals():
|
||||
global outofrange
|
||||
outofrange = False if 0 <= current <= frame_end else True
|
||||
# print('out of range ', outofrange )
|
||||
|
||||
if 0 <= current <= frame_end:
|
||||
if outofrange:
|
||||
frameend_update_callback()
|
||||
outofrange = False
|
||||
return
|
||||
outofrange = True
|
||||
# if current <= frame_end:
|
||||
# return
|
||||
#iterate only through objects with anim layers turned on
|
||||
objects = [obj.object for obj in scene.AL_objects]
|
||||
for obj in objects:
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
if anim_data is None:
|
||||
return
|
||||
nla_tracks = anim_data.nla_tracks
|
||||
if not len(nla_tracks):
|
||||
return
|
||||
for i, track in enumerate(nla_tracks):
|
||||
if len(track.strips) != 1:
|
||||
continue
|
||||
|
||||
#checks if the layer has a custom frame range
|
||||
layer = obj.Anim_Layers[i]
|
||||
if layer.frame_range:
|
||||
continue
|
||||
if not reset_subscription:
|
||||
subscriptions_remove(handler = False)
|
||||
reset_subscription = True
|
||||
strip = track.strips[0]
|
||||
if current < 0:
|
||||
# anim_layers.strip_action_recalc(layer, track.strips[0])
|
||||
strip.frame_start_ui = current
|
||||
anim_layers.update_action_frame_range(current, frame_end, layer, strip)
|
||||
# track.strips[0].action_frame_start = current * 1/layer.speed - layer.offset * 1/layer.speed
|
||||
strip.frame_end_ui = frame_end
|
||||
|
||||
elif current > frame_end:
|
||||
if strip.frame_start < 0:
|
||||
strip.frame_start_ui = 0
|
||||
anim_layers.update_action_frame_range(0, frame_end, layer, strip)
|
||||
# print('animlayers_frame ', current)
|
||||
strip.frame_end_ui = current + 10
|
||||
|
||||
if reset_subscription:
|
||||
subscriptions_add(scene, handler = False)
|
||||
|
||||
|
||||
def check_handler(self, context):
|
||||
'''A main function that performs a series of checks using a handler'''
|
||||
scene = bpy.context.scene
|
||||
#if there are no objects included in animation layers then return
|
||||
if not len(scene.AL_objects):
|
||||
return
|
||||
|
||||
obj = bpy.context.object
|
||||
|
||||
#if the object was removed from the scene, then remove it from anim layers object list
|
||||
if obj is None:
|
||||
i = 0
|
||||
while i < len(scene.AL_objects):
|
||||
if scene.AL_objects[i].object not in scene.objects.values():
|
||||
scene.AL_objects.remove(i)
|
||||
else:
|
||||
i += 1
|
||||
return
|
||||
|
||||
if not obj.als.turn_on:
|
||||
return
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
if anim_data is None:
|
||||
return
|
||||
if not anim_data.use_nla:
|
||||
obj.als.turn_on = False
|
||||
return
|
||||
if not len(obj.Anim_Layers):
|
||||
return
|
||||
if not hasattr(anim_data, 'nla_tracks') or not obj.als.turn_on: #obj.select_get() == False or
|
||||
return
|
||||
|
||||
anim_layers.add_obj_to_animlayers(obj, [item.object for item in scene.AL_objects])
|
||||
nla_tracks = anim_data.nla_tracks
|
||||
layer = obj.Anim_Layers[obj.als.layer_index]
|
||||
active_action_update(obj, anim_data, nla_tracks)
|
||||
#check if a keyframe was removed
|
||||
if bpy.context.active_operator is not None:
|
||||
if bpy.context.active_operator.name in ['Transform', 'Delete Keyframes'] and obj.als.edit_all_keyframes:
|
||||
anim_layers.edit_all_keyframes()
|
||||
|
||||
if bpy.context.active_operator.name == 'Enter Tweak Mode':
|
||||
if not bpy.context.active_operator.properties['use_upper_stack_evaluation']:
|
||||
obj.als.upper_stack = False
|
||||
|
||||
if bpy.context.active_operator.name == 'Move Channels':
|
||||
anim_layers.visible_layers(obj, nla_tracks)
|
||||
|
||||
|
||||
# check if track and layers are synchronized
|
||||
if track_layer_synchronization(obj, nla_tracks):
|
||||
return
|
||||
|
||||
anim_layers.add_obj_to_animlayers(obj, [item.object for item in scene.AL_objects])
|
||||
|
||||
track = nla_tracks[obj.als.layer_index]
|
||||
always_sync_range(track, layer)
|
||||
# sync_strip_range(track, layer)
|
||||
|
||||
if anim_data.use_tweak_mode and layer.lock:
|
||||
layer['lock'] = False
|
||||
elif not anim_data.use_tweak_mode and not layer.lock:
|
||||
layer['lock'] = True
|
||||
|
||||
influence_sync(obj, nla_tracks)
|
||||
|
||||
# continue if locked
|
||||
if layer.lock:
|
||||
return
|
||||
|
||||
if obj.als.view_all_keyframes:
|
||||
anim_layers.hide_view_all_keyframes(obj, anim_data)
|
||||
check_selected_bones(obj)
|
||||
|
||||
influence_check(nla_tracks[obj.als.layer_index])
|
||||
|
||||
def track_layer_synchronization(obj, nla_tracks):
|
||||
'''check if track and layers are synchronized'''
|
||||
|
||||
if len(nla_tracks) == len(obj.Anim_Layers):
|
||||
return False
|
||||
|
||||
new_layers_names = set(track.name for track in nla_tracks).difference(set(layer.name for layer in obj.Anim_Layers))
|
||||
anim_layers.visible_layers(obj, nla_tracks)
|
||||
if obj.als.layer_index > len(obj.Anim_Layers)-1:
|
||||
obj.als.layer_index = len(obj.Anim_Layers)-1
|
||||
|
||||
#update new layer with strip settings
|
||||
frame_start, frame_end = bake_ops.frame_start_end(bpy.context.scene)
|
||||
|
||||
for layer_name in new_layers_names:
|
||||
if len(nla_tracks[layer_name].strips) != 1:
|
||||
continue
|
||||
strip = get_strip_in_meta(nla_tracks[layer_name].strips[0])
|
||||
layer = obj.Anim_Layers[layer_name]
|
||||
if (strip.frame_start, strip.frame_end) != (frame_start, frame_end):
|
||||
layer['frame_range'] = True
|
||||
update_strip_layer_settings(strip, layer)
|
||||
layer['action'] = strip.action
|
||||
return True
|
||||
|
||||
def active_action_update(obj, anim_data, nla_tracks):
|
||||
'''updating the active action into the selected layer'''
|
||||
if obj.Anim_Layers[obj.als.layer_index].lock:
|
||||
if anim_data.action != None:
|
||||
subscriptions_remove()
|
||||
anim_data.use_tweak_mode = False
|
||||
anim_data.action = None
|
||||
subscriptions_add(bpy.context.scene)
|
||||
return
|
||||
if anim_data.action == nla_tracks[obj.als.layer_index].strips[0].action:
|
||||
return
|
||||
if not len(nla_tracks[obj.als.layer_index].strips):
|
||||
return
|
||||
if not anim_data.action or anim_data.is_property_readonly('action'):
|
||||
return
|
||||
subscriptions_remove()
|
||||
action = anim_data.action
|
||||
anim_data.action = None
|
||||
obj.Anim_Layers[obj.als.layer_index].action = action
|
||||
subscriptions_add(bpy.context.scene)
|
||||
|
||||
def get_strip_in_meta(strip):
|
||||
'''check if it's meta strip then access the last strip inside meta hierarchy'''
|
||||
while len(strip.strips):
|
||||
strip = strip.strips[0]
|
||||
return strip
|
||||
|
||||
def sync_strip_range():
|
||||
|
||||
scene = bpy.context.scene
|
||||
frame_start, frame_end = bake_ops.frame_start_end(scene)
|
||||
|
||||
if scene.frame_current_final > frame_end:
|
||||
frame_end = scene.frame_current_final + 10
|
||||
|
||||
frame_start = scene.frame_current_final if scene.frame_current_final < 0 else 0.0
|
||||
|
||||
objects = [obj.object for obj in scene.AL_objects]
|
||||
for obj in objects:
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
if anim_data is None:
|
||||
continue
|
||||
nla_tracks = anim_data.nla_tracks
|
||||
if not len(nla_tracks):
|
||||
continue
|
||||
for i, track in enumerate(nla_tracks):
|
||||
if len(track.strips) != 1:
|
||||
continue
|
||||
if obj.Anim_Layers[i]['frame_range']:
|
||||
continue
|
||||
strip = track.strips[0]
|
||||
strip_frame_start = strip.frame_start
|
||||
strip_frame_end = strip.frame_end
|
||||
|
||||
if (strip_frame_start, round(strip_frame_end, 2)) != (frame_start, float(frame_end)):
|
||||
obj.Anim_Layers[i]['frame_range'] = True
|
||||
|
||||
def always_sync_range(track, layer):
|
||||
'''sync frame range when always sync turned on'''
|
||||
if not len(track.strips):
|
||||
return
|
||||
if not layer.frame_range:
|
||||
if track.strips[0].use_sync_length:
|
||||
track.strips[0].use_sync_length = False
|
||||
return
|
||||
if not track.strips[0].use_sync_length:
|
||||
if tuple(layer.action_range) != (0.0, 0.0): #reset action range when turned off
|
||||
layer.action_range = (0.0, 0.0)
|
||||
return
|
||||
strip = track.strips[0]
|
||||
if tuple(layer.action_range) == tuple((strip.action.frame_range[0], strip.action.frame_range[1])):
|
||||
return
|
||||
anim_layers.sync_frame_range(bpy.context)
|
||||
layer.action_range = strip.action.frame_range
|
||||
|
||||
def influence_sync(obj, nla_tracks):
|
||||
|
||||
#Tracks that dont have keyframes are locked
|
||||
for i, track in enumerate(nla_tracks):
|
||||
|
||||
if obj.Anim_Layers[i].lock:
|
||||
continue
|
||||
if not len(track.strips):
|
||||
continue
|
||||
if not len(track.strips[0].fcurves):
|
||||
continue
|
||||
if not len(track.strips[0].fcurves[0].keyframe_points):
|
||||
#apply the influence property to the temp property when keyframes are removed (but its still locked)
|
||||
if not track.strips[0].fcurves[0].lock:
|
||||
obj.Anim_Layers[i].influence = track.strips[0].influence
|
||||
track.strips[0].fcurves[0].lock = True
|
||||
|
||||
if obj.animation_data is None:
|
||||
return
|
||||
action = obj.animation_data.action
|
||||
if action is None:
|
||||
return
|
||||
#if a keyframe was found in the temporary property then add it to the
|
||||
data_path = 'Anim_Layers[' + str(obj.als.layer_index) + '].influence'
|
||||
fcurves = anim_layers.get_fcurves_channelbag(obj, action)
|
||||
fcu_influence = fcurves.find(data_path)
|
||||
if fcu_influence is None:
|
||||
return
|
||||
if not len(fcu_influence.keyframe_points):
|
||||
return
|
||||
#remove the temporary influence
|
||||
fcurves.remove(fcu_influence)
|
||||
#if the action was created just for the influence because of empty object data type then remove the action
|
||||
if action.name == obj.name + 'Action' and not len(obj.animation_data.nla_tracks) and not len(fcurves):
|
||||
bpy.data.actions.remove(action)
|
||||
if obj.Anim_Layers[obj.als.layer_index].influence_mute:
|
||||
return
|
||||
strip = nla_tracks[obj.als.layer_index].strips[0]
|
||||
strip.fcurves[0].lock = False
|
||||
strip.keyframe_insert('influence')
|
||||
strip.fcurves[0].update()
|
||||
|
||||
|
||||
def influence_check(selected_track):
|
||||
'''update influence when a keyframe was added without autokey'''
|
||||
#skip the next steps if a strip is missing or tracks were removed from the nla tracks
|
||||
if len(selected_track.strips) != 1:# or obj.als.layer_index > len(nla_tracks)-2:
|
||||
return
|
||||
if not len(selected_track.strips[0].fcurves):
|
||||
return
|
||||
|
||||
global influence_keys
|
||||
|
||||
if selected_track.strips[0].fcurves[0].mute or not len(selected_track.strips[0].fcurves[0].keyframe_points) or bpy.context.scene.tool_settings.use_keyframe_insert_auto:
|
||||
if 'influence_keys' in globals():
|
||||
del influence_keys
|
||||
return #when the fcurve doesnt have keyframes, or when autokey is turned on, then return
|
||||
|
||||
#update if the influence keyframes are changed. influence_keys are first added in influence_update_callback
|
||||
if 'influence_keys' not in globals():
|
||||
return
|
||||
if influence_keys != [tuple(key.co) for key in selected_track.strips[0].fcurves[0].keyframe_points]:
|
||||
selected_track.strips[0].fcurves[0].update()
|
||||
del influence_keys
|
||||
|
||||
|
||||
def check_selected_bones(obj):
|
||||
'''running in the handler and checking if the selected bones were changed during view multiply layer keyframes'''
|
||||
if not obj.als.only_selected_bones:
|
||||
return
|
||||
global selected_bones
|
||||
try:
|
||||
selected_bones
|
||||
except NameError:
|
||||
selected_bones = bpy.context.selected_pose_bones
|
||||
return
|
||||
else:
|
||||
if selected_bones != bpy.context.selected_pose_bones:
|
||||
selected_bones = bpy.context.selected_pose_bones
|
||||
obj.als.view_all_keyframes = True
|
||||
|
||||
########################### MSGBUS SUBSCRIPTIONS #############################
|
||||
|
||||
#Callback function for Scene frame end
|
||||
def frameend_update_callback():
|
||||
'''End the strips at the end of the scene or scene preview'''
|
||||
scene = bpy.context.scene
|
||||
if not scene.AL_objects:
|
||||
return
|
||||
frame_start, frame_end = bake_ops.frame_start_end(scene)
|
||||
if scene.frame_current_final > frame_end:
|
||||
frame_end = scene.frame_current_final + 10
|
||||
frame_start = scene.frame_current_final if scene.frame_current_final < 0 else 0
|
||||
|
||||
# subscriptions_remove(handler = False)
|
||||
for AL_item in scene.AL_objects:
|
||||
obj = AL_item.object
|
||||
if obj is None or obj not in scene.objects.values():
|
||||
continue
|
||||
#anim_data = anim_data_type(obj)
|
||||
anim_datas = anim_layers.anim_datas_append(obj)
|
||||
|
||||
for anim_data in anim_datas:
|
||||
if anim_data is None:
|
||||
continue
|
||||
if len(anim_data.nla_tracks) != len(obj.Anim_Layers):
|
||||
continue
|
||||
for layer, track in zip(obj.Anim_Layers, anim_data.nla_tracks):
|
||||
if layer.frame_range:
|
||||
continue
|
||||
if len(track.strips) != 1:
|
||||
continue
|
||||
strip = track.strips[0]
|
||||
|
||||
strip.frame_start = frame_start
|
||||
anim_layers.update_action_frame_range(frame_start, frame_end, layer, strip)
|
||||
strip.scale = layer.speed
|
||||
strip.frame_end = frame_end
|
||||
|
||||
# subscriptions_add(scene, handler = False)
|
||||
|
||||
#Subscribe to the scene frame_end
|
||||
def subscribe_to_frame_end(scene):
|
||||
'''subscribe_to_frame_end and frame preview end'''
|
||||
subscribe_end = scene.path_resolve("frame_end", False)
|
||||
subscribe_preview_end = scene.path_resolve("frame_preview_end", False)
|
||||
subscribe_use_preview = scene.path_resolve("use_preview_range", False)
|
||||
|
||||
for subscribe in [subscribe_end, subscribe_preview_end, subscribe_use_preview]:
|
||||
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe,
|
||||
owner=scene,
|
||||
args=(),
|
||||
notify=frameend_update_callback,)
|
||||
|
||||
# bpy.msgbus.publish_rna(key=subscribe)
|
||||
|
||||
# def action_framestart_update_callback(*args):
|
||||
# ''' update the strip start with the action start'''
|
||||
|
||||
|
||||
def track_update_callback():
|
||||
'''update layers with the tracks name'''
|
||||
# global initial_call
|
||||
# if initial_call:
|
||||
# return
|
||||
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
obj = bpy.context.object
|
||||
if obj is None:
|
||||
return
|
||||
if not obj.als.turn_on:
|
||||
return
|
||||
current_anim_data = anim_layers.anim_data_type(obj)
|
||||
anim_datas = anim_layers.anim_datas_append(obj)
|
||||
for anim_data in anim_datas:
|
||||
if anim_data is None:
|
||||
return
|
||||
nla_tracks = anim_data.nla_tracks
|
||||
if not len(nla_tracks):# or len(nla_tracks[:-1]) != len(obj.Anim_Layers):
|
||||
return
|
||||
override_tracks = anim_layers.check_override_tracks(obj, anim_data)
|
||||
for i, track in enumerate(nla_tracks):
|
||||
if anim_data != current_anim_data:
|
||||
continue
|
||||
#make sure there are no duplicated names
|
||||
if track.name != obj.Anim_Layers[i].name:
|
||||
#If its an override track, then make sure the reference object name is also synchronized
|
||||
if obj.Anim_Layers[i].name in override_tracks:
|
||||
override_tracks[obj.Anim_Layers[i].name].name = track.name
|
||||
obj.Anim_Layers[i].name = track.name
|
||||
if len(track.strips) == 1:
|
||||
track.strips[0].name = track.name
|
||||
|
||||
def subscribe_to_track_name(scene):
|
||||
'''Subscribe to the name of track'''
|
||||
|
||||
#subscribe_track = nla_track.path_resolve("name", False)
|
||||
subscribe_track = (bpy.types.NlaTrack, 'name')
|
||||
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_track,
|
||||
# owner of msgbus subcribe (for clearing later)
|
||||
owner=scene,
|
||||
# Args passed to callback function (tuple)
|
||||
args=(),
|
||||
# Callback function for property update
|
||||
notify=track_update_callback,)
|
||||
|
||||
# bpy.msgbus.publish_rna(key=subscribe_track)
|
||||
|
||||
def action_name_callback():
|
||||
'''update layers with the tracks name'''
|
||||
# global initial_call
|
||||
# if initial_call:
|
||||
# return
|
||||
|
||||
obj = bpy.context.object
|
||||
if obj is None:
|
||||
return
|
||||
if not obj.als.turn_on:
|
||||
return
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
#anim_datas = anim_layers.anim_datas_append(obj)
|
||||
if anim_data is None:
|
||||
return
|
||||
nla_tracks = anim_data.nla_tracks
|
||||
if not len(nla_tracks):
|
||||
return
|
||||
layer = obj.Anim_Layers[obj.als.layer_index]
|
||||
if not len(nla_tracks[obj.als.layer_index].strips):
|
||||
return
|
||||
action = nla_tracks[obj.als.layer_index].strips[0].action
|
||||
if action is None:
|
||||
return
|
||||
if not obj.als.auto_rename or layer.name == action.name:
|
||||
return
|
||||
layer.name = action.name
|
||||
|
||||
def subscribe_to_action_name(scene):
|
||||
'''Subscribe to the name of track'''
|
||||
|
||||
#subscribe_track = nla_track.path_resolve("name", False)
|
||||
subscribe_action = (bpy.types.Action, 'name')
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_action,
|
||||
# owner of msgbus subcribe (for clearing later)
|
||||
owner=scene,
|
||||
# Args passed to callback function (tuple)
|
||||
args=(),
|
||||
# Callback function for property update
|
||||
notify=action_name_callback,)
|
||||
|
||||
# bpy.msgbus.publish_rna(key=subscribe_action)
|
||||
|
||||
def influence_update_callback(*args):
|
||||
'''update influence'''
|
||||
# global initial_call
|
||||
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
obj = bpy.context.object
|
||||
#checking if the object has nla tracks, when I used undo it was still calling the property on an object with no nla tracks
|
||||
if obj is None:
|
||||
return
|
||||
if not obj.als.turn_on:
|
||||
return
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
if anim_data is None:
|
||||
return
|
||||
if not len(anim_data.nla_tracks):
|
||||
return
|
||||
|
||||
track = anim_data.nla_tracks[obj.als.layer_index]
|
||||
if len(track.strips) != 1:
|
||||
return
|
||||
|
||||
if track.strips[0].fcurves[0].mute or track.strips[0].fcurves[0].lock:
|
||||
return
|
||||
|
||||
# print('influence_update_callback')
|
||||
#if the influence property and fcurve value are not the same then store the keyframes to check in the handler for a change
|
||||
if track.strips[0].influence == track.strips[0].fcurves[0].evaluate(bpy.context.scene.frame_current):
|
||||
return
|
||||
|
||||
global influence_keys
|
||||
influence_keys = [tuple(key.co) for key in track.strips[0].fcurves[0].keyframe_points]
|
||||
|
||||
if bpy.context.scene.tool_settings.use_keyframe_insert_auto and len(track.strips[0].fcurves[0].keyframe_points):
|
||||
track.strips[0].keyframe_insert('influence')
|
||||
track.strips[0].fcurves[0].update()
|
||||
return
|
||||
|
||||
|
||||
def subscribe_to_influence(scene):
|
||||
'''Subscribe to the influence of the track'''
|
||||
subscribe_influence = (bpy.types.NlaStrip, 'influence')
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_influence,
|
||||
# owner of msgbus subcribe (for clearing later)
|
||||
owner=scene,
|
||||
# Args passed to callback function (tuple)
|
||||
args=(scene,),
|
||||
# Callback function for property update
|
||||
notify=influence_update_callback,)
|
||||
|
||||
|
||||
def subscribe_to_strip_settings(scene):
|
||||
'''Subscribe to the strip settings of the track'''
|
||||
|
||||
frame_start = (bpy.types.NlaStrip, 'frame_start')
|
||||
frame_end = (bpy.types.NlaStrip, 'frame_end')
|
||||
action_frame_start = (bpy.types.NlaStrip, 'action_frame_start')
|
||||
action_frame_end = (bpy.types.NlaStrip, 'action_frame_end')
|
||||
scale = (bpy.types.NlaStrip, 'scale')
|
||||
repeat = (bpy.types.NlaStrip, 'repeat')
|
||||
|
||||
attributes = [frame_start, frame_end, action_frame_start, action_frame_end, scale, repeat, frame_start, frame_end]
|
||||
|
||||
if bpy.app.version > (3, 2, 0):
|
||||
#this properties exist only after Blender 3.2
|
||||
frame_start_ui = (bpy.types.NlaStrip, 'frame_start_ui')
|
||||
frame_end_ui = (bpy.types.NlaStrip, 'frame_end_ui')
|
||||
attributes += [frame_start_ui, frame_end_ui]
|
||||
|
||||
for key in attributes:
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=key,
|
||||
# owner of msgbus subcribe (for clearing later)
|
||||
owner=scene,
|
||||
# Args passed to callback function (tuple)
|
||||
args=(),
|
||||
# Callback function for property update
|
||||
notify=strip_settings_callback,)
|
||||
|
||||
|
||||
def update_strip_layer_settings(strip, layer):
|
||||
layer['speed'] = strip.scale
|
||||
|
||||
# start_offset = strip.action.frame_range[0] - strip.frame_start
|
||||
# offset = (strip.action_frame_start - strip.frame_start - start_offset) * strip.scale + start_offset
|
||||
# offset = (strip.action_frame_start - strip.frame_start) * strip.scale
|
||||
if strip.repeat <= 1:
|
||||
offset = (strip.frame_start * 1/strip.scale - strip.action_frame_start) * strip.scale
|
||||
else:
|
||||
#During repeat the offset is based on the distance from the action first keyframe
|
||||
offset = strip.frame_start - strip.action.frame_range[0]
|
||||
|
||||
layer['offset'] = round(offset, 3)
|
||||
|
||||
#If custom frame range is turned off return to not lose frame range values
|
||||
if not layer.frame_range:
|
||||
return
|
||||
layer['frame_end'] = strip.frame_end
|
||||
layer['frame_start'] = strip.frame_start
|
||||
layer['repeat'] = strip.repeat
|
||||
|
||||
|
||||
def strip_settings_callback():
|
||||
'''subscribe_to_strip_settings callback'''
|
||||
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
obj = bpy.context.object
|
||||
if obj is None:
|
||||
return
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
if anim_data is None:
|
||||
return
|
||||
if not len(anim_data.nla_tracks):
|
||||
return
|
||||
if not len(obj.Anim_Layers):
|
||||
return
|
||||
strip = anim_data.nla_tracks[obj.als.layer_index].strips[0]
|
||||
sync_strip_range()
|
||||
if not len(anim_data.nla_tracks[obj.als.layer_index].strips):
|
||||
return
|
||||
strip = anim_data.nla_tracks[obj.als.layer_index].strips[0]
|
||||
layer = obj.Anim_Layers[obj.als.layer_index]
|
||||
|
||||
update_strip_layer_settings(strip, layer)
|
||||
anim_layers.redraw_areas([ 'VIEW_3D'])
|
||||
BIN
Binary file not shown.
@@ -3,7 +3,7 @@ from . import anim_layers
|
||||
from . import subscriptions
|
||||
from mathutils import Vector, Quaternion
|
||||
import numpy as np
|
||||
#import time
|
||||
# import time
|
||||
|
||||
def frame_start_end(scene):
|
||||
if scene.use_preview_range:
|
||||
@@ -12,7 +12,7 @@ def frame_start_end(scene):
|
||||
else:
|
||||
frame_start = scene.frame_start
|
||||
frame_end = scene.frame_end
|
||||
return frame_start, frame_end
|
||||
return float(frame_start), float(frame_end)
|
||||
|
||||
def smart_start_end(smartkeys, frame_start, frame_end):
|
||||
'''add the first and last frame of the scene if necessery'''
|
||||
@@ -65,11 +65,11 @@ def smart_repeat(smartkeys, fcu, strip):
|
||||
#duplicate the keys on the cycle after
|
||||
keyframes_dup = []
|
||||
|
||||
|
||||
for i in range(1, int(strip.repeat)):
|
||||
for key in smartkeys[1:]:
|
||||
keydup = smartkey(key)
|
||||
keydup.frame += fcu_range*(i)
|
||||
keydup.frame += round(fcu_range*(i), 2)
|
||||
|
||||
#duplicate the tangents tuple values
|
||||
if hasattr(key, 'handle_left'):
|
||||
keydup.handle_left = Vector([key.handle_left[0] + fcu_range*(i+1), key.handle_left[1]])
|
||||
@@ -104,7 +104,6 @@ def smart_cycle(smartkeys, fcu, frame_start, frame_end):
|
||||
cycle_end_dup = int((mod.frame_end - fcu.range()[1])/fcu_range)+2
|
||||
|
||||
#copy the the right handle of the first keyframe to the last, and the left handle from the last keyframe to the first
|
||||
|
||||
smartkeys[-1].handle_right_type = smartkeys[0].handle_left_type
|
||||
smartkeys[0].handle_left_type = smartkeys[-1].handle_right_type
|
||||
# if smartkeys[-1].interpolation == 'BEZIER':
|
||||
@@ -113,13 +112,13 @@ def smart_cycle(smartkeys, fcu, frame_start, frame_end):
|
||||
# if smartkeys[0].interpolation == 'BEZIER':
|
||||
if hasattr(smartkeys[0], 'handle_left'):
|
||||
smartkeys[0].handle_left = [smartkeys[-1].handle_left[0] - fcu_range, smartkeys[0].handle_left[1]]
|
||||
|
||||
#duplicate the keys on the cycle after
|
||||
keyframes_dup = []
|
||||
for key in smartkeys[1:]:
|
||||
for i in range(cycle_end_dup):
|
||||
keydup = smartkey(key)
|
||||
keydup.frame += float(fcu_range*(i+1))
|
||||
keydup.frame += float(round(fcu_range, 2)*(i+1))
|
||||
|
||||
if hasattr(keydup, 'handle_left'):
|
||||
#duplicate the tangents tuple values
|
||||
keydup.handle_left = Vector([key.handle_left[0] + fcu_range*(i+1), key.handle_left[1]])
|
||||
@@ -127,8 +126,8 @@ def smart_cycle(smartkeys, fcu, frame_start, frame_end):
|
||||
#if it's the last keyframe then the right handle get the value from the first keyframes
|
||||
keydup.handle_right = Vector([key.handle_right[0] + fcu_range*(i+1), key.handle_right[1]])
|
||||
if keydup not in keyframes_dup:
|
||||
keydup.frame = round(keydup.frame, 2)
|
||||
keyframes_dup.append(keydup)
|
||||
|
||||
#if it's an iternal cycle then duplicate the keyframes before the cycle keyframes
|
||||
if not mod.cycles_before and mod.mode_before != 'None':
|
||||
cycle_start_dup = int((fcu.range()[0] - frame_start) /fcu_range)+2
|
||||
@@ -138,37 +137,52 @@ def smart_cycle(smartkeys, fcu, frame_start, frame_end):
|
||||
cycle_start_dup = mod.cycles_before
|
||||
if mod.use_restricted_range and mod.frame_start > (fcu.range()[0] + fcu_range * cycle_start_dup):
|
||||
cycle_start_dup = int((fcu.range()[0]-mod.frame_start)/fcu_range)+2
|
||||
|
||||
#duplicate the keys on the cycle before
|
||||
for key in smartkeys[:-1]:
|
||||
for i in range(cycle_start_dup):
|
||||
keydup = smartkey(key)
|
||||
keydup.frame -= float(fcu_range*(i+1))
|
||||
keydup.frame -= fcu_range*(i+1)
|
||||
if hasattr(keydup, 'handle_left'):
|
||||
#duplicate the tangents
|
||||
keydup.handle_left = [key.handle_left[0] - fcu_range*(i+1), key.handle_left[1]]
|
||||
if hasattr(keydup, 'handle_right'):
|
||||
keydup.handle_right = [key.handle_right[0] - fcu_range*(i+1), key.handle_right[1]]
|
||||
#if frame_end > key.frame > frame_start:
|
||||
|
||||
if keydup not in keyframes_dup:
|
||||
keydup.frame = round(keydup.frame, 2)
|
||||
keyframes_dup.append(keydup)
|
||||
#merge the keyframes from the cycle with the or iginal keyframes
|
||||
smartkeys.extend(keyframes_dup)
|
||||
smartkeys = list(set(smartkeys))
|
||||
smartkeys.sort()
|
||||
|
||||
|
||||
if mod.use_restricted_range:
|
||||
smartkeys = smart_start_end(smartkeys, mod.frame_start, mod.frame_end)
|
||||
smartkeys = smart_start_end(smartkeys, mod.frame_start+1, mod.frame_end-1)
|
||||
|
||||
return smartkeys
|
||||
|
||||
|
||||
def smart_bake(context):
|
||||
#record all the keyframes into smartkeys
|
||||
obj = context.object
|
||||
frame_start, frame_end = context.scene.als.bake_range
|
||||
fcu_smartkeys = {}
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
|
||||
# Initialize the progress bar
|
||||
wm = context.window_manager
|
||||
total_iterations = 0
|
||||
for track in anim_data.nla_tracks:
|
||||
if track.mute:
|
||||
continue
|
||||
if len(track.strips) != 1 or track.strips[0].action is None:
|
||||
continue
|
||||
fcurves = anim_layers.get_fcurves(obj, track.strips[0].action)
|
||||
total_iterations += len(fcurves)
|
||||
|
||||
wm.progress_begin(0, total_iterations)
|
||||
processed = 0
|
||||
|
||||
for layer, track in zip(obj.Anim_Layers, anim_data.nla_tracks):
|
||||
if track.mute:
|
||||
continue
|
||||
@@ -181,7 +195,8 @@ def smart_bake(context):
|
||||
for strip_fcu in strip.fcurves:
|
||||
strip_keyframes = [keyframe for keyframe in strip_fcu.keyframe_points if len(strip_fcu.keyframe_points) and not strip_fcu.mute]
|
||||
|
||||
for fcu in strip.action.fcurves:
|
||||
fcurves = anim_layers.get_fcurves(obj, strip.action)
|
||||
for fcu in fcurves:
|
||||
if not fcu.is_valid or fcu.mute or selected_bones_filter(obj, fcu.data_path):
|
||||
continue
|
||||
smartkeys = []
|
||||
@@ -192,9 +207,12 @@ def smart_bake(context):
|
||||
if strip.blend_type in {'COMBINE', 'REPLACE'}:
|
||||
keyframe.handle_left = Vector(key.handle_left)
|
||||
keyframe.handle_right = Vector(key.handle_right)
|
||||
|
||||
keyframe.handle_left[0] = strip_start * layer.speed + (keyframe.handle_left[0] - strip_start) * layer.speed + layer.offset
|
||||
keyframe.handle_right[0] = strip_start * layer.speed + (keyframe.handle_right[0] - strip_start) * layer.speed + layer.offset
|
||||
|
||||
#keyframe.frame += layer.offset
|
||||
keyframe.frame = strip_start * layer.speed + (keyframe.frame - strip_start) * layer.speed + layer.offset
|
||||
keyframe.frame = strip_start * layer.speed + (keyframe.frame - strip_start) * layer.speed + layer.offset# * layer.speed
|
||||
keyframe.frame = round(keyframe.frame, 2)
|
||||
if keyframe not in smartkeys:
|
||||
smartkeys.append(keyframe)
|
||||
|
||||
@@ -210,37 +228,44 @@ def smart_bake(context):
|
||||
smartkeys.sort()
|
||||
if len(fcu.modifiers) and obj.als.mergefcurves:
|
||||
smartkeys = smart_cycle(smartkeys, fcu, frame_start, frame_end)
|
||||
|
||||
|
||||
#apply strip action settings
|
||||
last_frame = (strip.frame_end - strip.frame_start) / strip.repeat + strip.frame_start
|
||||
if strip.use_reverse:
|
||||
for key in smartkeys:
|
||||
key.frame = last_frame - (key.frame - strip.frame_start)
|
||||
key.frame = last_frame - (key.frame - strip.frame_start)
|
||||
if strip.repeat > 1:
|
||||
smartkeys = smart_start_end(smartkeys, last_frame , last_frame+1) #strip.frame_start
|
||||
smartkeys = remove_outofrange_keys(smartkeys, strip.frame_start, last_frame+1) #+ layer.offset
|
||||
smartkeys = smart_repeat(smartkeys, fcu, strip)
|
||||
|
||||
if layer.frame_range:
|
||||
if layer.custom_frame_range:
|
||||
smartkeys = smart_start_end(smartkeys, strip.frame_start, strip.frame_end)
|
||||
smartkeys = remove_outofrange_keys(smartkeys, strip.frame_start, strip.frame_end)
|
||||
|
||||
#if the strip is cutting with a different strip, then add keyframes in the cut
|
||||
for layercut in obj.Anim_Layers:
|
||||
if layercut.mute or not layercut.frame_range or layercut == layer:
|
||||
if layercut.mute or not layercut.custom_frame_range or layercut == layer:
|
||||
continue
|
||||
if strip_start < layercut.frame_start < strip_end:
|
||||
smartkeys = smart_start_end(smartkeys, (layercut.frame_start-1), strip.frame_end-1)
|
||||
if strip_start < layercut.frame_end < strip_end:
|
||||
smartkeys = smart_start_end(smartkeys, (layercut.frame_end+1), strip.frame_end-1)
|
||||
|
||||
|
||||
#if the list of keyframes exists in a different track list then add them
|
||||
if (fcu.data_path, fcu.array_index) in fcu_smartkeys:
|
||||
smartkeys = list(set(fcu_smartkeys[(fcu.data_path, fcu.array_index)]+smartkeys))
|
||||
#Merge all duplicated keyframes
|
||||
smartkeys = list(set(smartkeys))
|
||||
|
||||
smartkeys.sort()
|
||||
|
||||
fcu_smartkeys.update({(fcu.data_path, fcu.array_index):smartkeys})
|
||||
|
||||
processed += 1
|
||||
wm.progress_update(processed)
|
||||
|
||||
wm.progress_end()
|
||||
|
||||
#add inbetweens
|
||||
for fcu, smartkeys in fcu_smartkeys.items():
|
||||
if not smartkeys:
|
||||
@@ -267,11 +292,13 @@ def add_inbetween(smartkeys):
|
||||
|
||||
key2 = smartkey()
|
||||
key2.frame = smartkeys[i].frame + (smartkeys[i+1].frame - smartkeys[i].frame)*2/3
|
||||
key1.frame = round(key1.frame, 2)
|
||||
key2.frame = round(key2.frame, 2)
|
||||
|
||||
key2.inbetween = True
|
||||
smartkeys.insert(i+1, key1)
|
||||
smartkeys.insert(i+2, key2)
|
||||
i += 3
|
||||
|
||||
return smartkeys
|
||||
|
||||
class smartkey:
|
||||
@@ -345,7 +372,8 @@ def mute_modifiers(obj, nla_tracks):
|
||||
for track in nla_tracks:
|
||||
if len(track.strips) != 1 or track.strips[0].action is None:
|
||||
continue
|
||||
for fcu in track.strips[0].action.fcurves:
|
||||
fcurves = anim_layers.get_fcurves(obj, track.strips[0].action)
|
||||
for fcu in fcurves:
|
||||
if selected_bones_filter(obj, fcu.data_path):
|
||||
continue
|
||||
if fcu.extrapolation == 'LINEAR':
|
||||
@@ -370,7 +398,8 @@ def unmute_modifiers(obj, nla_tracks, modifier_rec):
|
||||
for track in nla_tracks:
|
||||
if track.strips[0].action is None:
|
||||
continue
|
||||
for fcu in track.strips[0].action.fcurves:
|
||||
fcurves = anim_layers.get_fcurves(obj, track.strips[0].action)
|
||||
for fcu in fcurves:
|
||||
if not fcu.is_valid or selected_bones_filter(obj, fcu.data_path):
|
||||
continue
|
||||
if not len(fcu.modifiers):
|
||||
@@ -398,7 +427,7 @@ def select_keyframed_bones(self, context, obj):
|
||||
bpy.ops.object.posemode_toggle()
|
||||
bpy.ops.pose.select_all(action='DESELECT')
|
||||
for i in range(0, obj.als.layer_index+1):
|
||||
obj.als.layer_index = i
|
||||
obj.als['layer_index'] = i
|
||||
anim_layers.select_layer_bones(self, context)
|
||||
|
||||
def mute_constraints(obj):
|
||||
@@ -422,14 +451,15 @@ def smartbake_apply(obj, nla_tracks, fcu_keys, extrapolations):
|
||||
# return
|
||||
|
||||
# action_range = strip.frame_end - strip.frame_start
|
||||
for fcu in strip.action.fcurves:
|
||||
fcurves = anim_layers.get_fcurves(obj, strip.action)
|
||||
for fcu in fcurves:
|
||||
if not fcu.is_valid:
|
||||
continue
|
||||
if selected_bones_filter(obj, fcu.data_path):
|
||||
continue
|
||||
fcu_key = (fcu.data_path, fcu.array_index)
|
||||
if fcu_key not in fcu_keys.keys():
|
||||
strip.action.fcurves.remove(fcu)
|
||||
fcurves.remove(fcu)
|
||||
continue
|
||||
#get all the frames of the smart keys
|
||||
smartkeys = fcu_keys[fcu_key]
|
||||
@@ -453,34 +483,6 @@ def smartbake_apply(obj, nla_tracks, fcu_keys, extrapolations):
|
||||
fcu.keyframe_points[-1].co = (smart_key.frame, value)
|
||||
fcu.update()
|
||||
|
||||
#remove unnecessery keyframes
|
||||
# for i in range(int(strip.action.frame_range[0]),int(strip.action.frame_range[1]+1)):
|
||||
# if i in smart_frames:
|
||||
# #get the index of the smart key based on the smart frames + interpolations
|
||||
# smart_index = (smart_frames.index(i)+1)*3-3
|
||||
|
||||
# #if key was founded add the interpolation and handles
|
||||
# for key in fcu.keyframe_points:
|
||||
# if key.co[0] != i:
|
||||
# continue
|
||||
# key.co[1] = round(key.co[1], 4)
|
||||
# key.interpolation = smartkeys[smart_index].interpolation
|
||||
# # key.handle_left_type = smartkeys[smart_index].handle_left_type
|
||||
# # key.handle_right_type = smartkeys[smart_index].handle_right_type
|
||||
# key.handle_left_type = 'AUTO_CLAMPED' if smartkeys[smart_index].handle_left_type != 'VECTOR' else 'VECTOR'
|
||||
# key.handle_right_type = 'AUTO_CLAMPED' if smartkeys[smart_index].handle_right_type != 'VECTOR' else 'VECTOR'
|
||||
# break
|
||||
#delete the keys that are not in the list
|
||||
# else:
|
||||
# if fcu.data_path.split(".")[-1] in transform_types:
|
||||
# print('fcu.id_data', fcu.id_data, obj.animation_data.action)
|
||||
# fcu.keyframe_delete(fcu.data_path.split(".")[-1] ,index = fcu_key[1], frame = i)
|
||||
# else:
|
||||
# try:
|
||||
# fcu.keyframe_delete(fcu.data_path.split(".")[-1], frame = i)
|
||||
# except TypeError:
|
||||
# pass
|
||||
|
||||
i = 0
|
||||
while i < len(fcu.keyframe_points):
|
||||
key = fcu.keyframe_points[i]
|
||||
@@ -518,7 +520,7 @@ def armature_restore(obj, b_layers, layers_rec, constraint_rec):
|
||||
def attr_default(obj, fcu_key):
|
||||
'''Returns the default value or default array value in a list'''
|
||||
#check if the fcurve source belongs to a bone or obj
|
||||
if fcu_key[0][:10] == 'pose.bones':
|
||||
if fcu_key[0][:10] == 'pose.bones':
|
||||
transform = fcu_key[0].split('.')[-1]
|
||||
attr = fcu_key[0].split('"')[-2]
|
||||
bone = fcu_key[0].split('"')[1]
|
||||
@@ -557,7 +559,7 @@ def attr_default(obj, fcu_key):
|
||||
attr = fcu_key[0].split('"')[1]
|
||||
|
||||
if 'attr' not in locals():
|
||||
print(fcu_key[0], 'has no attributes returning 0')
|
||||
# print(fcu_key[0], 'has no attributes returning 0')
|
||||
return [0]
|
||||
|
||||
#since blender 3 access to custom property settings changed
|
||||
@@ -571,6 +573,7 @@ def attr_default(obj, fcu_key):
|
||||
return [0]
|
||||
|
||||
def selected_bones_filter(obj, fcu_data_path):
|
||||
'''using obj.als.onlyselected property for the bake'''
|
||||
if not obj.als.onlyselected:
|
||||
return False
|
||||
if obj.mode != 'POSE':
|
||||
@@ -597,7 +600,6 @@ def evaluate_combine(data_path, added_array, eval_array, array_default, influenc
|
||||
return eval_array
|
||||
|
||||
def frame_evaluation(frame, strip):
|
||||
|
||||
frame_eval = frame
|
||||
#change the frame if the strip is on hold
|
||||
if frame < strip.frame_start:
|
||||
@@ -620,6 +622,17 @@ def frame_evaluation(frame, strip):
|
||||
|
||||
return frame_eval
|
||||
|
||||
def clean_no_user_slots(action):
|
||||
'''Remove all the slots that are connected to the action and object but not part of the action slot'''
|
||||
if not hasattr(action, 'slots'):
|
||||
return
|
||||
for slot in action.slots:
|
||||
if slot is None:
|
||||
continue
|
||||
if len(slot.users()):
|
||||
continue
|
||||
|
||||
action.slots.remove(slot)
|
||||
|
||||
def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, actioncopy, baked_layer = None):
|
||||
|
||||
@@ -627,19 +640,37 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
|
||||
if obj is None:
|
||||
return
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
baked_action = anim_data.action
|
||||
if obj.als.operator == 'MERGE' and not additive and anim_data.action is not None: #and obj.als.onlyselected
|
||||
# baked_action = anim_data.action
|
||||
track = nla_tracks[obj.als.layer_index]
|
||||
baked_action = track.strips[0].action
|
||||
clean_no_user_slots(baked_action)
|
||||
#create the baked fcurve
|
||||
baked_channelbag = anim_layers.get_channelbag(obj, baked_action)
|
||||
baked_fcurves = baked_channelbag.fcurves
|
||||
|
||||
if obj.als.operator == 'MERGE':# and not additive: # and anim_data.action is not None: #and obj.als.onlyselected
|
||||
#overwrite action
|
||||
anim_data.use_tweak_mode = False
|
||||
|
||||
#create a duplicate of the action on the merged layer to have a clean action in order to not write over the calculation
|
||||
action_copy = bpy.data.actions[baked_action.name].copy()
|
||||
nla_tracks[obj.als.layer_index].strips[0].action = action_copy
|
||||
|
||||
baked_action.id_root = obj.als.data_type
|
||||
track.strips[0].action = action_copy
|
||||
|
||||
|
||||
if hasattr(baked_action, 'id_root'):
|
||||
baked_action.id_root = obj.als.data_type
|
||||
|
||||
blend_types = {'ADD' : '+', 'SUBTRACT' : '-', 'MULTIPLY' : '*'}
|
||||
fcu_paths = []
|
||||
|
||||
|
||||
# Initialize the progress bar
|
||||
wm = bpy.context.window_manager
|
||||
fcu_set = {fcu_key[0] for fcu_key in fcu_keys.keys()}
|
||||
total_iterations = len(fcu_set)
|
||||
wm.progress_begin(0, total_iterations) # (start, end range)
|
||||
processed = 0
|
||||
|
||||
for fcu_key in fcu_keys.keys():
|
||||
if fcu_key[0] in fcu_paths:
|
||||
continue
|
||||
@@ -674,18 +705,30 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
|
||||
for track in nla_tracks:
|
||||
if track.mute:
|
||||
continue
|
||||
if not len(track.strips):
|
||||
continue
|
||||
if track.strips[0].action is None:
|
||||
continue
|
||||
# if track == baked_layer:
|
||||
# continue
|
||||
#finding the channel group of array
|
||||
fcu = track.strips[0].action.fcurves.find(fcu_key[0], index = i)
|
||||
channelbag = anim_layers.get_channelbag(obj, track.strips[0].action)
|
||||
if channelbag is None:
|
||||
continue
|
||||
|
||||
fcurves = channelbag.fcurves
|
||||
fcu = fcurves.find(fcu_key[0], index = i)
|
||||
# print(f'track {track.name} fcurves {len(fcurves)}')
|
||||
if fcu is None:
|
||||
# print('fcu is none', fcu_key[0], track.name)
|
||||
continue
|
||||
group = fcu.group if fcu.group is not None else None
|
||||
|
||||
if group is not None:
|
||||
if group.name in baked_action.groups:
|
||||
group = baked_action.groups[group.name]
|
||||
if group.name in baked_channelbag.groups:
|
||||
group = baked_channelbag.groups[group.name]
|
||||
else:
|
||||
group = baked_action.groups.new(group.name)
|
||||
group = baked_channelbag.groups.new(group.name)
|
||||
|
||||
#copy and append Modifiers into mod_list. Mute them if turned on
|
||||
if len(fcu.modifiers) and not obj.als.mergefcurves:
|
||||
@@ -697,20 +740,22 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
|
||||
# modifier_rec.append(mod)
|
||||
mod.mute = True
|
||||
extrapolation = True if fcu.extrapolation == 'LINEAR' else False
|
||||
|
||||
#### Creating or overwritting (during merge) the new baked fcurves####
|
||||
|
||||
baked_fcu = ensure_fcurves_bversion(baked_fcurves, fcu_key[0], i)
|
||||
|
||||
#create the baked fcurve
|
||||
baked_fcu = baked_action.fcurves.find(fcu_key[0], index = i)
|
||||
if baked_fcu is not None:
|
||||
baked_action.fcurves.remove(baked_fcu)
|
||||
baked_fcu = baked_action.fcurves.new(fcu_key[0], index = i)
|
||||
baked_fcu.color_mode = 'AUTO_RGB'
|
||||
|
||||
if group is not None:
|
||||
if baked_fcu.group != group:
|
||||
baked_fcu.group = group
|
||||
|
||||
if extrapolation:
|
||||
baked_fcu.extrapolation = 'LINEAR'
|
||||
|
||||
baked_fcus.append(baked_fcu)
|
||||
|
||||
|
||||
#select smart bake frame range or every frame in the range
|
||||
if obj.als.smartbake:
|
||||
#merge all the smartframe arrays
|
||||
@@ -726,9 +771,15 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
|
||||
|
||||
#ITERATE through all the layers to evaluate
|
||||
for track in nla_tracks:
|
||||
if not len(track.strips):
|
||||
continue
|
||||
|
||||
if track.mute or track == baked_layer or track.strips[0].action is None:
|
||||
continue
|
||||
strip = track.strips[0]
|
||||
fcurves = anim_layers.get_fcurves(obj, strip.action)
|
||||
if not len(fcurves):
|
||||
continue
|
||||
if (frame < strip.frame_start or frame > strip.frame_end) and strip.extrapolation == 'NOTHING':
|
||||
layers_count += 1
|
||||
continue
|
||||
@@ -750,11 +801,14 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
|
||||
added_array = []
|
||||
missing = 0
|
||||
for i in range(array_length):
|
||||
fcu = strip.action.fcurves.find(fcu_key[0], index = i)
|
||||
|
||||
fcu = fcurves.find(fcu_key[0], index = i)
|
||||
#if the fcurve is not found then get the default value
|
||||
if fcu is None:
|
||||
missing += 1
|
||||
value = array_default[i] if blend_type in ('REPLACE', 'COMBINE') else 0
|
||||
#getting the previous value if the fcurve is missing instead of just default
|
||||
#the other option would be to use an array for the influence as well
|
||||
value = eval_array[i] if blend_type in ('REPLACE', 'COMBINE') else 0
|
||||
else:
|
||||
value = fcu.evaluate(frame_eval)
|
||||
added_array.append(value)
|
||||
@@ -773,7 +827,6 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
|
||||
eval_array = evaluate_combine(fcu_key[0], added_array, eval_array, array_default, influence)
|
||||
else:
|
||||
eval_array = eval('eval_array' + blend_types[blend_type] +' added_array' + '*' + str(influence))
|
||||
#extrapolation = True if fcu.extrapolation == 'LINEAR' else False
|
||||
layers_count += 1
|
||||
|
||||
if not eval_array.size:
|
||||
@@ -792,18 +845,11 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
|
||||
smartkey.value = eval_array[i]
|
||||
if smartkey.inbetween:
|
||||
continue
|
||||
|
||||
# if hasattr(smartkey, 'handle_left') and not additive:
|
||||
# if not smartkey.handle_left[1]:
|
||||
# smartkey.handle_left[1] = array_default[i]
|
||||
# if hasattr(smartkey, 'handle_right') and not additive:
|
||||
# if not smartkey.handle_right[1]:
|
||||
# smartkey.handle_right[1] = array_default[i]
|
||||
|
||||
baked_fcu.keyframe_points.add(1)
|
||||
keyframe = baked_fcu.keyframe_points[-1]
|
||||
keyframe.co = (frame, eval_array[i])
|
||||
|
||||
|
||||
for baked_fcu in baked_fcus:
|
||||
if not len(baked_fcu.keyframe_points):
|
||||
continue
|
||||
@@ -818,12 +864,32 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
|
||||
#paste the modifiers to the new baked fcurve
|
||||
if i in mod_list and not len(baked_fcu.modifiers):
|
||||
anim_layers.paste_modifiers(baked_fcu, mod_list[i])
|
||||
|
||||
|
||||
processed += 1
|
||||
wm.progress_update(processed)
|
||||
wm.progress_end()
|
||||
|
||||
if not actioncopy and obj.als.operator == 'MERGE':
|
||||
bpy.data.actions.remove(action_copy)
|
||||
|
||||
return baked_action
|
||||
|
||||
def ensure_fcurves_bversion(fcurves, data_path, i):
|
||||
'''Either use ensure in Blender 5.0 and above or use find and new in older version'''
|
||||
|
||||
if hasattr(fcurves, 'ensure'):
|
||||
baked_fcu = fcurves.ensure(data_path, index = i)
|
||||
else:
|
||||
baked_fcu = fcurves.find(data_path, index = i)
|
||||
if baked_fcu is None:
|
||||
baked_fcu = fcurves.new(data_path, index = i)
|
||||
|
||||
|
||||
if len(baked_fcu.keyframe_points):
|
||||
baked_fcu.keyframe_points.clear()
|
||||
|
||||
return baked_fcu
|
||||
|
||||
def non_recalc_handle_type(baked_keys):
|
||||
|
||||
handles_type = bpy.context.scene.als.handles_type
|
||||
@@ -849,7 +915,9 @@ def add_interpolations(baked_fcu, smartkeys, layers_count = 0):
|
||||
P2index = 2
|
||||
if len(baked_keys) != len(keys):
|
||||
print('unequal length of keys ',baked_fcu.data_path, len(baked_keys), len(keys))
|
||||
print('set difference ', set([key.frame for key in keys]).difference(set([round(key.co[0], 2) for key in baked_keys])))
|
||||
print('keys ', [key.frame for key in keys])
|
||||
print('baked keys ', [round(key.co[0], 2) for key in baked_keys])
|
||||
print('set difference ', set([key.frame for key in keys]).difference(set([key.co[0] for key in baked_keys])))
|
||||
|
||||
baked_keys[0].handle_left_type = 'VECTOR'
|
||||
baked_keys[-1].handle_right_type = 'VECTOR'
|
||||
@@ -876,7 +944,6 @@ def add_interpolations(baked_fcu, smartkeys, layers_count = 0):
|
||||
skip = True
|
||||
|
||||
if not smartkeys[P1index].inbetween :
|
||||
# print(baked_fcu.data_path, baked_keys[i].co[0], 'not with inbetween', P1index, P2index, i)
|
||||
P1index += 1
|
||||
P2index += 1
|
||||
continue
|
||||
@@ -911,11 +978,10 @@ def add_interpolations(baked_fcu, smartkeys, layers_count = 0):
|
||||
#iterate through the inbetween smartkeys
|
||||
P1index += 3
|
||||
P2index += 3
|
||||
|
||||
# baked_fcu.update()
|
||||
|
||||
#add in-betweener
|
||||
|
||||
|
||||
def apply_handle_types(baked_keys, smartkeys, i):
|
||||
|
||||
handles_type = bpy.context.scene.als.handles_type
|
||||
@@ -933,35 +999,40 @@ def apply_handle_types(baked_keys, smartkeys, i):
|
||||
|
||||
def bake_range_type(self, context):
|
||||
if self.bake_range_type == 'SCENE':
|
||||
self.bake_range = frame_start_end(bpy.context.scene)
|
||||
frame_start, frame_end = frame_start_end(context.scene)
|
||||
self.bake_range = (int(frame_start), int(frame_end))
|
||||
|
||||
if self.bake_range_type == 'KEYFRAMES':
|
||||
obj = context.object
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
frame_end = []
|
||||
frame_start = []
|
||||
posebones = context.selected_pose_bones
|
||||
#if baking only selected bones then find the longest fcurves for the range
|
||||
if obj.als.onlyselected:
|
||||
posebones = context.selected_pose_bones
|
||||
if obj.als.onlyselected and posebones:
|
||||
bonespath = [bone.path_from_id() for bone in posebones]
|
||||
#get the frame range from
|
||||
if not bonespath:
|
||||
return
|
||||
for track in obj.animation_data.nla_tracks:
|
||||
if not len(track.strips):
|
||||
continue
|
||||
action = track.strips[0].action
|
||||
for fcu in action.fcurves:
|
||||
for track in anim_data.nla_tracks:
|
||||
if not len(track.strips):
|
||||
continue
|
||||
action = track.strips[0].action
|
||||
if obj.als.onlyselected and posebones:
|
||||
# Get fcurve range from the selected objects
|
||||
fcurves = anim_layers.get_fcurves(obj, action)
|
||||
for fcu in fcurves:
|
||||
#check if the fcurve is in the selected bones
|
||||
if any(path in fcu.data_path for path in bonespath):
|
||||
frame_start.append(fcu.range()[0])
|
||||
frame_end.append(fcu.range()[1])
|
||||
else:
|
||||
for track in obj.animation_data.nla_tracks:
|
||||
if not len(track.strips):
|
||||
continue
|
||||
else:
|
||||
# Get the action range
|
||||
action = track.strips[0].action
|
||||
frame_start.append(action.frame_range[0])
|
||||
frame_end.append(action.frame_range[1])
|
||||
|
||||
# Checking for the longest action in all the actions
|
||||
if frame_start:
|
||||
self.bake_range = (int(min(frame_start)), int(max(frame_end)))
|
||||
|
||||
@@ -1042,10 +1113,23 @@ class MergeAnimLayerDown(bpy.types.Operator):
|
||||
obj.als.smartbake = False
|
||||
|
||||
subscriptions.subscriptions_remove()
|
||||
#start = time.time()
|
||||
# start = time.time()
|
||||
#define the start and end frame of the bake, according to scene or preview length
|
||||
frame_start, frame_end = context.scene.als.bake_range
|
||||
|
||||
# Incase the strips are shorter then the keyframe range (because scene is shorter)
|
||||
# Then updating the strips length
|
||||
for layer, track in zip(obj.Anim_Layers, anim_data.nla_tracks):
|
||||
if layer.custom_frame_range:
|
||||
continue
|
||||
if len(track.strips) != 1:
|
||||
continue
|
||||
strip = track.strips[0]
|
||||
strip.frame_start =frame_start
|
||||
anim_layers.update_action_frame_range(frame_start, frame_end, layer, strip)
|
||||
strip.scale = layer.speed
|
||||
strip.frame_end = frame_end
|
||||
|
||||
obj.als.view_all_keyframes = False
|
||||
if context.scene.frame_current > frame_end:
|
||||
context.scene.frame_current = frame_end
|
||||
@@ -1054,8 +1138,9 @@ class MergeAnimLayerDown(bpy.types.Operator):
|
||||
blendings = [track.strips[0].blend_type for track in nla_tracks[layer_index:] if len(track.strips) == 1]
|
||||
|
||||
#define if the new baked layer is going to be additive or replace
|
||||
additive = False
|
||||
if obj.als.direction == 'UP' and 'REPLACE' not in blendings and obj.als.baketype == 'AL':
|
||||
additive = False
|
||||
|
||||
if obj.als.direction == 'UP' and 'REPLACE' not in blendings and obj.als.baketype == 'AL' and layer_index:
|
||||
if 'COMBINE' in blendings:
|
||||
blend = 'COMBINE'
|
||||
else:
|
||||
@@ -1063,15 +1148,19 @@ class MergeAnimLayerDown(bpy.types.Operator):
|
||||
additive = True
|
||||
else:
|
||||
blend = 'REPLACE'
|
||||
|
||||
|
||||
mute_rec = mute_unbaked_layers(layer_index, nla_tracks, additive)
|
||||
|
||||
fcu_keys = smart_bake(context)
|
||||
|
||||
|
||||
if obj.als.operator == 'MERGE':
|
||||
if obj.als.direction == 'DOWN':
|
||||
obj.als.layer_index = 0
|
||||
baked_layer = None
|
||||
action = anim_data.nla_tracks[obj.als.layer_index].strips[0].action
|
||||
strip = anim_data.nla_tracks[obj.als.layer_index].strips[0]
|
||||
action = strip.action
|
||||
if hasattr(strip, 'action_slot'):
|
||||
action_slot = strip.action_slot
|
||||
if action is not None: action_name = action.name
|
||||
|
||||
#if baking to a new layer then setup the new index and layer
|
||||
@@ -1109,7 +1198,8 @@ class MergeAnimLayerDown(bpy.types.Operator):
|
||||
for col in obj.data.collections:
|
||||
layers_rec.update({col : col.is_visible})
|
||||
col.is_visible = True
|
||||
|
||||
# Select the bones from all the layers
|
||||
self.shift = True
|
||||
select_keyframed_bones(self, context, obj)
|
||||
|
||||
constraint_rec = mute_constraints(obj)
|
||||
@@ -1123,7 +1213,7 @@ class MergeAnimLayerDown(bpy.types.Operator):
|
||||
if not obj.select_get():
|
||||
obj.select_set(True)
|
||||
bpy.ops.nla.bake(frame_start = frame_start, frame_end = frame_end, only_selected = True, visual_keying=True, clear_constraints=obj.als.clearconstraints, bake_types = bake_type, step = self.step)
|
||||
anim_data.action.fcurves.update()
|
||||
# anim_data.action.fcurves.update()
|
||||
strip = track.strips[0]
|
||||
old_action = strip.action
|
||||
|
||||
@@ -1133,7 +1223,6 @@ class MergeAnimLayerDown(bpy.types.Operator):
|
||||
track.strips.remove(strip)
|
||||
strip = track.strips.new(track.name, 0, action)
|
||||
anim_layers.tweak_mode_upper_stack(context, obj, anim_data, enter = False)
|
||||
#strip.action = anim_data.action
|
||||
|
||||
if obj.als.smartbake:
|
||||
smartbake_apply(obj, nla_tracks, fcu_keys, extrapolations)
|
||||
@@ -1141,35 +1230,39 @@ class MergeAnimLayerDown(bpy.types.Operator):
|
||||
armature_restore(obj, b_layers, layers_rec, constraint_rec)
|
||||
unmute_modifiers(obj, nla_tracks, modifier_rec)
|
||||
|
||||
anim_data.action = None
|
||||
# anim_data.action = None
|
||||
#bpy.data.actions.remove(old_action)
|
||||
if self.actioncopy:
|
||||
old_action.name = action_name + '_old'
|
||||
else:
|
||||
bpy.data.actions.remove(old_action)
|
||||
strip.action.name = action_name
|
||||
# strip.action.name = action_name
|
||||
if blendings.count('COMBINE') == len(blendings) and len(blendings) and obj.als.direction == 'UP':
|
||||
track.strips[0].blend_type = 'COMBINE'
|
||||
|
||||
|
||||
else: #use anim layers bake
|
||||
#print(frame_start, frame_end)
|
||||
action = AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, self.step, self.actioncopy, baked_layer)
|
||||
#action = AL_bake(self, frame_start, frame_end, nla_tracks, fcu_keys, additive, baked_layer)
|
||||
anim_layers.tweak_mode_upper_stack(context, obj, anim_data, enter = False)
|
||||
track.strips.remove(track.strips[0])
|
||||
strip = track.strips.new(track.name, 0, action)
|
||||
strip.blend_type = blend
|
||||
#track.strips[0].action = action
|
||||
|
||||
#removing layers after merge
|
||||
if obj.als.operator == 'MERGE':
|
||||
if hasattr(strip, 'action_slot'):
|
||||
if action_slot in strip.action.slots.values():
|
||||
strip.action_slot = action_slot
|
||||
else:
|
||||
# During NLA Bake slot doesn't exist need to find a new on
|
||||
strip.action_slot = anim_layers.get_obj_slot(obj, action)
|
||||
|
||||
#reset layer settings
|
||||
baked_layer = obj.Anim_Layers[obj.als.layer_index]
|
||||
baked_layer.repeat, baked_layer.speed, baked_layer.offset = 1, 1, 0
|
||||
strip.use_sync_length = False
|
||||
if baked_layer.frame_range:
|
||||
baked_layer.frame_range = False
|
||||
if baked_layer.custom_frame_range:
|
||||
baked_layer.custom_frame_range = False
|
||||
baked_layer.frame_start = frame_start
|
||||
baked_layer.frame_end = frame_end
|
||||
|
||||
@@ -1210,7 +1303,6 @@ class MergeAnimLayerDown(bpy.types.Operator):
|
||||
track.mute = True
|
||||
else:
|
||||
track.mute = False
|
||||
|
||||
action.use_fake_user = True
|
||||
anim_layers.register_layers(obj, nla_tracks)
|
||||
|
||||
|
||||
@@ -10,6 +10,46 @@ from mathutils import Quaternion
|
||||
from . import bake_ops
|
||||
from . import anim_layers
|
||||
|
||||
def attr_default(obj, fcu_key):
|
||||
#check if the fcurve source belongs to a bone or obj
|
||||
if fcu_key[0][:10] == 'pose.bones':
|
||||
transform = fcu_key[0].split('.')[-1]
|
||||
attr = fcu_key[0].split('"')[-2]
|
||||
bone = fcu_key[0].split('"')[1]
|
||||
source = obj.pose.bones[bone]
|
||||
|
||||
#in case of shapekey animation
|
||||
elif fcu_key[0][:10] == 'key_blocks':
|
||||
attr = fcu_key[0].split('"')[1]
|
||||
shapekey = obj.data.shape_keys.key_blocks[attr]
|
||||
return 0 if shapekey.slider_min <= 0 else shapekey.slider_min
|
||||
#in case of transforms in object mode
|
||||
else:# fcu_key[0] in transform_types:
|
||||
source = obj
|
||||
transform = fcu_key[0]
|
||||
|
||||
#check when it's transform property of Blender
|
||||
if transform in source.bl_rna.properties.keys():
|
||||
if hasattr(source.bl_rna.properties[transform], 'default_array'):
|
||||
if len(source.bl_rna.properties[transform].default_array) > fcu_key[1]:
|
||||
attrvalue = source.bl_rna.properties[transform].default_array[fcu_key[1]]
|
||||
return attrvalue
|
||||
|
||||
#in case of property on object
|
||||
elif fcu_key[0].split('"')[1] in obj.keys():
|
||||
attr = fcu_key[0].split('"')[1]
|
||||
|
||||
if 'attr' not in locals():
|
||||
return 0
|
||||
|
||||
#since blender 3 access to custom property settings changed
|
||||
if attr in source:
|
||||
id_attr = source.id_properties_ui(attr).as_dict()
|
||||
attrvalue = id_attr['default']
|
||||
return attrvalue
|
||||
|
||||
return 0
|
||||
|
||||
def store_handles(key):
|
||||
#storing the distance between the handles bezier to the key value
|
||||
handle_r = key.handle_right[1] - key.co[1]
|
||||
@@ -50,10 +90,17 @@ def add_value(key, value):
|
||||
apply_handles(key, handle_r, handle_l)
|
||||
|
||||
#calculate the difference between current value and the fcurve value
|
||||
def add_diff(obj, fcurves, path, current_value, eval_array):
|
||||
def add_diff(obj, fcurves, path, current_value, eval_array):
|
||||
'''Get the difference value and add it to all selected keyframes'''
|
||||
|
||||
if eval_array is None:
|
||||
return
|
||||
|
||||
array_value = current_value - eval_array
|
||||
|
||||
if not any(array_value):
|
||||
return
|
||||
|
||||
for i, value in enumerate(array_value):
|
||||
fcu = fcurves.find(path, index = i)
|
||||
if fcu is None or not filter_properties(obj, fcu):
|
||||
@@ -72,7 +119,9 @@ class ScaleValuesOp(bpy.types.Operator):
|
||||
#reset the values for dragging
|
||||
self.stop = False
|
||||
scene = context.scene
|
||||
scene.multikey['is_dragging'] = True
|
||||
global is_dragging
|
||||
is_dragging = True
|
||||
|
||||
self.avg_value = dict()
|
||||
#dictionary of the keyframes and their original INITIAL values
|
||||
self.keyframes_values = dict()
|
||||
@@ -81,15 +130,16 @@ class ScaleValuesOp(bpy.types.Operator):
|
||||
|
||||
#the average value for each fcurve
|
||||
self.keyframes_avg_value = dict()
|
||||
|
||||
|
||||
for obj in context.selected_objects:
|
||||
if obj.animation_data.action is None:
|
||||
continue
|
||||
action = obj.animation_data.action
|
||||
for fcu in action.fcurves:
|
||||
if obj.mode == 'POSE':
|
||||
if bake_ops.selected_bones_filter(obj, fcu.data_path):
|
||||
continue
|
||||
|
||||
fcurves = anim_layers.get_fcurves(obj, action)
|
||||
for fcu in fcurves:
|
||||
if anim_layers.selected_bones_filter(obj, fcu):
|
||||
continue
|
||||
if not filter_properties(obj, fcu):
|
||||
continue
|
||||
|
||||
@@ -112,6 +162,10 @@ class ScaleValuesOp(bpy.types.Operator):
|
||||
self.keyframes_avg_value.update({key : avg_value})
|
||||
|
||||
if not self.keyframes_avg_value:
|
||||
if 'is_dragging' in globals():
|
||||
del is_dragging
|
||||
scene.multikey['scale'] = 1
|
||||
anim_layers.redraw_areas(['VIEW_3D'])
|
||||
return('CANCELLED')
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
@@ -119,41 +173,52 @@ class ScaleValuesOp(bpy.types.Operator):
|
||||
|
||||
def modal(self, context, event):
|
||||
|
||||
scene = context.scene
|
||||
scale = scene.multikey.scale
|
||||
global is_dragging
|
||||
|
||||
try:
|
||||
scene = context.scene
|
||||
scale = scene.multikey.scale
|
||||
#Quit the modal operator when the slider is released
|
||||
if self.stop:
|
||||
del is_dragging
|
||||
scene.multikey['scale'] = 1
|
||||
anim_layers.redraw_areas(['VIEW_3D'])
|
||||
#modal is being cancelled because of undo issue with the modal running through the property
|
||||
return {'FINISHED'}
|
||||
if event.value == 'RELEASE': # Stop the modal on next frame. Don't block the event since we want to exit the field dragging
|
||||
self.stop = True
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
for key, key_value in self.keyframes_values.items():
|
||||
if not key.select_control_point:
|
||||
continue
|
||||
if key not in self.keyframes_avg_value:
|
||||
continue
|
||||
avg_value = self.keyframes_avg_value[key]
|
||||
handle_right_value = self.keyframes_handle_right[key]
|
||||
handle_left_value = self.keyframes_handle_left[key]
|
||||
|
||||
#add the value of the distance from the average * scale factor
|
||||
key.co[1] = avg_value + ((key_value - avg_value)*scale)
|
||||
key.handle_right[1] = avg_value + ((handle_right_value - avg_value)*scale)
|
||||
key.handle_left[1] = avg_value + ((handle_left_value - avg_value)*scale)
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
#Quit the modal operator when the slider is released
|
||||
if self.stop:
|
||||
scene.multikey['is_dragging'] = False
|
||||
scene.multikey['scale'] = 1
|
||||
anim_layers.redraw_areas(['VIEW_3D'])
|
||||
#modal is being cancelled because of undo issue with the modal running through the property
|
||||
return {'FINISHED'}
|
||||
|
||||
if event.value == 'RELEASE': # Stop the modal on next frame. Don't block the event since we want to exit the field dragging
|
||||
except Exception as e:
|
||||
# Log the error
|
||||
print("Error:", e)
|
||||
self['scale'] = 1
|
||||
self.stop = True
|
||||
|
||||
for key, key_value in self.keyframes_values.items():
|
||||
if not key.select_control_point:
|
||||
continue
|
||||
if key not in self.keyframes_avg_value:
|
||||
continue
|
||||
avg_value = self.keyframes_avg_value[key]
|
||||
handle_right_value = self.keyframes_handle_right[key]
|
||||
handle_left_value = self.keyframes_handle_left[key]
|
||||
|
||||
#add the value of the distance from the average * scale factor
|
||||
key.co[1] = avg_value + ((key_value - avg_value)*scale)
|
||||
key.handle_right[1] = avg_value + ((handle_right_value - avg_value)*scale)
|
||||
key.handle_left[1] = avg_value + ((handle_left_value - avg_value)*scale)
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
del is_dragging
|
||||
return {'CANCELLED'}
|
||||
|
||||
def scale_value(self, context):
|
||||
|
||||
if 'is_dragging' in globals():
|
||||
if is_dragging:
|
||||
return
|
||||
|
||||
scene = context.scene
|
||||
if scene.multikey.is_dragging:
|
||||
return
|
||||
obj = context.object
|
||||
|
||||
if obj is None:
|
||||
@@ -168,7 +233,6 @@ def scale_value(self, context):
|
||||
if context.mode == 'POSE' and not context.selected_pose_bones:
|
||||
self['scale'] = 1
|
||||
return
|
||||
|
||||
bpy.ops.anim.multikey_scale_value('INVOKE_DEFAULT')
|
||||
|
||||
def random_value(self, context):
|
||||
@@ -177,10 +241,11 @@ def random_value(self, context):
|
||||
if obj.animation_data.action is None:
|
||||
continue
|
||||
action = obj.animation_data.action
|
||||
for fcu in action.fcurves:
|
||||
if obj.mode == 'POSE':
|
||||
if bake_ops.selected_bones_filter(obj, fcu.data_path):
|
||||
continue
|
||||
fcurves = anim_layers.get_fcurves(obj, action)
|
||||
for fcu in fcurves:
|
||||
# if obj.mode == 'POSE':
|
||||
if anim_layers.selected_bones_filter(obj, fcu):
|
||||
continue
|
||||
if not filter_properties(obj, fcu):
|
||||
continue
|
||||
value_list = []
|
||||
@@ -197,20 +262,42 @@ def random_value(self, context):
|
||||
|
||||
self['randomness'] = 0.1
|
||||
|
||||
def evaluate_array(action, fcu_path, frame, array_len):
|
||||
def evaluate_combine(data_path, added_array, eval_array, array_default, influence):
|
||||
|
||||
if 'scale' in data_path:
|
||||
eval_array = eval_array * (added_array / array_default) ** influence
|
||||
elif 'rotation_quaternion' in data_path:
|
||||
#multiply first the influence with the w separatly
|
||||
added_array[0] = added_array[0] + (1- added_array[0])*(1 - influence)
|
||||
added_array[1:] *= influence
|
||||
eval_array = np.array(Quaternion(eval_array) @ Quaternion(added_array))# ** influence
|
||||
#if it's a custom property
|
||||
elif 'rotation_euler' not in data_path and 'location' not in data_path:
|
||||
eval_array = eval_array + (added_array - array_default) * influence
|
||||
|
||||
return eval_array
|
||||
|
||||
def evaluate_array(fcurves, fcu_path, frame, array_default = [0, 0, 0]):
|
||||
'''Create an array from all the indexes'''
|
||||
|
||||
fcu_array = []
|
||||
array_len = len(array_default)
|
||||
|
||||
#assigning the default array in case
|
||||
fcu_array = array_default.copy()
|
||||
#get the missing arrays in case quaternion is not complete
|
||||
for i in range(array_len):
|
||||
fcu = action.fcurves.find(fcu_path, index = i)
|
||||
fcu = fcurves.find(fcu_path, index = i)
|
||||
if fcu is None:
|
||||
continue
|
||||
fcu_array.append(fcu.evaluate(frame))
|
||||
if not len(fcu_array):
|
||||
return None
|
||||
fcu_array[i] = fcu.evaluate(frame)
|
||||
|
||||
# if (fcu_array == array_default).all():
|
||||
# # print('295 return none')
|
||||
# return None
|
||||
|
||||
return np.array(fcu_array)
|
||||
|
||||
def evaluate_layers(context, obj, anim_data, fcu, array_len):
|
||||
def evaluate_layers(context, obj, anim_data, fcu, array_default):
|
||||
'''Calculate the evaluation of all the layers when using the nla'''
|
||||
|
||||
if not hasattr(anim_data, 'nla_tracks') or not anim_data.use_nla:
|
||||
@@ -219,12 +306,11 @@ def evaluate_layers(context, obj, anim_data, fcu, array_len):
|
||||
if not len(nla_tracks):
|
||||
return None
|
||||
frame = context.scene.frame_current
|
||||
#blend_types = {'ADD' : '+', 'SUBTRACT' : '-', 'MULTIPLY' : '*'}
|
||||
blend_types = {'ADD' : '+', 'SUBTRACT' : '-', 'MULTIPLY' : '*'}
|
||||
fcu_path = fcu.data_path
|
||||
|
||||
#array_default = np.array([bake_ops.attr_default(obj, (fcu_path, i)) for i in range(4) if anim_data.action.fcurves.find(fcu_path, index = i) is not None])
|
||||
array_default = np.array(bake_ops.attr_default(obj, (fcu_path, fcu.array_index)))
|
||||
eval_array = array_default
|
||||
eval_array = array_default.copy()
|
||||
|
||||
for track in nla_tracks:
|
||||
if track.mute:
|
||||
continue
|
||||
@@ -264,43 +350,49 @@ def evaluate_layers(context, obj, anim_data, fcu, array_len):
|
||||
frame_eval = last_frame - (frame_eval - strip.frame_start)
|
||||
offset = (strip.frame_start * 1/strip.scale - strip.action_frame_start) * strip.scale
|
||||
frame_eval = strip.frame_start * 1/strip.scale + (frame_eval - strip.frame_start) * 1/strip.scale - offset * 1/strip.scale
|
||||
|
||||
fcu_array = evaluate_array(action, fcu_path, frame_eval, array_len)
|
||||
if fcu_array is None:
|
||||
continue
|
||||
|
||||
###EVALUATION###
|
||||
eval_array = evaluation(blend_type, fcu_path, fcu_array, eval_array, array_default, influence)
|
||||
fcurves = anim_layers.get_fcurves(obj, action)
|
||||
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame_eval, influence, array_default, blend_type, blend_types)
|
||||
|
||||
#If there is an action on top of the nla tracks (not using anim layers) add it to the evaluation
|
||||
if anim_data.action is not None and not anim_data.use_tweak_mode:
|
||||
fcu_array = evaluate_array(anim_data.action, fcu_path, frame, array_len)
|
||||
if fcu_array is not None:
|
||||
eval_array = evaluation(anim_data.action_blend_type, fcu_path, fcu_array, eval_array, array_default, anim_data.action_influence)
|
||||
|
||||
return eval_array
|
||||
#Adding an extra layer from the action outside and on top of the nla
|
||||
tweak_mode = anim_data.use_tweak_mode
|
||||
if tweak_mode:
|
||||
anim_data.use_tweak_mode = False
|
||||
|
||||
def evaluation(blend_type, fcu_path, fcu_array, eval_array, array_default, influence):
|
||||
|
||||
blend_types = {'ADD' : '+', 'SUBTRACT' : '-', 'MULTIPLY' : '*'}
|
||||
# fcu_array = evaluate_array(action, fcu_path, frame_eval, array_len)
|
||||
# if fcu_array is None:
|
||||
# continue
|
||||
action = anim_data.action
|
||||
if action:
|
||||
influence = anim_data.action_influence
|
||||
blend_type = anim_data.action_blend_type
|
||||
|
||||
fcurves = anim_layers.get_fcurves(obj, action)
|
||||
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame, influence, array_default, blend_type, blend_types)
|
||||
anim_data.use_tweak_mode = tweak_mode
|
||||
|
||||
return eval_array
|
||||
|
||||
|
||||
def evaluate_blend_type(fcurves, eval_array, fcu_path, frame, influence,
|
||||
array_default, blend_type, blend_types):
|
||||
'''Calculate the value based on the blend type'''
|
||||
|
||||
fcu_array = evaluate_array(fcurves, fcu_path, frame, array_default)
|
||||
if fcu_array is None:
|
||||
return eval_array
|
||||
###EVALUATION###
|
||||
if blend_type =='COMBINE':
|
||||
if 'location' in fcu_path or 'rotation_euler' in fcu_path:
|
||||
blend_type = 'ADD'
|
||||
|
||||
|
||||
if blend_type =='REPLACE':
|
||||
eval_array = eval_array * (1 - influence) + fcu_array * influence
|
||||
elif blend_type =='COMBINE':
|
||||
eval_array = bake_ops.evaluate_combine(fcu_path, fcu_array, eval_array, array_default, influence)
|
||||
eval_array = evaluate_combine(fcu_path, fcu_array, eval_array, array_default, influence)
|
||||
else:
|
||||
eval_array = eval('eval_array' + blend_types[blend_type] + 'fcu_array' + '*' + str(influence))
|
||||
|
||||
|
||||
return eval_array
|
||||
|
||||
|
||||
def evaluate_value(self, context):
|
||||
|
||||
for obj in context.selected_objects:
|
||||
|
||||
anim_data = obj.animation_data
|
||||
@@ -310,36 +402,51 @@ def evaluate_value(self, context):
|
||||
return
|
||||
|
||||
action = obj.animation_data.action
|
||||
fcu_paths = []
|
||||
# fcu_paths = []
|
||||
transformations = ["rotation_quaternion","rotation_euler", "location", "scale"]
|
||||
if obj.mode == 'POSE':
|
||||
bonelist = context.selected_pose_bones if obj.als.onlyselected else obj.pose.bones
|
||||
|
||||
for fcu in action.fcurves:
|
||||
if fcu in fcu_paths:
|
||||
|
||||
fcurves = anim_layers.get_fcurves(obj, action)
|
||||
for fcu in fcurves:
|
||||
# if fcu in fcu_paths:
|
||||
# continue
|
||||
current_value = None
|
||||
if not filter_properties(obj, fcu):
|
||||
continue
|
||||
if obj.mode == 'POSE':
|
||||
if bake_ops.selected_bones_filter(obj, fcu.data_path):
|
||||
if anim_layers.selected_bones_filter(obj, fcu):
|
||||
continue
|
||||
|
||||
|
||||
for bone in bonelist:
|
||||
#find the fcurve of the bone
|
||||
if fcu.data_path.rfind(bone.name) != 12 or fcu.data_path[12 + len(bone.name)] != '"':
|
||||
continue
|
||||
# transform = fcu.data_path[15 + len(bone.name):]
|
||||
transform = fcu.data_path.split('"].')[1]
|
||||
path_split = fcu.data_path.split('"].')
|
||||
|
||||
if len(path_split) <= 1:
|
||||
continue
|
||||
else:
|
||||
transform = fcu.data_path.split('"].')[1]
|
||||
if transform not in transformations:
|
||||
continue
|
||||
|
||||
current_value = getattr(obj.pose.bones[bone.name], transform)
|
||||
else:
|
||||
transform = fcu.data_path
|
||||
current_value = getattr(obj, transform)
|
||||
|
||||
eval_array = evaluate_layers(context, obj, anim_data, fcu, len(current_value))
|
||||
#In case it was completly filtered out and not current value available
|
||||
if not current_value:
|
||||
continue
|
||||
|
||||
array_default = np.array(bake_ops.attr_default(obj, (fcu.data_path, fcu.array_index)))
|
||||
eval_array = evaluate_layers(context, obj, anim_data, fcu, array_default)
|
||||
if eval_array is None:
|
||||
eval_array = evaluate_array(action, fcu.data_path, context.scene.frame_current, len(current_value))
|
||||
fcurves = anim_layers.get_fcurves(obj, action)
|
||||
eval_array = evaluate_array(fcurves, fcu.data_path, context.scene.frame_current, array_default)
|
||||
|
||||
#calculate the difference between current value and the fcurve value
|
||||
add_diff(obj, action.fcurves, fcu.data_path, np.array(current_value), eval_array)
|
||||
add_diff(obj, fcurves, fcu.data_path, np.array(current_value), eval_array)
|
||||
|
||||
class MULTIKEY_OT_Multikey(bpy.types.Operator):
|
||||
"""Edit all selected keyframes"""
|
||||
@@ -362,7 +469,7 @@ class MultikeyProperties(bpy.types.PropertyGroup):
|
||||
#handletype: bpy.props.BoolProperty(name="Keep handle types", description="Keep handle types", default=False, options={'HIDDEN'})
|
||||
scale: bpy.props.FloatProperty(name="Scale Values Factor", description="Scale percentage from the average value", default=1.0, soft_max = 10, soft_min = -10, step=0.1, precision = 3, update = scale_value)
|
||||
randomness: bpy.props.FloatProperty(name="Randomness", description="Random Threshold of keyframes", default=0.1, min=0.0, max = 1.0, update = random_value)
|
||||
is_dragging: bpy.props.BoolProperty(default = False)
|
||||
# is_dragging: bpy.props.BoolProperty(default = False)
|
||||
|
||||
#filters
|
||||
filter_location: bpy.props.BoolVectorProperty(name="Location", description="Filter Location properties", default=(True, True, True), size = 3, options={'HIDDEN'})
|
||||
|
||||
@@ -2,10 +2,26 @@ import bpy
|
||||
|
||||
from . import anim_layers
|
||||
from . import bake_ops
|
||||
import numpy as np
|
||||
import time
|
||||
import inspect
|
||||
|
||||
def subscriptions_remove(handler = True):
|
||||
#clear all handlers and subsciptions
|
||||
bpy.msgbus.clear_by_owner(bpy.context.scene)
|
||||
# if scene is None : scene = bpy.context.scene
|
||||
|
||||
global subscriptions_owner
|
||||
if 'subscriptions_owner' in globals():
|
||||
bpy.msgbus.clear_by_owner(subscriptions_owner)
|
||||
del subscriptions_owner
|
||||
|
||||
global influence_keys, selected_bones
|
||||
|
||||
if 'influence_keys' in globals():
|
||||
del influence_keys
|
||||
if 'selected_bones' in globals():
|
||||
del selected_bones
|
||||
|
||||
if not handler:
|
||||
return
|
||||
if check_handler in bpy.app.handlers.depsgraph_update_pre:
|
||||
@@ -14,31 +30,43 @@ def subscriptions_remove(handler = True):
|
||||
bpy.app.handlers.frame_change_post.remove(animlayers_frame)
|
||||
|
||||
def subscriptions_add(scene, handler = True):
|
||||
bpy.msgbus.clear_by_owner(scene)
|
||||
global initial_call
|
||||
|
||||
initial_call = True
|
||||
subscribe_to_frame_end(scene)
|
||||
subscribe_to_track_name(scene)
|
||||
subscribe_to_action_name(scene)
|
||||
subscribe_to_strip_settings(scene)
|
||||
subscribe_to_influence(scene)
|
||||
global func_running
|
||||
|
||||
#If I call initial call from here it calls before running the previous functions
|
||||
#initial_call = False
|
||||
func_running = False
|
||||
global subscriptions_owner
|
||||
if 'subscriptions_owner' in globals():
|
||||
bpy.msgbus.clear_by_owner(subscriptions_owner)
|
||||
|
||||
subscriptions_owner = object()
|
||||
|
||||
#Checking if frame range preview was turned on when pressing P
|
||||
|
||||
subscribe_to_preview_frame_end(scene)
|
||||
subscribe_to_track_name(subscriptions_owner)
|
||||
subscribe_to_action_name(subscriptions_owner)
|
||||
subscribe_to_strip_settings(subscriptions_owner)
|
||||
subscribe_to_influence(subscriptions_owner)
|
||||
if bpy.app.version >= (4, 4, 0):
|
||||
subscribe_to_action_slot(scene)
|
||||
|
||||
if not handler:
|
||||
return
|
||||
|
||||
|
||||
if check_handler not in bpy.app.handlers.depsgraph_update_pre:
|
||||
bpy.app.handlers.depsgraph_update_pre.append(check_handler)
|
||||
if animlayers_frame not in bpy.app.handlers.frame_change_post:
|
||||
bpy.app.handlers.frame_change_post.append(animlayers_frame)
|
||||
|
||||
def animlayers_frame(self, context):
|
||||
scene = bpy.context.scene
|
||||
current = scene.frame_current_final
|
||||
def animlayers_frame(scene, context):
|
||||
|
||||
current = scene.frame_current_final
|
||||
check_scene()
|
||||
#During Particles bake screen attribute is empty
|
||||
if bpy.context.screen is None:
|
||||
return
|
||||
#Make sure the animation is playing and not just running a motion path
|
||||
if not bpy.context.screen.is_animation_playing:
|
||||
return
|
||||
#Checking if preview range was turned on or off, when using hotkey P it doesn't recognize
|
||||
#only during the frame handler
|
||||
if scene.get('framerange_preview') != scene.use_preview_range:
|
||||
@@ -47,22 +75,28 @@ def animlayers_frame(self, context):
|
||||
return
|
||||
|
||||
frame_start, frame_end = bake_ops.frame_start_end(scene)
|
||||
# frame_start, frame_end = get_frame_range(scene)
|
||||
reset_subscription = False
|
||||
if 'outofrange' not in globals():
|
||||
global outofrange
|
||||
outofrange = False if frame_start <= current <= frame_end else True
|
||||
|
||||
if frame_start <= current <= frame_end:
|
||||
outofrange = False if 0 <= current < frame_end else True
|
||||
|
||||
if 0 <= current < frame_end:
|
||||
if outofrange:
|
||||
frameend_update_callback()
|
||||
outofrange = False
|
||||
return
|
||||
|
||||
outofrange = True
|
||||
if current <= frame_end:
|
||||
return
|
||||
|
||||
#In case of running into empty objects then clean AL_objects
|
||||
clean_AL_objects = False
|
||||
#iterate only through objects with anim layers turned on
|
||||
objects = [obj.object for obj in scene.AL_objects]
|
||||
for obj in objects:
|
||||
if obj is None:
|
||||
clean_AL_objects = True
|
||||
continue
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
if anim_data is None:
|
||||
return
|
||||
@@ -74,63 +108,57 @@ def animlayers_frame(self, context):
|
||||
continue
|
||||
|
||||
#checks if the layer has a custom frame range
|
||||
if obj.Anim_Layers[i].frame_range:
|
||||
layer = obj.Anim_Layers[i]
|
||||
if layer.custom_frame_range:
|
||||
continue
|
||||
if not reset_subscription:
|
||||
subscriptions_remove(handler = False)
|
||||
reset_subscription = True
|
||||
|
||||
track.strips[0].frame_end_ui = current + 10
|
||||
strip = track.strips[0]
|
||||
|
||||
if current < 0:
|
||||
# anim_layers.strip_action_recalc(layer, track.strips[0])
|
||||
strip.frame_start = current
|
||||
# track.strips[0].action_frame_start = current * 1/layer.speed - layer.offset * 1/layer.speed
|
||||
anim_layers.update_action_frame_range(current, frame_end, layer, strip)
|
||||
strip.frame_end = frame_end + 10.0
|
||||
|
||||
elif current >= frame_end:
|
||||
if strip.frame_start < 0:
|
||||
strip.frame_start = 0
|
||||
anim_layers.update_action_frame_range(0, frame_end, layer, strip)
|
||||
anim_layers.update_action_frame_range(strip.frame_start, current + 10.0, layer, strip)
|
||||
strip.frame_end = current + 10.0
|
||||
|
||||
if clean_AL_objects:
|
||||
anim_layers.clean_AL_objects(scene)
|
||||
|
||||
if reset_subscription:
|
||||
subscriptions_add(scene, handler = False)
|
||||
sync_strip_range()
|
||||
|
||||
def objects_viewlayer(scene):
|
||||
'''in case of an object excluded or included in the nla, update it because of an nla bug'''
|
||||
if len(bpy.context.view_layer.objects) == scene.als.viewlayer_objects:
|
||||
return
|
||||
i = 0
|
||||
while i < len(scene.AL_objects):
|
||||
obj = scene.AL_objects[i].object
|
||||
if obj is None:
|
||||
scene.AL_objects.remove(i)
|
||||
continue
|
||||
i += 1
|
||||
if obj.als.viewlayer and obj not in bpy.context.view_layer.objects.values():
|
||||
obj.als.viewlayer = False
|
||||
if not obj.als.viewlayer and obj in bpy.context.view_layer.objects.values():
|
||||
#anim_data = anim_layers.anim_data_type(obj)
|
||||
obj.als.upper_stack = False
|
||||
obj.als.viewlayer = True
|
||||
obj.als.layer_index = obj.als.layer_index
|
||||
#anim_layers.tweak_mode_upper_stack(bpy.context, anim_data)
|
||||
scene.als.viewlayer_objects = len(bpy.context.view_layer.objects)
|
||||
|
||||
def check_handler(self, context):
|
||||
|
||||
def check_handler(scene):
|
||||
'''A main function that performs a series of checks using a handler'''
|
||||
scene = bpy.context.scene
|
||||
# scene = bpy.context.scene
|
||||
#Timer for handler
|
||||
# if 'last_check_time' not in globals():
|
||||
# global last_check_time
|
||||
# last_check_time = 0
|
||||
# current_time = time.time()
|
||||
# if current_time - last_check_time < 0.01:
|
||||
# return
|
||||
# last_check_time = current_time
|
||||
|
||||
#if there are no objects included in animation layers then return
|
||||
if not len(scene.AL_objects):
|
||||
return
|
||||
|
||||
objects_viewlayer(scene)
|
||||
|
||||
obj = bpy.context.object
|
||||
|
||||
#if the object was removed from the scene, then remove it from anim layers object list
|
||||
if obj is None:
|
||||
i = 0
|
||||
while i < len(scene.AL_objects):
|
||||
if scene.AL_objects[i].object not in scene.objects.values():
|
||||
scene.AL_objects.remove(i)
|
||||
else:
|
||||
i += 1
|
||||
anim_layers.clean_AL_objects(scene)
|
||||
return
|
||||
|
||||
if not obj.als.turn_on:
|
||||
return
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
if anim_data is None:
|
||||
return
|
||||
if not anim_data.use_nla:
|
||||
@@ -146,8 +174,6 @@ def check_handler(self, context):
|
||||
active_action_update(obj, anim_data, nla_tracks)
|
||||
#check if a keyframe was removed
|
||||
if bpy.context.active_operator is not None:
|
||||
if bpy.context.active_operator.name in ['Transform', 'Delete Keyframes'] and obj.als.edit_all_keyframes:
|
||||
anim_layers.edit_all_keyframes()
|
||||
|
||||
if bpy.context.active_operator.name == 'Enter Tweak Mode':
|
||||
if not bpy.context.active_operator.properties['use_upper_stack_evaluation']:
|
||||
@@ -155,51 +181,76 @@ def check_handler(self, context):
|
||||
|
||||
if bpy.context.active_operator.name == 'Move Channels':
|
||||
anim_layers.visible_layers(obj, nla_tracks)
|
||||
|
||||
# check if track and layers are synchronized
|
||||
if len(nla_tracks) != len(obj.Anim_Layers):
|
||||
new_layers_names = set(track.name for track in nla_tracks).difference(set(layer.name for layer in obj.Anim_Layers))
|
||||
anim_layers.visible_layers(obj, nla_tracks)
|
||||
if obj.als.layer_index > len(obj.Anim_Layers)-1:
|
||||
obj.als.layer_index = len(obj.Anim_Layers)-1
|
||||
|
||||
#update new layer with strip settings
|
||||
frame_start, frame_end = bake_ops.frame_start_end(bpy.context.scene)
|
||||
|
||||
for layer_name in new_layers_names:
|
||||
if len(nla_tracks[layer_name].strips) != 1:
|
||||
continue
|
||||
strip = get_strip_in_meta(nla_tracks[layer_name].strips[0])
|
||||
layer = obj.Anim_Layers[layer_name]
|
||||
if (strip.frame_start, strip.frame_end) != (frame_start, frame_end):
|
||||
layer['frame_range'] = True
|
||||
update_strip_layer_settings(strip, layer)
|
||||
layer['action'] = strip.action
|
||||
# Making sure that scene.als.edit_all_layers_op is not somehow turned on
|
||||
if not any(item.object.als.edit_all_keyframes for item in scene.AL_objects) and scene.als.edit_all_layers_op:
|
||||
scene.als.edit_all_layers_op = False
|
||||
|
||||
# check if track and layers are synchronized
|
||||
if track_layer_synchronization(obj, nla_tracks):
|
||||
return
|
||||
|
||||
anim_layers.add_obj_to_animlayers(obj, [item.object for item in scene.AL_objects])
|
||||
|
||||
track = nla_tracks[obj.als.layer_index]
|
||||
|
||||
sync_frame_range(scene, track, layer)
|
||||
# sync_strip_range(scene)
|
||||
always_sync_range(track, layer)
|
||||
# sync_strip_range(track, layer)
|
||||
|
||||
if anim_data.use_tweak_mode and layer.lock:
|
||||
layer['lock'] = False
|
||||
elif not anim_data.use_tweak_mode and not layer.lock:
|
||||
layer['lock'] = True
|
||||
|
||||
influence_sync(obj, nla_tracks)
|
||||
|
||||
influence_sync(scene, obj, nla_tracks)
|
||||
|
||||
# continue if locked
|
||||
if layer.lock:
|
||||
return
|
||||
|
||||
#In case a keyframe was added and a new action slot was added to anim_data
|
||||
#Check that it's synchornized with the strip action slot
|
||||
strip = track.strips[0]
|
||||
if hasattr(strip, 'action_slot') and strip.action:
|
||||
if strip.action_slot != anim_data.action_slot:
|
||||
strip.action_slot = anim_data.action_slot
|
||||
|
||||
if obj.als.view_all_keyframes:
|
||||
anim_layers.hide_view_all_keyframes(obj, anim_data)
|
||||
check_selected_bones(obj)
|
||||
|
||||
influence_check(nla_tracks[obj.als.layer_index])
|
||||
|
||||
def track_layer_synchronization(obj, nla_tracks):
|
||||
'''check if track and layers are synchronized, running only when adding/removing tracks via the nla'''
|
||||
|
||||
if len(nla_tracks) == len(obj.Anim_Layers):
|
||||
return False
|
||||
|
||||
new_layers_names = set(track.name for track in nla_tracks).difference(set(layer.name for layer in obj.Anim_Layers))
|
||||
anim_layers.visible_layers(obj, nla_tracks)
|
||||
if obj.als.layer_index > len(obj.Anim_Layers)-1:
|
||||
obj.als.layer_index = len(obj.Anim_Layers)-1
|
||||
|
||||
#update new layer with strip settings
|
||||
frame_start, frame_end = get_frame_range(bpy.context.scene)
|
||||
|
||||
for layer_name in new_layers_names:
|
||||
if len(nla_tracks[layer_name].strips) != 1:
|
||||
continue
|
||||
strip = get_strip_in_meta(nla_tracks[layer_name].strips[0])
|
||||
layer = obj.Anim_Layers[layer_name]
|
||||
if not layer.custom_frame_range:
|
||||
continue
|
||||
if (strip.frame_start, strip.frame_end) != (frame_start, frame_end):
|
||||
subscriptions_remove()
|
||||
# print(f'strip.frame_start {strip.frame_start} strip.frame_end {strip.frame_end} frame_start {frame_start} frame_end {frame_end}')
|
||||
bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT')
|
||||
return
|
||||
|
||||
update_strip_layer_settings(strip, layer)
|
||||
layer['action'] = strip.action
|
||||
return True
|
||||
|
||||
def active_action_update(obj, anim_data, nla_tracks):
|
||||
'''updating the active action into the selected layer'''
|
||||
if obj.Anim_Layers[obj.als.layer_index].lock:
|
||||
@@ -227,16 +278,62 @@ def get_strip_in_meta(strip):
|
||||
strip = strip.strips[0]
|
||||
return strip
|
||||
|
||||
def sync_strip_range():
|
||||
|
||||
scene = bpy.context.scene
|
||||
frame_start, frame_end = bake_ops.frame_start_end(scene)
|
||||
def sync_frame_range(scene, track, layer):
|
||||
'''Nla strips are not updating with msgbus when changing frame range in the ui
|
||||
so it checks again during check handler if the frame range is changed and syncs it'''
|
||||
|
||||
if scene.frame_current_final > frame_end:
|
||||
frame_end = scene.frame_current_final + 10
|
||||
if bpy.context.screen.is_animation_playing:
|
||||
return
|
||||
# scene = bpy.context.scene
|
||||
if not len(track.strips):
|
||||
return
|
||||
|
||||
strip = track.strips[0]
|
||||
|
||||
#In case of Custom frame range
|
||||
if layer['custom_frame_range']:
|
||||
if (strip.frame_start, strip.frame_end) != (layer.frame_start, layer.frame_end):
|
||||
update_strip_layer_settings(strip, layer)
|
||||
|
||||
else:
|
||||
#In case of None custom frame range, make the strips adjust to scene frame range
|
||||
frame_start, frame_end = get_frame_range(scene)
|
||||
|
||||
#defining global frame range to check if it was changed in the handler,
|
||||
# msgbus subsciption is not updated before
|
||||
if 'frame_range' not in globals():
|
||||
global frame_range
|
||||
frame_range = (frame_start, frame_end)
|
||||
|
||||
if frame_range != (frame_start, frame_end):
|
||||
frame_range = (frame_start, frame_end)
|
||||
frameend_update_callback()
|
||||
return
|
||||
|
||||
#Turn on custom frame range if the current strip is not following the scene frame range
|
||||
if (round(strip.frame_start, 2), round(strip.frame_end, 2)) != (round(frame_start, 2), round(frame_end, 2)):
|
||||
subscriptions_remove()
|
||||
# print('315 custom frame range')
|
||||
bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT')
|
||||
return
|
||||
|
||||
def sync_strip_range(scene):
|
||||
'''Checking all the strips if a value was changed in the nla (not including UI changes)
|
||||
Similiar to sync custom frame range but iterating through all the layers
|
||||
Currently disabled'''
|
||||
|
||||
frame_start, frame_end = get_frame_range(scene)
|
||||
if 'frame_range' not in globals():
|
||||
global frame_range
|
||||
frame_range = (frame_start, frame_end)
|
||||
|
||||
clean_AL_objects = False
|
||||
objects = [obj.object for obj in scene.AL_objects]
|
||||
for obj in objects:
|
||||
if obj is None:
|
||||
#Turn on to clean AL_objects
|
||||
clean_AL_objects = True
|
||||
continue
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
if anim_data is None:
|
||||
continue
|
||||
@@ -246,20 +343,31 @@ def sync_strip_range():
|
||||
for i, track in enumerate(nla_tracks):
|
||||
if len(track.strips) != 1:
|
||||
continue
|
||||
if obj.Anim_Layers[i]['frame_range']:
|
||||
layer = obj.Anim_Layers[i]
|
||||
if layer['custom_frame_range']:
|
||||
if (strip.frame_start, strip.frame_end) != (layer.frame_start, layer.frame_end):
|
||||
update_strip_layer_settings(strip, layer)
|
||||
continue
|
||||
|
||||
strip = track.strips[0]
|
||||
strip_frame_start = strip.frame_start
|
||||
strip_frame_end = strip.frame_end
|
||||
|
||||
if (strip_frame_start, round(strip_frame_end)) != (0.0, float(frame_end)):
|
||||
obj.Anim_Layers[i]['frame_range'] = True
|
||||
|
||||
if (strip_frame_start, round(strip_frame_end, 2)) != (frame_start, float(frame_end)):
|
||||
subscriptions_remove()
|
||||
# print('357 custom_frame_range_warning ')
|
||||
# print(f'strip_frame_start {strip_frame_start} strip_frame_end {round(strip_frame_end, 2)} frame_start {frame_start} frame_end {float(frame_end)}')
|
||||
bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT')
|
||||
return
|
||||
|
||||
if clean_AL_objects:
|
||||
anim_layers.clean_AL_objects(scene)
|
||||
|
||||
def always_sync_range(track, layer):
|
||||
'''sync frame range when always sync turned on'''
|
||||
if not len(track.strips):
|
||||
return
|
||||
if not layer.frame_range:
|
||||
if not layer.custom_frame_range:
|
||||
if track.strips[0].use_sync_length:
|
||||
track.strips[0].use_sync_length = False
|
||||
return
|
||||
@@ -273,7 +381,7 @@ def always_sync_range(track, layer):
|
||||
anim_layers.sync_frame_range(bpy.context)
|
||||
layer.action_range = strip.action.frame_range
|
||||
|
||||
def influence_sync(obj, nla_tracks):
|
||||
def influence_sync(scene, obj, nla_tracks):
|
||||
|
||||
#Tracks that dont have keyframes are locked
|
||||
for i, track in enumerate(nla_tracks):
|
||||
@@ -287,56 +395,77 @@ def influence_sync(obj, nla_tracks):
|
||||
if not len(track.strips[0].fcurves[0].keyframe_points):
|
||||
#apply the influence property to the temp property when keyframes are removed (but its still locked)
|
||||
if not track.strips[0].fcurves[0].lock:
|
||||
obj.Anim_Layers[i].influence = track.strips[0].influence
|
||||
# obj.Anim_Layers[i]['influence'] = track.strips[0].influence
|
||||
scene.als['influence'] = track.strips[0].influence
|
||||
track.strips[0].fcurves[0].lock = True
|
||||
|
||||
if obj.animation_data is None:
|
||||
if scene.animation_data is None:
|
||||
return
|
||||
action = obj.animation_data.action
|
||||
action = scene.animation_data.action
|
||||
if action is None:
|
||||
return
|
||||
#if a keyframe was found in the temporary property then add it to the
|
||||
data_path = 'Anim_Layers[' + str(obj.als.layer_index) + '].influence'
|
||||
fcu_influence = action.fcurves.find(data_path)
|
||||
# data_path = 'Anim_Layers[' + str(obj.als.layer_index) + '].influence'
|
||||
data_path = 'als.influence'
|
||||
fcurves = anim_layers.get_fcurves(scene, action, data_type = 'SCENE')
|
||||
if not len(fcurves):
|
||||
return
|
||||
# fcurves = action.fcurves
|
||||
fcu_influence = fcurves.find(data_path)
|
||||
if fcu_influence is None:
|
||||
return
|
||||
if not len(fcu_influence.keyframe_points):
|
||||
return
|
||||
#remove the temporary influence
|
||||
action.fcurves.remove(fcu_influence)
|
||||
fcurves.remove(fcu_influence)
|
||||
#if the action was created just for the influence because of empty object data type then remove the action
|
||||
if action.name == obj.name + 'Action' and not len(obj.animation_data.nla_tracks) and not len(action.fcurves):
|
||||
if action.name == scene.name + 'Action' and not len(scene.animation_data.nla_tracks) and not len(fcurves):
|
||||
bpy.data.actions.remove(action)
|
||||
if obj.Anim_Layers[obj.als.layer_index].influence_mute:
|
||||
return
|
||||
|
||||
strip = nla_tracks[obj.als.layer_index].strips[0]
|
||||
if strip.fcurves[0].mute:
|
||||
return
|
||||
strip.fcurves[0].lock = False
|
||||
|
||||
# if not strip.influence:
|
||||
# strip.influence = 0.0001
|
||||
strip.keyframe_insert('influence')
|
||||
strip.fcurves[0].update()
|
||||
|
||||
|
||||
def influence_check(selected_track):
|
||||
'''update influence when a keyframe was added without autokey'''
|
||||
#skip the next steps if a strip is missing or tracks were removed from the nla tracks
|
||||
if len(selected_track.strips) != 1:# or obj.als.layer_index > len(nla_tracks)-2:
|
||||
return
|
||||
if not len(selected_track.strips[0].fcurves):
|
||||
strip = selected_track.strips[0]
|
||||
if not len(strip.fcurves):
|
||||
return
|
||||
|
||||
global influence_keys
|
||||
|
||||
if selected_track.strips[0].fcurves[0].mute or not len(selected_track.strips[0].fcurves[0].keyframe_points) or bpy.context.scene.tool_settings.use_keyframe_insert_auto:
|
||||
if strip.fcurves[0].mute or not len(strip.fcurves[0].keyframe_points) or bpy.context.scene.tool_settings.use_keyframe_insert_auto:
|
||||
if 'influence_keys' in globals():
|
||||
del influence_keys
|
||||
return #when the fcurve doesnt have keyframes, or when autokey is turned on, then return
|
||||
|
||||
#update if the influence keyframes are changed. influence_keys are first added in influence_update_callback
|
||||
if 'influence_keys' not in globals():
|
||||
initialize_influence_keys(strip)
|
||||
return
|
||||
wm = bpy.context.window_manager
|
||||
if not len(wm.operators):
|
||||
return
|
||||
if "ANIM_OT_keyframe_insert" not in wm.operators[-1].bl_idname:
|
||||
return
|
||||
if influence_keys != [tuple(key.co) for key in selected_track.strips[0].fcurves[0].keyframe_points]:
|
||||
selected_track.strips[0].fcurves[0].update()
|
||||
del influence_keys
|
||||
|
||||
length = len(strip.fcurves[0].keyframe_points)*2
|
||||
keyframes = np.zeros(length)
|
||||
strip.fcurves[0].keyframe_points.foreach_get('co', keyframes)
|
||||
# Comparing only the values, because if it updates while duplicating or moving frames than it's crashing
|
||||
if np.array_equal(influence_keys, keyframes):
|
||||
return
|
||||
|
||||
selected_track.strips[0].fcurves[0].update()
|
||||
influence_keys = keyframes
|
||||
|
||||
|
||||
def check_selected_bones(obj):
|
||||
'''running in the handler and checking if the selected bones were changed during view multiply layer keyframes'''
|
||||
@@ -353,23 +482,50 @@ def check_selected_bones(obj):
|
||||
selected_bones = bpy.context.selected_pose_bones
|
||||
obj.als.view_all_keyframes = True
|
||||
|
||||
def check_scene():
|
||||
'''update strip frame end after scene change, this is part of the animlayers_frame handler'''
|
||||
if 'current_scene' not in globals():
|
||||
global current_scene
|
||||
current_scene = bpy.context.scene
|
||||
return
|
||||
if current_scene != bpy.context.scene:
|
||||
#remove old scene from subscriptions
|
||||
subscriptions_remove(handler = False)
|
||||
frameend_update_callback()
|
||||
current_scene = bpy.context.scene
|
||||
#Add the new scene to subscriptions
|
||||
subscriptions_add(current_scene, handler = False)
|
||||
|
||||
########################### MSGBUS SUBSCRIPTIONS #############################
|
||||
|
||||
#Callback function for Scene frame end
|
||||
|
||||
def get_frame_range(scene):
|
||||
'''Getting the frame range also when outside of scene frame range'''
|
||||
frame_start, frame_end = bake_ops.frame_start_end(scene)
|
||||
|
||||
#if it's out of range add 10 frames to the current frame, else add 10 frames to the scene frame end
|
||||
frame_end = scene.frame_current_final + 10.0 if scene.frame_current_final >= frame_end else frame_end + 10.0
|
||||
|
||||
frame_start = scene.frame_current_final if scene.frame_current_final < 0 else 0.0
|
||||
|
||||
return frame_start, frame_end
|
||||
|
||||
def frameend_update_callback():
|
||||
'''End the strips at the end of the scene or scene preview'''
|
||||
|
||||
scene = bpy.context.scene
|
||||
if not scene.AL_objects:
|
||||
return
|
||||
frame_start, frame_end = bake_ops.frame_start_end(scene)
|
||||
if scene.frame_current_final > frame_end:
|
||||
frame_end = scene.frame_current_final + 10
|
||||
#return
|
||||
|
||||
|
||||
subscriptions_remove(handler = False)
|
||||
frame_start, frame_end = get_frame_range(scene)
|
||||
|
||||
clean_AL_objects = False
|
||||
#Iterating through all the tracks
|
||||
for AL_item in scene.AL_objects:
|
||||
obj = AL_item.object
|
||||
if obj is None or obj not in scene.objects.values():
|
||||
clean_AL_objects = True
|
||||
continue
|
||||
#anim_data = anim_data_type(obj)
|
||||
anim_datas = anim_layers.anim_datas_append(obj)
|
||||
@@ -380,32 +536,39 @@ def frameend_update_callback():
|
||||
if len(anim_data.nla_tracks) != len(obj.Anim_Layers):
|
||||
continue
|
||||
for layer, track in zip(obj.Anim_Layers, anim_data.nla_tracks):
|
||||
if layer.frame_range:
|
||||
if layer.custom_frame_range:
|
||||
continue
|
||||
if len(track.strips) == 1:
|
||||
|
||||
track.strips[0].action_frame_start = 0 - layer.offset * 1/layer.speed
|
||||
track.strips[0].action_frame_end = frame_end * 1/layer.speed - layer.offset * 1/layer.speed
|
||||
track.strips[0].frame_start = 0
|
||||
track.strips[0].frame_end = frame_end
|
||||
track.strips[0].scale = layer.speed
|
||||
|
||||
if len(track.strips) != 1:
|
||||
continue
|
||||
strip = track.strips[0]
|
||||
strip.frame_start = frame_start
|
||||
anim_layers.update_action_frame_range(frame_start, frame_end, layer, strip)
|
||||
strip.scale = layer.speed
|
||||
strip.frame_end = frame_end
|
||||
|
||||
if clean_AL_objects:
|
||||
anim_layers.clean_AL_objects(scene)
|
||||
subscriptions_add(scene, handler = False)
|
||||
|
||||
#Subscribe to the scene frame_end
|
||||
def subscribe_to_frame_end(scene):
|
||||
'''subscribe_to_frame_end and frame preview end'''
|
||||
subscribe_end = scene.path_resolve("frame_end", False)
|
||||
def subscribe_to_preview_frame_end(scene):
|
||||
'''subscribe_to_preview_frame_end and frame preview end'''
|
||||
global subscriptions_owner
|
||||
|
||||
# subscribe_end = scene.path_resolve("frame_end", False)
|
||||
# Subscribing to preview frame end since it's not registering in the depsgraph
|
||||
subscribe_preview_end = scene.path_resolve("frame_preview_end", False)
|
||||
subscribe_use_preview = scene.path_resolve("use_preview_range", False)
|
||||
|
||||
for subscribe in [subscribe_end, subscribe_preview_end, subscribe_use_preview]:
|
||||
# print('subscribe_to_preview_frame_end')
|
||||
for subscribe in [subscribe_preview_end, subscribe_use_preview]:
|
||||
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe,
|
||||
owner=scene,
|
||||
owner=subscriptions_owner,
|
||||
args=(),
|
||||
notify=frameend_update_callback,)
|
||||
|
||||
bpy.msgbus.publish_rna(key=subscribe)
|
||||
# bpy.msgbus.publish_rna(key=subscribe)
|
||||
|
||||
# def action_framestart_update_callback(*args):
|
||||
# ''' update the strip start with the action start'''
|
||||
@@ -413,11 +576,9 @@ def subscribe_to_frame_end(scene):
|
||||
|
||||
def track_update_callback():
|
||||
'''update layers with the tracks name'''
|
||||
global initial_call
|
||||
if initial_call:
|
||||
# initial_call = False
|
||||
return
|
||||
|
||||
# global initial_call
|
||||
# if initial_call:
|
||||
# return
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
obj = bpy.context.object
|
||||
@@ -446,7 +607,7 @@ def track_update_callback():
|
||||
if len(track.strips) == 1:
|
||||
track.strips[0].name = track.name
|
||||
|
||||
def subscribe_to_track_name(scene):
|
||||
def subscribe_to_track_name(subscriptions_owner):
|
||||
'''Subscribe to the name of track'''
|
||||
|
||||
#subscribe_track = nla_track.path_resolve("name", False)
|
||||
@@ -455,21 +616,19 @@ def subscribe_to_track_name(scene):
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_track,
|
||||
# owner of msgbus subcribe (for clearing later)
|
||||
owner=scene,
|
||||
owner=subscriptions_owner,
|
||||
# Args passed to callback function (tuple)
|
||||
args=(),
|
||||
# Callback function for property update
|
||||
notify=track_update_callback,)
|
||||
|
||||
bpy.msgbus.publish_rna(key=subscribe_track)
|
||||
# bpy.msgbus.publish_rna(key=subscribe_track)
|
||||
|
||||
def action_name_callback():
|
||||
'''update layers with the tracks name'''
|
||||
global initial_call
|
||||
if initial_call:
|
||||
# initial_call = False
|
||||
return
|
||||
|
||||
# global initial_call
|
||||
# if initial_call:
|
||||
# return
|
||||
obj = bpy.context.object
|
||||
if obj is None:
|
||||
return
|
||||
@@ -492,7 +651,7 @@ def action_name_callback():
|
||||
return
|
||||
layer.name = action.name
|
||||
|
||||
def subscribe_to_action_name(scene):
|
||||
def subscribe_to_action_name(subscriptions_owner):
|
||||
'''Subscribe to the name of track'''
|
||||
|
||||
#subscribe_track = nla_track.path_resolve("name", False)
|
||||
@@ -500,24 +659,22 @@ def subscribe_to_action_name(scene):
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_action,
|
||||
# owner of msgbus subcribe (for clearing later)
|
||||
owner=scene,
|
||||
owner=subscriptions_owner,
|
||||
# Args passed to callback function (tuple)
|
||||
args=(),
|
||||
# Callback function for property update
|
||||
notify=action_name_callback,)
|
||||
|
||||
bpy.msgbus.publish_rna(key=subscribe_action)
|
||||
# bpy.msgbus.publish_rna(key=subscribe_action)
|
||||
|
||||
def influence_update_callback(*args):
|
||||
def influence_update_callback():
|
||||
'''update influence'''
|
||||
global initial_call
|
||||
if initial_call:
|
||||
initial_call = False
|
||||
return
|
||||
# global initial_call
|
||||
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
obj = bpy.context.object
|
||||
scene = bpy.context.scene
|
||||
#checking if the object has nla tracks, when I used undo it was still calling the property on an object with no nla tracks
|
||||
if obj is None:
|
||||
return
|
||||
@@ -528,41 +685,93 @@ def influence_update_callback(*args):
|
||||
return
|
||||
if not len(anim_data.nla_tracks):
|
||||
return
|
||||
|
||||
track = anim_data.nla_tracks[obj.als.layer_index]
|
||||
i = obj.als.layer_index
|
||||
track = anim_data.nla_tracks[i]
|
||||
if len(track.strips) != 1:
|
||||
return
|
||||
strip = track.strips[0]
|
||||
scene.als['influence'] = strip.influence
|
||||
# obj.Anim_Layers[i]['influence'] = strip.influence
|
||||
|
||||
if track.strips[0].fcurves[0].mute or track.strips[0].fcurves[0].lock:
|
||||
if strip.fcurves[0].mute or strip.fcurves[0].lock:
|
||||
return
|
||||
|
||||
if bpy.context.scene.tool_settings.use_keyframe_insert_auto and len(track.strips[0].fcurves[0].keyframe_points):
|
||||
track.strips[0].keyframe_insert('influence')
|
||||
track.strips[0].fcurves[0].update()
|
||||
if not len(track.strips[0].fcurves[0].keyframe_points):
|
||||
return
|
||||
|
||||
# This is relevant only for autokey update
|
||||
if not bpy.context.scene.tool_settings.use_keyframe_insert_auto:
|
||||
return
|
||||
#if the influence property and fcurve value are not the same then store the keyframes to check in the handler for a change
|
||||
if track.strips[0].influence != track.strips[0].fcurves[0].evaluate(bpy.context.scene.frame_current):
|
||||
global influence_keys
|
||||
influence_keys = [tuple(key.co) for key in track.strips[0].fcurves[0].keyframe_points]
|
||||
|
||||
def subscribe_to_influence(scene):
|
||||
|
||||
if len(track.strips[0].fcurves[0].keyframe_points):
|
||||
strip.keyframe_insert('influence')
|
||||
strip.fcurves[0].update()
|
||||
return
|
||||
|
||||
def initialize_influence_keys(strip):
|
||||
'''Setting up the influence keys'''
|
||||
global influence_keys
|
||||
|
||||
length = len(strip.fcurves[0].keyframe_points)*2
|
||||
keyframes = np.zeros(length)
|
||||
strip.fcurves[0].keyframe_points.foreach_get('co', keyframes)
|
||||
influence_keys = keyframes
|
||||
|
||||
|
||||
def subscribe_to_influence(subscriptions_owner):
|
||||
'''Subscribe to the influence of the track'''
|
||||
subscribe_influence = (bpy.types.NlaStrip, 'influence')
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_influence,
|
||||
# owner of msgbus subcribe (for clearing later)
|
||||
owner=scene,
|
||||
owner=subscriptions_owner,
|
||||
# Args passed to callback function (tuple)
|
||||
args=(scene,),
|
||||
args=(),
|
||||
# Callback function for property update
|
||||
notify=influence_update_callback,)
|
||||
|
||||
bpy.msgbus.publish_rna(key=subscribe_influence)
|
||||
|
||||
def subscribe_to_action_slot(subscriptions_owner):
|
||||
'''Subscribe to the influence of the track'''
|
||||
subscribe_slot = (bpy.types.NlaStrip, 'action_slot')
|
||||
|
||||
def subscribe_to_strip_settings(scene):
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_slot,
|
||||
# owner of msgbus subcribe (for clearing later)
|
||||
owner=subscriptions_owner,
|
||||
# Args passed to callback function (tuple)
|
||||
args=(),
|
||||
# Callback function for property update
|
||||
notify=slot_update_callback,)
|
||||
|
||||
def slot_update_callback():
|
||||
'''Always updating action slot in the active action when updated in the strip'''
|
||||
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
obj = bpy.context.object
|
||||
if obj is None:
|
||||
return
|
||||
anim_data = anim_layers.anim_data_type(obj)
|
||||
if anim_data is None:
|
||||
return
|
||||
if anim_data.action is None:
|
||||
return
|
||||
if not len(anim_data.nla_tracks):
|
||||
return
|
||||
if not len(obj.Anim_Layers):
|
||||
return
|
||||
|
||||
if not len(anim_data.nla_tracks[obj.als.layer_index].strips):
|
||||
return
|
||||
|
||||
strip = anim_data.nla_tracks[obj.als.layer_index].strips[0]
|
||||
anim_data.action_slot = strip.action_slot
|
||||
|
||||
|
||||
def subscribe_to_strip_settings(subscriptions_owner):
|
||||
'''Subscribe to the strip settings of the track'''
|
||||
|
||||
|
||||
frame_start = (bpy.types.NlaStrip, 'frame_start')
|
||||
frame_end = (bpy.types.NlaStrip, 'frame_end')
|
||||
action_frame_start = (bpy.types.NlaStrip, 'action_frame_start')
|
||||
@@ -571,8 +780,8 @@ def subscribe_to_strip_settings(scene):
|
||||
repeat = (bpy.types.NlaStrip, 'repeat')
|
||||
|
||||
attributes = [frame_start, frame_end, action_frame_start, action_frame_end, scale, repeat, frame_start, frame_end]
|
||||
|
||||
if bpy.app.version > (3, 2, 0):
|
||||
|
||||
if bpy.app.version >= (3, 3, 0):
|
||||
#this properties exist only after Blender 3.2
|
||||
frame_start_ui = (bpy.types.NlaStrip, 'frame_start_ui')
|
||||
frame_end_ui = (bpy.types.NlaStrip, 'frame_end_ui')
|
||||
@@ -582,23 +791,29 @@ def subscribe_to_strip_settings(scene):
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=key,
|
||||
# owner of msgbus subcribe (for clearing later)
|
||||
owner=scene,
|
||||
owner=subscriptions_owner,
|
||||
# Args passed to callback function (tuple)
|
||||
args=(),
|
||||
# Callback function for property update
|
||||
notify=strip_settings_callback,)
|
||||
|
||||
#bpy.msgbus.publish_rna(key=frame_start)
|
||||
|
||||
def update_strip_layer_settings(strip, layer):
|
||||
layer['speed'] = strip.scale
|
||||
if not strip.action:
|
||||
return
|
||||
|
||||
if strip.repeat <= 1:
|
||||
#Reversing the offset calculation based on the action start frame, strip start and scale
|
||||
action_start = strip.action.frame_range[0]
|
||||
offset = strip.frame_start - action_start - (strip.action_frame_start - action_start) * strip.scale
|
||||
else:
|
||||
#During repeat the offset is based on the distance from the action first keyframe
|
||||
offset = strip.frame_start - strip.action.frame_range[0]
|
||||
|
||||
start_offset = strip.action.frame_range[0] - strip.frame_start
|
||||
offset = (strip.action_frame_start - strip.frame_start - start_offset) * strip.scale + start_offset
|
||||
layer['offset'] = round(-offset, 3)
|
||||
layer['speed'] = strip.scale
|
||||
layer['offset'] = round(offset, 3)
|
||||
|
||||
#If custom frame range is turned off return to not lose frame range values
|
||||
if not layer.frame_range:
|
||||
if not layer.custom_frame_range:
|
||||
return
|
||||
layer['frame_end'] = strip.frame_end
|
||||
layer['frame_start'] = strip.frame_start
|
||||
@@ -607,11 +822,6 @@ def update_strip_layer_settings(strip, layer):
|
||||
|
||||
def strip_settings_callback():
|
||||
'''subscribe_to_strip_settings callback'''
|
||||
global initial_call
|
||||
if initial_call:
|
||||
# initial_call = False
|
||||
return
|
||||
|
||||
if not bpy.context.selected_objects:
|
||||
return
|
||||
obj = bpy.context.object
|
||||
@@ -624,11 +834,12 @@ def strip_settings_callback():
|
||||
return
|
||||
if not len(obj.Anim_Layers):
|
||||
return
|
||||
strip = anim_data.nla_tracks[obj.als.layer_index].strips[0]
|
||||
sync_strip_range()
|
||||
|
||||
# sync_strip_range()
|
||||
if not len(anim_data.nla_tracks[obj.als.layer_index].strips):
|
||||
return
|
||||
strip = anim_data.nla_tracks[obj.als.layer_index].strips[0]
|
||||
layer = obj.Anim_Layers[obj.als.layer_index]
|
||||
|
||||
update_strip_layer_settings(strip, layer)
|
||||
anim_layers.redraw_areas([ 'VIEW_3D'])
|
||||
anim_layers.redraw_areas([ 'VIEW_3D'])
|
||||
|
||||
Reference in New Issue
Block a user