5
$\begingroup$

I'm trying to get the user-friendly name of an F-Curve that gets displayed in the Dope Sheet and Graph Editor. For example, I would like to get the name 'X Euler Rotation'.

User-Friendly Channel Name

If I use FCurve.data_path, I just get rotation_euler (which isn't describing the F-Curve's X dimension).

visible = context.visible_fcurves[:]
for fcurve in visible:
    print(fcurve.data_path)
    # rotation_euler

Can anyone guide me on how to get the F-Curve's user-friendly name used in the user interface? If it isn't possible, can I at least get the F-Curve's dimension? Thank you!

$\endgroup$
4
  • 1
    $\begingroup$ You're not going to get the 'X'. The internal code is relying on the convention 0 is X, 1 is Y and 2 is Z, for generating display names of transform curves. You need the position of the curve in Transform to generate that. $\endgroup$ Jan 18 at 0:37
  • $\begingroup$ Thank you for the reply. This seems like it would be tricky to code for every keyable property (e.g. the index 0 is W in Quaternion Rotation, but X in Euler Rotation). $\endgroup$ Jan 18 at 0:48
  • 3
    $\begingroup$ AFAICT it isn't accessible. I did locate the function that does it though: getname_anim_fcurve. XYZ etc is based on prop.subtype (code here); then prop.name; then the bone/constraint name if there is one. $\endgroup$
    – scurest
    Jan 18 at 1:36
  • $\begingroup$ @scurest Thank you for looking into this! $\endgroup$ Jan 18 at 3:09

2 Answers 2

6
$\begingroup$

F-Curves that are part of transforms come in sets of three or four. Internal code relies on their array_index value to generate the axis name, or "W" in case of Quaternions. Other F-Curves also don't have a friendly name and internal code translates the name to display names.

The following code needs tidying up to put into an actual application, but consider it more or less pseudo-code. It's roughly how the internal code generates the names. It doesn't include the translation from internal name to pretty name.

This is just an example, run on an object that has been translated and rotated using a quaternion rotation, but not scaled. It has also been hidden in renders:

import bpy

object = bpy.context.object

action = object.animation_data.action

axis_name = [ "X", "Y", "Z" ]
quat_name = [ "W", "X", "Y", "Z" ]

for curve in action.fcurves:
    if curve.data_path in ["location", "rotation_euler", "rotation_quaternion", "scale"]:
        if "quaternion" in curve.data_path:
            print(quat_name[curve.array_index], curve.data_path)
        else:
            print(axis_name[curve.array_index], curve.data_path)
    else:
        print(curve.data_path)

For the example I described it prints

X location
Y location
Z location
W rotation_quaternion
X rotation_quaternion
Y rotation_quaternion
Z rotation_quaternion
hide_render
$\endgroup$
2
  • 1
    $\begingroup$ Thank you very much for the insight and code, Marty! $\endgroup$ Jan 18 at 3:09
  • $\begingroup$ You are very welcome. $\endgroup$ Jan 18 at 4:13
7
$\begingroup$

Here's my attempt. I didn't test it extensively, and it won't match everything. But it matches what I tried.

def ugly_fcurve_name(fc):
    return "%s[%d]" % (fc.data_path, fc.array_index)


def nice_index_name(prop, index):
    vectoritems = "XYZW"
    quatitems = "WXYZ"
    coloritems = "RGBA"

    if 0 <= index < 4:
        if prop.subtype in ['QUATERNION', 'AXISANGLE']:
            return quatitems[index]
        if prop.subtype in [
            'TRANSLATION',
            'DIRECTION',
            'XYZ',
            'XYZ_LENGTH',
            'EULER',
            'VELOCITY',
            'ACCELERATION',
            'COORDS',
        ]:
            return vectoritems[index]
        if prop.subtype in ['COLOR', 'COLOR_GAMMA']:
            return coloritems[index]

    return ""


def nice_fcurve_name(target, fc):
    prop_name = fc.data_path.rsplit(".", 1)[-1]

    try:
        val = target.path_resolve(fc.data_path, False)
        owner = val.data
        prop = owner.bl_rna.properties[prop_name]

    except Exception:
        return ugly_fcurve_name(fc)

    # Struct name (eg bones)
    struct_part = ""
    if owner != target:
        try:
            struct_part = "(%s)" % owner.name
        except Exception:
            pass

    # Array index part
    try:
        val_len = len(val)
    except Exception:
        val_len = None

    if val_len is None:
        if fc.array_index != 0:
            return ugly_fcurve_name(fc)
        index_part = ""
    else:
        if not (0 <= fc.array_index < val_len):
            return ugly_fcurve_name(fc)
        index_part = nice_index_name(prop, fc.array_index)

    parts = [index_part, prop.name, struct_part]
    return " ".join([part for part in parts if part])

Call nice_fcurve_name(target, fc). fc is the fcurve, target is the thing the action is applied to (eg. an object, a node_tree, a mesh.shape_keys, etc).

$\endgroup$
1
  • $\begingroup$ Wow! Thank you! I'm sure this will help many more people too. $\endgroup$ Jan 18 at 21:30

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.