CM3D2 Converter.misc_DOPESHEET_MT_editor_menus

  1import bpy
  2import bmesh
  3import math
  4import mathutils
  5from . import common
  6from . import compat
  7from .translations.pgettext_functions import *
  8
  9
 10# メニュー等に項目追加
 11def menu_func(self, context):
 12    row = self.layout.row()
 13    row.operator('anim.convert_to_cm3d2_interpolation', icon_value=common.kiss_icon())
 14
 15
 16def check_fcurve_has_selected_keyframe(fcurve: bpy.types.FCurve) -> bool:
 17        for keyframe in fcurve.keyframe_points:
 18            if keyframe.select_control_point or keyframe.select_left_handle or keyframe.select_right_handle:
 19                return True
 20        return False
 21
 22REPORTS = []
 23
 24@compat.BlRegister()
 25class CNV_OT_FCURVE_convert_to_cm3d2_interpolation(bpy.types.Operator):
 26    bl_idname = 'fcurve.convert_to_cm3d2_interpolation'
 27    bl_label = "Convert to CM3D2 Interpolation"
 28    bl_description = "Convert keyframes to be compatible with CM3D2 Interpolation"
 29    bl_options = {'REGISTER', 'UNDO'}
 30
 31    only_selected = bpy.props.BoolProperty(name="Only Selected", default=True)
 32    keep_reports  = bpy.props.BoolProperty(name="Keep Reports",  default=False, options={'HIDDEN'})
 33
 34    @classmethod
 35    def poll(cls, context):
 36        fcurve = context.active_editable_fcurve
 37        return fcurve
 38    
 39    def invoke(self, context, event):
 40        fcurve = context.active_editable_fcurve
 41        self.only_selected = check_fcurve_has_selected_keyframe(fcurve)
 42        return context.window_manager.invoke_props_dialog(self)
 43    
 44    def draw(self, context):
 45        self.layout.prop(self, 'only_selected')
 46
 47    def do_report(self, **kwargs):
 48        if self.keep_reports:
 49            REPORTS.append(kwargs)
 50        else:
 51            self.report(**kwargs)
 52
 53    @staticmethod
 54    def get_slope_vector(from_keyframe: bpy.types.Keyframe, to_keyframe: bpy.types.Keyframe, interpolation=None, backwards=None):
 55        if not to_keyframe:
 56            interpolation = 'BEZIER'
 57        else:
 58            if backwards == None:
 59                # Figure out which keyframe is in charge of controlling the slope
 60                backwards = to_keyframe.co[0] < from_keyframe.co[0]
 61            master_keyframe = to_keyframe if backwards else from_keyframe
 62            interpolation = interpolation or master_keyframe.interpolation
 63        
 64        if   interpolation == 'BEZIER':
 65            if backwards:
 66                slope_vec = mathutils.Vector(from_keyframe.handle_left ) - mathutils.Vector(from_keyframe.co)
 67            else:
 68                slope_vec = mathutils.Vector(from_keyframe.handle_right) - mathutils.Vector(from_keyframe.co)
 69        elif interpolation == 'LINEAR':
 70            slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 71        elif interpolation == 'CONSTANT':
 72            if backwards:
 73                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 74                slope_vec.x /= 3
 75            else:
 76                slope_vec = mathutils.Vector( (1, 0) )
 77        elif interpolation == 'CONSTANT':
 78            if backwards:
 79                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 80                slope_vec.x /= 3
 81            else:
 82                slope_vec = mathutils.Vector( (1, 0) )
 83        elif interpolation == 'CONSTANT':
 84            if backwards:
 85                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 86                slope_vec.x /= 3
 87            else:
 88                slope_vec = mathutils.Vector( (1, 0) )
 89        elif interpolation in {'SINE', 'QUAD', 'CUBIC', 'QUART', 'QUINT'}:#, 'EXPO', 'CIRC'}: # Easing by strength
 90            easing = 'EASE_IN' if master_keyframe.easing == 'AUTO' else master_keyframe.easing
 91            if (   (                  easing == 'EASE_IN_OUT' )
 92                or (not backwards and easing == 'EASE_IN'     )
 93                or (    backwards and easing == 'EASE_OUT'    )
 94            ):
 95                slope_vec = mathutils.Vector( (1, 0) )
 96            else:
 97                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 98                
 99                strength = dict()
