# FCurve handles to 3D Bézier spline control points

I'm trying to export Bézier curve animations from Blender, but i can't seem to figure out how the control points of FCurves should be interpreted.

A Bézier keyframe for a 3D position typically has one vector for the position at the keyframe, one vector for the position of the first control point, and one vector for the position of the second control point. It is easy enough to get the position keyframe from a Blender FCurve - this is just the keyframe values of the X, Y, and Z curves combined into a vector. However, each FCurve keyframe has two "handles", and each handle has two coordinates - the frame on the X axis and the value on the Y axis. This means that each control point has a total of 6 values (2 per channel) governing it. How is this translated into a control point in 3D space?

• You don't need to worry about the handles, use fcurve.evaluate() to get blender to calculate the value at each frame. – sambler Aug 29 '18 at 10:59
• But i don't want to export each frame, i want to export the keyframes and their control points in Bézier form. – Kalle Halvarsson Aug 29 '18 at 20:41

The points used by an fcurve are a 2D version of the bezier curves you use in the 3D viewport. To convert an fcurve into a 3D bezier curve we just need to add a Z axis value to the locations.

import bpy

anim_fcurves = bpy.context.object.animation_data.action.fcurves

# index 1 is the Y location fcurve
src_fcurve = anim_fcurves.find('location', index=1)

vis_curve = bpy.data.curves.new('YLocCurve', 'CURVE')
vis_spline = vis_curve.splines.new('BEZIER')

for i,fcp in enumerate(src_fcurve.keyframe_points):
p = vis_spline.bezier_points[i]
p.co = (fcp.co[0], fcp.co[1], 0.0)
p.handle_left = (fcp.handle_left[0], fcp.handle_left[1], 0.0)
p.handle_left_type = 'FREE'
p.handle_right = (fcp.handle_right[0], fcp.handle_right[1], 0.0)
p.handle_right_type = 'FREE'

data = bpy.data.objects.new('YLocCurve', vis_curve)


That will turn an fcurve into a bezier curve object in the 3D viewport. How you export that to another application depends on the file format you are using, but in general you want the three locations for each point so that the other program uses the three points to define each point and it handles.

While mostly you can copy the handle type, the fcurve can have an AUTO_CLAMPED handle type that is not valid in a 3D bezier curve.

• Thanks, but this is not the correct solution to the problem. What i want is to export the handles of a 3D keyframe (such as location, scale). In Blender, these are represented as one curve per channel - your solution is taking a single channel and turning it into a 3D-curve. In a 3-channel animation, if you combine the fcp.co[1] of the x, y, and z channels into a vector, you get the animated coordinate of the object at that frame (where the frame is fcp.co[0]). For Beziér curves, control points are also defined in 3D space (think of the 3D handles of a spline), but the frame is the same. – Kalle Halvarsson Aug 31 '18 at 16:41
• To clarify - for each Bezier keyframe, there are two control points, also expressed in 3D space. You'd think that this would be as simple as exporting the keyframe_left[1] and keyframe_right[1] values of the keyframe for the x, y, and z curves, but notice that the keyframe_left[0] and keyframe_right[0] values also have an impact on the shape of the actual curve. This of course is just a visual representation used to make sense of the curves - internally, Blender normalizes the control points somehow into coordinates. What i want to know is how this works. – Kalle Halvarsson Aug 31 '18 at 16:47
• The handles define the direction the curve takes leaving the point on its way to the next point and when it starts curving towards the next point. You can lookup how to draw a bezier curve. My example make s a 3D version of an fcurve, you could get all three location fcurves and combine the locations into one curve. I am thinking you may be after a motion path to represent the motion of the object. – sambler Aug 31 '18 at 20:12
• Look, i know perfectly well how to draw a Bezier curve. Each point on the curve should have an X, Y, and Z value with two control points which each also have X, Y, Z values. But the control points in the graph have 2 coordinates per channel, not 1. Combining 3 curves with your method will not create correct results, you are just projecting the curve along the Z axis, and thus the Z coordinate will ALWAYS be 0, which is incorrect. Each curve is a 1-DIMENSIONAL representation of each channel, you can't just interpret the time value as X and amplitude as Y, that's not how it works. – Kalle Halvarsson Aug 31 '18 at 22:28
• An fcurve relates to a single animated value, for an animated location, there are three fcurves, one for each single value that defines the location. Each keyframe point in the fcurve contains a frame and value pair for that specific property, these two values are used as the x,y positions when representing the fcurve in the graph editor. Each keyframe point also contains the position and type for two handles, the handle positions aren't part of the animated data, they are extra data used to calculate the keyframed property value in between keyframe points. – sambler Sep 1 '18 at 4:38

