save startup to fix blender opening on 2nd monitor
This commit is contained in:
2026-03-24 14:03:54 -06:00
parent 9fcddeca02
commit e75144efca
28 changed files with 337 additions and 129 deletions
+1 -1
View File
@@ -20,7 +20,7 @@
bl_info = {
"name": "Animation Layers",
"author": "Tal Hershkovich",
"version" : (2, 3, 5),
"version" : (2, 3, 7),
"blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel",
"description": "Simplifying the NLA editor into an animation layers UI and workflow",
@@ -84,7 +84,7 @@ def make_annotations(cls):
if bl_props:
if '__annotations__' not in cls.__dict__:
setattr(cls, '__annotations__', {})
annotations = cls.__dict__['__annotations__']
annotations = cls.__dict__.get('__annotations__', {})
for k, v in bl_props.items():
annotations[k] = v
delattr(cls, k)
@@ -1376,10 +1376,12 @@ def register(bl_info):
# other considerations and suggestions from a security standpoint
# "tokenstring" Need read api and read repo permission
# updater.private_token = "glpat-K6GjFFka_J6o3GTbe3JK"
updater.private_token = "glpat-js__ikigVSQ_tKWgjVn1"
# updater.private_token = "glpat-js__ikigVSQ_tKWgjVn1"
updater.private_token = "glpat-5rHfAdBG2Br3ww6H8U4vI286MQp1OjFpcXEK.01.100el42cj"
# choose your own username, must match website (not needed for GitLab)
updater.user = ""
# choose your own repository, must match git name for GitHUb and Bitbucket,
# for GitLab use project ID (numbers only)
updater.repo = "22294607"
+136 -17
View File
@@ -2,7 +2,8 @@ import bpy
import os
import numpy as np
from bpy_extras import anim_utils
import sys
import bisect
# import sys
from bpy.app.handlers import persistent
from . import bake_ops
@@ -270,6 +271,7 @@ def register_layers(obj, nla_tracks):
continue
strip = track.strips[0]
use_animated_influence(strip)
strip.influence = 1
#updating the ui list with the nla track names
def visible_layers(obj, nla_tracks):
@@ -342,15 +344,20 @@ def use_animated_influence(strip):
return
fcu_len = len(strip.fcurves)
strip.use_animated_influence = True
if fcu_len != len(strip.fcurves):
if hasattr(strip.fcurves[0].keyframe_points, 'clear'):
strip.fcurves[0].keyframe_points.clear()
else:
keyframe = strip.fcurves[0].keyframe_points[0]
strip.fcurves[0].keyframe_points.remove(keyframe)
strip.influence = 1
if len(strip.fcurves[0].keyframe_points):
keyframes = strip.fcurves[0].keyframe_points
# value = keyframes[0].co.y
# print('influence value ', keyframes[0])
if hasattr(keyframes, 'clear'):
keyframes.clear()
else:
keyframe = keyframes[0]
keyframes.remove(keyframe)
strip.influence = 1
def check_override_layers(obj):
if obj.override_library is None:
return False
@@ -1291,6 +1298,7 @@ def load_action(self, context):
subscriptions.frameend_update_callback()
strip.use_sync_length = False
use_animated_influence(strip)
strip.influence = 1
return
subscriptions.subscriptions_remove()
strip = track.strips[0]
@@ -1941,6 +1949,7 @@ def add_animlayer(layer_name = 'Anim_Layer' , duplicate = False, index = 1, blen
new_strip.blend_type = blend_type
new_strip.use_sync_length = False
use_animated_influence(new_strip)
new_strip.influence = 1
return new_track
@@ -2324,12 +2333,13 @@ class ShareLayerKeys(bpy.types.Operator):
#if the group doesn't exist in the current layer then create one and assign it
if fcu.group is None:
if group.name in anim_data.action.groups:
new_group = anim_data.action.groups[group.name]
else:
new_group = anim_data.action.groups.new(group.name)
new_group = add_group_to_fcurve(obj, fcu, group.name)
# if group.name in anim_data.action.groups:
# new_group = anim_data.action.groups[group.name]
# else:
# new_group = anim_data.action.groups.new(group.name)
fcu.group = new_group
# fcu.group = new_group
#exclude the frames that already exist in the current layer action fcurve
if len(fcu.keyframe_points):
@@ -2771,6 +2781,110 @@ def sync_frame_range(context):
if use_frame_range:
action.use_frame_range = True
class InfluenceNextKeyframe(bpy.types.Operator):
"""Select the influence keyframes of the current layer"""
bl_idname = "anim.influence_next_key"
bl_label = "Move to the next influence keyframe"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
anim_data = anim_data_type(context.object)
return hasattr(anim_data,"nla_tracks") and len(anim_data.nla_tracks[context.object.als.layer_index].strips)
def execute(self, context):
strips = influence_get_strips(context)
current_frame = context.scene.frame_current
# The next keyframes from all the strips together
next_keyframes = []
#Assign selection to all strips
for strip in strips:
for fcu in strip.fcurves:
if fcu.data_path != 'influence':
continue
if not len(fcu.keyframe_points):
continue
keys_len = len(fcu.keyframe_points)
#Get the selection of the current keys or the next one in the list
keyframes = np.zeros(keys_len*2)
fcu.keyframe_points.foreach_get('co', keyframes)
frames = keyframes[::2]
next_i = bisect.bisect_right(frames, current_frame)
if next_i >= len(frames):
continue
next_frame = frames[next_i]
next_keyframes.append(next_frame)
if not len(next_keyframes):
return {'CANCELLED'}
next_keyframes.sort()
next_i = bisect.bisect_right(next_keyframes, current_frame)
if next_i >= len(next_keyframes):
return {'CANCELLED'}
next_frame = next_keyframes[next_i]
context.scene.frame_set(int(next_frame))
return {'FINISHED'}
class InfluencePrevKeyframe(bpy.types.Operator):
"""Select the influence keyframes of the current layer"""
bl_idname = "anim.influence_prev_key"
bl_label = "Move to the previous influence keyframe"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
anim_data = anim_data_type(context.object)
return hasattr(anim_data,"nla_tracks") and len(anim_data.nla_tracks[context.object.als.layer_index].strips)
def execute(self, context):
strips = influence_get_strips(context)
current_frame = context.scene.frame_current
# The prev keyframes from all the strips together
prev_keyframes = []
#Assign selection to all strips
for strip in strips:
for fcu in strip.fcurves:
if fcu.data_path != 'influence':
continue
if not len(fcu.keyframe_points):
continue
keys_len = len(fcu.keyframe_points)
#Get the selection of the current keys or the prev one in the list
keyframes = np.zeros(keys_len*2)
fcu.keyframe_points.foreach_get('co', keyframes)
frames = keyframes[::2]
prev_i = bisect.bisect_left(frames, current_frame)
if prev_i:
prev_i -= 1
prev_frame = frames[prev_i]
prev_keyframes.append(prev_frame)
if not len(prev_keyframes):
return {'CANCELLED'}
prev_keyframes.sort()
prev_i = bisect.bisect_left(prev_keyframes, current_frame)
if prev_i:
prev_i -= 1
prev_frame = prev_keyframes[prev_i]
context.scene.frame_set(int(prev_frame))
return {'FINISHED'}
class SelectInfluenceKeys(bpy.types.Operator):
"""Select the influence keyframes of the current layer"""
bl_idname = "anim.select_influence_keys"
@@ -2921,8 +3035,8 @@ def panelFactory(space_type):
track = anim_data.nla_tracks[obj.als.layer_index]
col=layout.column(align = True)
row = col.row()
# col=layout.column(align = True)
row = layout.row(align = True)
if not len(track.strips):
return
@@ -2933,18 +3047,22 @@ def panelFactory(space_type):
#Drawing the influence slider
if len(strip.fcurves[0].keyframe_points) and not strip.fcurves[0].mute:
row.prop(strip, 'influence', slider = True, text = 'Influence')
row.operator('anim.influence_prev_key', text = '', icon = 'PREV_KEYFRAME')
row.separator(factor = 0.1)
row.operator('anim.influence_next_key', text = '', icon = 'NEXT_KEYFRAME')
else:
row.prop(context.scene.als, 'influence', slider = True, text = 'Influence')
# row.prop(obj.Anim_Layers[obj.als.layer_index], 'influence', slider = True, text = 'Influence')
#Influence SETTINGS
row.separator(factor = 1.5)
row.prop(context.scene.als, 'influence_settings', text ='', icon = 'SETTINGS')
if context.scene.als.influence_settings:
split = layout.split(factor = 0.4)
global_local = 'Global' if context.scene.als.influence_global == True else 'Local'
split.prop(context.scene.als, 'influence_global', text = global_local, toggle = True)
row = split.row()
row = split.row(align = True)
row.alignment = 'RIGHT'
#Drawing Select influence keys
row.operator('anim.select_influence_keys', text="", icon = 'RESTRICT_SELECT_OFF')
@@ -2957,6 +3075,7 @@ def panelFactory(space_type):
mute_icon = 'MUTE_IPO_OFF' if track.strips[0].fcurves[0].mute else 'MUTE_IPO_ON'
row.prop(obj.als, 'influence_mute', icon_only=True, icon = mute_icon)
row.separator(factor = 1.5)
lock_icon = 'DECORATE_LOCKED' if strip.fcurves[0].lock else 'DECORATE_UNLOCKED'
row.prop(obj.als, 'influence_lock', icon_only=True, icon = lock_icon)
@@ -3289,7 +3408,7 @@ def remove_empty_slots(action):
classes = (AutoCustomFrameRange, ResetLayerKeyframes, LAYERS_UL_list, AddAnimLayer, ExtractSelection, ExtractMarkers, DuplicateAnimLayer, RemoveAnimLayer, CyclicFcurves, RemoveFcurves, MoveAnimLayerUp,
MoveAnimLayerDown, SelectBonesInLayer, ClearNLA, ClearActiveAction, OverrideError, AddAction, SyncActionLength, RemoveAction, ShareLayerKeys, SelectInfluenceKeys,
AddSlot, RemoveSlot, EditAllLayersOperator)
AddSlot, RemoveSlot, EditAllLayersOperator, InfluencePrevKeyframe, InfluenceNextKeyframe)
spaceTypes = ['VIEW_3D', 'GRAPH_EDITOR', 'DOPESHEET_EDITOR', 'NLA_EDITOR']
# panel_classes = (cls for spaceType in spaceTypes for cls in panelFactory(spaceType))
@@ -1,6 +1,6 @@
{
"last_check": "2026-03-13 10:11:53.265170",
"backup_date": "December-11-2025",
"last_check": "2026-03-20 10:19:09.867653",
"backup_date": "March-20-2026",
"update_ready": false,
"ignore": false,
"just_restored": false,
@@ -20,7 +20,7 @@
bl_info = {
"name": "Animation Layers",
"author": "Tal Hershkovich",
"version" : (2, 3, 4),
"version" : (2, 3, 5),
"blender" : (3, 2, 0),
"location": "View3D - Properties - Animation Panel",
"description": "Simplifying the NLA editor into an animation layers UI and workflow",
@@ -1049,13 +1049,13 @@ def only_selected_bones(self, context):
def data_type_update(self, context):
obj = self.id_data
anim_data = anim_data_type(obj)
obj.als['layer_index'] = 0
if anim_data is None:
obj.Anim_Layers.clear()
return
if not len(anim_data.nla_tracks):
obj.Anim_Layers.clear()
return
obj.als.layer_index = 0
register_layers(obj, anim_data.nla_tracks)
#change bake method if working with shapekeys
@@ -1980,12 +1980,14 @@ class AddAnimLayer(bpy.types.Operator):
#starting animation layers and getting the default sync layer names
obj.als.auto_rename = context.preferences.addons[__package__].preferences.auto_rename
#Adding base layer
obj.als['layer_index'] = 0
add_animlayer(base_name, index = 0, blend_type = blend_type)
#using a temporary variable instead of calling update_track_list all the time with obj.als.layer_index
index = 0
if anim_data.action:
add_animlayer(layer_name, blend_type = blend_type)
add_animlayer(layer_name, index, blend_type = blend_type)
index += 1
anim_data.action.use_fake_user = True
anim_data.action = None
@@ -2705,10 +2707,10 @@ class AddSlot(bpy.types.Operator):
return {'FINISHED'}
if not hasattr(anim_data, 'action_slot'):
return {'FINISHED'}
index = obj.als.layer_index
if not len(anim_data.nla_tracks[index].strips):
return {'FINISHED'}
index = obj.als.layer_index
strip = anim_data.nla_tracks[index].strips[0]
action = strip.action
@@ -2734,10 +2736,10 @@ class RemoveSlot(bpy.types.Operator):
return {'FINISHED'}
if not hasattr(anim_data, 'action_slot'):
return {'FINISHED'}
index = obj.als.layer_index
if not len(anim_data.nla_tracks[index].strips):
return {'FINISHED'}
index = obj.als.layer_index
strip = anim_data.nla_tracks[index].strips[0]
action = strip.action
action_slot = strip.action_slot
@@ -3260,7 +3262,12 @@ def add_action_slot(obj, action):
if action.slots:
for slot in action.slots:
if obj in slot.users():
if obj.als.data_type != slot.target_id_type:
continue
# Shapekey slot users are the shapekey data, object slot users are the objects
item = obj.data.shape_keys if hasattr(obj.data, 'shape_keys') and obj.als.data_type == 'KEY' else obj
if item in slot.users():
return slot
slot = action.slots.new(obj.als.data_type, obj.name)
@@ -1,16 +1,16 @@
{
"last_check": "2025-12-11 10:42:47.932340",
"backup_date": "",
"last_check": "2026-03-20 10:19:09.867653",
"backup_date": "December-11-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=b3bccbaf23e3171ca198e64cb265eb9bf3810f21",
"link": "https://gitlab.com/api/v4/projects/22294607/repository/archive.zip?sha=3868a0c2bd99dde13a2d390a18ffb1db25883f17",
"version": [
2,
3,
5
7
]
}
}
+24 -1
View File
@@ -648,6 +648,14 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
baked_channelbag = anim_layers.get_channelbag(obj, baked_action)
baked_fcurves = baked_channelbag.fcurves
if not obj.als.smartbake:
interpolation_types = ('CONSTANT', 'LINEAR', 'BEZIER', 'SINE', 'QUAD', 'CUBIC', 'QUART', 'QUINT', 'EXPO', 'CIRC', 'BACK', 'BOUNCE', 'ELASTIC')
handle_types = ('FREE', 'ALIGNED', 'VECTOR', 'AUTO', 'AUTO_CLAMPED')
# Get the index position to assign with foreach_set
interpolation = interpolation_types.index(bpy.context.preferences.edit.keyframe_new_interpolation_type)
handle_type = handle_types.index(bpy.context.preferences.edit.keyframe_new_handle_type)
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
@@ -845,7 +853,7 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
smartkey.value = eval_array[i]
if smartkey.inbetween:
continue
baked_fcu.keyframe_points.add(1)
keyframe = baked_fcu.keyframe_points[-1]
keyframe.co = (frame, eval_array[i])
@@ -859,6 +867,10 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
continue
if obj.als.smartbake:
add_interpolations(baked_fcu, fcu_keys[(fcu_key[0], i)], layers_count)
else:
# Add the interpolation and handle types from the preferences defaults
set_all_handles(baked_fcu, interpolation, handle_type)
baked_fcu.update()
#paste the modifiers to the new baked fcurve
@@ -874,6 +886,17 @@ def AL_bake(frame_start, frame_end, nla_tracks, fcu_keys, additive, step, action
return baked_action
def set_all_handles(baked_fcu, interpolation, handle_type):
'''Set all the handle types and interpolations in an fcurve using foreach_set and numpy
should use numerical value instead of a string'''
keyframes_len = len(baked_fcu.keyframe_points)
interpolations = np.full(keyframes_len, interpolation)
handles = np.full(keyframes_len, handle_type)
kp = baked_fcu.keyframe_points
kp.foreach_set('interpolation', interpolations)
kp.foreach_set('handle_left_type', handles)
kp.foreach_set('handle_right_type', handles)
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'''
+3 -6
View File
@@ -291,10 +291,6 @@ def evaluate_array(fcurves, fcu_path, frame, array_default = [0, 0, 0]):
continue
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_default):
@@ -317,7 +313,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
if not len(track.strips):
continue
for strip in track.strips:
if not strip.frame_start < frame < strip.frame_end:
if not strip.frame_start <= frame <= strip.frame_end:
continue
action = strip.action
if action is None:
@@ -332,6 +328,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
#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':
@@ -445,7 +442,7 @@ def evaluate_value(self, context):
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
#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):
@@ -183,7 +183,7 @@ def check_handler(scene):
anim_layers.visible_layers(obj, nla_tracks)
# 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:
if not any(item.object.als.edit_all_keyframes for item in scene.AL_objects if item.object is not None) and scene.als.edit_all_layers_op:
scene.als.edit_all_layers_op = False
# check if track and layers are synchronized
@@ -385,24 +385,32 @@ def influence_sync(scene, 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):
strip = track.strips[0]
if not len(strip.fcurves):
if not strip.use_animated_influence:
influence = strip.influence
anim_layers.use_animated_influence(strip)
strip.influence = influence
continue
if not len(track.strips[0].fcurves[0].keyframe_points):
if not len(strip.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:
if not strip.fcurves[0].lock:
# obj.Anim_Layers[i]['influence'] = track.strips[0].influence
scene.als['influence'] = track.strips[0].influence
scene.als['influence'] = strip.influence
track.strips[0].fcurves[0].lock = True
if scene.animation_data is None:
return
action = scene.animation_data.action
if action is None:
return
# strip.use_animated_influence = True
#if a keyframe was found in the temporary property then add it to the
# data_path = 'Anim_Layers[' + str(obj.als.layer_index) + '].influence'
data_path = 'als.influence'