858 lines
31 KiB
Python
858 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)
|
|
return
|
|
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 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
|
|
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
|
|
|
|
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)
|
|
|
|
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()
|
|
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
|
|
# 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)):
|
|
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)
|
|
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 ')
|
|
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
|
|
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(strip.fcurves[0].keyframe_points):
|
|
#apply the influence property to the temp property when keyframes are removed (but its still locked)
|
|
if not strip.fcurves[0].lock:
|
|
# obj.Anim_Layers[i]['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'
|
|
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)
|
|
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'])
|