100                strength['SINE']  = math.sin( (1/3) * math.pi/2 )
101                strength['QUAD']  = strength['SINE']  ** (1/2)
102                strength['CUBIC'] = strength['QUAD']  ** (1/3)
103                strength['QUART'] = strength['CUBIC'] ** (1/4)
104                strength['QUINT'] = strength['QUART'] ** (1/5)
105                #strength['EXPO']  = strength['QUINT'] ** (1/math.e)
106                #strength['CIRC']  = strength['EXPO']  ** (1/math.pi)
107                
108                slope_vec.y *= strength[interpolation]
109
110                slope_vec.x /= 3
111        elif interpolation == 'BACK': # Dynamic easing
112            easing = 'EASE_IN' if master_keyframe.easing == 'AUTO' else master_keyframe.easing
113            if (   (                  easing == 'EASE_IN_OUT' )
114                or (not backwards and easing == 'EASE_IN'     )
115                or (    backwards and easing == 'EASE_OUT'    )
116            ):
117                slope_vec = mathutils.Vector( (1, 0) )
118            else:
119                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
120                # y = a + b(x - x_0) + c(x - x_0)^2 + d(x - x_0)^3
121                # dx = x_3 - x_0
122                # dy = y_3 - y_0
123                # y(x_0) = y_0 = a
124
125                # d = B+1
126                # c = -B
127
128                # y_3 = y_0 + b(dx) - B(dx)^2 + (B+1)dx^3
129                # y_3 - y_0 + B(dx)^2 - (B+1)dx^3 = b(dx)
130                # dy/dx + B(dx) - (B+1)dx^2 = b
131                # dy/dx + B(dx - dx^2) - dx^2  = b
132
133                # y_2 = (1/3)dx(c*dx+2b) + a
134                # y_2 = (1/3)dx((-B)*dx+2b) + y_0
135
136                # y_2 = (1/3)dx(c*dx) + y_0
137                dydx = slope_vec.y / slope_vec.x
138                b = dydx + master_keyframe.back * (slope_vec.x - slope_vec.x**2) - slope_vec.x**2
139                #slope_vec.y = (1/3) * slope_vec.x * ( -master_keyframe.back * slope_vec.x + 2 * b)
140                #slope_vec.y = (1/3) * slope_vec.x * ( -master_keyframe.back * slope_vec.x )
141                slope_vec.y = master_keyframe.back/3 + slope_vec.y
142                slope_vec.x /= 3
143
144                # d = 2.70158
145                # .33(-1.70158)
146            
147        else:
148            slope_vec = mathutils.Vector( (0, 0) )
149        
150        if slope_vec.x != 0:
151            slope_vec *= 1 / slope_vec.x
152
153        return slope_vec
154        
155
156    def execute(self, context):
157        factor = 3
158
159        fcurve = context.active_editable_fcurve
160        fcurve.update()
161        
162        this_interpolation = None
163        last_interpolation = None
164
165        found_unsupported = set()
166
167        for keyframe_index in range(len(fcurve.keyframe_points)):
168            this_keyframe = fcurve.keyframe_points[keyframe_index  ]
169            next_keyframe = fcurve.keyframe_points[keyframe_index+1] if keyframe_index+1 < len(fcurve.keyframe_points) else None
170            last_keyframe = fcurve.keyframe_points[keyframe_index-1] if keyframe_index-1 >= 0                          else None
171
172            last_interpolation = this_interpolation
173            this_interpolation = this_keyframe.interpolation
174
175            apply_in  = bool(
176                ( last_keyframe and last_keyframe.interpolation == 'BEZIER' and this_keyframe.select_left_handle ) 
177                or this_keyframe.select_control_point
178                or not self.only_selected
179            )
180            apply_out = bool(
181                ( this_interpolation == 'BEZIER' and this_keyframe.select_right_handle )
182                or this_keyframe.select_control_point 
183                or not self.only_selected
184            )
185
186            #print(keyframe_index, this_keyframe.select_left_handle, this_keyframe.select_right_handle, this_keyframe.select_control_point, self.only_selected, " | ", apply_in, apply_out)
187            if not apply_in and not apply_out:
188                continue
189
190            this_co = mathutils.Vector(this_keyframe.co)
191            next_co = mathutils.Vector(next_keyframe.co) if next_keyframe else None
192            last_co = mathutils.Vector(last_keyframe.co) if last_keyframe else None
193
194            vec_in  = self.get_slope_vector(this_keyframe, last_keyframe, last_interpolation, backwards=True )
195            vec_out = self.get_slope_vector(this_keyframe, next_keyframe, this_interpolation, backwards=False)
196            if vec_in.x  == 0:
197                apply_in = False
198                found_unsupported.add(last_interpolation)
199            if vec_out.x == 0:
200                apply_out = False
201                found_unsupported.add(this_interpolation)
202            elif apply_out:
203                if this_keyframe.interpolation != 'BEZIER':
204                    this_keyframe.interpolation = 'BEZIER'
205                    if next_keyframe:
206                        next_keyframe.select_left_handle = True
207
208
209            if vec_in.y != vec_out.y: #and not (this_keyframe.handle_left_type == 'ALIGNED' and this_keyframe.handle_right_type == 'ALIGNED'):
210                handle_type = 'FREE'
211            else:
212                handle_type = 'ALIGNED'
213
214            dist_in  = (last_co.x - this_co.x) / factor if last_co else this_keyframe.handle_left [0] - this_co.x
215            dist_out = (next_co.x - this_co.x) / factor if next_co else this_keyframe.handle_right[0] - this_co.x
216            
217            if apply_in:
218                this_keyframe.handle_left_type  = handle_type
219                this_keyframe.handle_left       = vec_in  * dist_in  + this_co
220            if apply_out:
221                this_keyframe.handle_right_type = handle_type
222                this_keyframe.handle_right      = vec_out * dist_out + this_co
223
224        
225        if found_unsupported:
226            for interpolation_type in found_unsupported:
227                self.do_report(type={'INFO'}, message=f_tip_("'{interpolation}' interpolation not convertable", interpolation=interpolation_type))
228            self.do_report(type={'WARNING'}, message="Found {count} unsupported interpolation type(s) in {id_data}'s FCurve {fcurve_path}[{fcurve_index}]. See log for more info.".format(
229                count        = len(found_unsupported),
230                id_data      = fcurve.id_data.name   ,
231                fcurve_path  = fcurve.data_path      ,
232                fcurve_index = fcurve.array_index    )
233            )
234
235        return {'FINISHED'}
236
237@compat.BlRegister()
238class CNV_OT_ANIM_convert_to_cm3d2_interpolation(bpy.types.Operator):
239    bl_idname = 'anim.convert_to_cm3d2_interpolation'
240    bl_label = "Convert to CM3D2 Interpolation"
241    bl_description = "Convert keyframes to be compatible with CM3D2 Interpolation"
242    bl_options = {'REGISTER', 'UNDO'}
243
244    only_selected = bpy.props.BoolProperty(name="Only Selected", default=True)
245    items = [
246        ('FCURVES'  , "FCurves"  , "", 'FCURVE'  , 1),
247        ('KEYFRAMES', "KeyFrames", "", 'KEYFRAME', 2),
248    ]
249    selection_type = bpy.props.EnumProperty(items=items, name="Selection Type", default='FCURVES')
250
251    @classmethod
252    def poll(cls, context):
253        fcurves = context.editable_fcurves
254        if not fcurves:
255            return False
256        return len(fcurves) > 0
257    
258    def invoke(self, context, event):
259        fcurves = context.selected_editable_fcurves
260        if not fcurves:
261            fcurves = context.editable_fcurves
262            self.only_selected = False
263        if fcurves:
264            self.selection_type = 'FCURVES'
265            for fcurve in fcurves:
266                if check_fcurve_has_selected_keyframe(fcurve):
267                    self.selection_type = 'KEYFRAMES'
268                    break
269        return context.window_manager.invoke_props_dialog(self)
270    
271    def draw(self, context):
272        row = self.layout.row(align=True)
273        row.alignment = 'LEFT'
274        row.prop(self, 'only_selected')
275        column = row.column(align=True)
276        column.alignment = 'LEFT'
277        column.enabled = self.only_selected
278        column.prop(self, 'selection_type', text='')
279        
280
281    def execute(self, context):
282        if self.selection_type == 'FCURVES' and self.only_selected:
283            fcurves = context.selected_editable_fcurves
284        else:
285            fcurves = context.editable_fcurves
286
287        if self.selection_type == 'KEYFRAMES':
288            used_fcurves = []
289            for fcurve in fcurves:
290                if check_fcurve_has_selected_keyframe(fcurve):
291                    print(fcurve)
292                    used_fcurves.append(fcurve)
293            fcurves = used_fcurves
294
295        context.window_manager.progress_begin(0, len(fcurves))
296        for fcurve_index, fcurve in enumerate(fcurves):
297            override = context.copy()
298            override['active_editable_fcurve'] = fcurve
299            bpy.ops.fcurve.convert_to_cm3d2_interpolation(override, 'EXEC_REGION_WIN', only_selected=self.only_selected, keep_reports=True)
300            for kwargs in REPORTS:
301                self.report(**kwargs)
302            REPORTS.clear()
303            #has_reports = bpy.ops.fcurve.convert_to_cm3d2_interpolation.has_reports
304            context.window_manager.progress_update(fcurve_index)
305        return {'FINISHED'}
def check_fcurve_has_selected_keyframe(fcurve: bpy.types.FCurve) -> bool:
17def check_fcurve_has_selected_keyframe(fcurve: bpy.types.FCurve) -> bool:
18        for keyframe in fcurve.keyframe_points:
19            if keyframe.select_control_point or keyframe.select_left_handle or keyframe.select_right_handle:
20                return True
21        return False
REPORTS = []
@compat.BlRegister()
class CNV_OT_FCURVE_convert_to_cm3d2_interpolation(bpy_types.Operator):
 25@compat.BlRegister()
 26class CNV_OT_FCURVE_convert_to_cm3d2_interpolation(bpy.types.Operator):
 27    bl_idname = 'fcurve.convert_to_cm3d2_interpolation'
 28    bl_label = "Convert to CM3D2 Interpolation"
 29    bl_description = "Convert keyframes to be compatible with CM3D2 Interpolation"
 30    bl_options = {'REGISTER', 'UNDO'}
 31
 32    only_selected = bpy.props.BoolProperty(name="Only Selected", default=True)
 33    keep_reports  = bpy.props.BoolProperty(name="Keep Reports",  default=False, options={'HIDDEN'})
 34
 35    @classmethod
 36    def poll(cls, context):
 37        fcurve = context.active_editable_fcurve
 38        return fcurve
 39    
 40    def invoke(self, context, event):
 41        fcurve = context.active_editable_fcurve
 42        self.only_selected = check_fcurve_has_selected_keyframe(fcurve)
 43        return context.window_manager.invoke_props_dialog(self)
 44    
 45    def draw(self, context):
 46        self.layout.prop(self, 'only_selected')
 47
 48    def do_report(self, **kwargs):
 49        if self.keep_reports:
 50            REPORTS.append(kwargs)
 51        else:
 52            self.report(**kwargs)
 53
 54    @staticmethod
 55    def get_slope_vector(from_keyframe: bpy.types.Keyframe, to_keyframe: bpy.types.Keyframe, interpolation=None, backwards=None):
 56        if not to_keyframe:
 57            interpolation = 'BEZIER'
 58        else:
 59            if backwards == None:
 60                # Figure out which keyframe is in charge of controlling the slope
 61                backwards = to_keyframe.co[0] < from_keyframe.co[0]
 62            master_keyframe = to_keyframe if backwards else from_keyframe
 63            interpolation = interpolation or master_keyframe.interpolation
 64        
 65        if   interpolation == 'BEZIER':
 66            if backwards:
 67                slope_vec = mathutils.Vector(from_keyframe.handle_left ) - mathutils.Vector(from_keyframe.co)
 68            else:
 69                slope_vec = mathutils.Vector(from_keyframe.handle_right) - mathutils.Vector(from_keyframe.co)
 70        elif interpolation == 'LINEAR':
 71            slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 72        elif interpolation == 'CONSTANT':
 73            if backwards:
 74                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 75                slope_vec.x /= 3
 76            else:
 77                slope_vec = mathutils.Vector( (1, 0) )
 78        elif interpolation == 'CONSTANT':
 79            if backwards:
 80                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 81                slope_vec.x /= 3
 82            else:
 83                slope_vec = mathutils.Vector( (1, 0) )
 84        elif interpolation == 'CONSTANT':
 85            if backwards:
 86                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 87                slope_vec.x /= 3
 88            else:
 89                slope_vec = mathutils.Vector( (1, 0) )
 90        elif interpolation in {'SINE', 'QUAD', 'CUBIC', 'QUART', 'QUINT'}:#, 'EXPO', 'CIRC'}: # Easing by strength
 91            easing = 'EASE_IN' if master_keyframe.easing == 'AUTO' else master_keyframe.easing
 92            if (   (                  easing == 'EASE_IN_OUT' )
 93                or (not backwards and easing == 'EASE_IN'     )
 94                or (    backwards and easing == 'EASE_OUT'    )
 95            ):
 96                slope_vec = mathutils.Vector( (1, 0) )
 97            else:
 98                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 99                
