Compare commits

...

8 Commits
master ... dev

Author SHA1 Message Date
MMaker 3b5ba4f977
fix: Checking wrong variable
oops, lol
2022-03-27 04:12:16 -04:00
MMaker 5391ebaa42
fix: Warn user when no keys are
selected when copying
2022-03-26 03:51:13 -04:00
MMaker 2e94e2c684
lint: Ignore missing bl_math import 2022-03-26 03:18:14 -04:00
MMaker 3d5e65bb88
feat: Support copying from multiple keys 2022-03-26 03:17:19 -04:00
MMaker 0d82095ea5
Simply data structure, only store index 2022-03-26 03:17:18 -04:00
MMaker 5a520c59c9
Bump version number and year 2022-03-26 03:17:18 -04:00
MMaker 46ccc77235
Add .zip files to .gitignore 2022-03-26 03:17:18 -04:00
MMaker 706525fde1
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.
2022-03-26 03:17:18 -04:00
2 changed files with 99 additions and 42 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
.vscode
*.code-workspace
__pycache__
__pycache__
*.zip

View File

@ -1,4 +1,4 @@
# Copyright (C) 2021 MMaker <mmaker@mmaker.moe>
# Copyright (C) 2021-2022 MMaker <mmaker@mmaker.moe>
#
# This file is part of FCurveHandleCopy.
#
@ -20,13 +20,13 @@ bl_info = {
"author" : "MMaker",
"description" : ":)",
"blender" : (2, 83, 0),
"version" : (0, 0, 1),
"version" : (0, 0, 2),
"category" : "Animation"
}
import bpy
import mathutils
import bl_math
import bl_math # type: ignore
class G:
selected_keys = []
@ -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,20 @@ class FCurveHandleCopyValue(bpy.types.Operator):
def execute(self, context):
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 = {}
G.selected_keys = list(filter(lambda x: x.select_control_point, fcurves[0].keyframe_points))
for fcurve in fcurves:
for key_index, key in enumerate(fcurve.keyframe_points):
if key.select_control_point:
if fcurve not in G.selected_keys:
G.selected_keys[fcurve] = []
G.selected_keys[fcurve].append(key_index)
if (len(G.selected_keys) != 2):
self.report({"WARNING"}, "Please select exactly two keyframes when copying an ease.")
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.")
return {'CANCELLED'}
G.bezier = convert_handles_to_bezier(G.selected_keys)
return {'FINISHED'}
@ -99,30 +150,35 @@ class FCurveHandlePasteValue(bpy.types.Operator):
if (context.selected_visible_fcurves):
fcurves = context.selected_visible_fcurves
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)
selected_keys = {}
if (len(selected_keys) == 0):
for fcurve in fcurves:
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, 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(selected_keys) == 1):
if (len(key_indexes) == 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]
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])
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'}