Fcurves to 3D bezier.

AFAIK blender doesn't make any "3D bezier keyframe points" from fcurves. For one there is no requirement for there to be an fcurve nor any or matching frame keyframe points from one location component fcurve to another.

Sample script to turn 3 location component FCurves into 1 3D bezier curve

• Each fcurve must have matching number of points keyed at same frames.
• The default handle type for fcurves is 'AUTO_CLAMPED' there is no such option for bezier curves, have used 'AUTO' instead. Need to make an edit such as ht = 'AUTO' if p.handle_left == 'AUTO_CLAMPED' else p.handle_Left_type My experments lead me to using 'FREE' emulates well the differing handle types of fcurves.
• The handles in an fcurve (frame, value) represent the first derivative or tangent at that point. Each fcurve handle y value is used component wise on the 3D handle on 3D curve. Once again there is a chance that handle types don't match. AFAIK a handle cannot have different type per component.
• Basically this makes a motion path with the same amount of points in the bezier as keyframe points in single fcurve.

Code cobbled together using @Samblers answer as a base.

import bpy

anim_fcurves = bpy.context.object.animation_data.action.fcurves

# index 1 is the Y location fcurve
src_fcurve = anim_fcurves.find('location', index=1)

vis_curve = bpy.data.curves.new('YLocCurve', 'CURVE')
vis_curve.dimensions = '3D'
vis_spline = vis_curve.splines.new('BEZIER')
fcurves = zip(*(anim_fcurves.find('location', index=i).keyframe_points[:] for i in range(3)))

i = 0
for xp, yp, zp in fcurves:
p = vis_spline.bezier_points[i]
p.co = (xp.co.y, yp.co.y, zp.co.y)
p.handle_left = (xp.handle_left.y, yp.handle_left.y, zp.handle_left.y)
#ht = 'AUTO' if xp.handle_left == 'AUTO_CLAMPED' else xp.handle_left_type
p.handle_left_type = 'FREE'
p.handle_right = (xp.handle_right.y, yp.handle_right.y, zp.handle_right.y)
#ht = 'AUTO' if xp.handle_right == 'AUTO_CLAMPED' else xp.handle_right_type
p.handle_right_type = 'FREE'
i += 1
data = bpy.data.objects.new('YLocCurve', vis_curve)


Simple 4 keyframe animation on cube, all handles VECTOR type.

Mixture of FREE, VECTOR and ALIGNED handle types

EDIT

In response to comment below,

Thank you for making this very detailed answer - it looks very similar to the solution i use right now, but from what i can tell, it still doesn't solve the entire problem. You use only the Y values of the handles as 3D handle coordinates, but as i mentioned earlier, the X value also affects the shape of the curve in the graph as well as the animated path (I have some nice images illustrating this but don't know how to attach them). Does your cube animation exactly follow the generated spline even if you drag some FCurve handles along the X axis? Because i'm not sure that it does.

the x value in an fcurve is analogous to time, time is the frame in 3d view. Could arbitrarily move each fcurve handle, (particularly for vector type) such that the x component is a constant, and calculate it's new y for each fcurve point for each fcurve.

For example if the tangent is 1, ie 45 degrees, at the point (0, 0) knot could have a handle (4, 4) which to standardize to a 1 unit time step, is a handle at (1, 1).

This will standardize what is the tangent or first derivative of the curve at the knot for the arbitrary value of time chosen. For time is one this is derivative dv / dt in blender units per frame.

Remember the first derivative is the limit as the time approaches zero, so perhaps choose a smaller value than 1.

Given I am the only person to have UV'd on this question and answer, not worth my while to fix.

• Thank you for making this very detailed answer - it looks very similar to the solution i use right now, but from what i can tell, it still doesn't solve the entire problem. You use only the Y values of the handles as 3D handle coordinates, but as i mentioned earlier, the X value also affects the shape of the curve in the graph as well as the animated path (I have some nice images illustrating this but don't know how to attach them). Does your cube animation exactly follow the generated spline even if you drag some FCurve handles along the X axis? Because i'm not sure that it does. – Kalle Halvarsson Oct 25 '18 at 16:17