From 706525fde1659bd10f27d0f58b6988faef40ff33 Mon Sep 17 00:00:00 2001 From: MMaker Date: Fri, 25 Mar 2022 13:43:19 -0400 Subject: [PATCH 1/8] fix: Loop through fcurves and get selected only This is potentially more expensive, but is more convenient to the user when they may potentially have more than one fcurve selected in the graph editor. --- __init__.py | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/__init__.py b/__init__.py index 86700da..9a281b8 100644 --- a/__init__.py +++ b/__init__.py @@ -75,12 +75,14 @@ class FCurveHandleCopyValue(bpy.types.Operator): if (context.selected_visible_fcurves): fcurves = context.selected_visible_fcurves G.selected_keys = [] - - if (len(fcurves) > 1): - self.report({"WARNING"}, "Please only select one curve when copying an ease.") - return {'CANCELLED'} - G.selected_keys = list(filter(lambda x: x.select_control_point, fcurves[0].keyframe_points)) + for fcurve in fcurves: + for key in fcurve.keyframe_points: + if key.select_control_point: + G.selected_keys.append(key) + if (len(G.selected_keys) > 2): + self.report({"WARNING"}, "Please select exactly two keyframes when copying an ease.") + return {'CANCELLED'} if (len(G.selected_keys) != 2): self.report({"WARNING"}, "Please select exactly two keyframes when copying an ease.") @@ -99,30 +101,35 @@ class FCurveHandlePasteValue(bpy.types.Operator): if (context.selected_visible_fcurves): fcurves = context.selected_visible_fcurves + selected_keys = {} + for fcurve in fcurves: keys = fcurve.keyframe_points - selected_keys = [] for i in range(0, len(keys)): if (keys[i].select_control_point): - selected_keys.append(i) + if fcurve not in selected_keys: + selected_keys[fcurve] = [] + selected_keys[fcurve].append(keys[i]) - if (len(selected_keys) == 0): + for fcurve, keys in selected_keys.items(): + if (len(keys) == 0): self.report({"WARNING"}, "Please select some keyframes to paste an ease to.") return {'CANCELLED'} - if (len(selected_keys) == 1): + if (len(keys) == 1): # TODO: Implement logic for this soon pass else: - selected_keys.pop() # TODO: Related to above, implement soon - for i in selected_keys: - if (i < len(keys) - 1): - new_handles = generate_new_handles(keys[i], keys[i + 1]) - keys[i].interpolation = 'BEZIER' - keys[i + 1].interpolation = 'BEZIER' - keys[i].handle_right_type = 'FREE' - keys[i + 1].handle_left_type = 'FREE' - keys[i].handle_right = new_handles[0] - keys[i + 1].handle_left = new_handles[1] + keys.pop() # TODO: Related to above, implement soon + for i, _ in enumerate(keys): + f_keys = fcurve.keyframe_points + if (i < len(f_keys) - 1): + new_handles = generate_new_handles(f_keys[i], f_keys[i + 1]) + f_keys[i].interpolation = 'BEZIER' + f_keys[i + 1].interpolation = 'BEZIER' + f_keys[i].handle_right_type = 'FREE' + f_keys[i + 1].handle_left_type = 'FREE' + f_keys[i].handle_right = new_handles[0] + f_keys[i + 1].handle_left = new_handles[1] return {'FINISHED'} From 46ccc7723515012f2b312f73949d6e7e94220ad7 Mon Sep 17 00:00:00 2001 From: MMaker Date: Fri, 25 Mar 2022 13:46:00 -0400 Subject: [PATCH 2/8] Add .zip files to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6525d15..fcf1046 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vscode *.code-workspace -__pycache__ \ No newline at end of file +__pycache__ +*.zip \ No newline at end of file From 5a520c59c90f7cc08fad08373c39ccb582b70ac0 Mon Sep 17 00:00:00 2001 From: MMaker Date: Fri, 25 Mar 2022 13:46:24 -0400 Subject: [PATCH 3/8] Bump version number and year --- __init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__init__.py b/__init__.py index 9a281b8..4cdeab5 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 MMaker +# Copyright (C) 2021-2022 MMaker # # This file is part of FCurveHandleCopy. # @@ -20,7 +20,7 @@ bl_info = { "author" : "MMaker", "description" : ":)", "blender" : (2, 83, 0), - "version" : (0, 0, 1), + "version" : (0, 0, 2), "category" : "Animation" } From 0d82095ea5586f944e17832a15fa1808ba335499 Mon Sep 17 00:00:00 2001 From: MMaker Date: Fri, 25 Mar 2022 14:08:17 -0400 Subject: [PATCH 4/8] Simply data structure, only store index --- __init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__init__.py b/__init__.py index 4cdeab5..25c9b8c 100644 --- a/__init__.py +++ b/__init__.py @@ -109,7 +109,7 @@ class FCurveHandlePasteValue(bpy.types.Operator): if (keys[i].select_control_point): if fcurve not in selected_keys: selected_keys[fcurve] = [] - selected_keys[fcurve].append(keys[i]) + selected_keys[fcurve].append(i) for fcurve, keys in selected_keys.items(): if (len(keys) == 0): @@ -120,7 +120,7 @@ class FCurveHandlePasteValue(bpy.types.Operator): pass else: keys.pop() # TODO: Related to above, implement soon - for i, _ in enumerate(keys): + for i in keys: f_keys = fcurve.keyframe_points if (i < len(f_keys) - 1): new_handles = generate_new_handles(f_keys[i], f_keys[i + 1]) From 3d5e65bb88071d74778a19efbc742118ed8b9809 Mon Sep 17 00:00:00 2001 From: MMaker Date: Sat, 26 Mar 2022 02:58:35 -0400 Subject: [PATCH 5/8] feat: Support copying from multiple keys --- __init__.py | 101 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 28 deletions(-) diff --git a/__init__.py b/__init__.py index 25c9b8c..9134f9f 100644 --- a/__init__.py +++ b/__init__.py @@ -34,18 +34,68 @@ class G: def inverse_lerp(minimum, maximum, val): return (val - minimum) / (maximum - minimum) - + +def create_bezier(handles, co_left_side, co_right_side): + return list( + map( + lambda x: list(map( + lambda v, dimension: inverse_lerp(co_left_side[dimension], co_right_side[dimension], v), + x, + range(2) + )), + handles + ) + ) + def convert_handles_to_bezier(keyframes): - handles = [keyframes[0].handle_right, keyframes[1].handle_left] + # TODO(mmaker): Some of the logic here, particularly when selecting one key, + # is likely not the correct way to calculate the bezier. + # Should do a second pass over this and see if it could be cleaned up to be more accurate. + # (I'm not very math pilled, sorry) + # + # This at minimum should handle any types of user selections though. + # (Two keys, a single key, several keys across different fcurves) + + beziers = [] + for fcurve, key_indexes in keyframes.items(): + f_keys = fcurve.keyframe_points + + # Case when only one key is selected + # TODO(mmaker): Clean up this logic + if len(key_indexes) == 1: + key = f_keys[key_indexes[0]] + beziers.append(create_bezier([ + key.handle_left, key.handle_right + ], key.co, [0.0, 0.0])) + + for i in key_indexes[:-1]: + # NOTE(mmaker): This naming could probably be better, lol + handle_left_side = f_keys[i].handle_right + co_left_side = f_keys[i].co + if i >= len(f_keys): + # Case when selected key is the last in the fcurve + handle_right_side = [0.0, 0.0] + co_right_side = f_keys[i].co + else: + handle_right_side = f_keys[i + 1].handle_left + co_right_side = f_keys[i + 1].co + + handles = [handle_left_side, handle_right_side] + beziers.append(create_bezier(handles, co_left_side, co_right_side)) + + # Average beziers + # NOTE(mmaker): I'm sure there is a way to vectorize this, but until then, B) + bezier = [ + [ + sum([x[0] for x in list(zip(*beziers))[0]]) / len(beziers), + sum([x[1] for x in list(zip(*beziers))[0]]) / len(beziers) + ], + [ + sum([x[0] for x in list(zip(*beziers))[1]]) / len(beziers), + sum([x[1] for x in list(zip(*beziers))[1]]) / len(beziers) + ] + ] - bezier = list(map( - lambda x: list(map( - lambda v, dimension: inverse_lerp(keyframes[0].co[dimension], keyframes[1].co[dimension], v), - x, - range(2) - )), - handles - )) return bezier def generate_new_handles(in_key, out_key): @@ -74,19 +124,14 @@ class FCurveHandleCopyValue(bpy.types.Operator): def execute(self, context): if (context.selected_visible_fcurves): fcurves = context.selected_visible_fcurves - G.selected_keys = [] + G.selected_keys = {} for fcurve in fcurves: - for key in fcurve.keyframe_points: + for key_index, key in enumerate(fcurve.keyframe_points): if key.select_control_point: - G.selected_keys.append(key) - if (len(G.selected_keys) > 2): - self.report({"WARNING"}, "Please select exactly two keyframes when copying an ease.") - return {'CANCELLED'} - - if (len(G.selected_keys) != 2): - self.report({"WARNING"}, "Please select exactly two keyframes when copying an ease.") - return {'CANCELLED'} + if fcurve not in G.selected_keys: + G.selected_keys[fcurve] = [] + G.selected_keys[fcurve].append(key_index) G.bezier = convert_handles_to_bezier(G.selected_keys) @@ -104,23 +149,23 @@ class FCurveHandlePasteValue(bpy.types.Operator): selected_keys = {} for fcurve in fcurves: - keys = fcurve.keyframe_points - for i in range(0, len(keys)): - if (keys[i].select_control_point): + f_keys = fcurve.keyframe_points + for i in range(0, len(f_keys)): + if (f_keys[i].select_control_point): if fcurve not in selected_keys: selected_keys[fcurve] = [] selected_keys[fcurve].append(i) - for fcurve, keys in selected_keys.items(): - if (len(keys) == 0): + for fcurve, key_indexes in selected_keys.items(): + if (len(key_indexes) == 0): self.report({"WARNING"}, "Please select some keyframes to paste an ease to.") return {'CANCELLED'} - if (len(keys) == 1): + if (len(key_indexes) == 1): # TODO: Implement logic for this soon pass else: - keys.pop() # TODO: Related to above, implement soon - for i in keys: + key_indexes.pop() # TODO: Related to above, implement soon + for i in key_indexes: f_keys = fcurve.keyframe_points if (i < len(f_keys) - 1): new_handles = generate_new_handles(f_keys[i], f_keys[i + 1]) From 2e94e2c6841b6ac3d1cffedbf0b31f7a083284f7 Mon Sep 17 00:00:00 2001 From: MMaker Date: Sat, 26 Mar 2022 03:18:14 -0400 Subject: [PATCH 6/8] lint: Ignore missing bl_math import --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 9134f9f..a6c7022 100644 --- a/__init__.py +++ b/__init__.py @@ -26,7 +26,7 @@ bl_info = { import bpy import mathutils -import bl_math +import bl_math # type: ignore class G: selected_keys = [] From 5391ebaa42a5874b3bf486e681b4e46921688407 Mon Sep 17 00:00:00 2001 From: MMaker Date: Sat, 26 Mar 2022 03:51:13 -0400 Subject: [PATCH 7/8] fix: Warn user when no keys are selected when copying --- __init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index a6c7022..182cf90 100644 --- a/__init__.py +++ b/__init__.py @@ -133,7 +133,11 @@ class FCurveHandleCopyValue(bpy.types.Operator): G.selected_keys[fcurve] = [] G.selected_keys[fcurve].append(key_index) - G.bezier = convert_handles_to_bezier(G.selected_keys) + if G.bezier: + G.bezier = convert_handles_to_bezier(G.selected_keys) + else: + self.report({"WARNING"}, "Please select some keyframes to copy an ease from.") + return {'CANCELLED'} return {'FINISHED'} From 3b5ba4f977bb242807903b6f3d1ec3742d482aa5 Mon Sep 17 00:00:00 2001 From: MMaker Date: Sun, 27 Mar 2022 04:12:16 -0400 Subject: [PATCH 8/8] fix: Checking wrong variable oops, lol --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 182cf90..85fc2f7 100644 --- a/__init__.py +++ b/__init__.py @@ -133,7 +133,7 @@ class FCurveHandleCopyValue(bpy.types.Operator): G.selected_keys[fcurve] = [] G.selected_keys[fcurve].append(key_index) - if G.bezier: + if G.selected_keys: G.bezier = convert_handles_to_bezier(G.selected_keys) else: self.report({"WARNING"}, "Please select some keyframes to copy an ease from.")