100                strength = dict()
101                strength['SINE']  = math.sin( (1/3) * math.pi/2 )
102                strength['QUAD']  = strength['SINE']  ** (1/2)
103                strength['CUBIC'] = strength['QUAD']  ** (1/3)
104                strength['QUART'] = strength['CUBIC'] ** (1/4)
105                strength['QUINT'] = strength['QUART'] ** (1/5)
106                #strength['EXPO']  = strength['QUINT'] ** (1/math.e)
107                #strength['CIRC']  = strength['EXPO']  ** (1/math.pi)
108                
109                slope_vec.y *= strength[interpolation]
110
111                slope_vec.x /= 3
112        elif interpolation == 'BACK': # Dynamic easing
113            easing = 'EASE_IN' if master_keyframe.easing == 'AUTO' else master_keyframe.easing
114            if (   (                  easing == 'EASE_IN_OUT' )
115                or (not backwards and easing == 'EASE_IN'     )
116                or (    backwards and easing == 'EASE_OUT'    )
117            ):
118                slope_vec = mathutils.Vector( (1, 0) )
119            else:
120                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
121                # y = a + b(x - x_0) + c(x - x_0)^2 + d(x - x_0)^3
122                # dx = x_3 - x_0
123                # dy = y_3 - y_0
124                # y(x_0) = y_0 = a
125
126                # d = B+1
127                # c = -B
128
129                # y_3 = y_0 + b(dx) - B(dx)^2 + (B+1)dx^3
130                # y_3 - y_0 + B(dx)^2 - (B+1)dx^3 = b(dx)
131                # dy/dx + B(dx) - (B+1)dx^2 = b
132                # dy/dx + B(dx - dx^2) - dx^2  = b
133
134                # y_2 = (1/3)dx(c*dx+2b) + a
135                # y_2 = (1/3)dx((-B)*dx+2b) + y_0
136
137                # y_2 = (1/3)dx(c*dx) + y_0
138                dydx = slope_vec.y / slope_vec.x
139                b = dydx + master_keyframe.back * (slope_vec.x - slope_vec.x**2) - slope_vec.x**2
140                #slope_vec.y = (1/3) * slope_vec.x * ( -master_keyframe.back * slope_vec.x + 2 * b)
141                #slope_vec.y = (1/3) * slope_vec.x * ( -master_keyframe.back * slope_vec.x )
142                slope_vec.y = master_keyframe.back/3 + slope_vec.y
143                slope_vec.x /= 3
144
145                # d = 2.70158
146                # .33(-1.70158)
147            
148        else:
149            slope_vec = mathutils.Vector( (0, 0) )
150        
151        if slope_vec.x != 0:
152            slope_vec *= 1 / slope_vec.x
153
154        return slope_vec
155        
156
157    def execute(self, context):
158        factor = 3
159
160        fcurve = context.active_editable_fcurve
161        fcurve.update()
162        
163        this_interpolation = None
164        last_interpolation = None
165
166        found_unsupported = set()
167
168        for keyframe_index in range(len(fcurve.keyframe_points)):
169            this_keyframe = fcurve.keyframe_points[keyframe_index  ]
170            next_keyframe = fcurve.keyframe_points[keyframe_index+1] if keyframe_index+1 < len(fcurve.keyframe_points) else None
171            last_keyframe = fcurve.keyframe_points[keyframe_index-1] if keyframe_index-1 >= 0                          else None
172
173            last_interpolation = this_interpolation
174            this_interpolation = this_keyframe.interpolation
175
176            apply_in  = bool(
177                ( last_keyframe and last_keyframe.interpolation == 'BEZIER' and this_keyframe.select_left_handle ) 
178                or this_keyframe.select_control_point
179                or not self.only_selected
180            )
181            apply_out = bool(
182                ( this_interpolation == 'BEZIER' and this_keyframe.select_right_handle )
183                or this_keyframe.select_control_point 
184                or not self.only_selected
185            )
186
187            #print(keyframe_index, this_keyframe.select_left_handle, this_keyframe.select_right_handle, this_keyframe.select_control_point, self.only_selected, " | ", apply_in, apply_out)
188            if not apply_in and not apply_out:
189                continue
190
191            this_co = mathutils.Vector(this_keyframe.co)
192            next_co = mathutils.Vector(next_keyframe.co) if next_keyframe else None
193            last_co = mathutils.Vector(last_keyframe.co) if last_keyframe else None
194
195            vec_in  = self.get_slope_vector(this_keyframe, last_keyframe, last_interpolation, backwards=True )
196            vec_out = self.get_slope_vector(this_keyframe, next_keyframe, this_interpolation, backwards=False)
197            if vec_in.x  == 0:
198                apply_in = False
199                found_unsupported.add(last_interpolation)
200            if vec_out.x == 0:
201                apply_out = False
202                found_unsupported.add(this_interpolation)
203            elif apply_out:
204                if this_keyframe.interpolation != 'BEZIER':
205                    this_keyframe.interpolation = 'BEZIER'
206                    if next_keyframe:
207                        next_keyframe.select_left_handle = True
208
209
210            if vec_in.y != vec_out.y: #and not (this_keyframe.handle_left_type == 'ALIGNED' and this_keyframe.handle_right_type == 'ALIGNED'):
211                handle_type = 'FREE'
212            else:
213                handle_type = 'ALIGNED'
214
215            dist_in  = (last_co.x - this_co.x) / factor if last_co else this_keyframe.handle_left [0] - this_co.x
216            dist_out = (next_co.x - this_co.x) / factor if next_co else this_keyframe.handle_right[0] - this_co.x
217            
218            if apply_in:
219                this_keyframe.handle_left_type  = handle_type
220                this_keyframe.handle_left       = vec_in  * dist_in  + this_co
221            if apply_out:
222                this_keyframe.handle_right_type = handle_type
223                this_keyframe.handle_right      = vec_out * dist_out + this_co
224
225        
226        if found_unsupported:
227            for interpolation_type in found_unsupported:
228                self.do_report(type={'INFO'}, message=f_tip_("'{interpolation}' interpolation not convertable", interpolation=interpolation_type))
229            self.do_report(type={'WARNING'}, message="Found {count} unsupported interpolation type(s) in {id_data}'s FCurve {fcurve_path}[{fcurve_index}]. See log for more info.".format(
230                count        = len(found_unsupported),
231                id_data      = fcurve.id_data.name   ,
232                fcurve_path  = fcurve.data_path      ,
233                fcurve_index = fcurve.array_index    )
234            )
235
236        return {'FINISHED'}
bl_idname = 'fcurve.convert_to_cm3d2_interpolation'
bl_label = 'Convert to CM3D2 Interpolation'
bl_description = 'Convert keyframes to be compatible with CM3D2 Interpolation'
bl_options = {'REGISTER', 'UNDO'}
only_selected: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Only Selected', 'default': True, 'attr': 'only_selected'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Only Selected', 'default': True, 'attr': 'only_selected'}>
keep_reports: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Keep Reports', 'default': False, 'options': {'HIDDEN'}, 'attr': 'keep_reports'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Keep Reports', 'default': False, 'options': {'HIDDEN'}, 'attr': 'keep_reports'}>
@classmethod
def poll(cls, context):
35    @classmethod
36    def poll(cls, context):
37        fcurve = context.active_editable_fcurve
38        return fcurve
def invoke(self, context, event):
40    def invoke(self, context, event):
41        fcurve = context.active_editable_fcurve
42        self.only_selected = check_fcurve_has_selected_keyframe(fcurve)
43        return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
45    def draw(self, context):
46        self.layout.prop(self, 'only_selected')
def do_report(self, **kwargs):
48    def do_report(self, **kwargs):
49        if self.keep_reports:
50            REPORTS.append(kwargs)
51        else:
52            self.report(**kwargs)
@staticmethod
def get_slope_vector( from_keyframe: bpy.types.Keyframe, to_keyframe: bpy.types.Keyframe, interpolation=None, backwards=None):
 54    @staticmethod
 55    def get_slope_vector(from_keyframe: bpy.types.Keyframe, to_keyframe: bpy.types.Keyframe, interpolation=None, backwards=None):
 56        if not to_keyframe:
 57            interpolation = 'BEZIER'
 58        else:
 59            if backwards == None:
 60                # Figure out which keyframe is in charge of controlling the slope
 61                backwards = to_keyframe.co[0] < from_keyframe.co[0]
 62            master_keyframe = to_keyframe if backwards else from_keyframe
 63            interpolation = interpolation or master_keyframe.interpolation
 64        
 65        if   interpolation == 'BEZIER':
 66            if backwards:
 67                slope_vec = mathutils.Vector(from_keyframe.handle_left ) - mathutils.Vector(from_keyframe.co)
 68            else:
 69                slope_vec = mathutils.Vector(from_keyframe.handle_right) - mathutils.Vector(from_keyframe.co)
 70        elif interpolation == 'LINEAR':
 71            slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 72        elif interpolation == 'CONSTANT':
 73            if backwards:
 74                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 75                slope_vec.x /= 3
 76            else:
 77                slope_vec = mathutils.Vector( (1, 0) )
 78        elif interpolation == 'CONSTANT':
 79            if backwards:
 80                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 81                slope_vec.x /= 3
 82            else:
 83                slope_vec = mathutils.Vector( (1, 0) )
 84        elif interpolation == 'CONSTANT':
 85            if backwards:
 86                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 87                slope_vec.x /= 3
 88            else:
 89                slope_vec = mathutils.Vector( (1, 0) )
 90        elif interpolation in {'SINE', 'QUAD', 'CUBIC', 'QUART', 'QUINT'}:#, 'EXPO', 'CIRC'}: # Easing by strength
 91            easing = 'EASE_IN' if master_keyframe.easing == 'AUTO' else master_keyframe.easing
 92            if (   (                  easing == 'EASE_IN_OUT' )
 93                or (not backwards and easing == 'EASE_IN'     )
 94                or (    backwards and easing == 'EASE_OUT'    )
 95            ):
 96                slope_vec = mathutils.Vector( (1, 0) )
 97            else:
 98                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
 99                
