| __author__ = 'thoth'
import bpy
import random
def create_sample_fcurve(obj):
    obj.animation_data_clear()
    obj.animation_data_create()
    action = bpy.data.actions.new("demo")
    obj.animation_data.action = action
    fcurve1 = action.fcurves.new(data_path="location", index=1)
    fcurve2 = action.fcurves.new(data_path="location", index=2)
    offset = 0
    n_keyframes = 10
    fcurve1.keyframe_points.add(n_keyframes)
    fcurve2.keyframe_points.add(n_keyframes)
    time_scale = 10
    for i in range(n_keyframes):
        kp1 = fcurve1.keyframe_points[i]
        kp2 = fcurve2.keyframe_points[i]
        kp1.co = (i* time_scale, random.random())
        kp1.handle_left = ( (i-random.random()) * time_scale, random.random()*0.5)
        kp1.handle_right = ( (i+random.random()) * time_scale, random.random()*0.5)
        kp1.handle_left_type = 'FREE'
        kp1.handle_right_type = 'FREE'
        kp2.co = (kp1.co[0], kp1.co[1]+ offset)
        kp2.handle_left = (kp1.handle_left[0], kp1.handle_left[1]+ offset)
        kp2.handle_right = (kp1.handle_right[0], kp1.handle_right[1]+ offset)
        kp2.handle_left_type = 'FREE'
        kp2.handle_right_type = 'FREE'
#
def correct_bezpart(p):
    if p[1].x <= p[2].x:
        return p
    scale = (p[3].x - p[0].x) / ( (p[1].x - p[0].x) + (p[3].x - p[2].x) )
    p = list(p)
    p[1] = p[0] + scale * (p[1]-p[0])
    p[2] = p[3] + scale * (p[2]-p[3])
    return p
def interp(v0, t, v1):
    return (1-t)*v0 + t*v1
def de_casteljeu( p , t):
    q = [0,0,0,0]
    r = [0,0,0,0]
    q[0] = p[0]
    r[3] = p[3]
    q[1] = interp(p[0], t, p[1])
    x    = interp(p[1], t, p[2])
    r[2] = interp(p[2], t, p[3])
    #print([ r[2], '= interp(', p[2], t, p[3], ")" ])
    q[2] = interp(q[1], t, x)
    r[1] = interp(x, t, r[2])
    q[3] = r[0] = interp(q[2], t, r[1])
    return (q,r)
def subdivide_fcurve(fc, frame):
    orig_keyframe_count = len(fc.keyframe_points)
    fc.keyframe_points.add(1) # do this before we have any keyframe_points[i] references that would be invalidated
    kp9 = fc.keyframe_points[orig_keyframe_count]
    for i in range(1, orig_keyframe_count):
        kp0 = fc.keyframe_points[i-1]
        kp1 = fc.keyframe_points[i]
        if (kp1.co.x >=frame):
            break
    p = (kp0.co, kp0.handle_right, kp1.handle_left, kp1.co)
    p = correct_bezpart(p)
    t = tForFrame(frame, p[0].x, p[1].x, p[2].x, p[3].x)
    q,r = de_casteljeu(p,t)
    kp0.handle_right_type='FREE'
    kp9.handle_left_type='FREE'
    kp9.handle_right_type='FREE'
    kp1.handle_left_type='FREE'
    kp0.handle_right = q[1]
    kp9.handle_left = q[2]
    kp9.co = q[3]
    kp9.handle_right = r[1]
    kp1.handle_left = r[2]
    fc.update()
#
def bez_root_score(t):
    return max(abs(t.imag), 0-t.real, t.real-1)
def favorite_root(roots):
    """ Since we are confident that at least one of the roots is real, pick the one that has the imaginary component closest to zero
    """
    fav = roots[0]
    score = bez_root_score(fav)
    for i in range(1, len(roots)):
        if bez_root_score(roots[i]) < score:
            fav = roots[i]
            score = bez_root_score(roots[i])
    return fav
def tForFrame(fr, p0, p1, p2, p3):
    """
    fr = (1-t)**3 *p0 + 3*(1-t)**2 *t *p1 + 3*(1-t)* t**2 * p2 + t**3 *p3
    """
    import numpy
    coefficients = [
        -p0+3*p1-3*p2+p3,
        3*p0-6*p1+3*p2,
        -3*p0+3*p1,
        p0-fr
    ]
    roots = numpy.roots(coefficients)
    #print(roots)
    rval = favorite_root(roots).real
    sanity = bez(rval, p0,p1,p2,p3)
    if abs(sanity-fr) >1e-6:
        print(["defective", sanity, fr])
        print(roots)
        print([rval, "for", fr, p0, p1,p2,p3])
    if rval<0 or rval>1:
        print(["wiggy, bez(",rval,p0, p1,p2,p3,") = ",fr])
        print(roots)
        print([rval, "for", fr, p0, p1,p2,p3])
    return rval
def bez(t, p0, p1, p2, p3):
    s = 1-t
    return s*s*s*p0 + 3*s*s*t*p1 + 3*s*t*t*p2 + t*t*t*p3
def subdivide_fcurves(fc):
    import math
    idx = len(fc.keyframe_points)
    #for kp in fc.keyframe_points:
    #    print(kp.co)
    fc.keyframe_points.add(idx-1)
    #for i in range(idx):
    #    print(fc.keyframe_points[i].co)
    for i in range(idx-1):
        kp9 = fc.keyframe_points[i+idx]
        kp0 = fc.keyframe_points[i]
        kp1 = fc.keyframe_points[i+1]
        p = (kp0.co, kp0.handle_right, kp1.handle_left, kp1.co)
        p = correct_bezpart(p)
        #print([ p[0], p[3]])
        if False:
            t = random.random() * 0.4 + 0.3
        else:
            fr = math.floor(p[0].x * 0.35 + p[3].x*0.65)
            t = tForFrame(fr, p[0].x, p[1].x, p[2].x, p[3].x)
            #print(["frame",fr, "t=",t])
        (q,r) = de_casteljeu(p, t)
        kp9.handle_left_type='FREE'
        kp9.handle_right_type='FREE'
        kp0.handle_right = q[1]
        kp9.handle_left = q[2]
        kp9.co = q[3]
        kp9.handle_right = r[1]
        kp1.handle_left = r[2]
        #print(p)
        #print(q)
        #print(r)
        #print( [ p[0], q[3], p[3]])
    fc.update()
#
def mission1(obj):
    create_sample_fcurve(obj)
    subdivide_fcurves(obj.animation_data.action.fcurves[1])
def mission2(obj):
    create_sample_fcurve(obj)
    subdivide_fcurve(obj.animation_data.action.fcurves[1], 33)
#
#
random.seed=4262
mission2(bpy.context.active_object)
 | 
Blender python API quick-start
Syntax highlighting by Pygments.