Access only visible F-curve in dopesheet/graph editor through API


(pullup) #1

Hi everyone.

EDIT: see BATfinger answer on blender stack exchange

How can I access F-curves points ONLY for visible Fcurve (when “show_only_selected” is on) in the graph editor ?
A way to directly iterate on f-curves would be really nice (if not necessary) to apply some script on what the user see/isolate.

Currently I’m checking in the context of the graph editor if we are looking only at the selection with

context.area.spaces[0].dopesheet.show_only_selected

then iterating in all action if only selected is On or in the actions of selected objects in scene if it’s Off.

But even if you iterate over selected object’s actions only, you still have the problem of unselected bones in selected armatures that aren’t showing up in the graph editor and are still affected by your script without the user knowing (and maybe for animated scene properties too) !

After searching a bit. I found that the great Jacques Luke (animation node creator :RocknRoll:) had the same question back in 2015 (see here)
I asked him if he found a solution since then but it’s a negative and he isn’t working on this anymore…

Any Ideas ?


(Secrop) #2

I think the best would be to operate on the object’s fcurves directly, instead of doing it through the editor.
Fcurves are part of the objects, and you can access to everything through them.

I.e. context.active_object.animation_data.actions.fcurves


(pullup) #3

Hi, thank you for your answer Secrop. But that’s actually what I’m doing already. I just check the ‘show_only_selected’ props in the graph editor to know if i have to filter and then take the actions from the objects directly.

But the problem is (reformulation here ^^) : what you see in the graph editor is not in sync with what you will get by getting actions from objects.

In the Graph editor, some fcurves might be hided and you can put some filter on (visibility, hided objects, etc). And I want my script to act only on the curve that are showing up for the user.

Here is what I’m currently doing :
I send a specific frame/value for the selected keyframe to snap on


def keyset_action_selection(frame, value):
    objs = bpy.context.selected_objects
    actions = []
    #todo : case of bone selection in armature if only selected is on
    for obj in objs:
        if obj.animation_data is not None and obj.animation_data.action is not None:                
            actions.append(obj.animation_data.action)
    if actions:
        print(9*'-', 'selection')
        set_keyframe_values(actions,frame, value)

That call the ‘set_keyframe_values’ function


def set_keyframe_values(actions,frame, value):
    '''Get a list of actions with a frame and value to set selected fcurve points'''
    print("actions", actions)#Dbg
    ct = 0
    for action in actions:
        for fcurve in action.fcurves :
            if not fcurve.hide:#avoid hided curve
                for p in fcurve.keyframe_points :
                    if p.select_control_point:
                        ct += 1
                        print("key {:.2f} {:.2f}".format(p.co[0], p.co[1]) )
                        if frame != None:# 0 must pass
                            p.co.x = frame
                        if value != None:
                            p.co.y = value


    print(ct, 'points')
    return ct

Here the selected keyframe on unselected objects aren’t affected by the script (what I want).
But if an armature is selected and some of it’s bones are unselected, there are hided in editor but still affected by the script (not cool !)…

Of course I can go on with this solution and upgrade with a lot of condition to finally affect only visible fcurve. But what I’m asking for here is a completely different approach…
It would be so much simpler (painless) to act directly on filtered curves…
Am I being more understandable ? :smiley:


(Secrop) #4

Unfortunately, I don’t know any other workaround…

For armatures, you need to loop through the bones that are selected:

SelectedBones = [bone for bone in D.objects['Armature'].data.bones if bone.select==True]

(batFINGER) #5

Secrop, Would need to look at pose bones

selected_bones = [pb for pb in obj.pose.bones if pb.select]

# or 

selected_bones = context.selected_pose_bones

as for fcurves, what about


context.selected_editable_fcurves

when run in graph eidtor context?


(Secrop) #6

Cool… I never wrote scripts for the graph editor, so I’m not familiar with its context… But if that works, great!! :slight_smile:


(pullup) #7