100                strength = dict()
101                strength['SINE']  = math.sin( (1/3) * math.pi/2 )
102                strength['QUAD']  = strength['SINE']  ** (1/2)
103                strength['CUBIC'] = strength['QUAD']  ** (1/3)
104                strength['QUART'] = strength['CUBIC'] ** (1/4)
105                strength['QUINT'] = strength['QUART'] ** (1/5)
106                #strength['EXPO']  = strength['QUINT'] ** (1/math.e)
107                #strength['CIRC']  = strength['EXPO']  ** (1/math.pi)
108                
109                slope_vec.y *= strength[interpolation]
110
111                slope_vec.x /= 3
112        elif interpolation == 'BACK': # Dynamic easing
113            easing = 'EASE_IN' if master_keyframe.easing == 'AUTO' else master_keyframe.easing
114            if (   (                  easing == 'EASE_IN_OUT' )
115                or (not backwards and easing == 'EASE_IN'     )
116                or (    backwards and easing == 'EASE_OUT'    )
117            ):
118                slope_vec = mathutils.Vector( (1, 0) )
119            else:
120                slope_vec = mathutils.Vector(to_keyframe.co) - mathutils.Vector(from_keyframe.co)
121                # y = a + b(x - x_0) + c(x - x_0)^2 + d(x - x_0)^3
122                # dx = x_3 - x_0
123                # dy = y_3 - y_0
124                # y(x_0) = y_0 = a
125
126                # d = B+1
127                # c = -B
128
129                # y_3 = y_0 + b(dx) - B(dx)^2 + (B+1)dx^3
130                # y_3 - y_0 + B(dx)^2 - (B+1)dx^3 = b(dx)
131                # dy/dx + B(dx) - (B+1)dx^2 = b
132                # dy/dx + B(dx - dx^2) - dx^2  = b
133
134                # y_2 = (1/3)dx(c*dx+2b) + a
135                # y_2 = (1/3)dx((-B)*dx+2b) + y_0
136
137                # y_2 = (1/3)dx(c*dx) + y_0
138                dydx = slope_vec.y / slope_vec.x
139                b = dydx + master_keyframe.back * (slope_vec.x - slope_vec.x**2) - slope_vec.x**2
140                #slope_vec.y = (1/3) * slope_vec.x * ( -master_keyframe.back * slope_vec.x + 2 * b)
141                #slope_vec.y = (1/3) * slope_vec.x * ( -master_keyframe.back * slope_vec.x )
142                slope_vec.y = master_keyframe.back/3 + slope_vec.y
143                slope_vec.x /= 3
144
145                # d = 2.70158
146                # .33(-1.70158)
147            
148        else:
149            slope_vec = mathutils.Vector( (0, 0) )
150        
151        if slope_vec.x != 0:
152            slope_vec *= 1 / slope_vec.x
153
154        return slope_vec
def execute(self, context):
157    def execute(self, context):
158        factor = 3
159
160        fcurve = context.active_editable_fcurve
161        fcurve.update()
162        
163        this_interpolation = None
164        last_interpolation = None
165
166        found_unsupported = set()
167
168        for keyframe_index in range(len(fcurve.keyframe_points)):
169            this_keyframe = fcurve.keyframe_points[keyframe_index  ]
170            next_keyframe = fcurve.keyframe_points[keyframe_index+1] if keyframe_index+1 < len(fcurve.keyframe_points) else None
171            last_keyframe = fcurve.keyframe_points[keyframe_index-1] if keyframe_index-1 >= 0                          else None
172
173            last_interpolation = this_interpolation
174            this_interpolation = this_keyframe.interpolation
175
176            apply_in  = bool(
177                ( last_keyframe and last_keyframe.interpolation == 'BEZIER' and this_keyframe.select_left_handle ) 
178                or this_keyframe.select_control_point
179                or not self.only_selected
180            )
181            apply_out = bool(
182                ( this_interpolation == 'BEZIER' and this_keyframe.select_right_handle )
183                or this_keyframe.select_control_point 
184                or not self.only_selected
185            )
186
187            #print(keyframe_index, this_keyframe.select_left_handle, this_keyframe.select_right_handle, this_keyframe.select_control_point, self.only_selected, " | ", apply_in, apply_out)
188            if not apply_in and not apply_out:
189                continue
190
191            this_co = mathutils.Vector(this_keyframe.co)
192            next_co = mathutils.Vector(next_keyframe.co) if next_keyframe else None
193            last_co = mathutils.Vector(last_keyframe.co) if last_keyframe else None
194
195            vec_in  = self.get_slope_vector(this_keyframe, last_keyframe, last_interpolation, backwards=True )
196            vec_out = self.get_slope_vector(this_keyframe, next_keyframe, this_interpolation, backwards=False)
197            if vec_in.x  == 0:
198                apply_in = False
199                found_unsupported.add(last_interpolation)
200            if vec_out.x == 0:
201                apply_out = False
202                found_unsupported.add(this_interpolation)
203            elif apply_out:
204                if this_keyframe.interpolation != 'BEZIER':
205                    this_keyframe.interpolation = 'BEZIER'
206                    if next_keyframe:
207                        next_keyframe.select_left_handle = True
208
209
210            if vec_in.y != vec_out.y: #and not (this_keyframe.handle_left_type == 'ALIGNED' and this_keyframe.handle_right_type == 'ALIGNED'):
211                handle_type = 'FREE'
212            else:
213                handle_type = 'ALIGNED'
214
215            dist_in  = (last_co.x - this_co.x) / factor if last_co else this_keyframe.handle_left [0] - this_co.x
216            dist_out = (next_co.x - this_co.x) / factor if next_co else this_keyframe.handle_right[0] - this_co.x
217            
218            if apply_in:
219                this_keyframe.handle_left_type  = handle_type
220                this_keyframe.handle_left       = vec_in  * dist_in  + this_co
221            if apply_out:
222                this_keyframe.handle_right_type = handle_type
223                this_keyframe.handle_right      = vec_out * dist_out + this_co
224
225        
226        if found_unsupported:
227            for interpolation_type in found_unsupported:
228                self.do_report(type={'INFO'}, message=f_tip_("'{interpolation}' interpolation not convertable", interpolation=interpolation_type))
229            self.do_report(type={'WARNING'}, message="Found {count} unsupported interpolation type(s) in {id_data}'s FCurve {fcurve_path}[{fcurve_index}]. See log for more info.".format(
230                count        = len(found_unsupported),
231                id_data      = fcurve.id_data.name   ,
232                fcurve_path  = fcurve.data_path      ,
233                fcurve_index = fcurve.array_index    )
234            )
235
236        return {'FINISHED'}
bl_rna = <bpy_struct, Struct("FCURVE_OT_convert_to_cm3d2_interpolation")>
Inherited Members
bpy_types.Operator
as_keywords
poll_message_set
builtins.bpy_struct
keys
values
items
get
pop
as_pointer
keyframe_insert
keyframe_delete
driver_add
driver_remove
is_property_set
property_unset
is_property_hidden
is_property_readonly
is_property_overridable_library
property_overridable_library_set
path_resolve
path_from_id
type_recast
bl_rna_get_subclass_py
bl_rna_get_subclass
id_properties_ensure
id_properties_clear
id_properties_ui
id_data
@compat.BlRegister()
class CNV_OT_ANIM_convert_to_cm3d2_interpolation(bpy_types.Operator):
238@compat.BlRegister()
239class CNV_OT_ANIM_convert_to_cm3d2_interpolation(bpy.types.Operator):
240    bl_idname = 'anim.convert_to_cm3d2_interpolation'
241    bl_label = "Convert to CM3D2 Interpolation"
242    bl_description = "Convert keyframes to be compatible with CM3D2 Interpolation"
243    bl_options = {'REGISTER', 'UNDO'}
244
245    only_selected = bpy.props.BoolProperty(name="Only Selected", default=True)
246    items = [
247        ('FCURVES'  , "FCurves"  , "", 'FCURVE'  , 1),
248        ('KEYFRAMES', "KeyFrames", "", 'KEYFRAME', 2),
249    ]
250    selection_type = bpy.props.EnumProperty(items=items, name="Selection Type", default='FCURVES')
251
252    @classmethod
253    def poll(cls, context):
254        fcurves = context.editable_fcurves
255        if not fcurves:
256            return False
257        return len(fcurves) > 0
258    
259    def invoke(self, context, event):
260        fcurves = context.selected_editable_fcurves
261        if not fcurves:
262            fcurves = context.editable_fcurves
263            self.only_selected = False
264        if fcurves:
265            self.selection_type = 'FCURVES'
266            for fcurve in fcurves:
267                if check_fcurve_has_selected_keyframe(fcurve):
268                    self.selection_type = 'KEYFRAMES'
269                    break
270        return context.window_manager.invoke_props_dialog(self)
271    
272    def draw(self, context):
273        row = self.layout.row(align=True)
274        row.alignment = 'LEFT'
275        row.prop(self, 'only_selected')
276        column = row.column(align=True)
277        column.alignment = 'LEFT'
278        column.enabled = self.only_selected
279        column.prop(self, 'selection_type', text='')
280        
281
282    def execute(self, context):
283        if self.selection_type == 'FCURVES' and self.only_selected:
284            fcurves = context.selected_editable_fcurves
285        else:
286            fcurves = context.editable_fcurves
287
288        if self.selection_type == 'KEYFRAMES':
289            used_fcurves = []
290            for fcurve in fcurves:
291                if check_fcurve_has_selected_keyframe(fcurve):
292                    print(fcurve)
293                    used_fcurves.append(fcurve)
294            fcurves = used_fcurves
295
296        context.window_manager.progress_begin(0, len(fcurves))
297        for fcurve_index, fcurve in enumerate(fcurves):
298            override = context.copy()
299            override['active_editable_fcurve'] = fcurve
300            bpy.ops.fcurve.convert_to_cm3d2_interpolation(override, 'EXEC_REGION_WIN', only_selected=self.only_selected, keep_reports=True)
301            for kwargs in REPORTS:
302                self.report(**kwargs)
303            REPORTS.clear()
304            #has_reports = bpy.ops.fcurve.convert_to_cm3d2_interpolation.has_reports
305            context.window_manager.progress_update(fcurve_index)
306        return {'FINISHED'}
bl_idname = 'anim.convert_to_cm3d2_interpolation'
bl_label = 'Convert to CM3D2 Interpolation'
bl_description = 'Convert keyframes to be compatible with CM3D2 Interpolation'
bl_options = {'REGISTER', 'UNDO'}
only_selected: <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Only Selected', 'default': True, 'attr': 'only_selected'}> = <_PropertyDeferred, <built-in function BoolProperty>, {'name': 'Only Selected', 'default': True, 'attr': 'only_selected'}>
items = [('FCURVES', 'FCurves', '', 'FCURVE', 1), ('KEYFRAMES', 'KeyFrames', '', 'KEYFRAME', 2)]
selection_type: <_PropertyDeferred, <built-in function EnumProperty>, {'items': [('FCURVES', 'FCurves', '', 'FCURVE', 1), ('KEYFRAMES', 'KeyFrames', '', 'KEYFRAME', 2)], 'name': 'Selection Type', 'default': 'FCURVES', 'attr': 'selection_type'}> = <_PropertyDeferred, <built-in function EnumProperty>, {'items': [('FCURVES', 'FCurves', '', 'FCURVE', 1), ('KEYFRAMES', 'KeyFrames', '', 'KEYFRAME', 2)], 'name': 'Selection Type', 'default': 'FCURVES', 'attr': 'selection_type'}>
@classmethod
def poll(cls, context):
252    @classmethod
253    def poll(cls, context):
254        fcurves = context.editable_fcurves
255        if not fcurves:
256            return False
257        return len(fcurves) > 0
def invoke(self, context, event):
259    def invoke(self, context, event):
260        fcurves = context.selected_editable_fcurves
261        if not fcurves:
262            fcurves = context.editable_fcurves
263            self.only_selected = False
264        if fcurves:
265            self.selection_type = 'FCURVES'
266            for fcurve in fcurves:
267                if check_fcurve_has_selected_keyframe(fcurve):
268                    self.selection_type = 'KEYFRAMES'
269                    break
270        return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
272    def draw(self, context):
273        row = self.layout.row(align=True)
274        row.alignment = 'LEFT'
275        row.prop(self, 'only_selected')
276        column = row.column(align=True)
277        column.alignment = 'LEFT'
278        column.enabled = self.only_selected
279        column.prop(self, 'selection_type', text='')
def execute(self, context):
282    def execute(self, context):
283        if self.selection_type == 'FCURVES' and self.only_selected:
284            fcurves = context.selected_editable_fcurves
285        else:
286            fcurves = context.editable_fcurves
287
288        if self.selection_type == 'KEYFRAMES':
289            used_fcurves = []
290            for fcurve in fcurves:
291                if check_fcurve_has_selected_keyframe(fcurve):
292                    print(fcurve)
293                    used_fcurves.append(fcurve)
294            fcurves = used_fcurves
295
296        context.window_manager.progress_begin(0, len(fcurves))
297        for fcurve_index, fcurve in enumerate(fcurves):
298            override = context.copy()
299            override['active_editable_fcurve'] = fcurve
300            bpy.ops.fcurve.convert_to_cm3d2_interpolation(override, 'EXEC_REGION_WIN', only_selected=self.only_selected, keep_reports=True)
301            for kwargs in REPORTS:
302                self.report(**kwargs)
303            REPORTS.clear()
304            #has_reports = bpy.ops.fcurve.convert_to_cm3d2_interpolation.has_reports
305            context.window_manager.progress_update(fcurve_index)
306        return {'FINISHED'}
bl_rna = <bpy_struct, Struct("ANIM_OT_convert_to_cm3d2_interpolation")>
Inherited Members
bpy_types.Operator
as_keywords
poll_message_set
builtins.bpy_struct
keys
values
get
pop
as_pointer
keyframe_insert
keyframe_delete
driver_add
driver_remove
is_property_set
property_unset
is_property_hidden
is_property_readonly
is_property_overridable_library
property_overridable_library_set
path_resolve
path_from_id
type_recast
bl_rna_get_subclass_py
bl_rna_get_subclass
id_properties_ensure
id_properties_clear
id_properties_ui
id_data