Fカーブのグラフの曲線をコピペする

Blenderのアニメーションのカーブの曲線をコピペしたいという話があったので
アドオンとして作ってみました
![23021901.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151493/f425e2df-3f4f-78d9-bf9d-8cb5a40f9726.gif)
キーフレーム自体の値を変更せずに**選択したキーフレームと次のキーフレームの間**の補完の曲線が同じになるようにします.


Blenderのグラフエディタの機能の都合で使い勝手は今一つかもしれませんが目的の動作は可能かと思います
因みに　曲線の形はテキストデータとしてクリップボードに書き出すようにしたので
別途テキストとして保存しておいて　再度その文字列をクリップボードにコピーすることで別のファイル等でもカーブを再現できます


``` py:y_CopyPasetCurveEasing.py
bl_info = {
    "name" : "Copy Paset F-Curve Easing ",
    "author" : "Yukimituki",
    "description" : "",
    "blender" : (2, 80, 0),
    "version" : (0, 0, 1),
    "location" : "",
    "warning" : "",
    "category" : "F-Curves"
}

import bpy
import json
class CURVE_PT_EasingCopyPaste(bpy.types.Panel):
    bl_space_type = 'GRAPH_EDITOR'
    bl_region_type = 'UI'
    bl_category = "F-Curve"
    bl_label = "Easing copy&paste"
    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.label(text="Fカーブの補完曲線をコピペ")
        row = layout.row()
        row.label(text="コントロールポイントを一つだけ選択してください")
        row = layout.row(align=True)
        row.operator("cureasing.copy")
        row.operator("cureasing.paste")

class CURVE_OT_EasingCopy(bpy.types.Operator):
    bl_idname = "cureasing.copy"
    bl_label = "Copy"
    def execute(self, context):
        obj = bpy.context.active_object
        animation_data = obj.animation_data
        if  hasattr(animation_data,"action"):
            fcurves = animation_data.action.fcurves
            for curve in fcurves:
                if curve.select:
                    keyframe_count = len(curve.keyframe_points)
                    for i in range(keyframe_count -1):
                        if curve.keyframe_points[i].select_control_point:
                            dic = self.get_ease_settings(curve, i)
                            # テキストをクリップボードに
                            bpy.context.window_manager.clipboard = json.dumps(dic)
                        break
        return {'FINISHED'}
    def get_ease_settings(self, curve, i):
        attr_dic = {}
        point_1 = curve.keyframe_points[i]
        handle_1 = curve.keyframe_points[i].handle_right - point_1.co
        point_2 = curve.keyframe_points[i +1]
        handle_2 = curve.keyframe_points[i +1].handle_left - point_2.co
        diff = point_2.co - point_1.co
        if diff[1] == 0: return()
        attr_dic['interpolation'] = point_1.interpolation #'BEZIER', 'BACK', 'ELASTIC', ...
        attr_dic['back'] = point_1.back #interpolation 'BACK'
        attr_dic['easing'] = point_1.easing
        attr_dic['out_handle'] = [handle_1[0]/diff[0], handle_1[1]/diff[1]]
        attr_dic['out_type'] = point_1.handle_right_type
        attr_dic['in_handle'] = [handle_2[0]/diff[0], handle_2[1]/diff[1]]
        attr_dic['in_type'] = point_2.handle_left_type
        attr_dic['interpolation'] = point_1.interpolation
        attr_dic['amplitude'] = point_1.amplitude #interpolation 'ELASTIC'
        attr_dic['period'] = point_1.period/diff[0] #interpolation 'ELASTIC'
        return attr_dic

class CURVE_OT_EasingPaste(bpy.types.Operator):
    bl_idname = "cureasing.paste"
    bl_label = "Paste"
    def execute(self, context):
        # クリップボードのテキストを利用して生成
        dic = json.loads(bpy.context.window_manager.clipboard)
        obj = bpy.context.active_object
        animation_data = obj.animation_data
        if  hasattr(animation_data,"action"):
            fcurves = animation_data.action.fcurves
            for curve in fcurves:
                if curve.select:
                    keyframe_count = len(curve.keyframe_points)
                    for i in range(keyframe_count -1):
                        if curve.keyframe_points[i].select_control_point:
                            self.set_ease_settings(curve, i, dic)
                        break
        return {'FINISHED'}
    def set_ease_settings(self, curve, i, attr_dic):
        point_1 = curve.keyframe_points[i]
        point_2 = curve.keyframe_points[i +1]
        diff = point_2.co - point_1.co
        if diff[1] == 0: return()
        out_handle = attr_dic['out_handle']
        handle_1 = [ float(out_handle[0]) *diff[0] +point_1.co[0], float(out_handle[1]) *diff[1] +point_1.co[1] ]
        curve.keyframe_points[i].handle_right = handle_1
        point_1.handle_right_type = attr_dic['out_type']

        in_handle = attr_dic['in_handle']
        handle_2 = [ float(in_handle[0]) *diff[0] +point_2.co[0], float(in_handle[1]) *diff[1] +point_2.co[1] ]
        curve.keyframe_points[i +1].handle_left = handle_2
        point_2.handle_left_type = attr_dic['in_type']
        
        point_1.interpolation = attr_dic['interpolation']
        point_1.back = float(attr_dic['back'])
        point_1.easing = attr_dic['easing']
        point_1.interpolation = attr_dic['interpolation']
        point_1.amplitude = float(attr_dic['amplitude'])
        point_1.period = float(attr_dic['period']) *diff[0]

classes = (
    CURVE_PT_EasingCopyPaste,
    CURVE_OT_EasingCopy,
    CURVE_OT_EasingPaste,
    )

    
#####################################
def register():
    Scene = bpy.types.Scene
    for cls in classes:
        bpy.utils.register_class(cls)


def unregister():
    Scene = bpy.types.Scene
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)


if __name__ == "__main__":
    register()
#######################################

```


冒頭の動画のカーブではこんなテキストがクリップボードにコピーされます

```
{"interpolation": "BEZIER", "back": 1.7015800476074219, "easing": "EASE_IN", "out_handle": [0.49889544436806127, 0.5134480499751198], "out_type": "ALIGNED", "in_handle": [-0.14829334459806742, -0.7493565886372822], "in_type": "FREE", "amplitude": 0.800000011920929, "period": 0.21578946866487203}
```
Blenderのカーブのベジェ曲線ではハンドルの座標はキーフレームと同様の座標値になるように
一旦　正規化して記録しています