2
$\begingroup$

I have a BezierCurve which I'm using to create a camera animation. I've set up a maximum Y Limit to stop the action of the curve on the camera and have set an Influence value of 0.7 to slow down the effect of the change (from moving along the path to stopped):

enter image description here

What I'd like to do is to completely stop the influence of the curve on the camera after a certain frame number. I've found I can do this by setting the Influence value to 1.0 (manually) but I need to animate this change. I can't seem to add a key frame, or a Driver on this setting so I'm trying to do it using Python.

After lots of Googling and cannibalising bits of code I've found without fully understanding it, this is the best I've been able to come up with:

import bpy

bpy.app.handlers.frame_change_pre.clear()

ob = bpy.data.curves["BezierCircle"]
 # Is there an animation?
if ob.animation_data is not None and ob.animation_data.action is not None:
    action = ob.animation_data.action


# every frame change, this function is called.
def my_handler(scene):
    frame = scene.frame_current

    if frame<544:
        action.type.FModifier.influence = 0.7
    else:
        action.type.FModifier.influence = 1


bpy.app.handlers.frame_change_pre.append(my_handler)

but obviously it doesn't work, I get

AttributeError: 'Action' object has no attribute 'type'

I just can't get the right syntax to target that FModifier on my curve.

The Blender API seems to suggest that I should be able to use

bpy.types.FModifier(influence) = 0.7

or is it?

bpy.types.FModifier.influence. = 0.7

But then how do I target my BezierCircle ?

Any help much appreciated, I'm only slowly getting up to speed with Blender programming!


UPDATE with fixed code from answer

import bpy

bpy.app.handlers.frame_change_pre.clear()

ob = bpy.data.curves["BezierCircle"]


# debug - v2 iterates through modifiers until it find LIMITS
action = bpy.data.actions.get("BezierCircleAction")

mods = [m for f in action.fcurves if f.data_path == 'eval_time' for m in f.modifiers if m.type == 'LIMITS']

for m in mods:
    print(m.influence)

# end debug


# every frame change, this function is called.
def my_handler(scene):
    frame = scene.frame_current

    if frame<544:
        ob.animation_data.action.fcurves[0].modifiers[1].influence = 0.7
    else:
        ob.animation_data.action.fcurves[0].modifiers[1].influence = 1


bpy.app.handlers.frame_change_pre.append(my_handler)
$\endgroup$
3
$\begingroup$

You have to specify a particular animation channel (fcurve) and a specific modifier. In this example I access the first fcurve in the fcurve list ([0]), which, because I animated the location of my object is the 'x' channel, and the first modifier ([0]) found on that fcurve:

ob = bpy.data.objects['Cube']
ob.animation_data.action.fcurves[0].modifiers[0].influence = 0

Because you have two modifiers on your fcurve it will be modifiers[1].

A good way to explore the Blender Python API is to use the 'autocomplete' feature of Blender's Python console, which allows you to type part of a command, and using the autocomplete feature, view all possible sub-modules (in green):

enter image description here

This may not necessarily show you exactly which option you need, but will at least list the possible options, which you can further research.

Whether your current logic will do what you want, I don't know, but this does address what seemed to be the main point of your question.

$\endgroup$
  • $\begingroup$ That's very useful and really helps me to problem solve any similar issues I might have. You suggested using an index of 1 on the fcurve parameter but what worked for me was an index of 1 on the modifier (i.e. the second modifier: ob.animation_data.action.fcurves[0].modifiers[1].influence = 0.7 I've added the working scipt to my questino - althugh as you hinted it doesn't quite work as I want, but your answer definetely set me on my way :) $\endgroup$ – baroquedub Aug 27 '15 at 8:38
  • $\begingroup$ You are correct, that was a mistake on my part. I have updated the answer to reflect this small change. $\endgroup$ – Ray Mairlot Aug 27 '15 at 10:34
2
$\begingroup$

Adding to the previous answer, I often use something like

EDIT. Here is testing Overkill. Change the Name of the ob to match your BezierCircle

import bpy

#bpy.app.handlers.frame_change_pre.clear()
'''
rather than clearing all the handlers you can remove any
that begin with "my_" as in "my_handler"
For instance if an addon is using a frame_change_pre handler you will remove
it too with a call to clear.

'''
my_handlers = [h for h in bpy.app.handlers.frame_change_pre if h.__name__.startswith("my_")]

for h in my_handlers:
    bpy.app.handlers.frame_change_pre.remove(h)

'''
using get returns None if name doesn't exist
'''
ob = bpy.data.curves.get('BezierCircle')

'''
using get attr to return none if action is None, animation_data is None or ob is None
'''
action = getattr(ob.animation_data, "action", None) if ob else None


mods = [m for f in action.fcurves 
        if f.data_path == 'eval_time'
        for m in f.modifiers
        if m.type == 'LIMITS'] if action else []

def my_handler(scene):
    frame = scene.frame_current
    for m in mods:
        m.influence = 0.7 if frame < 544 else 1


bpy.app.handlers.frame_change_pre.append(my_handler)

This will work silently if the object or the action do not exist. Prob better to only append the handler if len(mods): (ie there are limits mods)

Even tho this appears to be overkill, I believe it pays to get into the habit of ironing out possible errors before they can happen.

$\endgroup$
  • $\begingroup$ If I could accept two answers, I'd pick this one too :) Makes a lot of sense to iterate through modifiers until you find LIMITS. Possibly overkill for my script which is being called every frame, but very useful to know. $\endgroup$ – baroquedub Aug 27 '15 at 8:42
  • $\begingroup$ Out of interest can you explain why it's possible to do: action = bpy.data.actions.get("BezierCircleAction") ? I can see BezierCircleAction.001 listed in the F-Curve editor but it doesn't appear to be a parent of 'Evaluation Time', more of a sibling (going by the little open/close triangles) $\endgroup$ – baroquedub Aug 27 '15 at 8:45
  • $\begingroup$ Using collection.get('key') returns None if there is no item matching key. Then can use something like $\endgroup$ – batFINGER Aug 27 '15 at 9:08
  • $\begingroup$ Edited previous answer, keep forgetting the 5min rule getting cuppas etc. $\endgroup$ – batFINGER Aug 27 '15 at 9:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.