846 lines
31 KiB
Python
846 lines
31 KiB
Python
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
|
|
# 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:
|
|
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):
|
|
global func_running
|
|
|
|
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(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:
|
|
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
|
|
if 'outofrange' not in globals():
|
|
global outofrange
|
|
outofrange = False if 0 <= current < frame_end else True
|
|
|
|
if 0 <= current < frame_end:
|
|
if outofrange:
|
|
frameend_update_callback()
|
|
outofrange = False
|
|
return
|
|
|
|
outofrange = True
|
|
|
|
#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
|
|
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.custom_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 = 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)
|
|
|
|
def check_handler(scene):
|
|
'''A main function that performs a series of checks using a handler'''
|
|
# 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
|
|
obj = bpy.context.object
|
|
#if the object was removed from the scene, then remove it from anim layers object list
|
|
if obj is None:
|
|
anim_layers.clean_AL_objects(scene)
|
|
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 == '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)
|
|
|
|
# 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
|
|
|
|
track = nla_tracks[obj.als.layer_index]
|
|
|
|
sync_frame_range(scene, track, layer)
|
|
# sync_strip_range(scene)
|
|
always_sync_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(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:
|
|
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_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 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
|
|
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
|
|
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, 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.custom_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(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):
|
|
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
|
|
scene.als['influence'] = track.strips[0].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
|
|
#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'
|
|
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
|
|
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 == 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]
|
|
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
|
|
strip = selected_track.strips[0]
|
|
if not len(strip.fcurves):
|
|
return
|
|
|
|
global influence_keys
|
|
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
|
|
|
|
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'''
|
|
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
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
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.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
|
|
|
|
if clean_AL_objects:
|
|
anim_layers.clean_AL_objects(scene)
|
|
subscriptions_add(scene, handler = False)
|
|
|
|
#Subscribe to the scene frame_end
|
|
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)
|
|
# print('subscribe_to_preview_frame_end')
|
|
for subscribe in [subscribe_preview_end, subscribe_use_preview]:
|
|
|
|
bpy.msgbus.subscribe_rna(
|
|
key=subscribe,
|
|
owner=subscriptions_owner,
|
|
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(subscriptions_owner):
|
|
'''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=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)
|
|
|
|
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(subscriptions_owner):
|
|
'''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=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)
|
|
|
|
def influence_update_callback():
|
|
'''update influence'''
|
|
# 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
|
|
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
|
|
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 strip.fcurves[0].mute or strip.fcurves[0].lock:
|
|
return
|
|
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 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=subscriptions_owner,
|
|
# Args passed to callback function (tuple)
|
|
args=(),
|
|
# Callback function for property update
|
|
notify=influence_update_callback,)
|
|
|
|
|
|
def subscribe_to_action_slot(subscriptions_owner):
|
|
'''Subscribe to the influence of the track'''
|
|
subscribe_slot = (bpy.types.NlaStrip, 'action_slot')
|
|
|
|
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')
|
|
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, 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')
|
|
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=subscriptions_owner,
|
|
# Args passed to callback function (tuple)
|
|
args=(),
|
|
# Callback function for property update
|
|
notify=strip_settings_callback,)
|
|
|
|
def update_strip_layer_settings(strip, layer):
|
|
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]
|
|
|
|
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.custom_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
|
|
|
|
# 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'])
|