update addons (for 5.2)

This commit is contained in:
Nathan
2026-07-14 14:44:58 -06:00
parent a232ea1c9c
commit e3310263f3
246 changed files with 21097 additions and 9607 deletions
@@ -0,0 +1,14 @@
# Claude Code
.claude/
# Python cache
__pycache__/
*.pyc
# Non-addon folders
diagnostics/
diagnostics.zip
animation_layers_updater/
# Experimental, not shipped
ultra_bake.py
+10 -2
View File
@@ -20,7 +20,7 @@
bl_info = {
"name": "Animation Layers",
"author": "Tal Hershkovich",
"version" : (2, 4, 1),
"version" : (2, 4, 2),
"blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel",
"description": "Simplifying the NLA editor into an animation layers UI and workflow",
@@ -110,7 +110,9 @@ class AnimLayersSettings(bpy.types.PropertyGroup):
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)
inbetweener : bpy.props.FloatProperty(name='Inbetween Keyframe', description="Adds an inbetween Keyframe between the Layer's neighbor keyframes", soft_min = 0.0, soft_max = 1.0, default=0.5, options = set(), override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.add_inbetween_key, get = anim_layers.get_inbetweener, set = anim_layers.set_inbetweener)
inbetweener_push : bpy.props.FloatProperty(name='Inbetween Keyframe', description="Adds an inbetween Keyframe between the Layer's neighbor keyframes, further values push past the neighbor keyframes", soft_min = -0.5, soft_max = 1.5, default=0.5, options = set(), override = {'LIBRARY_OVERRIDABLE'}, update = anim_layers.add_inbetween_key, get = anim_layers.get_inbetweener, set = anim_layers.set_inbetweener)
push: bpy.props.BoolProperty(name ='Toggle Push', description = 'Changing the min and max values further for pushing', default = False)
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'})
@@ -145,6 +147,12 @@ class AnimLayersItems(bpy.types.PropertyGroup):
assigned_group: bpy.props.EnumProperty(name='Parent', description='Set this layer\'s parent (None = root)',
items=anim_layers.layer_group_enum_items, get=anim_layers.layer_group_get, set=anim_layers.layer_group_set,
options={'HIDDEN'})
# Set once per file-load/turn-on by anim_layers.tag_unusable_layers: True on
# rows that come from a linked-library source file and have no matching NLA
# track locally. Such rows can't be removed here (the library owns them), so
# the row<->track index mapping skips them until the layer is deleted in the
# rig source file.
not_usable: bpy.props.BoolProperty(name="Not Usable", description="Layer stored in the linked source file without a matching NLA track. Remove the layer in the rig source file to get rid of it", default=False, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
# Legacy field for pre-migration data. Read by migrate_object_to_hierarchical
# and ignored thereafter. Schema retained so old .blend files load cleanly.
type: bpy.props.EnumProperty(name="Item Type (legacy)", default='LAYER',
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
{
"last_check": "2026-06-22 15:11:23.788257",
"backup_date": "May-27-2026",
"last_check": "2026-07-14 14:42:39.577911",
"backup_date": "July-14-2026",
"update_ready": false,
"ignore": false,
"just_restored": false,
@@ -20,7 +20,7 @@
bl_info = {
"name": "Animation Layers",
"author": "Tal Hershkovich",
"version" : (2, 4, 0),
"version" : (2, 4, 1),
"blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel",
"description": "Simplifying the NLA editor into an animation layers UI and workflow",
@@ -73,7 +73,10 @@ class AnimLayersSceneSettings(bpy.types.PropertyGroup):
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'})
# Active row in obj.Anim_Layers. Post-migration, 1:1 with NLA-track index.
layer_index: bpy.props.IntProperty(update = anim_layers.update_layer_index, options={'LIBRARY_EDITABLE'}, default = 0, override = {'LIBRARY_OVERRIDABLE'})
# Schema version. 0 = pre-hierarchical (legacy GROUP rows); 1 = hierarchical (parent_layer refs).
schema_version: bpy.props.IntProperty(name="Schema Version", default=0, options={'HIDDEN'}, 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
@@ -125,13 +128,31 @@ class AnimLayersItems(bpy.types.PropertyGroup):
action_range: bpy.props.FloatVectorProperty(name='action range', description="used to check if layer needs to update frame range", override = {'LIBRARY_OVERRIDABLE'}, size = 2)
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)
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)
# Hierarchical layer fields. Every layer is NLA-backed; a layer is a
# "group" iff something else points to it via parent_layer.
expanded: bpy.props.BoolProperty(name="Expanded", description="Show this layer's children in the UI list", default=True, options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
parent_layer: bpy.props.StringProperty(name="Parent Layer", description="Name of this layer's parent (empty = root)", default="", options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
group_color: bpy.props.FloatVectorProperty(name="Group Color", subtype='COLOR', size=4, min=0.0, max=1.0, default=(0.3, 0.5, 0.8, 1.0), options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
# Dynamic-enum dropdown for picking parent_layer in the UI. The setter
# refuses cycles (self + descendants are excluded from the options).
assigned_group: bpy.props.EnumProperty(name='Parent', description='Set this layer\'s parent (None = root)',
items=anim_layers.layer_group_enum_items, get=anim_layers.layer_group_get, set=anim_layers.layer_group_set,
options={'HIDDEN'})
# Legacy field for pre-migration data. Read by migrate_object_to_hierarchical
# and ignored thereafter. Schema retained so old .blend files load cleanly.
type: bpy.props.EnumProperty(name="Item Type (legacy)", default='LAYER',
items=[('LAYER', 'Layer', 'NLA-backed layer'),
('GROUP', 'Group', 'Legacy phantom group row')],
options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
parent_group: bpy.props.StringProperty(name="Parent Group (legacy)", description="Pre-migration field. Read once by the schema-v1 migration, then unused.", default="", options={'HIDDEN'}, override = {'LIBRARY_OVERRIDABLE'})
class AnimLayersObjects(bpy.types.PropertyGroup):
File diff suppressed because it is too large Load Diff
@@ -1,16 +1,16 @@
{
"last_check": "2026-05-27 14:41:11.263249",
"backup_date": "April-21-2026",
"last_check": "2026-07-14 14:42:39.577911",
"backup_date": "May-27-2026",
"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=321d411a449bc9acee2a759e30cd3d0f36bbd2ab",
"link": "https://gitlab.com/api/v4/projects/22294607/repository/archive.zip?sha=e32318861bdc35e0b02381eb2119f4492051027d",
"version": [
2,
4,
1
2
]
}
}
@@ -180,10 +180,11 @@ def smart_bake(context):
fcurves = anim_layers.get_fcurves(obj, track.strips[0].action)
total_iterations += len(fcurves)
wm.progress_begin(0, total_iterations)
wm.progress_begin(0, total_iterations)
processed = 0
for layer, track in zip(obj.Anim_Layers, anim_data.nla_tracks):
layer_items = [l for l in obj.Anim_Layers if l.type == 'LAYER']
for layer, track in zip(layer_items, anim_data.nla_tracks):
if track.mute:
continue
if len(track.strips) != 1 or track.strips[0].action is None:
@@ -242,9 +243,9 @@ def smart_bake(context):
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
#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.custom_frame_range or layercut == layer:
if layercut.type == 'GROUP' or 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)
@@ -407,7 +408,7 @@ def unmute_modifiers(obj, nla_tracks, modifier_rec):
for mod in fcu.modifiers:
if mod in modifier_rec:
mod.mute = False
elif obj.als.mergefcurves and track == nla_tracks[obj.als.layer_index]:
elif obj.als.mergefcurves and track == nla_tracks[anim_layers.nla_idx(obj)]:
mod.mute = True
def invisible_layers(b_layers):
@@ -426,7 +427,14 @@ def select_keyframed_bones(self, context, obj):
if obj.mode != 'POSE':
bpy.ops.object.posemode_toggle()
bpy.ops.pose.select_all(action='DESELECT')
for i in range(0, obj.als.layer_index+1):
# Iterate over LAYER rows up to (and including) the active row, skipping
# group headers (they have no NLA track and no bones to select).
current_row = obj.als.layer_index
for i, it in enumerate(obj.Anim_Layers):
if i > current_row:
break
if it.type != 'LAYER':
continue
obj.als['layer_index'] = i
anim_layers.select_layer_bones(self, context)
@@ -446,7 +454,7 @@ def smartbake_apply(obj, nla_tracks, fcu_keys, extrapolations):
#apply smartbake for blenders bake
#smart bake - delete unnecessery keyframes:
# transform_types = ['location', 'rotation_euler', 'rotation_quaternion', 'scale']
strip = nla_tracks[obj.als.layer_index].strips[0]
strip = nla_tracks[anim_layers.nla_idx(obj)].strips[0]
# if strip.action is None:
# return
@@ -641,7 +649,7 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
return
anim_data = anim_layers.anim_data_type(obj)
# baked_action = anim_data.action
track = nla_tracks[obj.als.layer_index]
track = nla_tracks[anim_layers.nla_idx(obj)]
baked_action = track.strips[0].action
clean_no_user_slots(baked_action)
#create the baked fcurve
@@ -1065,10 +1073,18 @@ class MergeAnimLayerDown(bpy.types.Operator):
bl_idname = "anim.layers_merge_down"
bl_label = "Merge_Layers_Down"
bl_options = {'REGISTER', 'UNDO'}
step: bpy.props.IntProperty(name='Step', description='Bake every number of frame steps', default=1)
actioncopy: bpy.props.BoolProperty(name='Copy original merged action', description='Create a copy of the original action that is being overwritten', default = False)
@classmethod
def poll(cls, context):
obj = context.object
if obj is None:
return False
# Disable when active row is a group header — merge only applies to NLA-backed layers.
return anim_layers.is_layer_row_active(obj)
def invoke(self, context, event):
obj = context.object
bake_range_type(context.scene.als, context)
@@ -1143,7 +1159,8 @@ class MergeAnimLayerDown(bpy.types.Operator):
# 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):
layer_items = [l for l in obj.Anim_Layers if l.type == 'LAYER']
for layer, track in zip(layer_items, anim_data.nla_tracks):
if layer.custom_frame_range:
continue
if len(track.strips) != 1:
@@ -1181,7 +1198,7 @@ class MergeAnimLayerDown(bpy.types.Operator):
if obj.als.direction == 'DOWN':
obj.als.layer_index = 0
baked_layer = None
strip = anim_data.nla_tracks[obj.als.layer_index].strips[0]
strip = anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips[0]
action = strip.action
if hasattr(strip, 'action_slot'):
action_slot = strip.action_slot
@@ -1190,21 +1207,29 @@ class MergeAnimLayerDown(bpy.types.Operator):
#if baking to a new layer then setup the new index and layer
elif obj.als.operator == 'NEW':
self.actioncopy = False
# `add_at_nla` is the NLA-track index passed to add_animlayer.
if obj.als.direction == 'UP' and additive and 'REPLACE' in blendings:
obj.als.layer_index = layer_index + blendings.index('REPLACE') - 1
add_at_nla = layer_index + blendings.index('REPLACE') - 1
elif obj.als.direction == 'UP' or obj.als.direction == 'ALL':
obj.als.layer_index = len(obj.Anim_Layers)-1
add_at_nla = anim_layers.nla_layer_count(obj) - 1
else:
add_at_nla = anim_layers.nla_idx(obj)
layer_names = [layer.name for layer in obj.Anim_Layers]
baked_layer = anim_layers.add_animlayer(layer_name = anim_layers.unique_name(layer_names, 'Baked_Layer') , duplicate = False, index = obj.als.layer_index, blend_type = blend)
layer_names = [layer.name for layer in obj.Anim_Layers if layer.type == 'LAYER']
baked_layer = anim_layers.add_animlayer(layer_name = anim_layers.unique_name(layer_names, 'Baked_Layer') , duplicate = False, index = add_at_nla, blend_type = blend)
anim_layers.register_layers(obj, nla_tracks)
obj.als.layer_index += 1
# Point layer_index at the newly-added baked layer's collection row.
if baked_layer is not None:
for ridx, it in enumerate(obj.Anim_Layers):
if it.type == 'LAYER' and it.name == baked_layer.name:
obj.als.layer_index = ridx
break
#remove subsciption again after adding a layer there was new subsciption applied
subscriptions.subscriptions_remove()
track = nla_tracks[obj.als.layer_index]
track = nla_tracks[anim_layers.nla_idx(obj)]
#use internal bake
if obj.als.baketype =='NLA':
modifier_rec, extrapolations = mute_modifiers(obj, nla_tracks)
@@ -1282,7 +1307,7 @@ class MergeAnimLayerDown(bpy.types.Operator):
strip.action_slot = anim_layers.get_obj_slot(obj, action)
#reset layer settings
baked_layer = obj.Anim_Layers[obj.als.layer_index]
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.custom_frame_range:
@@ -106,7 +106,10 @@ def animlayers_frame(scene, context):
if len(track.strips) != 1:
continue
#checks if the layer has a custom frame range
layer = obj.Anim_Layers[i]
row_idx = anim_layers.layer_to_row_index(obj, i)
if row_idx < 0 or row_idx >= len(obj.Anim_Layers):
continue
layer = obj.Anim_Layers[row_idx]
if layer.custom_frame_range:
continue
if not reset_subscription:
@@ -169,6 +172,10 @@ def check_handler(scene):
return
anim_layers.add_obj_to_animlayers(obj, [item.object for item in scene.AL_objects])
nla_tracks = anim_data.nla_tracks
# When the active UIList row is a group header (no NLA track of its own),
# skip the LAYER-specific syncs below — they assume a real layer.
if not anim_layers.is_layer_row_active(obj):
return
layer = obj.Anim_Layers[obj.als.layer_index]
active_action_update(obj, anim_data, nla_tracks)
#check if a keyframe was removed
@@ -189,7 +196,7 @@ def check_handler(scene):
if track_layer_synchronization(obj, nla_tracks):
return
track = nla_tracks[obj.als.layer_index]
track = nla_tracks[anim_layers.nla_idx(obj)]
sync_frame_range(scene, track, layer)
# sync_strip_range(scene)
@@ -217,18 +224,20 @@ def check_handler(scene):
anim_layers.hide_view_all_keyframes(obj, anim_data)
check_selected_bones(obj)
influence_check(nla_tracks[obj.als.layer_index])
influence_check(nla_tracks[anim_layers.nla_idx(obj)])
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):
if len(nla_tracks) == anim_layers.nla_layer_count(obj):
return False
new_layers_names = set(track.name for track in nla_tracks).difference(set(layer.name for layer in obj.Anim_Layers))
layer_items = [layer for layer in obj.Anim_Layers if layer.type == 'LAYER']
new_layers_names = set(track.name for track in nla_tracks).difference(set(layer.name for layer in layer_items))
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
row_count = len(obj.Anim_Layers)
if row_count and obj.als.layer_index > row_count - 1:
obj.als.layer_index = row_count - 1
if not bpy.context.preferences.addons[__package__].preferences.auto_custom_range:
return
@@ -261,9 +270,9 @@ def active_action_update(obj, anim_data, nla_tracks):
anim_data.action = None
subscriptions_add(bpy.context.scene)
return
if anim_data.action == nla_tracks[obj.als.layer_index].strips[0].action:
if anim_data.action == nla_tracks[anim_layers.nla_idx(obj)].strips[0].action:
return
if not len(nla_tracks[obj.als.layer_index].strips):
if not len(nla_tracks[anim_layers.nla_idx(obj)].strips):
return
if not anim_data.action or anim_data.is_property_readonly('action'):
return
@@ -434,7 +443,7 @@ def influence_sync(scene, obj, nla_tracks):
if action.name == scene.name + 'Action' and not len(scene.animation_data.nla_tracks) and not len(fcurves):
bpy.data.actions.remove(action)
strip = nla_tracks[obj.als.layer_index].strips[0]
strip = nla_tracks[anim_layers.nla_idx(obj)].strips[0]
if strip.fcurves[0].mute:
return
strip.fcurves[0].lock = False
@@ -546,9 +555,10 @@ def frameend_update_callback():
for anim_data in anim_datas:
if anim_data is None:
continue
if len(anim_data.nla_tracks) != len(obj.Anim_Layers):
if len(anim_data.nla_tracks) != anim_layers.nla_layer_count(obj):
continue
for layer, track in zip(obj.Anim_Layers, anim_data.nla_tracks):
layer_items = [l for l in obj.Anim_Layers if l.type == 'LAYER']
for layer, track in zip(layer_items, anim_data.nla_tracks):
if layer.custom_frame_range:
continue
if len(track.strips) != 1:
@@ -653,10 +663,12 @@ def action_name_callback():
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):
if not anim_layers.is_layer_row_active(obj):
return
action = nla_tracks[obj.als.layer_index].strips[0].action
layer = obj.Anim_Layers[obj.als.layer_index]
if not len(nla_tracks[anim_layers.nla_idx(obj)].strips):
return
action = nla_tracks[anim_layers.nla_idx(obj)].strips[0].action
if action is None:
return
if not obj.als.auto_rename or layer.name == action.name:
@@ -774,10 +786,10 @@ def slot_update_callback():
if not len(obj.Anim_Layers):
return
if not len(anim_data.nla_tracks[obj.als.layer_index].strips):
if not len(anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips):
return
strip = anim_data.nla_tracks[obj.als.layer_index].strips[0]
strip = anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips[0]
anim_data.action_slot = strip.action_slot
@@ -848,9 +860,11 @@ def strip_settings_callback():
return
# sync_strip_range()
if not len(anim_data.nla_tracks[obj.als.layer_index].strips):
if not anim_layers.is_layer_row_active(obj):
return
strip = anim_data.nla_tracks[obj.als.layer_index].strips[0]
if not len(anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips):
return
strip = anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips[0]
layer = obj.Anim_Layers[obj.als.layer_index]
update_strip_layer_settings(strip, layer)
+12 -7
View File
@@ -183,7 +183,7 @@ def smart_bake(context):
wm.progress_begin(0, total_iterations)
processed = 0
layer_items = [l for l in obj.Anim_Layers if l.type == 'LAYER']
layer_items = [l for l in obj.Anim_Layers if l.type == 'LAYER' and not l.not_usable]
for layer, track in zip(layer_items, anim_data.nla_tracks):
if track.mute:
continue
@@ -433,7 +433,7 @@ def select_keyframed_bones(self, context, obj):
for i, it in enumerate(obj.Anim_Layers):
if i > current_row:
break
if it.type != 'LAYER':
if it.type != 'LAYER' or it.not_usable:
continue
obj.als['layer_index'] = i
anim_layers.select_layer_bones(self, context)
@@ -1141,12 +1141,17 @@ class MergeAnimLayerDown(bpy.types.Operator):
return {'CANCELLED'}
anim_data = anim_layers.anim_data_type(obj)
nla_tracks = anim_data.nla_tracks
if obj.als.direction == 'DOWN' and not obj.als.layer_index:
# Track-space index of the active layer (skips not_usable rows).
nla_index = anim_layers.nla_idx(obj)
if nla_index == -1:
return {'CANCELLED'}
if obj.als.direction == 'DOWN' and not nla_index:
return {'CANCELLED'}
#disable baking up from Blender's bake
if obj.als.direction == 'UP' and obj.als.layer_index == len(nla_tracks)-1:
if obj.als.direction == 'UP' and nla_index == len(nla_tracks)-1:
return {'CANCELLED'}
if obj.als.baketype == 'NLA':
@@ -1159,7 +1164,7 @@ class MergeAnimLayerDown(bpy.types.Operator):
# Incase the strips are shorter then the keyframe range (because scene is shorter)
# Then updating the strips length
layer_items = [l for l in obj.Anim_Layers if l.type == 'LAYER']
layer_items = [l for l in obj.Anim_Layers if l.type == 'LAYER' and not l.not_usable]
for layer, track in zip(layer_items, anim_data.nla_tracks):
if layer.custom_frame_range:
continue
@@ -1175,7 +1180,7 @@ class MergeAnimLayerDown(bpy.types.Operator):
if context.scene.frame_current > frame_end:
context.scene.frame_current = frame_end
layer_index = obj.als.layer_index
layer_index = nla_index
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
@@ -0,0 +1,75 @@
schema_version = "1.0.0"
# Example of manifest file for a Blender extension
# Change the values according to your extension
id = "animation_layers"
version = "2.4.1"
name = "Animation Layers"
tagline = "Simplifying the NLA editor into an animation layers UI and workflow"
maintainer = "Tal Hershkovich <dancingshapes@gmail.com>"
# Supported types: "add-on", "theme"
type = "add-on"
# # Optional: link to documentation, support, source files, etc
website = "https://blendermarket.com/products/animation-layers/docs"
# Tag list defined by Blender and server, see:
# https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html
tags = ["Animation"]
blender_version_min = "4.2.0"
# # Optional: Blender version that the extension does not support, earlier versions are supported.
# # This can be omitted and defined later on the extensions platform if an issue is found.
# blender_version_max = "5.1.0"
# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix)
# https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html
license = [
"SPDX:GPL-3.0-or-later",
]
# # Optional: required by some licenses.
# copyright = [
# "2002-2024 Developer Name",
# "1998 Company Name",
# ]
# # Optional: list of supported platforms. If omitted, the extension will be available in all operating systems.
# platforms = ["windows-x64", "macos-arm64", "linux-x64"]
# # Other supported platforms: "windows-arm64", "macos-x64"
# # Optional: bundle 3rd party Python modules.
# # https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html
# wheels = [
# "./wheels/hexdump-3.3-py3-none-any.whl",
# "./wheels/jsmin-3.0.1-py3-none-any.whl",
# ]
# # Optional: add-ons can list which resources they will require:
# # * files (for access of any filesystem operations)
# # * network (for internet access)
# # * clipboard (to read and/or write the system clipboard)
# # * camera (to capture photos and videos)
# # * microphone (to capture audio)
# #
# # If using network, remember to also check `bpy.app.online_access`
# # https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access
# #
# # For each permission it is important to also specify the reason why it is required.
# # Keep this a single short sentence without a period (.) at the end.
# # For longer explanations use the documentation or detail page.
#
# [permissions]
# network = "Need to sync motion-capture data to server"
# files = "Import/export FBX from/to disk"
# clipboard = "Copy and paste bone transforms"
# Build settings — exclude the bundled addon-updater (Blender's extension system
# handles updates), runtime caches, and the updater's own backup folder.
# https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build
[build]
paths_exclude_pattern = [
"__pycache__/",
"/.git/",
"/*.zip",
"/animation_layers_updater/",
]
@@ -196,7 +196,10 @@ def check_handler(scene):
if track_layer_synchronization(obj, nla_tracks):
return
track = nla_tracks[anim_layers.nla_idx(obj)]
nla_index = anim_layers.nla_idx(obj)
if nla_index == -1:
return
track = nla_tracks[nla_index]
sync_frame_range(scene, track, layer)
# sync_strip_range(scene)
@@ -223,8 +226,8 @@ def check_handler(scene):
if obj.als.view_all_keyframes:
anim_layers.hide_view_all_keyframes(obj, anim_data)
check_selected_bones(obj)
influence_check(nla_tracks[anim_layers.nla_idx(obj)])
influence_check(track)
def track_layer_synchronization(obj, nla_tracks):
'''check if track and layers are synchronized, running only when adding/removing tracks via the nla'''
@@ -270,9 +273,10 @@ def active_action_update(obj, anim_data, nla_tracks):
anim_data.action = None
subscriptions_add(bpy.context.scene)
return
if anim_data.action == nla_tracks[anim_layers.nla_idx(obj)].strips[0].action:
nla_idx = anim_layers.nla_idx(obj)
if nla_idx == -1 or not len(nla_tracks[nla_idx].strips):
return
if not len(nla_tracks[anim_layers.nla_idx(obj)].strips):
if anim_data.action == nla_tracks[nla_idx].strips[0].action:
return
if not anim_data.action or anim_data.is_property_readonly('action'):
return
@@ -443,7 +447,10 @@ def influence_sync(scene, obj, nla_tracks):
if action.name == scene.name + 'Action' and not len(scene.animation_data.nla_tracks) and not len(fcurves):
bpy.data.actions.remove(action)
strip = nla_tracks[anim_layers.nla_idx(obj)].strips[0]
nla_index = anim_layers.nla_idx(obj)
if nla_index == -1 or not len(nla_tracks[nla_index].strips):
return
strip = nla_tracks[nla_index].strips[0]
if strip.fcurves[0].mute:
return
strip.fcurves[0].lock = False
@@ -557,7 +564,7 @@ def frameend_update_callback():
continue
if len(anim_data.nla_tracks) != anim_layers.nla_layer_count(obj):
continue
layer_items = [l for l in obj.Anim_Layers if l.type == 'LAYER']
layer_items = [l for l in obj.Anim_Layers if l.type == 'LAYER' and not l.not_usable]
for layer, track in zip(layer_items, anim_data.nla_tracks):
if layer.custom_frame_range:
continue
@@ -617,15 +624,22 @@ def track_update_callback():
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)
# Pair each track with its layer row, skipping not_usable rows (layers
# from a linked source file without a track) so names don't shift onto
# the wrong row.
layer_rows = [it for it in obj.Anim_Layers if not it.not_usable]
for i, track in enumerate(nla_tracks):
if anim_data != current_anim_data:
continue
if i >= len(layer_rows):
break
layer = layer_rows[i]
#make sure there are no duplicated names
if track.name != obj.Anim_Layers[i].name:
if track.name != layer.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 layer.name in override_tracks:
override_tracks[layer.name].name = track.name
layer.name = track.name
if len(track.strips) == 1:
track.strips[0].name = track.name
@@ -666,9 +680,10 @@ def action_name_callback():
if not anim_layers.is_layer_row_active(obj):
return
layer = obj.Anim_Layers[obj.als.layer_index]
if not len(nla_tracks[anim_layers.nla_idx(obj)].strips):
nla_index = anim_layers.nla_idx(obj)
if nla_index == -1 or not len(nla_tracks[nla_index].strips):
return
action = nla_tracks[anim_layers.nla_idx(obj)].strips[0].action
action = nla_tracks[nla_index].strips[0].action
if action is None:
return
if not obj.als.auto_rename or layer.name == action.name:
@@ -709,7 +724,9 @@ def influence_update_callback():
return
if not len(anim_data.nla_tracks):
return
i = obj.als.layer_index
i = anim_layers.nla_idx(obj)
if i == -1:
return
track = anim_data.nla_tracks[i]
if len(track.strips) != 1:
return
@@ -786,10 +803,11 @@ def slot_update_callback():
if not len(obj.Anim_Layers):
return
if not len(anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips):
nla_index = anim_layers.nla_idx(obj)
if nla_index == -1 or not len(anim_data.nla_tracks[nla_index].strips):
return
strip = anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips[0]
strip = anim_data.nla_tracks[nla_index].strips[0]
anim_data.action_slot = strip.action_slot
@@ -862,9 +880,10 @@ def strip_settings_callback():
# sync_strip_range()
if not anim_layers.is_layer_row_active(obj):
return
if not len(anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips):
nla_index = anim_layers.nla_idx(obj)
if nla_index == -1 or not len(anim_data.nla_tracks[nla_index].strips):
return
strip = anim_data.nla_tracks[anim_layers.nla_idx(obj)].strips[0]
strip = anim_data.nla_tracks[nla_index].strips[0]
layer = obj.Anim_Layers[obj.als.layer_index]
update_strip_layer_settings(strip, layer)