work: restore shift+spacebar for media play/pause
maybe put in maya config? idk what funiman's preference is
This commit is contained in:
@@ -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
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"last_check": "2026-04-21 12:47:39.126086",
|
||||
"backup_date": "April-21-2026",
|
||||
"last_check": "2026-05-27 14:41:11.263249",
|
||||
"backup_date": "May-27-2026",
|
||||
"update_ready": false,
|
||||
"ignore": false,
|
||||
"just_restored": false,
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
bl_info = {
|
||||
"name": "Animation Layers",
|
||||
"author": "Tal Hershkovich",
|
||||
"version" : (2, 3, 8),
|
||||
"version" : (2, 4, 0),
|
||||
"blender" : (3, 2, 0),
|
||||
"location": "View3D - Properties - Animation Panel",
|
||||
"description": "Simplifying the NLA editor into an animation layers UI and workflow",
|
||||
@@ -153,7 +153,6 @@ def update_panel(self, context):
|
||||
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)
|
||||
|
||||
@@ -176,8 +175,9 @@ 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)
|
||||
|
||||
lock_nlatracks: bpy.props.BoolProperty(name="Automatically lock the NLA tracks", description="Automatically lock nla tracks when creating layers for safety", default = True)
|
||||
auto_custom_range: bpy.props.BoolProperty(name="Switch automatically to custom frame range when editing NLA Strips", description="Automatically use custom frame range when adjusting NLA Strips manually", default = False)
|
||||
|
||||
#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 = [
|
||||
@@ -260,8 +260,9 @@ class AnimLayersAddonPreferences(bpy.types.AddonPreferences):
|
||||
row.label(text = "Custom Frame Range Settings")
|
||||
row.prop(self, "frame_range_settings", text = '')
|
||||
|
||||
col.prop(self, "lock_nlatracks")
|
||||
|
||||
row = col.row()
|
||||
row.prop(self, "auto_custom_range")
|
||||
row.prop(self, "lock_nlatracks")
|
||||
|
||||
classes = (AnimLayersSettings, AnimLayersSceneSettings, AnimLayersItems, AnimLayersObjects)
|
||||
|
||||
|
||||
@@ -399,7 +399,7 @@ def get_fcu_layer_keyframes(obj, context, track):
|
||||
keyframes = []
|
||||
# fcurves = get_fcurves(track.strips[0].action)
|
||||
# fcurves = track.strips[0].action.fcurves
|
||||
fcurves = get_fcurves(obj, track.strips[0].action)
|
||||
fcurves = get_fcurves(obj, track.strips[0].action, obj.als.data_type)
|
||||
#store all the keyframe locations from the fcurves of the layer
|
||||
for fcu in fcurves:
|
||||
if fcu.group is not None:
|
||||
@@ -1546,6 +1546,8 @@ def strip_action_recalc(self, strip):
|
||||
|
||||
###################################################### HELPER FUNCTIONS ################################################
|
||||
def redraw_areas(areas):
|
||||
if not len(bpy.context.window_manager.windows):
|
||||
return
|
||||
for area in bpy.context.window_manager.windows[0].screen.areas:
|
||||
if area.type in areas:
|
||||
area.tag_redraw()
|
||||
@@ -1621,7 +1623,7 @@ def select_layer_bones(self, context):
|
||||
|
||||
###################################################### CLASSES ###########################################################
|
||||
class SelectBonesInLayer(bpy.types.Operator):
|
||||
"""Select bones with keyframes in the current layer"""
|
||||
"""Select bones with keyframes in the current layer, use shift to add to the current selection"""
|
||||
bl_idname = "anim.bones_in_layer"
|
||||
bl_label = "Select layer bones"
|
||||
bl_icon = "BONE_DATA"
|
||||
@@ -1864,18 +1866,9 @@ class AutoCustomFrameRange(bpy.types.Operator):
|
||||
# return {'CANCELLED'}
|
||||
|
||||
def restore(self, context):
|
||||
if hasattr(subscriptions, 'frame_range'):
|
||||
frame_start, frame_end = subscriptions.frame_range
|
||||
|
||||
else:
|
||||
frame_start, frame_end = subscriptions.get_frame_range(context.scene)
|
||||
print('restore')
|
||||
subscriptions.frameend_update_callback()
|
||||
|
||||
self.strip.repeat = 1 #change strip repeat but keep self.repeat value stored
|
||||
self.strip.use_reverse = False
|
||||
self.strip.frame_start = frame_start
|
||||
self.strip.scale = self.layer.speed
|
||||
self.strip.frame_end = frame_end
|
||||
# update_action_frame_range(frame_start, frame_end, layer, strip)
|
||||
subscriptions.subscriptions_add(context.scene)
|
||||
|
||||
def update_action_list(scene):
|
||||
@@ -2596,9 +2589,7 @@ class RemoveFcurves(bpy.types.Operator):
|
||||
if mod.type == 'CYCLES':
|
||||
fcu.modifiers.remove(mod)
|
||||
fcu.update()
|
||||
for area in context.window_manager.windows[0].screen.areas:
|
||||
if area.type == 'GRAPH_EDITOR' or area.type == 'VIEW_3D':
|
||||
area.tag_redraw()
|
||||
redraw_areas(['GRAPH_EDITOR', 'VIEW_3D'])
|
||||
break
|
||||
return {'FINISHED'}
|
||||
|
||||
@@ -3301,25 +3292,36 @@ def copy_action(action):
|
||||
|
||||
return new_action
|
||||
|
||||
def get_obj_slot(obj, action, data_type = 'OBJECT'):
|
||||
def get_obj_slot(obj, action, data_type = None):
|
||||
'''Get the slot in the action that this object is using either it's object, or shapekeys'''
|
||||
|
||||
if data_type is None:
|
||||
data_type = obj.als.data_type
|
||||
|
||||
if not hasattr(action, 'slots'):
|
||||
return None
|
||||
|
||||
if not len(action.slots):
|
||||
# If no slots exist, create one for the object and return it
|
||||
slot = add_action_slot(obj, action)
|
||||
return slot
|
||||
|
||||
# data_type = obj.als.data_type
|
||||
for slot in action.slots:
|
||||
if slot.target_id_type != data_type:
|
||||
continue
|
||||
# if obj.als.data_type == 'OBJECT' and obj in slot.users():
|
||||
# return slot
|
||||
|
||||
if data_type == 'KEY' and obj.data.shape_keys in slot.users():
|
||||
return slot
|
||||
elif obj in slot.users():
|
||||
return slot
|
||||
|
||||
return None
|
||||
|
||||
return add_action_slot(obj, action)
|
||||
|
||||
def get_fcurves(obj: bpy.types.Object, action: bpy.types.Action, data_type = 'OBJECT'):
|
||||
def get_fcurves(obj: bpy.types.Object, action: bpy.types.Action, data_type = None):
|
||||
|
||||
if data_type is None:
|
||||
data_type = obj.als.data_type
|
||||
|
||||
if hasattr(action, 'layers'):
|
||||
slot = get_obj_slot(obj, action, data_type)
|
||||
@@ -3335,10 +3337,13 @@ def get_fcurves(obj: bpy.types.Object, action: bpy.types.Action, data_type = 'OB
|
||||
return action.fcurves
|
||||
return []
|
||||
|
||||
def get_channelbag(obj: bpy.types.Object, action: bpy.types.Action, data_type = 'OBJECT'):
|
||||
def get_channelbag(obj: bpy.types.Object, action: bpy.types.Action, data_type = None):
|
||||
'''Getting the container of the fcurves, either the action or channelbag
|
||||
Using this when adding a new group to the action'''
|
||||
|
||||
if data_type is None:
|
||||
data_type = obj.als.data_type
|
||||
|
||||
if hasattr(action, 'layers'):
|
||||
slot = get_obj_slot(obj, action, data_type)
|
||||
channelbag = None
|
||||
|
||||
+4
-4
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"last_check": "2026-04-21 12:47:39.126086",
|
||||
"backup_date": "March-27-2026",
|
||||
"last_check": "2026-05-27 14:41:11.263249",
|
||||
"backup_date": "April-21-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=dddd6932039b8a3e5fae3ce2de957f21a5942c84",
|
||||
"link": "https://gitlab.com/api/v4/projects/22294607/repository/archive.zip?sha=321d411a449bc9acee2a759e30cd3d0f36bbd2ab",
|
||||
"version": [
|
||||
2,
|
||||
4,
|
||||
0
|
||||
1
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -645,6 +645,7 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
|
||||
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, obj.als.data_type)
|
||||
baked_channelbag = anim_layers.get_channelbag(obj, baked_action)
|
||||
baked_fcurves = baked_channelbag.fcurves
|
||||
|
||||
|
||||
@@ -73,7 +73,6 @@ def animlayers_frame(scene, context):
|
||||
scene['framerange_preview'] = scene.use_preview_range
|
||||
frameend_update_callback()
|
||||
return
|
||||
|
||||
frame_start, frame_end = bake_ops.frame_start_end(scene)
|
||||
# frame_start, frame_end = get_frame_range(scene)
|
||||
reset_subscription = False
|
||||
@@ -106,7 +105,6 @@ def animlayers_frame(scene, context):
|
||||
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.custom_frame_range:
|
||||
@@ -127,6 +125,7 @@ def animlayers_frame(scene, context):
|
||||
if strip.frame_start < 0:
|
||||
strip.frame_start = 0
|
||||
anim_layers.update_action_frame_range(0, frame_end, layer, strip)
|
||||
return
|
||||
anim_layers.update_action_frame_range(strip.frame_start, current + 10.0, layer, strip)
|
||||
strip.frame_end = current + 10.0
|
||||
|
||||
@@ -231,6 +230,9 @@ def track_layer_synchronization(obj, nla_tracks):
|
||||
if obj.als.layer_index > len(obj.Anim_Layers)-1:
|
||||
obj.als.layer_index = len(obj.Anim_Layers)-1
|
||||
|
||||
if not bpy.context.preferences.addons[__package__].preferences.auto_custom_range:
|
||||
return
|
||||
|
||||
#update new layer with strip settings
|
||||
frame_start, frame_end = get_frame_range(bpy.context.scene)
|
||||
|
||||
@@ -243,7 +245,6 @@ def track_layer_synchronization(obj, nla_tracks):
|
||||
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
|
||||
|
||||
@@ -311,11 +312,16 @@ def sync_frame_range(scene, track, layer):
|
||||
return
|
||||
|
||||
#Turn on custom frame range if the current strip is not following the scene frame range
|
||||
# Should be activated when nla strips are edited manually in the nla editor, only when auto custom range is turned on, otherwise just update the strip frame range to 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
|
||||
if bpy.context.preferences.addons[__package__].preferences.auto_custom_range:
|
||||
subscriptions_remove()
|
||||
# print('321 custom frame range')
|
||||
bpy.ops.anim.custom_frame_range_warning('INVOKE_DEFAULT')
|
||||
return
|
||||
else:
|
||||
frameend_update_callback()
|
||||
return
|
||||
|
||||
def sync_strip_range(scene):
|
||||
'''Checking all the strips if a value was changed in the nla (not including UI changes)
|
||||
@@ -356,7 +362,6 @@ def sync_strip_range(scene):
|
||||
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
|
||||
|
||||
@@ -567,7 +572,6 @@ def subscribe_to_preview_frame_end(scene):
|
||||
# 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)
|
||||
# print('subscribe_to_preview_frame_end')
|
||||
for subscribe in [subscribe_preview_end, subscribe_use_preview]:
|
||||
|
||||
bpy.msgbus.subscribe_rna(
|
||||
|
||||
BIN
Binary file not shown.
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user