copy paste a f-curve driver

Hi everyone,

I’m looking for a way to copy past with python some f-curve driver… I can’t find the way to do it for now.

I found
bpy.ops.anim.copy_driver_button()

but first, I don’t know you it works, and second, I’m not sure that it is the best solution…

Thank you for your help.

Mathias.

Nobody? - is it possible?

maybe instead of copying the whole driver at once, I need to copy all the component of the driver, how do I get the variable in the driver and how do I copy the f-curve?

thank you.
Mathias.

It is possible, I’m sure and I think that I’d be able to do it, but I’m afraid it’s not a one-liner solution.
The operator you mentioned won’t work in this case.
I think it’s necessary to store all properties of the driver, then use them in newly created driver.
I will try to create a code for this, but I need to try something. Give me some time, I’ll try to figure it out.

thanks BartekShorupa!

don’t worry about the time, I can wait!

here is a place I found some info about the drivers (for a shape key):
bpy.data.shape_keys[‘Key’].animation_data.drivers[0].driver

but I cannot find the way to retrive the variables and the f-curve info.

Thank you :slight_smile:

Mathias.

Hi mathiasA,

in speaker_tools I was running an operator from a button by overriding the view docs operator. The copy_driver_button operator may be usable by working out the context from the button and overriding in a call.

Here is some code to copy a driver.


import bpy


def copy_driver(from_fcurve, target_fcurve):
    td = target_fcurve.driver
    fd = from_fcurve.driver
    td.type = fd.type
    td.expression = fd.expression
    for var in fd.variables:
        v = td.variables.new()
        v.name = var.name
        v.type = var.type
        for i,target in var.targets.items():
            v.targets[i].id = target.id_data
            
            
bpy.app.driver_namespace["copydriver"] = copy_driver

added the method to the driver namespace to test in console

Here I add a driver to location.y of the context.object which is a copy of the first driver fcurve (C.object.drivers[0])

The new driver fcurve is created, then the method is used to copy the attributes of an existing driver.




>>> cd = bpy.app.driver_namespace['copydriver']
>>> drivers = C.object.animation_data.drivers
>>> len(drivers)
16


>>> new_driver = C.object.driver_add('location',1)
>>> driver = drivers[0]
>>> cd(driver, new_driver)
>>> 


thanks batFinger, I will check that! :slight_smile:

Mathias.

I was able to make it work, the only thing is that I wasn’t able to copy past the keyframes I used for the driver.

But I was definitely able to go further with your help BatFINGER
Thank you!

Mathias.

ok, I’m almost there. I’m able to find all info about the f-curve and store the keyframes info… my only problem now is to create the f-curve (adding keyframe) for the driver.

bpy.data.objects[‘Rene_obj’].animation_data.drivers[0].keyframe_insert(‘location’, index=0, frame=0, group="")
doesn’t work, even for the driver for the X location… can’t find the location

and if I do
bpy.data.objects[‘Rene_obj’].keyframe_insert(‘location’, index=0, frame=0, group="")
it creates a keyframe on the transfrom x loc but not on the driver

so it seems that the data_path for the f-curve drivers are a bit hidden.

If someone can help I would appreciate it!

Thanks
Mathias.

I’m confused as to what you are after here. Drivers don’t have keyframes. Some or all of the target variables may be keyframed.
If the fcurve has keyframes it is an action.

Here is the console output for a driver on the location.x fcurve of default Cube.


>>> driven_fcurve = C.object.animation_data.drivers[0]
>>> driven_fcurve.data_path
'location'


>>> driven_fcurve.array_index
0


>>> driven_fcurve.id_data
bpy.data.objects['Cube']



The type of driver, expression variables are in


>>> driven_fcurve.driver.expression
'0.000'


>>> driven_fcurve.driver.type
'SCRIPTED'


>>> 

a good example of this is my previous post.

If you are trying to “copy” the driver as an fcurve

In speaker_tools I use this bit of code to “bake” an action from a driver, that is it changes the property from being driven by a driver to being keyframed as an action.

(from eqmenu.py in speaker_tools

driver is the fcurve part of a driver eg driven_fcurve above
which, in this case, is being driven by an action that is baked from a sound file.)


            while frame <= frame_end:
                scene.frame_set(frame)
                # quick fix try array, then without
                #print(type(driver.id_data))
                try:
                    driver.id_data.keyframe_insert(driver.data_path,
                                               index=driver.array_index)
                except:
                    driver.id_data.keyframe_insert(driver.data_path)
                finally:
                    frame = frame + 1

The keyframed action takes precedence in blender over the driver so the driver doesn’t need to be removed. Removing the action fcurve will re-expose the driver.

I attached an example with the driver and the curve I’m speaking about. And the script I have so far (with your help batFINGER). you can see the curve in the driver section, but when I try to create the same curve for the new driver, I can’t find the data_path to insert the keyframe. As it is, the script create the keyframe directly in the location.

