The following script uses the formula for cubic Bezier curves (which Blender approximates) to first determine t (which is the parametrization of the curve) from x and then gets y from t.
import bpy
import numpy as np
def main():
'''Here's an example usage of these functions'''
x = 5.0
bez_obj = bpy.data.objects['BezierCurve']
print('The y-value is', getYfromXforBezierObject(bez_obj, x))
def getYfromXforBezierObject(bez_obj, x):
'''Given a Bezier object which, when projected on to xy-plane, is a
well-behaved function (i.e. each x-value has only one associated y-value),
this returns the y-value for a given x-value
The higher resolution your curve is, the more accurate the resultant y-value
will be.'''
#find appropriate segment
segment_i = determineBezSegment(bez_obj, x)
if len(bez_obj.data.splines) > 1:
print("WARNING: your Bezier object has multiple splines!")
spline = bez_obj.data.splines[0]
#get the four points that control the cubic bezier segment
P0 = spline.bezier_points[segment_i].co[:2]
P1 = spline.bezier_points[segment_i].handle_right[:2]
P2 = spline.bezier_points[segment_i+1].handle_left[:2]
P3 = spline.bezier_points[segment_i+1].co[:2]
y = getYfromXforBezSegment(P0, P1, P2, P3, x)
return y
def determineBezSegment(bez_obj, x):
'''Given a Bezier object and an x-value, determine which of the cubic
segments corresponds to that x-value.
A return value of 0 would represent the first segment.'''
print(bez_obj.data)
bez_points = bez_obj.data.splines[0].bezier_points
if len(bez_obj.data.splines) > 1:
print("WARNING: your Bezier object has multiple splines!")
#loop through all segments and find which one contains x
for segment_i in range(len(bez_points)-1):
start_point = bez_points[segment_i]
end_point = bez_points[segment_i+1]
#break if this segment contains x
if start_point.co[0] <= x < end_point.co[0]:
break
#check if no segment was found
else:
print("WARNING: no segment found for given x-value and Bezier object")
return None
return segment_i
def getYfromXforBezSegment(P0, P1, P2, P3, x):
'''For a cubic Bezier segment described by the 2-tuples P0, ..., P3, return
the y-value associated with the given x-value.
Ex: getXfromYforCubicBez((0,0), (1,1), (2,1), (2,2), 3.2)'''
#First, get the t-value associated with x-value, where t is the
#parameterization of the Bezier curve and ranges from 0 to 1.
#We need the coefficients of the polynomial describing cubic Bezier
#(cubic polynomial in t)
coefficients = [-P0[0] + 3*P1[0] - 3*P2[0] + P3[0],
3*P0[0] - 6*P1[0] + 3*P2[0],
-3*P0[0] + 3*P1[0],
P0[0] - x]
#find roots of this polynomial to determine the parameter t
roots = np.roots(coefficients)
#find the root which is between 0 and 1, and is also real
correct_root = None
for root in roots:
if np.isreal(root) and 0 <= root <= 1:
correct_root = root
#check to make sure a valid root was found
if correct_root is None:
print('Error, no valid root found. Are you sure your Bezier curve '
'represents a valid function when projected into the xy-plane?')
param_t = correct_root
#from our value for the t parameter, find the corresponding y-value using formula for
#cubic Bezier curves
y = (1-param_t)**3*P0[1] + 3*(1-param_t)**2*param_t*P1[1] + 3*(1-param_t)*param_t**2*P2[1] + param_t**3*P3[1]
assert np.isreal(y)
# typecast y from np.complex128 to float64
y = y.real
return y
if __name__ == '__main__':
main()
This script will work only if your Bezier curve is a function when projected into the xy-plane (hit NUM7 and make sure it passes the "vertical line test").
.x
to.y
or[0]
to[1]
$\endgroup$