2026-02-16
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
# ***** END GPL LICENCE BLOCK *****
|
||||
|
||||
import bpy
|
||||
from . import Tools
|
||||
|
||||
class GizmoSizeUp(bpy.types.Operator):
|
||||
"""Share keyframes between all the selected objects and bones"""
|
||||
@@ -144,7 +145,8 @@ class SwitchBoneCollectionsVisibility(bpy.types.Operator):
|
||||
start = 'pose.bones["'
|
||||
end = '"]'
|
||||
#get all the animated bones from the fcurves
|
||||
for fcu in obj.animation_data.action.fcurves:
|
||||
fcurves = Tools.get_fcurves(obj, obj.animation_data.action)
|
||||
for fcu in fcurves:
|
||||
start_index = fcu.data_path.find(start)
|
||||
end_index = fcu.data_path.find(end)
|
||||
#if it's not a posebone fcurve then skip
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+289
-189
@@ -8,6 +8,7 @@ from mathutils import Vector
|
||||
# import math
|
||||
from math import pi
|
||||
from . import emp
|
||||
from . import TempCtrls
|
||||
# import time
|
||||
from bpy.app.handlers import persistent
|
||||
from bpy_extras import anim_utils
|
||||
@@ -68,17 +69,21 @@ def draw_text_callback_px(self, context):
|
||||
font_id = 0 # XXX, need to find out how best to get this.
|
||||
#fade in
|
||||
factor = 0.5
|
||||
if not self.fade_out_start:
|
||||
alpha = (self.timer.time_duration / self.fade_duration) * factor if not self.no_timer else factor
|
||||
else:
|
||||
timer = self.timer.time_duration - self.fade_out_start
|
||||
alpha = (1 * factor) - ((timer / self.fade_duration ) * factor)
|
||||
try:
|
||||
if not self.fade_out_start:
|
||||
alpha = (self.timer.time_duration / self.fade_duration) * factor if not self.no_timer else factor
|
||||
else:
|
||||
timer = self.timer.time_duration - self.fade_out_start
|
||||
alpha = (1 * factor) - ((timer / self.fade_duration ) * factor)
|
||||
except ReferenceError:
|
||||
return
|
||||
|
||||
as_height = asset_shelf_height(context)
|
||||
# draw some text
|
||||
blf.position(font_id, 70, 45 + as_height, 0)
|
||||
blf.position(font_id, 70, 50 + as_height, 0)
|
||||
blf.size(font_id, self.size)
|
||||
blf.color(0, 1, 1, 0, alpha)
|
||||
|
||||
blf.draw(font_id, self.text)
|
||||
|
||||
class Markers_Retimer(bpy.types.Operator):
|
||||
@@ -188,8 +193,8 @@ class Markers_Retimer(bpy.types.Operator):
|
||||
if strip.mute:
|
||||
continue
|
||||
# actions.add(strip.action)
|
||||
all_fcurves.add(get_fcurves_channelbag(obj, strip.action))
|
||||
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
all_fcurves.add(get_fcurves(obj, strip.action))
|
||||
fcurves = get_fcurves(obj, obj.animation_data.action)
|
||||
if fcurves not in all_fcurves:
|
||||
all_fcurves.add(fcurves)
|
||||
# actions.add(obj.animation_data.action)
|
||||
@@ -352,19 +357,23 @@ class Markers_Retimer(bpy.types.Operator):
|
||||
self.report({'ERROR'}, str(e) + '. Quitting RetimerMarkers')
|
||||
return {'CANCELLED'}
|
||||
|
||||
def init_frame_start_marker(self, scene, name = "Frame_Start"):
|
||||
def init_frame_start_marker(self, scene, name = "Frame Start"):
|
||||
'''Create the start frame marker'''
|
||||
self.frame_start_marker = scene.timeline_markers.new(name, frame = self.frame_start)
|
||||
self.frame_start_marker[ "Frame_Start"] = True
|
||||
self.frame_start_marker.select = False
|
||||
self.frame_start_marker_name = name
|
||||
if name in scene.timeline_markers:
|
||||
self.frame_start_marker = scene.timeline_markers[name]
|
||||
else:
|
||||
self.frame_start_marker = scene.timeline_markers.new(name, frame = self.frame_start)
|
||||
self.frame_start_marker.select = False
|
||||
|
||||
def init_frame_end_marker(self, scene, name = "Frame_End"):
|
||||
def init_frame_end_marker(self, scene, name = "Frame End"):
|
||||
'''Create the start frame marker'''
|
||||
self.frame_end_marker = scene.timeline_markers.new(name, frame = self.frame_end)
|
||||
self.frame_end_marker["Frame_End"] = True
|
||||
self.frame_end_marker.select = False
|
||||
self.frame_end_marker_name = name
|
||||
if name in scene.timeline_markers:
|
||||
self.frame_end_marker = scene.timeline_markers[name]
|
||||
else:
|
||||
self.frame_end_marker = scene.timeline_markers.new(name, frame = self.frame_end)
|
||||
self.frame_end_marker.select = False
|
||||
|
||||
def check_removed_markers(self, scene):
|
||||
#check if markers are gone in case of undo or if removed
|
||||
@@ -507,7 +516,7 @@ class Markers_BakeRange(bpy.types.Operator):
|
||||
atb.bake_frame_range = False
|
||||
return {'CANCELLED'}
|
||||
|
||||
scene.animtoolbox.bake_frame_range = True
|
||||
atb.bake_frame_range = True
|
||||
|
||||
self.frame_start = atb.bake_frame_start
|
||||
self.frame_end = atb.bake_frame_end
|
||||
@@ -640,15 +649,23 @@ def update_cursor_matrix(context):
|
||||
item_matrix = item.id_data.matrix_world @ item.matrix
|
||||
else:
|
||||
item_matrix = item.matrix_world
|
||||
|
||||
#apply the new cursor position based on previous cursor
|
||||
relative_matrix = Matrix(item['relative_cursor'])
|
||||
cursor.matrix = item_matrix @ relative_matrix.inverted()
|
||||
|
||||
if bpy.app.version < (5, 0, 0):
|
||||
relative_matrix = Matrix(item['relative_cursor'])
|
||||
else:
|
||||
# Since Blender 5 I need to reshape the array into a matrix using numpy
|
||||
matrix_4x4 = np.array(item['relative_cursor']).reshape(4, 4).T
|
||||
relative_matrix = Matrix(matrix_4x4)
|
||||
|
||||
cursor.matrix = item_matrix @ relative_matrix.inverted()
|
||||
cursor_matrix = cursor.matrix
|
||||
|
||||
def assign_relative_matrix(context, items):
|
||||
def assign_relative_matrix(context, items, prop = 'relative_cursor'):
|
||||
'''get the cursor relative matrices either from object or bones'''
|
||||
|
||||
global last_matrices
|
||||
if 'last_matrices' not in globals():
|
||||
last_matrices = []
|
||||
|
||||
cursor_matrix = context.scene.cursor.matrix
|
||||
matrix_avg = get_matrix_avg(items)
|
||||
|
||||
@@ -664,9 +681,9 @@ def assign_relative_matrix(context, items):
|
||||
|
||||
item_matrix = matrix_avg if len(items) > 1 else item_matrix
|
||||
matrix_dist = cursor_matrix.inverted() @ item_matrix
|
||||
item['relative_cursor'] = matrix_dist
|
||||
item[prop] = matrix_dist
|
||||
|
||||
return last_matrices
|
||||
return matrix_dist
|
||||
|
||||
def assign_relative_cursor_prop(context):
|
||||
'''Updating the matrix property of the cursor distance on the bone'''
|
||||
@@ -762,6 +779,7 @@ class Keyframe_Offset(bpy.types.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.mouse_x = event.mouse_x
|
||||
self.initial_offset = context.scene.animtoolbox.keyframes_offset
|
||||
# self.mouse_y = event.mouse_y
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
@@ -779,8 +797,11 @@ class Keyframe_Offset(bpy.types.Operator):
|
||||
self.mouse_x = event.mouse_x
|
||||
|
||||
scene.animtoolbox.keyframes_offset += value*0.01
|
||||
|
||||
if event.type in {'ESC'} or event.value == 'RELEASE': # Cancel
|
||||
if event.type in {'ESC', 'RIGHTMOUSE'} and event.value == 'RELEASE': # Cancel
|
||||
scene.animtoolbox.keyframes_offset = self.initial_offset
|
||||
return {'CANCELLED'}
|
||||
|
||||
if event.value == 'RELEASE' and event.type == 'LEFTMOUSE':
|
||||
self.execute(context)
|
||||
return {'FINISHED'}
|
||||
return {'RUNNING_MODAL'}
|
||||
@@ -838,7 +859,7 @@ def remove_offset_property(context):
|
||||
|
||||
def add_offset(obj, offset, prev_offset, bone_id = None):
|
||||
|
||||
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
fcurves = get_fcurves(obj, obj.animation_data.action)
|
||||
|
||||
for fcu in fcurves:
|
||||
if bone_id is not None:
|
||||
@@ -876,83 +897,71 @@ def add_offset(obj, offset, prev_offset, bone_id = None):
|
||||
fcu.keyframe_points.foreach_set('handle_left', updated_left_handle)
|
||||
fcu.keyframe_points.foreach_set('handle_right', updated_right_handle)
|
||||
fcu.update()
|
||||
|
||||
def add_keyframe(bone):
|
||||
|
||||
def get_locked_fcus(obj, locked_fcus = None):
|
||||
# Locked fcus might have been already created, if it's not None it was already not checked
|
||||
if locked_fcus is not None:
|
||||
return locked_fcus
|
||||
locked_fcus = set()
|
||||
if not obj.animation_data:
|
||||
return locked_fcus
|
||||
if not obj.animation_data.action:
|
||||
return locked_fcus
|
||||
fcurves = get_fcurves(obj, obj.animation_data.action)
|
||||
locked_fcus = {(fcu.data_path, fcu.array_index) for fcu in fcurves if fcu.lock}
|
||||
return locked_fcus
|
||||
|
||||
def add_keyframe(bone, locked_fcus = None):
|
||||
|
||||
obj = bone.id_data
|
||||
|
||||
if bone.rotation_mode == 'QUATERNION':
|
||||
rotation = 'rotation_quaternion'
|
||||
elif bone.rotation_mode == 'AXIS_ANGLE':
|
||||
rotation = 'rotation_axis_angle'
|
||||
else:
|
||||
rotation = 'rotation_euler'
|
||||
rotation = 'rotation_euler' if len(bone.rotation_mode) == 3 else 'rotation_' + bone.rotation_mode.lower()
|
||||
|
||||
# rotation = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler'
|
||||
transforms = ['location', rotation, 'scale']
|
||||
path = bone.path_from_id() if obj.mode == 'POSE' else ''
|
||||
groups = dict()
|
||||
#find fcurves that are locked to exclude them
|
||||
excluded_fcus = {}
|
||||
if obj.animation_data:
|
||||
if obj.animation_data.action:
|
||||
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
for fcu in fcurves:
|
||||
if path not in fcu.data_path or not fcu.lock:
|
||||
continue
|
||||
groups.update({(fcu.data_path, fcu.array_index ): fcu.group.name})
|
||||
if fcu.data_path in excluded_fcus:
|
||||
excluded_fcus[fcu.data_path].append(fcu.array_index)
|
||||
else:
|
||||
excluded_fcus.update({fcu.data_path : [fcu.array_index]})
|
||||
|
||||
path = bone.path_from_id() + '.' if obj.mode == 'POSE' else ''
|
||||
|
||||
locked_fcus = get_locked_fcus(obj, locked_fcus)
|
||||
|
||||
#insert keyframe only to channels that are not excluded
|
||||
for transform in transforms:
|
||||
#path = bone.path_from_id() + '.' + transform
|
||||
path = bone.path_from_id() + '.' + transform if obj.mode == 'POSE' else transform
|
||||
# if path not in excluded_fcus:
|
||||
# bone.keyframe_insert(transform)
|
||||
# continue
|
||||
data_path = path + transform
|
||||
length = len(getattr(bone, transform))
|
||||
for i in range(length):
|
||||
if path in excluded_fcus:
|
||||
if i in excluded_fcus[path]:
|
||||
continue
|
||||
if (path, i) in groups:
|
||||
group_name = groups[(path, i)]
|
||||
else:
|
||||
group_name = bone.name
|
||||
|
||||
bone.keyframe_insert(transform, index = i, group = group_name, frame = bpy.context.scene.frame_current_final)
|
||||
|
||||
def get_fcu_inbetweens(bone, frame, fcu_inbetweens):
|
||||
|
||||
for i in range(length):
|
||||
# Check if this channel being filtered
|
||||
if TempCtrls.filter_transform(transform, i):
|
||||
continue
|
||||
# Skip if the fcurve was locked
|
||||
if (data_path, i) in locked_fcus:
|
||||
continue
|
||||
|
||||
bone.keyframe_insert(transform, index = i, group = bone.name, frame = bpy.context.scene.frame_current_final)
|
||||
|
||||
def get_fcu_inbetweens(bone, frame, fcu_inbetweens, inbetweens):
|
||||
obj = bone.id_data
|
||||
|
||||
if bone.rotation_mode == 'QUATERNION':
|
||||
rotation = 'rotation_quaternion'
|
||||
elif bone.rotation_mode == 'AXIS_ANGLE':
|
||||
rotation = 'rotation_axis_angle'
|
||||
else:
|
||||
rotation = 'rotation_euler'
|
||||
|
||||
rot_channel = rot_mode_to_channel(bone.rotation_mode)
|
||||
|
||||
# rotation = 'rotation_quaternion' if bone.rotation_mode == 'QUATERNION' else 'rotation_euler'
|
||||
transforms = ['location', rotation, 'scale']
|
||||
# fcurves = obj.animation_data.action.fcurves
|
||||
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
transforms = ['location', rot_channel, 'scale']
|
||||
|
||||
fcurves = get_fcurves(obj, obj.animation_data.action)
|
||||
|
||||
#insert keyframe only to channels that are not excluded
|
||||
location, rotation, scale = bone.matrix_basis.decompose()
|
||||
for transform in transforms:
|
||||
path = bone.path_from_id() + '.' + transform if obj.mode == 'POSE' else transform
|
||||
length = len(getattr(bone, transform))
|
||||
|
||||
for i in range(length):
|
||||
fcu = fcurves.find(data_path = path, index = i)
|
||||
if not fcu:
|
||||
continue
|
||||
if fcu.lock:
|
||||
continue
|
||||
if not inbetweens:
|
||||
# If there is no inbetweens then we just want to store the fcurves for auto handles
|
||||
fcu_inbetweens[fcu] = None
|
||||
continue
|
||||
|
||||
if 'rotation' in transform:
|
||||
if transform == 'rotation_euler':
|
||||
coords = bone.matrix_basis.to_euler(bone.rotation_mode)
|
||||
@@ -960,7 +969,7 @@ def get_fcu_inbetweens(bone, frame, fcu_inbetweens):
|
||||
coords = rotation
|
||||
else:
|
||||
coords = locals()[transform]
|
||||
|
||||
|
||||
if fcu in fcu_inbetweens:
|
||||
fcu_inbetweens[fcu].update({frame : coords[i]})
|
||||
else:
|
||||
@@ -987,8 +996,10 @@ def add_inbetween_key(self, context):
|
||||
#strip = anim_data.nla_tracks[obj.als.layer_index].strips[0]
|
||||
#frame = round(bake_ops.frame_evaluation(context.scene.frame_current, strip), 3)
|
||||
frame = context.scene.frame_current
|
||||
paths = [bone.path_from_id() for bone in context.selected_pose_bones]
|
||||
fcurves = get_fcurves_channelbag(obj, anim_data.action)
|
||||
if obj.mode == 'POSE':
|
||||
paths = [bone.path_from_id() for bone in context.selected_pose_bones]
|
||||
|
||||
fcurves = get_fcurves(obj, anim_data.action)
|
||||
for fcu in fcurves:
|
||||
#filter selected bones
|
||||
if obj.mode == 'POSE': #apply only to selected bones
|
||||
@@ -1059,10 +1070,8 @@ def add_inbetween_worldmatrix(self, context):
|
||||
if action is None:
|
||||
self['inbetween_worldmatrix'] = 0.0
|
||||
return
|
||||
# frame = round(context.scene.frame_current, 2)
|
||||
|
||||
if not self.inbetween_worldmatrix:
|
||||
# scene.frame_set(frame)
|
||||
return
|
||||
|
||||
if obj.mode == 'POSE' and not context.selected_pose_bones:
|
||||
@@ -1073,37 +1082,38 @@ def add_inbetween_worldmatrix(self, context):
|
||||
|
||||
def get_matrix_other(self, context):
|
||||
'''Get the matrix from the other frame, either previous or the next'''
|
||||
|
||||
#get all the list of the frames from the different channels for each bone
|
||||
#rest of the code is only for posebones
|
||||
#creating a new dict to avoid frame_set at the same frame multiple times. It will get the matrix of multiple bones from each frame
|
||||
#This is done just to avoid extra scene evaluation using frame_set
|
||||
#adding the bone to the new flipped dict key using the frame number
|
||||
|
||||
scene = context.scene
|
||||
prev_frame_bones = dict()
|
||||
next_frame_bones = dict()
|
||||
for item, frames in self.items_frames.items():
|
||||
#get the next and previous frames
|
||||
next_frames = [frame for frame in frames if frame > self.frame]
|
||||
if next_frames:
|
||||
next_frame = next_frames[0]
|
||||
|
||||
next_frame = next_frames[0]
|
||||
#the bones that are on the next frame
|
||||
if next_frame in next_frame_bones:
|
||||
next_frame_bones[next_frame].append(item)
|
||||
else:
|
||||
next_frame_bones.update({next_frame : [item]})
|
||||
|
||||
prev_frames = [frame for frame in frames if frame < self.frame]
|
||||
if prev_frames:
|
||||
prev_frame = prev_frames[-1]
|
||||
if not prev_frame and not next_frame:
|
||||
|
||||
prev_frame = prev_frames[-1]
|
||||
if prev_frame in prev_frame_bones:
|
||||
prev_frame_bones[prev_frame].append(item)
|
||||
else:
|
||||
prev_frame_bones.update({prev_frame : [item]})
|
||||
|
||||
if not prev_frames and not next_frames:
|
||||
continue
|
||||
|
||||
#get all the list of the frames from the different channels for each bone
|
||||
#rest of the code is only for posebones
|
||||
#creating a new dict to avoid frame_set at the same frame multiple times. It will get the matrix of multiple bones from each frame
|
||||
#This is done just to avoid extra scene evaluation using frame_set
|
||||
#adding the bone to the new flipped dict key using the frame number
|
||||
prev_frame_bones = dict()
|
||||
if prev_frame in prev_frame_bones:
|
||||
prev_frame_bones[prev_frame].append(item)
|
||||
else:
|
||||
prev_frame_bones.update({prev_frame : [item]})
|
||||
|
||||
#the bones that are on the next frame
|
||||
next_frame_bones = dict()
|
||||
if next_frame in next_frame_bones:
|
||||
next_frame_bones[next_frame].append(item)
|
||||
else:
|
||||
next_frame_bones.update({next_frame : [item]})
|
||||
|
||||
#if item.id_data.mode == 'POSE':
|
||||
|
||||
@@ -1154,7 +1164,7 @@ class InbetweenWorldMatrix(bpy.types.Operator):
|
||||
|
||||
bone = None
|
||||
|
||||
fcurves = get_fcurves_channelbag(obj, action)
|
||||
fcurves = get_fcurves(obj, action)
|
||||
for fcu in fcurves:
|
||||
#filter selected bones
|
||||
if obj.mode == 'POSE': #apply only to selected bones
|
||||
@@ -1188,7 +1198,6 @@ class InbetweenWorldMatrix(bpy.types.Operator):
|
||||
self.items_frames.update({bone : frames})
|
||||
|
||||
get_matrix_other(self, context)
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
@@ -1205,6 +1214,20 @@ class InbetweenWorldMatrix(bpy.types.Operator):
|
||||
#modal is being cancelled because of undo issue with the modal running through the property
|
||||
return {'FINISHED'}
|
||||
|
||||
if event.type in {'ESC', 'RIGHTMOUSE'} and event.value == 'RELEASE': # Cancel
|
||||
# Revert to the original matrix
|
||||
for bone, matrix in self.items_matrix_org.items():
|
||||
if bone.id_data.mode == 'POSE':
|
||||
bone.matrix = matrix
|
||||
else:
|
||||
bone.matrix_world = matrix
|
||||
|
||||
# if scene.tool_settings.use_keyframe_insert_auto:
|
||||
# add_keyframe(bone)
|
||||
context.window_manager.atb_ui['is_dragging'] = False
|
||||
self.atb['inbetween_worldmatrix'] = 0
|
||||
return {'CANCELLED'}
|
||||
|
||||
if event.value == 'RELEASE': # Stop the modal on next frame. Don't block the event since we want to exit the field dragging
|
||||
self.stop = True
|
||||
|
||||
@@ -1227,8 +1250,8 @@ class InbetweenWorldMatrix(bpy.types.Operator):
|
||||
else:
|
||||
bone.matrix_world = matrix
|
||||
|
||||
if scene.tool_settings.use_keyframe_insert_auto:
|
||||
add_keyframe(bone)
|
||||
# if scene.tool_settings.use_keyframe_insert_auto:
|
||||
# add_keyframe(bone)
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
@@ -1296,18 +1319,19 @@ class BlendToMirroModal(bpy.types.Operator):
|
||||
if self.stop:
|
||||
context.window_manager.atb_ui['is_dragging'] = False
|
||||
for bone in context.selected_pose_bones:
|
||||
if scene.tool_settings.use_keyframe_insert_auto:
|
||||
if scene.tool_settings.use_keyframe_insert_auto and self.finish == {'FINISHED'}:
|
||||
add_keyframe(bone)
|
||||
if 'matrix' in bone:
|
||||
del bone['matrix']
|
||||
self.ui['blend_mirror'] = 0
|
||||
redraw_areas(['PROPERTIES'])
|
||||
#modal is being cancelled because of undo issue with the modal running through the property
|
||||
return {'FINISHED'}
|
||||
return self.finish
|
||||
|
||||
if event.value == 'RELEASE': # Stop the modal on next frame. Don't block the event since we want to exit the field dragging
|
||||
self.stop = True
|
||||
|
||||
self.finish = {'CANCELLED'} if event.type in {'ESC', 'RIGHTMOUSE'} else {'FINISHED'}
|
||||
|
||||
for bone in context.selected_pose_bones:
|
||||
mirror_bone = find_mirror_bone(bone)
|
||||
if mirror_bone is None:
|
||||
@@ -1315,7 +1339,10 @@ class BlendToMirroModal(bpy.types.Operator):
|
||||
mirror_bone_matrix = mirror_bone.matrix_basis.copy()
|
||||
mirror_plane = Matrix.Scale(-1, 4, (1, 0, 0))
|
||||
mirrored_matrix = mirror_plane @ mirror_bone_matrix @ mirror_plane
|
||||
matrix = Matrix(bone['matrix']).lerp(mirrored_matrix, self.ui.blend_mirror)
|
||||
|
||||
bone_matrix = bone['matrix'] if bpy.app.version < (5, 0, 0) else np.array(bone['matrix']).reshape(4, 4).T
|
||||
|
||||
matrix = Matrix(bone_matrix).lerp(mirrored_matrix, self.ui.blend_mirror)
|
||||
matrix = filter_matrix_properties(context, bone.matrix_basis, matrix)
|
||||
bone.matrix_basis = matrix
|
||||
|
||||
@@ -1357,7 +1384,7 @@ class SelectKeyframesOffset(bpy.types.Operator):
|
||||
if 'keyframes_offset' in bone:
|
||||
#check if there are bones with an offset
|
||||
posemode = True
|
||||
obj.data.bones[bone.name].select = True
|
||||
TempCtrls.posebone_select(bone, True)
|
||||
if posemode and obj.mode != 'POSE':
|
||||
context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode = 'POSE')
|
||||
@@ -1365,6 +1392,25 @@ class SelectKeyframesOffset(bpy.types.Operator):
|
||||
return {'FINISHED'}
|
||||
|
||||
def get_frame_range(context, obj):
|
||||
|
||||
def selected_frame_range(frame_range):
|
||||
smartframes = set()
|
||||
|
||||
if obj.mode == 'POSE':
|
||||
bones_paths = [bone.path_from_id() for bone in context.selected_pose_bones if bone.name in obj.pose.bones]
|
||||
fcurves = get_fcurves(obj, obj.animation_data.action)
|
||||
|
||||
for fcu in fcurves:
|
||||
path = fcu.data_path.split('"].')[0] + '"]'
|
||||
if obj.mode == 'POSE' and path not in bones_paths:
|
||||
continue
|
||||
for keyframe in fcu.keyframe_points:
|
||||
smartframes.add(round(keyframe.co[0], 2))
|
||||
if keyframe.select_control_point:
|
||||
frame_range.append(round(keyframe.co[0], 2))
|
||||
|
||||
return smartframes
|
||||
|
||||
#get the frame range
|
||||
frame_range = []
|
||||
inbetweens = []
|
||||
@@ -1377,27 +1423,21 @@ def get_frame_range(context, obj):
|
||||
if atb.bake_frame_start > atb.bake_frame_end:
|
||||
atb.bake_frame_start = atb.bake_frame_end
|
||||
frame_range = list(range(atb.bake_frame_start, atb.bake_frame_end+1))
|
||||
|
||||
elif atb.range_type == 'SELECTED':
|
||||
if obj.mode == 'POSE':
|
||||
bones_paths = [bone.path_from_id() for bone in context.selected_pose_bones]
|
||||
if obj.animation_data is None:
|
||||
return frame_range
|
||||
if obj.animation_data.action is None:
|
||||
return frame_range
|
||||
|
||||
smartframes = set()
|
||||
|
||||
elif obj.animation_data is None:
|
||||
return frame_range, inbetweens
|
||||
elif obj.animation_data.action is None:
|
||||
return frame_range, inbetweens
|
||||
|
||||
elif atb.range_type == 'SELECTED_RANGE':
|
||||
# Create a list of a keyframes between the first selected keyframes and the last selected one
|
||||
selected_frame_range(frame_range)
|
||||
if frame_range:
|
||||
frame_range = list(range(int(min(frame_range)), int(max(frame_range))+1))
|
||||
|
||||
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
for fcu in fcurves:
|
||||
path = fcu.data_path.split('"].')[0] + '"]'
|
||||
if obj.mode == 'POSE' and path not in bones_paths:
|
||||
continue
|
||||
for keyframe in fcu.keyframe_points:
|
||||
smartframes.add(round(keyframe.co[0], 2))
|
||||
if keyframe.select_control_point:
|
||||
frame_range.append(round(keyframe.co[0], 2))
|
||||
|
||||
elif atb.range_type == 'SELECTED':
|
||||
|
||||
smartframes = selected_frame_range(frame_range)
|
||||
smartframes = sorted(smartframes)
|
||||
frame_range = sorted(set(frame_range))
|
||||
|
||||
@@ -1422,6 +1462,7 @@ def get_frame_range(context, obj):
|
||||
|
||||
def notification_invoke(self, context, text = 'Copy Matrix', size = 20.0):
|
||||
'''get ready and lauch the notification'''
|
||||
|
||||
wm = context.window_manager
|
||||
self.timer = wm.event_timer_add(0.1, window=context.window)
|
||||
self.no_timer = False
|
||||
@@ -1432,16 +1473,21 @@ def notification_invoke(self, context, text = 'Copy Matrix', size = 20.0):
|
||||
self.fade_out_start = 0
|
||||
self.draw_handle = bpy.types.SpaceView3D.draw_handler_add(draw_text_callback_px, (self, context), 'WINDOW', 'POST_PIXEL')
|
||||
redraw_areas(['VIEW_3D'])
|
||||
|
||||
|
||||
def update_notification(self, context, event, esc = False, fade_out = True):
|
||||
'''notify with a modal operator that the matrix was being copied'''
|
||||
|
||||
if not hasattr(self, 'draw_handle'):
|
||||
return {'FINISHED'}
|
||||
|
||||
#Quit from the notification
|
||||
if event.type in {'ESC'} or esc:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(self.draw_handle, 'WINDOW')
|
||||
context.area.tag_redraw()
|
||||
|
||||
if context.area is not None:
|
||||
context.area.tag_redraw()
|
||||
elif hasattr(self, 'area'):
|
||||
self.area.tag_redraw()
|
||||
|
||||
context.window_manager.event_timer_remove(self.timer)
|
||||
del self.draw_handle
|
||||
return {'FINISHED'}
|
||||
@@ -1470,6 +1516,14 @@ def update_notification(self, context, event, esc = False, fade_out = True):
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def compatible_rotation(bone, bones_prevrot):
|
||||
'''Make sure the bone is not flipping and is compatible with previous frame rotation'''
|
||||
rotation_channel = rot_mode_to_channel(bone.rotation_mode)
|
||||
if bone in bones_prevrot:
|
||||
rot = getattr(bone, rotation_channel)
|
||||
rot.make_compatible(bones_prevrot[bone])
|
||||
bones_prevrot[bone] = getattr(bone, rotation_channel).copy()
|
||||
|
||||
class CopyMatrix(bpy.types.Operator):
|
||||
"""Copy the matrix of the selection and store it"""
|
||||
bl_idname = "anim.copy_matrix"
|
||||
@@ -1546,6 +1600,7 @@ class PasteMatrix(bpy.types.Operator):
|
||||
frame_current_final = scene.frame_current_final
|
||||
|
||||
bones_matrices = dict()
|
||||
bones_prevrot = dict()
|
||||
constrained = set()
|
||||
#constraint evaluation is
|
||||
# con.target.matrix_world.copy() @ con.inverse_matrix @ basis
|
||||
@@ -1564,6 +1619,11 @@ class PasteMatrix(bpy.types.Operator):
|
||||
for obj in context.selected_objects:
|
||||
frame_range, inbetweens = get_frame_range(context, obj)
|
||||
fcu_inbetweens = dict()
|
||||
|
||||
# Checking for locked fcurves before iterating
|
||||
locked_fcus = get_locked_fcus(obj)
|
||||
|
||||
|
||||
for frame in sorted(frame_range + inbetweens):
|
||||
scene.frame_set(int(frame), subframe = frame % 1)
|
||||
if obj.mode == 'POSE':
|
||||
@@ -1588,7 +1648,7 @@ class PasteMatrix(bpy.types.Operator):
|
||||
|
||||
#adding the keyframes
|
||||
for bone in bones_matrices.keys():
|
||||
paste_keyframes_get_inbetweens(scene, bone, inbetweens, frame, frame_range, fcu_inbetweens)
|
||||
paste_keyframes_get_inbetweens(scene, bone, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot)
|
||||
|
||||
else:
|
||||
target = obj
|
||||
@@ -1603,10 +1663,13 @@ class PasteMatrix(bpy.types.Operator):
|
||||
matrix_copied = reverse_constraint_offset(target.matrix_world, matrix_copied)
|
||||
target.matrix_world = filter_matrix_properties(context, target.matrix_world, matrix_copied)
|
||||
|
||||
paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens)
|
||||
paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot)
|
||||
|
||||
if inbetweens and len(frame_range) > 1:
|
||||
add_interpolations(fcu_inbetweens.keys(), fcu_inbetweens, frames = frame_range[:-1])
|
||||
if len(frame_range) > 1:
|
||||
if inbetweens:
|
||||
add_interpolations(fcu_inbetweens.keys(), fcu_inbetweens, frames = frame_range[:-1])
|
||||
else:
|
||||
set_auto_handles(fcu_inbetweens.keys(), frame_range)
|
||||
|
||||
if context.scene.frame_current_final != frame_current_final:
|
||||
scene.frame_current = int(frame_current_final)
|
||||
@@ -1678,7 +1741,8 @@ class CopyRelativeMatrix(bpy.types.Operator):
|
||||
if obj.type == 'ARMATURE':
|
||||
#Get the distance from the selected bones
|
||||
for bone_relative in obj.pose.bones:
|
||||
if not bone_relative.bone.select:
|
||||
selected = bone_relative.bone.select if bpy.app.version < (5, 0, 0) else bone_relative.select
|
||||
if not selected:
|
||||
continue
|
||||
if bone_relative == source_active:
|
||||
continue
|
||||
@@ -1704,14 +1768,18 @@ class CopyRelativeMatrix(bpy.types.Operator):
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens):
|
||||
def paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot):
|
||||
|
||||
#Make sure the rotation is compatible with the previous rotation
|
||||
compatible_rotation(target, bones_prevrot)
|
||||
|
||||
#if autokey is turned on then add a keyframe
|
||||
if (scene.tool_settings.use_keyframe_insert_auto or scene.animtoolbox.range_type != 'CURRENT') and 'target' in locals():
|
||||
if frame not in inbetweens:
|
||||
add_keyframe(target)
|
||||
add_keyframe(target, locked_fcus)
|
||||
#store the inbetween values
|
||||
elif len(frame_range) > 1:
|
||||
fcu_inbetweens = get_fcu_inbetweens(target, frame, fcu_inbetweens)
|
||||
if len(frame_range) > 1:
|
||||
fcu_inbetweens = get_fcu_inbetweens(target, frame, fcu_inbetweens, inbetweens)
|
||||
|
||||
def reverse_bone_constraints(context, bone, matrix_copied):
|
||||
|
||||
@@ -1865,6 +1933,8 @@ class PasteRelativeMatrix(bpy.types.Operator):
|
||||
source_bone = None
|
||||
source_obj = None
|
||||
bones_matrices = dict()
|
||||
# Store previous rotations to be compared and avoid flips
|
||||
bones_prevrot = dict()
|
||||
constrained = set()
|
||||
#get the source matrix
|
||||
if 'source_rig_name' in globals():
|
||||
@@ -1890,6 +1960,8 @@ class PasteRelativeMatrix(bpy.types.Operator):
|
||||
frame_range, inbetweens = get_frame_range(context, obj)
|
||||
fcu_inbetweens = dict()
|
||||
|
||||
# Checking for locked fcurves before iterating
|
||||
locked_fcus = get_locked_fcus(obj)
|
||||
|
||||
for frame in sorted(frame_range+inbetweens):
|
||||
scene.frame_set(int(frame))
|
||||
@@ -1925,7 +1997,7 @@ class PasteRelativeMatrix(bpy.types.Operator):
|
||||
paste_bones_matrices(bones_matrices, constrained)
|
||||
|
||||
for bone in bones_matrices.keys():
|
||||
paste_keyframes_get_inbetweens(scene, bone, inbetweens, frame, frame_range, fcu_inbetweens)
|
||||
paste_keyframes_get_inbetweens(scene, bone, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot)
|
||||
|
||||
else:
|
||||
target = obj
|
||||
@@ -1950,11 +2022,14 @@ class PasteRelativeMatrix(bpy.types.Operator):
|
||||
matrix_new = reverse_constraint_offset(target.matrix_world, matrix_new)
|
||||
target.matrix_world = filter_matrix_properties(context, target.matrix_world, matrix_new)
|
||||
|
||||
paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens)
|
||||
|
||||
if inbetweens and len(frame_range) > 1:
|
||||
add_interpolations(fcu_inbetweens.keys(), fcu_inbetweens, frames = frame_range[:-1])
|
||||
paste_keyframes_get_inbetweens(scene, target, inbetweens, frame, frame_range, fcu_inbetweens, locked_fcus, bones_prevrot)
|
||||
|
||||
if len(frame_range) > 1:
|
||||
if inbetweens:
|
||||
add_interpolations(fcu_inbetweens.keys(), fcu_inbetweens, frames = frame_range[:-1])
|
||||
else:
|
||||
set_auto_handles(fcu_inbetweens.keys(), frame_range)
|
||||
|
||||
#return to the original frame in case of using frame range
|
||||
if context.scene.frame_current != frame_current:
|
||||
scene.frame_current = frame_current
|
||||
@@ -2005,8 +2080,7 @@ def sharekeys_add_missing_fcurves(obj, attr_index, fcurves):
|
||||
action = obj.id_data.animation_data.action
|
||||
|
||||
#Get the container, either action of channelbag because of adding groups
|
||||
fcurves_container = get_fcurves_container(obj, action)
|
||||
|
||||
channelbag = get_channelbag(obj.id_data, action)
|
||||
if 'rotation' in attr:
|
||||
#converting the rotation depending on the rotation mode
|
||||
mode = 'euler' if len(obj.rotation_mode) == 3 else obj.rotation_mode.lower()
|
||||
@@ -2019,14 +2093,14 @@ def sharekeys_add_missing_fcurves(obj, attr_index, fcurves):
|
||||
continue
|
||||
elif rot != 'rotation_euler' and (path, 3) not in datapaths_arrays:
|
||||
#adding an extra curve for quaternion or axis_angle
|
||||
extra_fcu = fcurves_container.fcurves.new(data_path = path, index = 3, action_group = group)
|
||||
extra_fcu = channelbag.fcurves.new(data_path = path, index = 3, action_group = group)
|
||||
fcurves.append(extra_fcu)
|
||||
datapaths_arrays.add((extra_fcu.data_path, extra_fcu.array_index))
|
||||
|
||||
if (path, index) in datapaths_arrays:
|
||||
continue
|
||||
|
||||
fcu = fcurves_container.fcurves.new(data_path = path, index = index)
|
||||
fcu = channelbag.fcurves.new(data_path = path, index = index)
|
||||
add_group_to_fcurve(obj.id_data, fcu, group)
|
||||
fcurves.append(fcu)
|
||||
datapaths_arrays.add((fcu.data_path, fcu.array_index))
|
||||
@@ -2102,7 +2176,7 @@ class ShareKeys(bpy.types.Operator):
|
||||
def get_fcurves_set(self, obj_actions):
|
||||
fcurves = set()
|
||||
for obj, action in obj_actions.items():
|
||||
fcurves = fcurves.union(set(get_fcurves_channelbag(obj, action)))
|
||||
fcurves = fcurves.union(set(get_fcurves(obj, action)))
|
||||
return fcurves
|
||||
|
||||
def add_get_action(self, obj):
|
||||
@@ -2141,7 +2215,7 @@ class ShareKeys(bpy.types.Operator):
|
||||
continue
|
||||
#if there is no animation data or an action then create it
|
||||
action = self.add_get_action(obj)
|
||||
fcurves = get_fcurves_channelbag(obj, action)
|
||||
fcurves = get_fcurves(obj, action)
|
||||
frames, attr_index_obj = get_fcurves_frames([obj], fcurves, all_fcurves, frames, attr_index_obj)
|
||||
|
||||
if obj.data.animation_data is None:
|
||||
@@ -2150,7 +2224,7 @@ class ShareKeys(bpy.types.Operator):
|
||||
continue
|
||||
#add the type of object if the data is also animated
|
||||
actions_data_types.add(obj.type)
|
||||
fcurves = get_fcurves_channelbag(obj.data, obj.data.animation_data.action)
|
||||
fcurves = get_fcurves(obj.data, obj.data.animation_data.action)
|
||||
frames, attr_index_obj = get_fcurves_frames([obj], fcurves, all_fcurves, frames, attr_index_obj)
|
||||
|
||||
#Getting all the available transforms and array index
|
||||
@@ -2333,6 +2407,15 @@ def add_inbetweens(smartframes):
|
||||
# all_frames = sorted(self.frames + self.inbetweens)
|
||||
return inbetweens
|
||||
|
||||
def set_auto_handles(fcurves, frame_range):
|
||||
|
||||
for fcu in fcurves:
|
||||
for keyframe in fcu.keyframe_points:
|
||||
if keyframe.co[0] in frame_range:
|
||||
keyframe.handle_right_type = 'AUTO'
|
||||
keyframe.handle_left_type = 'AUTO'
|
||||
fcu.update()
|
||||
|
||||
def add_interpolations(fcurves, fcu_inbetweens, frames = None):
|
||||
|
||||
inbetweens = sorted(set([frame for inbetweens in fcu_inbetweens.values() for frame in inbetweens.keys()]))
|
||||
@@ -2419,7 +2502,7 @@ class FindRotationMode(bpy.types.Operator):
|
||||
rot_mode = posebone.rotation_mode
|
||||
obj = posebone.id_data
|
||||
# fcurves = obj.animation_data.action.fcurves
|
||||
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
fcurves = get_fcurves(obj, obj.animation_data.action)
|
||||
new_rot_eulers = []
|
||||
|
||||
#get the values from the original rotation
|
||||
@@ -2533,7 +2616,7 @@ class ConvertRotationMode(bpy.types.Operator):
|
||||
def switch_rot_mode_keyframes(self, obj, posebone):
|
||||
# Switching any rotation mode keyframes to the new rotation mode value using to_rot_mode_index
|
||||
bone_path = posebone.path_from_id() + '.' if type(posebone) == bpy.types.PoseBone else ''
|
||||
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
fcurves = get_fcurves(obj, obj.animation_data.action)
|
||||
fcu_rotation_mode = fcurves.find(data_path = bone_path + 'rotation_mode', index = 0)
|
||||
if fcu_rotation_mode:
|
||||
for keyframe in fcu_rotation_mode.keyframe_points:
|
||||
@@ -2556,7 +2639,7 @@ class ConvertRotationMode(bpy.types.Operator):
|
||||
obj = posebone.id_data
|
||||
# fcurves = obj.animation_data.action.fcurves
|
||||
action = obj.animation_data.action
|
||||
fcurves = get_fcurves_channelbag(obj, action)
|
||||
fcurves = get_fcurves(obj, action)
|
||||
fcu_inbetweens = dict()
|
||||
fcu_keyframes = dict()
|
||||
#get the transform from the original rot mode
|
||||
@@ -2858,6 +2941,10 @@ def get_obj_slot(obj, action):
|
||||
if obj in slot.users():
|
||||
return slot
|
||||
|
||||
# If no slot with the users was found then get the next available slot
|
||||
if obj.animation_data.action_suitable_slots:
|
||||
return next(iter(obj.animation_data.action_suitable_slots))
|
||||
|
||||
return None
|
||||
|
||||
def get_all_fcurves(action):
|
||||
@@ -2868,21 +2955,48 @@ def get_all_fcurves(action):
|
||||
for strip in layer.strips:
|
||||
for channelbag in strip.channelbags:
|
||||
yield from channelbag.fcurves
|
||||
|
||||
def get_fcurves(obj, action: bpy.types.Action):
|
||||
|
||||
if hasattr(action, 'layers'):
|
||||
channelbag = get_channelbag(obj, action)
|
||||
|
||||
return channelbag.fcurves
|
||||
|
||||
# action.fcurves not available anymore from Blender 5.0
|
||||
if hasattr(action, 'fcurves'):
|
||||
return action.fcurves
|
||||
|
||||
def get_fcurves_channelbag(obj, action: bpy.types.Action):
|
||||
return []
|
||||
|
||||
def get_channelbag(obj, action: bpy.types.Action):
|
||||
'''Getting the container of the fcurves, either the action or channelbag
|
||||
Using this when adding a new group to the action'''
|
||||
|
||||
if hasattr(action, 'layers'):
|
||||
slot = get_obj_slot(obj, action)
|
||||
if not slot:
|
||||
return action.fcurves
|
||||
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
|
||||
if channelbag:
|
||||
return channelbag.fcurves
|
||||
|
||||
return action.fcurves
|
||||
channelbag = None
|
||||
if slot:
|
||||
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
|
||||
else:
|
||||
# If a signed slot was not found then add a new one
|
||||
slot = add_action_slot(obj, action)
|
||||
obj.animation_data.action_slot = slot
|
||||
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
|
||||
|
||||
if channelbag is None:
|
||||
# action_ensure_channelbag_for_slot works only from Blender 5
|
||||
if hasattr(anim_utils, 'action_ensure_channelbag_for_slot'):
|
||||
channelbag = anim_utils.action_ensure_channelbag_for_slot(action, slot)
|
||||
else:
|
||||
channelbag = add_channelbag(obj, action)
|
||||
|
||||
return channelbag
|
||||
else:
|
||||
return action
|
||||
|
||||
def add_channelbag(obj, action):
|
||||
|
||||
'''Old might need to remove'''
|
||||
if not hasattr(action, 'layers'):
|
||||
return
|
||||
slot = get_obj_slot(obj, action)
|
||||
@@ -2904,26 +3018,12 @@ def add_channelbag(obj, action):
|
||||
|
||||
return channelbag
|
||||
|
||||
def get_fcurves_container(obj: bpy.types.Object, action: bpy.types.Action):
|
||||
'''Getting the container of the fcurves, either the action or channelbag
|
||||
Using this when adding a new group to the action'''
|
||||
|
||||
if hasattr(action, 'layers'):
|
||||
slot = get_obj_slot(obj, action)
|
||||
if not slot:
|
||||
return action
|
||||
channelbag = anim_utils.action_get_channelbag_for_slot(action, slot)
|
||||
return channelbag
|
||||
|
||||
else:
|
||||
return action
|
||||
|
||||
def add_group_to_fcurve(obj, fcu, groupname):
|
||||
'''Add an fcurve group based on the fcurve container, either action or channelbag'''
|
||||
|
||||
action = fcu.id_data
|
||||
#get the container which is either a channelbag or a group
|
||||
fcu_container = get_fcurves_container(obj, action)
|
||||
fcu_container = get_channelbag(obj, action)
|
||||
group = fcu_container.groups.get(groupname)
|
||||
if group is None:
|
||||
group = fcu_container.groups.new(groupname)
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
bl_info = {
|
||||
"name": "AnimToolBox",
|
||||
"author": "Tal Hershkovich",
|
||||
"version" : (0, 0, 8),
|
||||
"version" : (0, 2, 3),
|
||||
"blender" : (3, 2, 0),
|
||||
"location": "View3D - Properties - Animation Panel",
|
||||
"description": "A set of animation tools",
|
||||
@@ -119,6 +119,11 @@ class TempCtrlsSceneSettings(bpy.types.PropertyGroup):
|
||||
add_ik_ctrl: bpy.props.BoolProperty(name = 'Add an Extra IK Ctrl Bone', description = "Adds an extra bone ctrl as the ik ctrl", default = False, update = TempCtrls.add_ik_prop)
|
||||
pole_target: bpy.props.BoolProperty(name = 'Add Pole Target', description = "Adding Pole Target to the IK Chain", default = True, update = TempCtrls.pole_prop)
|
||||
pole_offset: bpy.props.FloatProperty(name="Offset", description="Offset the bone in the axis direction", default=1.0, update = TempCtrls.pole_offset)
|
||||
pole_axis: bpy.props.EnumProperty(name = 'Axis Direction', description="Which direction should the pole bone point", default = 4, update = TempCtrls.update_axis_prop,
|
||||
items = [('+X', '+X','Target Bone the +X Axis', 0), ('+Y', '+Y', 'Target Bone the +Y Axis', 1),
|
||||
('-X', '-X','Target Bone the -X Axis', 2), ('-Y', '-Y', 'Target Bone the -Y Axis', 3),
|
||||
('AUTO', 'Auto','Select pole bone direction based on the current pose', 4)])
|
||||
|
||||
child: bpy.props.BoolProperty(name = 'Add extra child Ctrls', description = "Add an child control for an overlay control", default = False, update = TempCtrls.child_prop)
|
||||
orientation: bpy.props.BoolProperty(name = 'Use World Space Orientation', description = "Orient the bones to world space instead of to the original bones", default = True)
|
||||
|
||||
@@ -252,8 +257,9 @@ class AnimToolBoxGlobalSettings(bpy.types.PropertyGroup):
|
||||
#Copy/Pase Matrix
|
||||
range_type: bpy.props.EnumProperty(name = 'Paste to Frames', description="Paste to current frame or a range of frames.", update = Tools.bake_range_type,
|
||||
items = [('CURRENT', 'Current Frame','Paste Matrix to only current frame', 0),
|
||||
('SELECTED', 'Selected Keyframe','Paste Matrix to only selected keyframes', 1),
|
||||
('RANGE', 'Frame Range','Paste Matrix to a Frame Range', 2)])
|
||||
('SELECTED', 'Selected Keyframes','Paste Matrix only to the selected keyframes', 1),
|
||||
('RANGE', 'Frame Range','Paste Matrix to a Custom Frame Range', 2),
|
||||
('SELECTED_RANGE', 'Selected Keyframes Range','Paste Matrix to every frame within a selected keyframe range', 3)])
|
||||
bake_frame_start: bpy.props.IntProperty(name = "Bake Frame Start", description = "Define the start frame to paste the matrix", min = 0, update = Tools.bake_frame_start_limit)
|
||||
bake_frame_end: bpy.props.IntProperty(name = "Bake Frame End", description = "Define the end frame to paste the matrix", min = 0, update = Tools.bake_frame_end_limit)
|
||||
|
||||
@@ -298,6 +304,11 @@ class AnimToolBoxPreferences(bpy.types.AddonPreferences):
|
||||
mp_handle_selection_color: bpy.props.FloatVectorProperty(name="Handles Selection", subtype='COLOR', default=(0.8, 0.65, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Handles selection color")
|
||||
mp_key_selection_color: bpy.props.FloatVectorProperty(name="Keyframe Selection", subtype='COLOR', default=(0.8, 0.8, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Keyframe selection color")
|
||||
|
||||
# Motion Path brush colors
|
||||
mp_brush_disabled: bpy.props.FloatVectorProperty(name="Disabled", subtype='COLOR', default=(0.7, 0.7, 0.9, 0.75), size=4, min=0.0, max=1.0, description="Brush is disabled and have no influence")
|
||||
mp_brush_hover: bpy.props.FloatVectorProperty(name="Hover", subtype='COLOR', default=(0.8, 0.7, 0.2, 0.8), size=4, min=0.0, max=1.0, description="Brush is hovering and can be activated")
|
||||
mp_brush_active: bpy.props.FloatVectorProperty(name="Active", subtype='COLOR', default=(1.0, 1.0, 1.0, 0.9), size=4, min=0.0, max=1.0, description="Brush is activated")
|
||||
|
||||
# addon updater preferences from `__init__`, be sure to copy all of them
|
||||
auto_check_update: bpy.props.BoolProperty(
|
||||
name = "Auto-check for Update",
|
||||
@@ -371,6 +382,12 @@ class AnimToolBoxPreferences(bpy.types.AddonPreferences):
|
||||
row.prop(self, 'mp_hover_color')
|
||||
row.prop(self, 'mp_key_selection_color')
|
||||
row.prop(self, 'mp_handle_selection_color')
|
||||
col = box.row()
|
||||
col.label(text = 'Brush Colors')
|
||||
row = box.row()
|
||||
row.prop(self, 'mp_brush_disabled')
|
||||
row.prop(self, 'mp_brush_hover')
|
||||
row.prop(self, 'mp_brush_active')
|
||||
|
||||
layout.separator()
|
||||
col = layout.column()
|
||||
@@ -392,6 +409,8 @@ def loadanimtoolbox_pre(self, context):
|
||||
if 'mp_dh' in dns:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
|
||||
bpy.app.driver_namespace.pop('mp_dh')
|
||||
emp.remove_draw_circle()
|
||||
|
||||
bpy.context.scene.emp.selected_keyframes = '{}'
|
||||
|
||||
if 'markers_retimer_dh' in dns:
|
||||
@@ -401,20 +420,38 @@ def loadanimtoolbox_pre(self, context):
|
||||
#remove the motion path app handler if it's still inside
|
||||
if emp.mp_value_update in bpy.app.handlers.depsgraph_update_post:
|
||||
bpy.app.handlers.depsgraph_update_post.remove(emp.mp_value_update)
|
||||
if emp.mp_frame_change in bpy.app.handlers.frame_change_post:
|
||||
bpy.app.handlers.frame_change_post.remove(emp.mp_frame_change)
|
||||
if emp.mp_undo_update in bpy.app.handlers.undo_pre:
|
||||
bpy.app.handlers.undo_pre.remove(emp.mp_undo_update)
|
||||
|
||||
@persistent
|
||||
def loadanimtoolbox_post(self, context):
|
||||
scene = bpy.context.scene
|
||||
|
||||
def remove_markers(prop, marker_type, names):
|
||||
'''Remove Frame Range or Bake Range Markers if they were turned on'''
|
||||
if not getattr(prop, marker_type):
|
||||
return
|
||||
setattr(prop, marker_type, False)
|
||||
for marker_name in names:
|
||||
if marker_name in scene.timeline_markers:
|
||||
scene.timeline_markers.remove(scene.timeline_markers[marker_name])
|
||||
|
||||
dns = bpy.app.driver_namespace
|
||||
if scene.animtoolbox.isolate_pose_mode:
|
||||
if Display.isolate_pose_mode not in bpy.app.handlers.depsgraph_update_pre:
|
||||
bpy.app.handlers.depsgraph_update_pre.append(Display.isolate_pose_mode)
|
||||
|
||||
if scene.emp.motion_path:
|
||||
# In case motion path was active in the last save, make sure atb mp name property
|
||||
# Is not included inside the bones, for a proper cleanup
|
||||
for obj in bpy.context.view_layer.objects:
|
||||
if "atb_mp_name" in obj:
|
||||
del obj["atb_mp_name"]
|
||||
if obj.type == 'ARMATURE':
|
||||
for pbone in obj.pose.bones:
|
||||
if "atb_mp_name" in pbone:
|
||||
del pbone["atb_mp_name"]
|
||||
|
||||
scene.emp.motion_path = False
|
||||
bpy.context.workspace.status_text_set(None)
|
||||
if 'mp_dh' in dns:
|
||||
@@ -424,6 +461,10 @@ def loadanimtoolbox_post(self, context):
|
||||
# Because it is used for undo
|
||||
bpy.context.scene.emp.selected_keyframes = '{}'
|
||||
|
||||
remove_markers(scene.animtoolbox, 'marker_frame_range', {'Frame Start', 'Frame End'})
|
||||
remove_markers(scene.animtoolbox, 'bake_frame_range', {'Bake Start', 'Bake End'})
|
||||
remove_markers(scene.emp, 'marker_frame_range', {'MotionPath Start', 'MotionPath End'})
|
||||
|
||||
Tools.selection_order(self, context)
|
||||
|
||||
classes = (TempCtrlsItems, TempCtrlsOrgIds, TempCtrlsObjectSetups, TempCtrlsSceneSettings,TempCtrlsBoneSettings, MultikeyProperties,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"last_check": "2025-09-23 10:58:29.201165",
|
||||
"backup_date": "September-23-2025",
|
||||
"last_check": "2026-02-09 15:54:54.419194",
|
||||
"backup_date": "February-9-2026",
|
||||
"update_ready": false,
|
||||
"ignore": false,
|
||||
"just_restored": false,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1803,7 +1803,7 @@ def reorder_bones_matrices(bones_matrices, constrained):
|
||||
|
||||
return re_bones_matrices
|
||||
|
||||
def paste_bone_matrix(bone, matrix_copied, constrained, x_filter = True):
|
||||
def paste_bone_matrix(bone, matrix_copied, constrained, bones = {}, x_filter = True):
|
||||
#running again separatly in case the bones are in a hierarchy and influencing each other
|
||||
# for bone, matrix_copied in bones_matrices.items():
|
||||
# Determine whether to use bone.matrix or bone.matrix_world
|
||||
@@ -1818,10 +1818,14 @@ def paste_bone_matrix(bone, matrix_copied, constrained, x_filter = True):
|
||||
if x_filter : matrix_copied = filter_matrix_properties(context, getattr(bone, matrix_attr), matrix_copied)
|
||||
# bone.matrix = bone.id_data.matrix_world.inverted() @ matrix_copied
|
||||
setattr(bone, matrix_attr, matrix_copied) # bone.id_data.matrix_world.inverted() @
|
||||
|
||||
children = set(bone.children_recursive).intersection(bones)
|
||||
if children or bone in constrained:
|
||||
# print(f'found children {[child.name for child in children]} in bone {bone.name}' )
|
||||
context.view_layer.update()
|
||||
#Check if the bone has constrainsts on it that need extra iteration
|
||||
if bone not in constrained:
|
||||
# filter_matrix_properties(context, bone.matrix, matrix_copied)
|
||||
return
|
||||
context.view_layer.update()
|
||||
|
||||
matrix_copied = reverse_bone_constraints(context, bone, matrix_copied)
|
||||
if x_filter : matrix_copied = filter_matrix_properties(context, bone.matrix, matrix_copied)
|
||||
@@ -1832,8 +1836,12 @@ def paste_bone_matrix(bone, matrix_copied, constrained, x_filter = True):
|
||||
|
||||
def paste_bones_matrices(bones_matrices, constrained, x_filter = True):
|
||||
#running again separatly in case the bones are in a hierarchy and influencing each other
|
||||
pasted_bones = set()
|
||||
for bone, matrix_copied in bones_matrices.items():
|
||||
paste_bone_matrix(bone, matrix_copied, constrained, x_filter)
|
||||
#Get the rest of the bones to check if they are children of the current bone
|
||||
bones = set(bones_matrices.keys()).difference(pasted_bones)
|
||||
paste_bone_matrix(bone, matrix_copied, constrained, bones, x_filter)
|
||||
pasted_bones.add(bone)
|
||||
|
||||
class PasteRelativeMatrix(bpy.types.Operator):
|
||||
"""paste the relative matrix of the selection"""
|
||||
@@ -2522,7 +2530,15 @@ class ConvertRotationMode(bpy.types.Operator):
|
||||
# @classmethod
|
||||
# def poll(cls, context):
|
||||
# return context.object.type == 'ARMATURE'
|
||||
|
||||
def switch_rot_mode_keyframes(self, obj, posebone):
|
||||
# Switching any rotation mode keyframes to the new rotation mode value using to_rot_mode_index
|
||||
bone_path = posebone.path_from_id() + '.' if type(posebone) == bpy.types.PoseBone else ''
|
||||
fcurves = get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
fcu_rotation_mode = fcurves.find(data_path = bone_path + 'rotation_mode', index = 0)
|
||||
if fcu_rotation_mode:
|
||||
for keyframe in fcu_rotation_mode.keyframe_points:
|
||||
keyframe.co[1] = self.to_rot_mode_index
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
selected_bones = context.selected_pose_bones
|
||||
@@ -2530,7 +2546,8 @@ class ConvertRotationMode(bpy.types.Operator):
|
||||
|
||||
to_rot_mode = scene.animtoolbox.rotation_mode
|
||||
to_rot_mode_fcu = rot_mode_to_channel(to_rot_mode)
|
||||
|
||||
#Getting the index of the rotation mode we want to convert to
|
||||
self.to_rot_mode_index = list(scene.animtoolbox.bl_rna.properties['rotation_mode'].enum_items.keys()).index(to_rot_mode)
|
||||
#get the keyframes from the bones
|
||||
for posebone in selected_bones:
|
||||
|
||||
@@ -2548,16 +2565,21 @@ class ConvertRotationMode(bpy.types.Operator):
|
||||
|
||||
keyframes = emp.get_bone_keyframes(posebone, transform)
|
||||
|
||||
#get all interpolations and handle types of the keyframes
|
||||
handle_types = emp.get_bone_keyframes(posebone, transform, property = 'interpolation')
|
||||
interpolations = handle_types[::3]
|
||||
handle_left_type = handle_types[1::3]
|
||||
handle_right_type = handle_types[2::3]
|
||||
|
||||
# rotation_mode_keyframes = emp.get_bone_keyframes(posebone, 'rotation_mode')
|
||||
self.switch_rot_mode_keyframes(obj, posebone)
|
||||
|
||||
inbetweens = []
|
||||
smartframes = sorted(set(map(lambda x: round(x, 2), keyframes[::2])))
|
||||
inbetweens = add_inbetweens(smartframes)
|
||||
all_frames = sorted(smartframes + inbetweens)
|
||||
#get all interpolations and handle types of the keyframes
|
||||
if len(smartframes) > 1:
|
||||
handle_types = emp.get_bone_keyframes(posebone, transform, property = 'interpolation')
|
||||
interpolations = handle_types[::3]
|
||||
handle_left_type = handle_types[1::3]
|
||||
handle_right_type = handle_types[2::3]
|
||||
|
||||
inbetweens = add_inbetweens(smartframes)
|
||||
|
||||
all_frames = sorted(smartframes + inbetweens)
|
||||
new_path = posebone.path_from_id() + '.' + to_rot_mode_fcu
|
||||
|
||||
#define array length
|
||||
@@ -2632,13 +2654,15 @@ class ConvertRotationMode(bpy.types.Operator):
|
||||
keyframe = new_fcu.keyframe_points[-1]
|
||||
|
||||
keyframe.co = (frame, fcu_keyframes[new_fcu][frame])
|
||||
keyframe.interpolation = interpolations[frame_index]
|
||||
keyframe.handle_left_type = handle_left_type[frame_index]
|
||||
keyframe.handle_right_type = handle_right_type[frame_index]
|
||||
if inbetweens:
|
||||
keyframe.interpolation = interpolations[frame_index]
|
||||
keyframe.handle_left_type = handle_left_type[frame_index]
|
||||
keyframe.handle_right_type = handle_right_type[frame_index]
|
||||
|
||||
new_fcu.update()
|
||||
|
||||
add_interpolations(new_fcurves, fcu_inbetweens)
|
||||
if inbetweens:
|
||||
add_interpolations(new_fcurves, fcu_inbetweens)
|
||||
|
||||
posebone.rotation_mode = to_rot_mode
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
bl_info = {
|
||||
"name": "AnimToolBox",
|
||||
"author": "Tal Hershkovich",
|
||||
"version" : (0, 0, 7, 3),
|
||||
"version" : (0, 0, 8),
|
||||
"blender" : (3, 2, 0),
|
||||
"location": "View3D - Properties - Animation Panel",
|
||||
"description": "A set of animation tools",
|
||||
@@ -133,15 +133,16 @@ class TempCtrlsBoneSettings(bpy.types.PropertyGroup):
|
||||
setup: bpy.props.EnumProperty(name = 'Setup Type', description="Describes what kind of setup the bone is part of", override = {'LIBRARY_OVERRIDABLE'},
|
||||
items = [('NONE', 'No Setup','No Setup Applied', 0),
|
||||
('WORLDSPACE', 'World Space Ctrl','World Space Ctrl setup', 1),
|
||||
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 2),
|
||||
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 3),
|
||||
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 4),
|
||||
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 5),
|
||||
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 6),
|
||||
('ROOT', 'Root', 'Root Ctrl for all the setups', 7),
|
||||
('EMPTY', 'Root', 'Root Ctrl for all the setups', 8),
|
||||
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 9),
|
||||
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 10)])
|
||||
('WORLDSPACE_CURSOR', 'World Space Cursor Ctrl','World Space Cursor pivot', 2),
|
||||
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 3),
|
||||
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 4),
|
||||
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 5),
|
||||
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 6),
|
||||
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 7),
|
||||
('ROOT', 'Root', 'Root Ctrl for all the setups', 8),
|
||||
('EMPTY', 'Root', 'Root Ctrl for all the setups', 9),
|
||||
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 10),
|
||||
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 11)])
|
||||
|
||||
#using org mostly to decide if it needs a custom shape
|
||||
org: bpy.props.EnumProperty(name = 'Org Type', description="Describes what if the function of the bone", override = {'LIBRARY_OVERRIDABLE'},
|
||||
@@ -152,21 +153,29 @@ class TempCtrlsBoneSettings(bpy.types.PropertyGroup):
|
||||
|
||||
shape: bpy.props.BoolProperty(name = "Apply shape", description = "Mark if the bone needs a shape", default = False, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
class TempCtrlsOrgIds(bpy.types.PropertyGroup):
|
||||
# A collection of the org ids used in each setup. Org ID is the direct connection
|
||||
# between original bones and ctrls
|
||||
pass
|
||||
|
||||
class TempCtrlsObjectSetups(bpy.types.PropertyGroup):
|
||||
#located at obj.animtoolbox.ctrl_setups
|
||||
#name: using string of the id
|
||||
setup: bpy.props.EnumProperty(name = 'Setup Type', description="Describes what kind of setup the bone is part of",
|
||||
items = [('NONE', 'No Setup','No Setup Applied', 0),
|
||||
('WORLDSPACE', 'World Space Ctrl','World Space Ctrl setup', 1),
|
||||
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 2),
|
||||
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 3),
|
||||
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 4),
|
||||
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 5),
|
||||
('ROOT', 'Root', 'Root Ctrl for all the setups', 6),
|
||||
('EMPTY', 'Root', 'Root Ctrl for all the setups', 8),
|
||||
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 9),
|
||||
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 10),
|
||||
])
|
||||
('WORLDSPACE_CURSOR', 'World Space Cursor Ctrl','World Space Cursor pivot', 2),
|
||||
('TEMPFK', 'Temporary FK setup','Temporary FK chain setup', 3),
|
||||
('TEMPFK_FLIP', 'Temporary flipped FK setup','Temporary flipped FK chain setup', 4),
|
||||
('TEMPIK', 'Temporary IK setup','Temporary IK setup', 5),
|
||||
('POLE', 'Temporary IK Pole setup','Temporary IK Pole', 6),
|
||||
('PARENTCTRL', 'Parent Ctrl from cursor setup','Parent Ctrl from cursor setup', 7),
|
||||
('ROOT', 'Root', 'Root Ctrl for all the setups', 8),
|
||||
('EMPTY', 'Root', 'Root Ctrl for all the setups', 9),
|
||||
('TRACK_TO', 'Track To','World Space Track to Ctrl setup', 10),
|
||||
('TRACK_TO_EMPTY', 'Track To Empty','World Space Track to Empty Ctrl setup', 11)])
|
||||
|
||||
org_ids: bpy.props.CollectionProperty(type = TempCtrlsOrgIds)
|
||||
|
||||
class MultikeyProperties(bpy.types.PropertyGroup):
|
||||
|
||||
@@ -238,37 +247,6 @@ class AnimToolBoxGlobalSettings(bpy.types.PropertyGroup):
|
||||
#Blendings
|
||||
inbetweener : bpy.props.FloatProperty(name='Inbetween Keyframe', description="Adds an inbetween Keyframe between the Layer's neighbor keyframes", soft_min = -1, soft_max = 1, default=0.0, options = set(), update = Tools.add_inbetween_key, override = {'LIBRARY_OVERRIDABLE'})
|
||||
|
||||
motion_path: bpy.props.BoolProperty(name = "Motion Path", description = "Flag when Motion Path is on", default = False, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_settings: bpy.props.BoolProperty(name = "Motion Path Settings", description = "Open the settings Menu", default = False, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_keyframe_scale: bpy.props.FloatProperty(name = "Scale Selecgted Keyframes Bounding Box", description = "Change the scale of the bounding box around the selected keyframes ", default = 0.1, step = 0.1, precision = 3)
|
||||
mp_color_before: bpy.props.FloatVectorProperty(name="Motion Path Before Color", subtype='COLOR', default=(1.0, 0.0, 0.0), min=0.0, max=1.0, description="Motion path color before the current frame")
|
||||
mp_color_after: bpy.props.FloatVectorProperty(name="Motion Path After Color", subtype='COLOR', default=(0.0, 1.0, 0.0), min=0.0, max=1.0, description="Motion path color before the current frame")
|
||||
mp_infront: bpy.props.BoolProperty(name = "Motion Path In Front", description = "Display motion path in front of all the objects", default = True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_points: bpy.props.BoolProperty(name = "Motion Path Points", description = "Display motion path points", default = True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_lines: bpy.props.BoolProperty(name = "Motion Path Lines", description = "Display motion path lines", default = True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_handles: bpy.props.BoolProperty(name = "Motion Path Handles", description = "Display motion path handles on keyframe selection", default = True, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_display_frames: bpy.props.BoolProperty(name = "Frame Numbers", description = "Display frame numbers on all the keyframes", default = False, override = {'LIBRARY_OVERRIDABLE'})
|
||||
mp_handle_types: bpy.props.EnumProperty(name = 'Set Keyframe Handle Type', description="Set handle type for selected keyframes", default = 'AUTO', update = emp.update_handle_type_prop,
|
||||
items = [('FREE', 'Free', 'Free', 'HANDLE_FREE', 0),
|
||||
('ALIGNED','Aligned', 'Aligned', 'HANDLE_ALIGNED', 1),
|
||||
('VECTOR', 'Vector', 'Vector', 'HANDLE_VECTOR', 2),
|
||||
('AUTO','Automatic', 'Automatic', 'HANDLE_AUTO', 3),
|
||||
('AUTO_CLAMPED','Auto Clamped', 'Auto Clamped', 'HANDLE_AUTOCLAMPED', 4)])
|
||||
|
||||
mp_interpolation: bpy.props.EnumProperty(name = 'Set Interpolation', description="Set Keyframe Interpolation", default = 'BEZIER', update = emp.update_interpolation_prop,
|
||||
items = [('BEZIER', 'Bezier', 'Bezier', 'IPO_BEZIER', 0),
|
||||
('LINEAR','Linear', 'Linear', 'IPO_LINEAR', 1),
|
||||
('CONSTANT', 'Constant', 'Constant', 'IPO_CONSTANT', 2)])
|
||||
|
||||
mp_frame_range: bpy.props.EnumProperty(name = 'Frame Range', description="Type of Frame Range", default = 'SCENE', #update = emp.mp_frame_range_update,
|
||||
items = [('KEYS_ALL', 'All_Keys','Use the Scene Frame Length for the Range', 0),
|
||||
('SCENE', 'Scene','Use the Scene Frame Length for the Range', 1),
|
||||
('MANUAL', 'Manual','Custom Frame range using numerical input or the markers frame ranger', 2),
|
||||
('AROUND', 'Around Frames','Show only around the current frame', 3)])
|
||||
mp_before: bpy.props.IntProperty(name = "Before", description = "Show the frames Before the current frame", min = 0, default = 10)
|
||||
mp_after: bpy.props.IntProperty(name = "After", description = "Show the frames After the current frame", min = 0, default = 10)
|
||||
selected_keyframes: bpy.props.StringProperty(name="Selected Keyframes", description="Serialized representation of selected keyframes")
|
||||
|
||||
gizmo_size: bpy.props.IntProperty(name = "Add to Gizmo Size", description = "Addition to Gizmo Size", max = 100, min = -100, default = 10)
|
||||
|
||||
#Copy/Pase Matrix
|
||||
@@ -312,6 +290,13 @@ class AnimToolBoxPreferences(bpy.types.AddonPreferences):
|
||||
|
||||
#Editable motion path
|
||||
keyframes_range: bpy.props.IntProperty(name = "Keyframe Range", description = "The range of distance from the keyframes while hovering over them", min = 5, max = 100, default = 15)
|
||||
mp_pref: bpy.props.BoolProperty(name = "Editable Motion Path Colors Theme", description = "Set the Color them of editable motion path visualization", default = False)
|
||||
mp_keyframe_color: bpy.props.FloatVectorProperty(name="Keyframes", subtype='COLOR', default=(1.0, 1.0, 0.0, 1.0), size=4, min=0.0, max=1.0, description="Handles selection color")
|
||||
mp_handle_color: bpy.props.FloatVectorProperty(name="Handles", subtype='COLOR', default=(1.0, 0.8, 0.2, 1.0), size=4, min=0.0, max=1.0, description="Handles selection color")
|
||||
mp_remove_color: bpy.props.FloatVectorProperty(name="Remove Keyframes", subtype='COLOR', default=(0.0, 0.5, 1.0, 1.0), size=4, min=0.0, max=1.0, description="Keyframe color displayed before removing")
|
||||
mp_hover_color: bpy.props.FloatVectorProperty(name="Hover", subtype='COLOR', default=(1.0, 0.4, 0.2, 1.0), size=4, min=0.0, max=1.0, description="Color during Hovering")
|
||||
mp_handle_selection_color: bpy.props.FloatVectorProperty(name="Handles Selection", subtype='COLOR', default=(0.8, 0.65, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Handles selection color")
|
||||
mp_key_selection_color: bpy.props.FloatVectorProperty(name="Keyframe Selection", subtype='COLOR', default=(0.8, 0.8, 0.6, 0.8), size=4, min=0.0, max=1.0, description="Keyframe selection color")
|
||||
|
||||
# addon updater preferences from `__init__`, be sure to copy all of them
|
||||
auto_check_update: bpy.props.BoolProperty(
|
||||
@@ -371,6 +356,22 @@ class AnimToolBoxPreferences(bpy.types.AddonPreferences):
|
||||
row.prop(self, 'clear_setup')
|
||||
row.prop(self, 'in_front')
|
||||
|
||||
layout.separator()
|
||||
box = layout.box()
|
||||
col = box.column()
|
||||
col.prop(self, 'mp_pref', icon = 'DOWNARROW_HLT', text = 'Editable Motion Path Preferences')
|
||||
if self.mp_pref:
|
||||
col.prop(self, 'keyframes_range', text = 'Keyframe Distance Range')
|
||||
col.label(text = 'Colors Theme')
|
||||
row = box.row()
|
||||
row.prop(self, 'mp_keyframe_color')
|
||||
row.prop(self, 'mp_handle_color')
|
||||
row.prop(self, 'mp_remove_color')
|
||||
row = box.row()
|
||||
row.prop(self, 'mp_hover_color')
|
||||
row.prop(self, 'mp_key_selection_color')
|
||||
row.prop(self, 'mp_handle_selection_color')
|
||||
|
||||
layout.separator()
|
||||
col = layout.column()
|
||||
col.label(text = 'Include Extras: ')
|
||||
@@ -385,12 +386,14 @@ def loadanimtoolbox_pre(self, context):
|
||||
if scene.animtoolbox.bake_frame_range:
|
||||
scene.animtoolbox.bake_frame_range = False
|
||||
|
||||
if scene.animtoolbox.motion_path:
|
||||
scene.animtoolbox.motion_path = False
|
||||
if scene.emp.motion_path:
|
||||
scene.emp.motion_path = False
|
||||
bpy.context.workspace.status_text_set(None)
|
||||
if 'mp_dh' in dns:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
|
||||
bpy.app.driver_namespace.pop('mp_dh')
|
||||
|
||||
bpy.context.scene.emp.selected_keyframes = '{}'
|
||||
|
||||
if 'markers_retimer_dh' in dns:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(dns['markers_retimer_dh'], 'WINDOW')
|
||||
bpy.app.driver_namespace.pop('markers_retimer_dh')
|
||||
@@ -411,16 +414,20 @@ def loadanimtoolbox_post(self, context):
|
||||
if Display.isolate_pose_mode not in bpy.app.handlers.depsgraph_update_pre:
|
||||
bpy.app.handlers.depsgraph_update_pre.append(Display.isolate_pose_mode)
|
||||
|
||||
if scene.animtoolbox.motion_path:
|
||||
scene.animtoolbox.motion_path = False
|
||||
if scene.emp.motion_path:
|
||||
scene.emp.motion_path = False
|
||||
bpy.context.workspace.status_text_set(None)
|
||||
if 'mp_dh' in dns:
|
||||
bpy.types.SpaceView3D.draw_handler_remove(dns['mp_dh'], 'WINDOW')
|
||||
bpy.app.driver_namespace.pop('mp_dh')
|
||||
# Reset keyframe selection for motion paths it is not used in window manager
|
||||
# Because it is used for undo
|
||||
bpy.context.scene.emp.selected_keyframes = '{}'
|
||||
|
||||
Tools.selection_order(self, context)
|
||||
|
||||
classes = (TempCtrlsItems, TempCtrlsObjectSetups, TempCtrlsSceneSettings,TempCtrlsBoneSettings, MultikeyProperties, IsolatedRigs,
|
||||
AnimToolBoxObjectSettings, AnimToolBoxUILayout, AnimToolBoxGlobalSettings) + ui.classes
|
||||
classes = (TempCtrlsItems, TempCtrlsOrgIds, TempCtrlsObjectSetups, TempCtrlsSceneSettings,TempCtrlsBoneSettings, MultikeyProperties,
|
||||
IsolatedRigs, AnimToolBoxObjectSettings, AnimToolBoxUILayout, AnimToolBoxGlobalSettings) + ui.classes
|
||||
|
||||
addon_keymaps = []
|
||||
|
||||
|
||||
+5
-5
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"last_check": "2025-09-23 10:57:14.918240",
|
||||
"backup_date": "June-19-2025",
|
||||
"last_check": "2026-02-09 15:54:54.419194",
|
||||
"backup_date": "September-23-2025",
|
||||
"update_ready": true,
|
||||
"ignore": false,
|
||||
"just_restored": false,
|
||||
"just_updated": false,
|
||||
"version_text": {
|
||||
"link": "https://gitlab.com/api/v4/projects/45739913/repository/archive.zip?sha=cb445f00491a54eb763f0d8e72eaae3e44e7d0ba",
|
||||
"link": "https://gitlab.com/api/v4/projects/45739913/repository/archive.zip?sha=7a9ad24a463bf1c8ae095ec853409979344e0738",
|
||||
"version": [
|
||||
0,
|
||||
0,
|
||||
8
|
||||
2,
|
||||
3
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,7 @@ def attr_default(obj, fcu_key):
|
||||
if attr not in obj.data.shape_keys.key_blocks:
|
||||
return [0]
|
||||
shapekey = obj.data.shape_keys.key_blocks[attr]
|
||||
return 0 if shapekey.slider_min <= 0 else shapekey.slider_min
|
||||
return [0] if shapekey.slider_min <= 0 else shapekey.slider_min
|
||||
#in case of transforms in object mode
|
||||
else:# fcu_key[0] in transform_types:
|
||||
source = obj
|
||||
@@ -273,6 +273,9 @@ def random_value(self, context):
|
||||
for key in fcu.keyframe_points:
|
||||
add_value(key, value * random.uniform(-threshold, threshold))
|
||||
fcu.update()
|
||||
|
||||
self['randomness'] = 0.1
|
||||
|
||||
|
||||
def evaluate_combine(data_path, added_array, eval_array, array_default, influence):
|
||||
|
||||
@@ -293,23 +296,17 @@ def evaluate_array(fcurves, fcu_path, frame, array_default = [0, 0, 0]):
|
||||
'''Create an array from all the indexes'''
|
||||
|
||||
array_len = len(array_default)
|
||||
fcu_array = []
|
||||
|
||||
#assigning the default array in case
|
||||
fcu_array = array_default.copy()
|
||||
#get the missing arrays in case quaternion is not complete
|
||||
missing_arrays = []
|
||||
for i in range(array_len):
|
||||
fcu = fcurves.find(fcu_path, index = i)
|
||||
if fcu is None:
|
||||
missing_arrays.append(i)
|
||||
continue
|
||||
fcu_array[i] = fcu.evaluate(frame)
|
||||
|
||||
fcu_array.append(fcu.evaluate(frame))
|
||||
|
||||
#In case it's a quaternion and missing attributes, then adding from default value
|
||||
if fcu_array and array_len == 4 and missing_arrays:
|
||||
for i in missing_arrays:
|
||||
fcu_array.insert(i, array_default[i])
|
||||
|
||||
if not len(fcu_array):
|
||||
if (fcu_array == array_default).all():
|
||||
return None
|
||||
return np.array(fcu_array)
|
||||
|
||||
@@ -325,7 +322,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
|
||||
blend_types = {'ADD' : '+', 'SUBTRACT' : '-', 'MULTIPLY' : '*'}
|
||||
fcu_path = fcu.data_path
|
||||
|
||||
eval_array = array_default
|
||||
eval_array = array_default.copy()
|
||||
|
||||
for track in nla_tracks:
|
||||
if track.mute:
|
||||
@@ -373,6 +370,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
|
||||
tweak_mode = anim_data.use_tweak_mode
|
||||
if tweak_mode:
|
||||
anim_data.use_tweak_mode = False
|
||||
|
||||
action = anim_data.action
|
||||
if action:
|
||||
influence = anim_data.action_influence
|
||||
@@ -425,6 +423,7 @@ def evaluate_value(self, context):
|
||||
for fcu in fcurves:
|
||||
if fcu in fcu_paths:
|
||||
continue
|
||||
current_value = None
|
||||
if Tools.filter_properties(context.scene.animtoolbox, fcu):
|
||||
continue
|
||||
if obj.mode == 'POSE':
|
||||
@@ -447,10 +446,11 @@ def evaluate_value(self, context):
|
||||
else:
|
||||
transform = fcu.data_path
|
||||
current_value = getattr(obj, transform)
|
||||
|
||||
#In case it was completly filtered out and not current value available
|
||||
if not current_value:
|
||||
continue
|
||||
|
||||
array_default = np.array(attr_default(obj, (fcu.data_path, fcu.array_index)))
|
||||
# array_default = np.array([attr_default(obj, (fcu.data_path, i)) for i in range(4)
|
||||
# if fcurves.find(fcu.data_path, index = i) is not None])
|
||||
eval_array = evaluate_layers(context, obj, anim_data, fcu, array_default)
|
||||
if eval_array is None:
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
|
||||
@@ -63,6 +63,7 @@ class ANIMTOOLBOX_MT_Temp_Ctrls(bpy.types.Menu):
|
||||
layout = self.layout
|
||||
custom_icons = preview_collections["main"]
|
||||
layout.operator('anim.bake_to_ctrl', text="World Space Ctrls", icon = 'WORLD')
|
||||
# layout.operator('anim.worldspace_cursor', text="World Space Cursor Ctrls", icon ='ORIENTATION_CURSOR')
|
||||
layout.operator('anim.bake_to_temp_fk', text="Temp FK Ctrls", icon = 'BONE_DATA')
|
||||
layout.operator('anim.bake_to_temp_ik', text="Temp IK Ctrls", icon = 'CON_KINEMATIC')
|
||||
layout.separator()
|
||||
@@ -163,7 +164,7 @@ class ANIMTOOLBOX_MT_operators(bpy.types.Menu):
|
||||
layout.separator(factor = 0.5)
|
||||
layout.operator('anim.switch_collections_visibility', icon = 'COLLECTION_COLOR_06', text = 'Animated Collections Visibilty')
|
||||
layout.operator('anim.isolate_pose_mode', icon_value = custom_icons["isolate"].icon_id, depress = scene.animtoolbox.isolate_pose_mode)
|
||||
layout.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
|
||||
layout.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.emp.motion_path, icon_value = custom_icons["mt"].icon_id)
|
||||
layout.separator()
|
||||
layout.prop(context.preferences.addons[__package__].preferences, 'quick_menu', text = 'Use Quick Icons Menu')
|
||||
# layout.prop(context.window_manager.atb_ui, 'quick_menu', text = 'Use Quick Icons Menu')
|
||||
@@ -257,7 +258,10 @@ class TEMPCTRLS_PT_Panel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
layout = self.layout
|
||||
box = layout.box()
|
||||
col = box.column()
|
||||
col.operator('anim.bake_to_ctrl', text="WorldSpace Ctrls", icon ='WORLD') #
|
||||
row = col.row()
|
||||
row.operator('anim.bake_to_ctrl', text="WorldSpace Ctrls", icon ='WORLD') #
|
||||
row.operator('anim.add_empty_ctrl', text="", icon ='EMPTY_AXIS')
|
||||
# col.operator('anim.worldspace_cursor', text="WorldSpace Cursor Ctrls", icon ='ORIENTATION_CURSOR')
|
||||
col.operator('anim.bake_to_temp_fk', text="Temp FK Ctrls", icon = 'BONE_DATA')
|
||||
col.operator('anim.bake_to_temp_ik', text="Temp IK Ctrls", icon = 'CON_KINEMATIC')
|
||||
|
||||
@@ -540,44 +544,65 @@ class ANIMTOOLBOX_PT_Display(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
|
||||
row.prop(scene.animtoolbox, 'mp_settings', text = '', icon = 'SETTINGS')
|
||||
if scene.animtoolbox.mp_settings:
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_color_before', text = '')
|
||||
row.prop(scene.animtoolbox, 'mp_color_after', text = '')
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_keyframe_scale', text = 'Scale Keyframes Box', icon = 'CUBE')
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_points', text = 'Points')
|
||||
row.prop(scene.animtoolbox, 'mp_lines', text = 'Lines')
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_handles', text = 'Handles')
|
||||
row.prop(scene.animtoolbox, 'mp_infront', text = 'In Front')
|
||||
row = box.row()
|
||||
row.prop(scene.animtoolbox, 'mp_display_frames', text = 'Display Keyframe Numbers')
|
||||
row = box.row()
|
||||
row.prop(context.preferences.addons[__package__].preferences, 'keyframes_range', text = 'Keyframe Distance Range')
|
||||
if context.object is None:
|
||||
return
|
||||
|
||||
row.operator('object.motion_path_operator', text = 'Editable Motion Path', depress = scene.emp.motion_path, icon_value = custom_icons["mt"].icon_id)
|
||||
emp = scene.emp
|
||||
row.prop(scene.emp, 'settings', text = '', icon = 'SETTINGS')
|
||||
if scene.emp.settings:
|
||||
|
||||
split = box.split(factor = 0.4)
|
||||
split.label(text ='Frame Range')
|
||||
# row.prop(scene.animtoolbox, 'mp_frame_range', text = '')
|
||||
|
||||
split.prop(context.scene.animtoolbox, 'mp_frame_range', text = '')
|
||||
if context.scene.animtoolbox.mp_frame_range == 'MANUAL':
|
||||
split.prop(emp, 'frame_range', text = '')
|
||||
if emp.frame_range == 'MANUAL':
|
||||
row = box.row()
|
||||
row.prop(context.scene.animtoolbox, 'bake_frame_start', text = 'Start')
|
||||
row.prop(context.scene.animtoolbox, 'bake_frame_end', text = 'End')
|
||||
row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
|
||||
|
||||
elif context.scene.animtoolbox.mp_frame_range == 'AROUND':
|
||||
row = box.row()
|
||||
row.prop(context.scene.animtoolbox, 'mp_before', text = 'Before')
|
||||
row.prop(context.scene.animtoolbox, 'mp_after', text = 'After')
|
||||
row.prop(emp, 'frame_start', text = 'Start')
|
||||
row.prop(emp, 'frame_end', text = 'End')
|
||||
# row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
|
||||
|
||||
elif emp.frame_range == 'AROUND':
|
||||
row = box.row()
|
||||
row.prop(emp, 'before', text = 'Before')
|
||||
row.prop(emp, 'after', text = 'After')
|
||||
|
||||
box.separator(factor = 0.1)
|
||||
col = box.column()
|
||||
col.prop(emp, 'display_size', icon = 'DOWNARROW_HLT')
|
||||
if emp.display_size:
|
||||
col.prop(emp, 'thickness', text = 'Line Thickness')
|
||||
col.prop(emp, 'keyframe_size', text = 'Keyframes Size')
|
||||
col.prop(emp, 'frame_size', text = 'Frames Size')
|
||||
|
||||
box.separator(factor = 0.1)
|
||||
row = box.row()
|
||||
row.label(text = 'Visualization Type')
|
||||
row.prop(emp, 'vis_type', text = '')
|
||||
if emp.vis_type == 'VELOCITY':
|
||||
row = box.row()
|
||||
row.prop(emp, 'velocity_factor', text = '')
|
||||
row.prop(emp, 'clamp_min', text = '')
|
||||
row.prop(emp, 'clamp_max', text = '')
|
||||
|
||||
row = box.row()
|
||||
row.prop(emp, 'color_before', text = '')
|
||||
row.prop(emp, 'color_after', text = '')
|
||||
|
||||
row = box.row()
|
||||
row.prop(emp, 'points', text = 'Points')
|
||||
row.prop(emp, 'lines', text = 'Lines')
|
||||
row = box.row()
|
||||
row.prop(emp, 'handles', text = 'Handles')
|
||||
row.prop(emp, 'infront', text = 'In Front')
|
||||
row = box.row()
|
||||
row.prop(emp, 'display_frames', text = 'Display Keyframe Numbers')
|
||||
row.separator()
|
||||
row = box.row()
|
||||
row.operator('anim.go_to_keyframe')
|
||||
# row = box.row()
|
||||
# row.prop(context.preferences.addons[__package__].preferences, 'keyframes_range', text = 'Keyframe Distance Range')
|
||||
# if context.object is None:
|
||||
# return
|
||||
# row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
|
||||
|
||||
class RIGGERTOOLBOX_PT_Panel(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
bl_label = "Rigger Toolbox"
|
||||
@@ -697,4 +722,4 @@ def draw_menu(self, context):
|
||||
layout.operator('anim.isolate_pose_mode', text = '', depress = scene.animtoolbox.isolate_pose_mode, icon_value = custom_icons["isolate"].icon_id)
|
||||
|
||||
layout.separator()
|
||||
layout.operator('object.motion_path_operator', text = '', depress = scene.animtoolbox.motion_path, icon_value = custom_icons["mt"].icon_id)
|
||||
layout.operator('object.motion_path_operator', text = '', depress = scene.emp.motion_path, icon_value = custom_icons["mt"].icon_id)
|
||||
Binary file not shown.
+1964
-664
File diff suppressed because it is too large
Load Diff
@@ -124,7 +124,9 @@ def add_value(key, value):
|
||||
apply_handles(key, handle_r, handle_l)
|
||||
|
||||
#calculate the difference between current value and the fcurve value
|
||||
def add_diff(fcurves, path, current_value, eval_array):
|
||||
def add_diff(fcurves, path, current_value, eval_array):
|
||||
if eval_array is None:
|
||||
return
|
||||
array_value = current_value - eval_array
|
||||
if not any(array_value):
|
||||
return
|
||||
@@ -160,7 +162,7 @@ class ScaleValuesOp(bpy.types.Operator):
|
||||
for obj in context.selected_objects:
|
||||
if obj.animation_data.action is None:
|
||||
continue
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
fcurves = Tools.get_fcurves(obj, obj.animation_data.action)
|
||||
for fcu in fcurves:
|
||||
if obj.mode == 'POSE':
|
||||
if selected_bones_filter(obj, fcu.data_path):
|
||||
@@ -255,7 +257,7 @@ def random_value(self, context):
|
||||
for obj in context.selected_objects:
|
||||
if obj.animation_data.action is None:
|
||||
continue
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, obj.animation_data.action)
|
||||
fcurves = Tools.get_fcurves(obj, obj.animation_data.action)
|
||||
for fcu in fcurves:
|
||||
if obj.mode == 'POSE':
|
||||
if selected_bones_filter(obj, fcu.data_path):
|
||||
@@ -306,8 +308,8 @@ 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():
|
||||
return None
|
||||
# if (fcu_array == array_default).all():
|
||||
# return None
|
||||
return np.array(fcu_array)
|
||||
|
||||
def evaluate_layers(context, obj, anim_data, fcu, array_default):
|
||||
@@ -363,7 +365,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
|
||||
frame_eval = last_frame - (frame_eval - strip.frame_start)
|
||||
offset = (strip.frame_start * 1/strip.scale - strip.action_frame_start) * strip.scale
|
||||
frame_eval = strip.frame_start * 1/strip.scale + (frame_eval - strip.frame_start) * 1/strip.scale - offset * 1/strip.scale
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
fcurves = Tools.get_fcurves(obj, action)
|
||||
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame_eval, influence, array_default, blend_type, blend_types)
|
||||
|
||||
#Adding an extra layer from the action outside and on top of the nla
|
||||
@@ -376,7 +378,7 @@ def evaluate_layers(context, obj, anim_data, fcu, array_default):
|
||||
influence = anim_data.action_influence
|
||||
blend_type = anim_data.action_blend_type
|
||||
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
fcurves = Tools.get_fcurves(obj, action)
|
||||
eval_array = evaluate_blend_type(fcurves, eval_array, fcu_path, frame, influence, array_default, blend_type, blend_types)
|
||||
anim_data.use_tweak_mode = tweak_mode
|
||||
|
||||
@@ -419,7 +421,7 @@ def evaluate_value(self, context):
|
||||
if obj.mode == 'POSE':
|
||||
bonelist = context.selected_pose_bones if ui.multikey.selectedbones else obj.pose.bones
|
||||
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
fcurves = Tools.get_fcurves(obj, action)
|
||||
for fcu in fcurves:
|
||||
if fcu in fcu_paths:
|
||||
continue
|
||||
@@ -453,7 +455,7 @@ def evaluate_value(self, context):
|
||||
array_default = np.array(attr_default(obj, (fcu.data_path, fcu.array_index)))
|
||||
eval_array = evaluate_layers(context, obj, anim_data, fcu, array_default)
|
||||
if eval_array is None:
|
||||
fcurves = Tools.get_fcurves_channelbag(obj, action)
|
||||
# fcurves = Tools.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
|
||||
|
||||
@@ -558,7 +558,7 @@ class ANIMTOOLBOX_PT_Display(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
row = box.row()
|
||||
row.prop(emp, 'frame_start', text = 'Start')
|
||||
row.prop(emp, 'frame_end', text = 'End')
|
||||
# row.operator("anim.markers_bakerange", icon = 'MARKER', text ='', depress = scene.animtoolbox.bake_frame_range)
|
||||
row.operator("anim.emp_markers_range", icon = 'MARKER', text ='', depress = scene.emp.marker_frame_range)
|
||||
|
||||
elif emp.frame_range == 'AROUND':
|
||||
row = box.row()
|
||||
@@ -596,8 +596,33 @@ class ANIMTOOLBOX_PT_Display(ANIMTOOLBOX_PT_Panel, bpy.types.Panel):
|
||||
row = box.row()
|
||||
row.prop(emp, 'display_frames', text = 'Display Keyframe Numbers')
|
||||
row.separator()
|
||||
box = layout.box()
|
||||
split = box.split(factor = 0.35)
|
||||
split.label(text = 'Channels')
|
||||
split.prop(emp, 'channels', text ='')
|
||||
row = box.row()
|
||||
row.operator('anim.go_to_keyframe')
|
||||
if bpy.app.version >= (4, 2, 0):
|
||||
row.prop(emp, 'camera_space', icon = 'CON_CAMERASOLVER')
|
||||
#row = box.row()
|
||||
row.prop(emp, 'cursor_offset', icon = 'PIVOT_CURSOR')
|
||||
box.separator(factor = 0.05)
|
||||
row = box.row()
|
||||
row.operator('anim.go_to_keyframe', icon = 'NEXT_KEYFRAME')
|
||||
box.separator(factor = 0.05)
|
||||
# split = box.split(factor = 0.75)
|
||||
row = box.row()
|
||||
row.prop(emp, 'select_circle', text = 'Selection Brush', icon = 'MESH_CIRCLE')
|
||||
row = box.row()
|
||||
prop_edit_icon = 'PROP_ON' if emp.prop_edit else 'PROP_OFF'
|
||||
row.prop(emp, 'prop_edit', text = 'Proportional Editing', icon = prop_edit_icon)
|
||||
row.prop(emp, 'smooth', text = 'Smooth', icon = 'MOD_SMOOTH')
|
||||
row = box.row()
|
||||
if emp.prop_edit:
|
||||
row.prop(emp, 'prop_edit_falloff', text = '')
|
||||
row.prop(emp, 'radius', text = '')
|
||||
elif emp.smooth:
|
||||
row.prop(emp, 'smooth_strength', text = '')
|
||||
row.prop(emp, 'radius', text = '')
|
||||
# row = box.row()
|
||||
# row.prop(context.preferences.addons[__package__].preferences, 'keyframes_range', text = 'Keyframe Distance Range')
|
||||
# if context.object is None:
|
||||
|
||||
Reference in New Issue
Block a user