I hope with this example it will make more sens.

Thank you.
Mathias.

Attachments

exemple.blend (471 KB)

Hmm ok,

is this created from old IPO curves, anyhow it’s a new one on me. Can you explain what the result is? it appears to be a combination of the driver fcurve and the variable input.

To copy the keyframe_points try


new_fcurve_driver.keyframe_points.add(numberofpointsinoriginal)
for point in driver.keyframe_points:
    point.co = originalpoint.co

batFINGER you are a genius! thanks it works, with this code, it recreate exactly the same curve:

    if len(fc.keyframe_points)>0:
        dest_anim.drivers[0].keyframe_points.add(count=len(fc.keyframe_points))
        i=0
        for key in dest_anim.drivers[0].keyframe_points:
            key.co = fc.keyframe_points[i].co
            key.interpolation = fc.keyframe_points[i].interpolation
            key.handle_left = fc.keyframe_points[i].handle_left
            key.handle_left_type = fc.keyframe_points[i].handle_left_type
            key.handle_right = fc.keyframe_points[i].handle_right
            key.handle_right_type = fc.keyframe_points[i].handle_right_type            
            i+=1   

the purpose is to have a bit more control on how the object react to it’s driver. For example if you want to connect a keyshape of a bicep contracting when you bend the arm, if you have a linear driver, it won’t look good. with a curve, you can create some ease in and ease out so that the bicep won’t contract too much at the beginning of the bending but the more you get closer to the 180deg and the more you can have the bicep get bigger.

I hope it makes sens.

Cool.

Just a small note


for i, key in blah.keyframe_points.items():

is a nice way to enumerate.

yes, I was looking at it and tried to use it. It was working fine when it was a driver for transform, but I got error when it was a driver for shapekey… I don’t know why, but I’m going to look at it!

thank you again batFinger

in fact using it now, everything works fine.

is there a way to simplify the code the same way than the variables for the modifiers?

here is the variables script that you gave me batFINGER:

    for var in fd.variables:
        v = td.variables.new()
        v.name = var.name
        v.type = var.type
                
        for i,target in var.targets.items():
             v.targets[i].id = target.id_data

Is there a way to do the same targets.items() for modifiers


    ### copy modifier from original        
    for mod in fc.modifiers:
        m = destination.animation_data.drivers[num_dr-1].modifiers.new(mod.type)

and for the keyframe info to replace all of these lines


        tc.keyframe_points.add(count=len(fc.keyframe_points))
        for i,key in tc.keyframe_points.items():
            key.co = fc.keyframe_points[i].co
            key.interpolation = fc.keyframe_points[i].interpolation
            key.handle_left = fc.keyframe_points[i].handle_left
            key.handle_left_type = fc.keyframe_points[i].handle_left_type
            key.handle_right = fc.keyframe_points[i].handle_right
            key.handle_right_type = fc.keyframe_points[i].handle_right_type  

Just want to make my code a bit more simple. :slight_smile: thank you.
Mathias.

I think I was confused with the items function. I thought it was looking at each parameters of a structure instead of needing a line for each one of them, but now I understand it’s just looking into an array.

is there a way to do that?

example
instead of:


tc.keyframe_points.add(count=len(fc.keyframe_points))         
   for i,key in tc.keyframe_points.items():             
      key.co = fc.keyframe_points[i].co             
      key.interpolation = fc.keyframe_points[i].interpolation             
      key.handle_left = fc.keyframe_points[i].handle_left             
      key.handle_left_type = fc.keyframe_points[i].handle_left_type             
      key.handle_right = fc.keyframe_points[i].handle_right             
      key.handle_right_type = fc.keyframe_points[i].handle_right_type

having:


tc.keyframe_points.add(count=len(fc.keyframe_points))         
    for i,key in tc.keyframe_points.items():              
        for j, parameter in key[i].parameters:
            parameter = tc.keyframe_points[i].parameters[j]

I’m wondering whether it may be simpler to copy the original object with its animation_data (there is a flag to turn this on or off I cant remember where off the top of my head,) and then change the underlying data. A quick test in the console and it appears this could be feasible, either using bpy.ops.object.duplicate() or object.copy().

Another suggestion is to use setattr


>>> setattr(
setattr(object, name, value)
Set a named attribute on an object; setattr(x, 'y', v) is equivalent to
``x.y = v''.

and the property list (the properties for an envelope modifier)


>>> fc.modifiers[0].bl_rna.properties.keys()
['rna_type', 'type', 'show_expanded', 'mute', 'is_valid', 'active', 'use_restricted_range', 'frame_start', 'frame_end', 'blend_in', 'blend_out', 'use_influence', 'influence', 'control_points', 'reference_value', 'default_min', 'default_max']


>>> 

For the modifiers you might require a switch statement or something, since different mod types have different props, also some features may not be available via the API, like envelope modifier control points add and remove… ( I’ve just finished a patch for as I need it in speaker_tools)