This look promising but does not work for me, throw an error :

AttributeError: 'Context' object has no attribute 'selected_editable_fcurves'

With a bunch of functions I made the filter I needed (except for the ‘ghost’ - show_hidden filter).

But I don’t want to mark the thread as Solved, because even if it’s working, it’s certainly not THE best solution to get only visible Fcurve (and still incomplete).

Here for those interested, search for ‘Get visible Fcurves’ with spacebar in graph editor to print detected fcurves in console (then do whatever you wnt with them in the class):


# coding: utf-8
import bpy, re


## global compiled regex (for fast use)
rebone = re.compile(r'(?<=\[\").*?(?=\"\])')
# get 'sauce' in 'pose.bones["sauce"]["props"]' or pose.bones["sauce"].location


def get_fcurves_from_action(actions):
    '''Get a list of actions and return visible Fcurves'''
    # print("actions:", actions)#Dbg
    global rebone
    fcu_list = []
    for obj, action in actions.items():
        for fcu in action.fcurves :
            if not fcu.hide:#avoid hided curve
                #print("fcurve data_path:", fcu.data_path)#Dbg
                if fcu.data_path.startswith('pose.bones['):#Case of armature : if either a pose bone or a property attached to a bone
                    bname = rebone.search(fcu.data_path).group()
                    if obj.data.bones[bname].select:#via posebone: pose.bones[bname].bone.select:
                        fcu_list.append(fcu)
                else:#else dont have the problem of bones filtering so take it directly
                    fcu_list.append(fcu)


    return fcu_list




def get_fcurves_selection():
    objs = bpy.context.selected_objects
    actions = {}
    for obj in objs:
        if obj.animation_data is not None and obj.animation_data.action is not None:
            actions[obj] = obj.animation_data.action
    if actions:
        print(9*'-', 'selection')
        return get_fcurves_from_action(actions)






def get_all_fcurves():
    '''get fcurves (expect hided) from all actions'''
    #todo : check if object hided are showing up with the show_hidden (ghost) property of graph editor...
    print(9*'-', "all")
    fcu_list = []
    for action in bpy.data.actions:
        for fcu in action.fcurves :
            if not fcu.hide:#avoid hided curve
                fcu_list.append(fcu)


    return fcu_list




class getVisibleFcurves(bpy.types.Operator):
    bl_idname = "animops.get_visible_fcurves"
    bl_label = "Get visible Fcurves"
    bl_description = "print all visible fcurves"
    bl_options = {"REGISTER"}


    @classmethod
    def poll(cls, context):
        return context.area.type == 'GRAPH_EDITOR'


    def execute(self, context):
        #check if it's selection only from context
        selection = context.area.spaces[0].dopesheet.show_only_selected
        
        #get fcurves with designated function
        if selection:
            fcurves = get_fcurves_selection()
        else:
            fcurves = get_all_fcurves()
        
        print("fcurves number:", len(fcurves))
        # print("fcurves", fcurves)
        
        #and now do whaterver you want with the fcurve
        return {"FINISHED"}




def register():
    bpy.utils.register_module(__name__)


def unregister():
    bpy.utils.unregister_module(__name__)


if __name__ == "__main__":
    register()




(Secrop) #8

…and what would be THE best solution?
Your question was to find a way to get the visible fcurves, and that’s what you get with this last script…
Unless there’s more than you have told us…

btw, the error is just a matter (as you already noticed and corrected) of defining the poll function.


(batFINGER) #9

context.selected_editable_fcurves is a new feature in 2.79.1 … build nightly from GIT, so didn’t realize it was so new. https://docs.blender.org/api/master/bpy.context.html?highlight=selected_editable_fcurves#bpy.context.selected_editable_fcurves

Used here https://blender.stackexchange.com/a/92147/15543 to list all visible fcurves.

You will need to upgrade to 2.79.1 to use.


(pullup) #10

Ok now it’s Solved :wink: