Ok, so once again, this is mostly here so I can remember…
For my Entoforms project I’ve been using sine waves to calculate nice “increases” and “decreases”, but… a sine wave is only so flexible. Thus I had a look at using beziers in stead. Now… I am an artist, and not a math wizz, so it was tricky to get my head around…
The concept
Because I’m just using this bit of math to calulate how much I want to transform stuff, I’m only doing it in 2D. And to keep it simple only with curves with a single segment (though you can combine multiple curves). Below here are a few nice ones.

Now in my case I only care about the “inside” of the curve… so we’re ignoring the black (unselected) handles. This results in each curve being defined by 4 points… 2 nodes (where the curve starts and ends) and 2 handles (that define the shape of the curve). So we have p0 (the first node), p1 (the handle for the first node), p2 (the handle for the second node), p3 (the second node).
The math
I was lucky enough to find some very basic math for this on the net… I really don’t know much about what it actually does, but it works great! So here’s a simple script that places empties along a bezier curve.
import bpy, math, mathutils
# Kappa is the position of the handle on a circular curve
kappa = ((math.sqrt(2)-1)/3)*4
# A series of nice bezier curves
beziers = {
'linear': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.5,0.5)),
'p2':mathutils.Vector((0.5,0.5)),
'p3':mathutils.Vector((1.0,1.0))
},
'increasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector((1.0,(1.0-kappa))),
'p3':mathutils.Vector((1.0,1.0))
},
'decreasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.0,kappa)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0))
},
'swoop': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.5,0.0)),
'p2':mathutils.Vector((0.5,1.0)),
'p3':mathutils.Vector((1.0,1.0))
},
}
# Find a point along a bezier curve
def findBezierPoint(r, curve):
c = 3 * (curve['p1'] - curve['p0'])
b = 3 * (curve['p2'] - curve['p1']) - c
a = curve['p3'] - curve['p0'] - c - b
r2 = r * r
r3 = r2 * r
return a * r3 + b * r2 + c * r + curve['p0']
# Pick a bezier curve
bezier = beziers['increasing']
# Loop through and find points along a bezier curve
for i in range(11):
b = findBezierPoint((i*0.1),bezier)
print((i+1),b)
b*=10
bpy.ops.object.add(type='EMPTY', view_align=False, enter_editmode=False, location=(b[0], 0, b[1]), rotation=(0, 0, 0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
Display clean python code for copying
import bpy, math, mathutils
# Kappa is the position of the handle on a circular curve
kappa = ((math.sqrt(2)-1)/3)*4
# A series of nice bezier curves
beziers = {
'linear': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.5,0.5)),
'p2':mathutils.Vector((0.5,0.5)),
'p3':mathutils.Vector((1.0,1.0))
},
'increasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector((1.0,(1.0-kappa))),
'p3':mathutils.Vector((1.0,1.0))
},
'decreasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.0,kappa)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0))
},
'swoop': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.5,0.0)),
'p2':mathutils.Vector((0.5,1.0)),
'p3':mathutils.Vector((1.0,1.0))
},
}
# Find a point along a bezier curve
def findBezierPoint(r, curve):
c = 3 * (curve['p1'] - curve['p0'])
b = 3 * (curve['p2'] - curve['p1']) - c
a = curve['p3'] - curve['p0'] - c - b
r2 = r * r
r3 = r2 * r
return a * r3 + b * r2 + c * r + curve['p0']
# Pick a bezier curve
bezier = beziers['increasing']
# Loop through and find points along a bezier curve
for i in range(11):
b = findBezierPoint((i*0.1),bezier)
print((i+1),b)
b*=10
bpy.ops.object.add(type='EMPTY', view_align=False, enter_editmode=False, location=(b[0], 0, b[1]), rotation=(0, 0, 0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
The use
Now this gives us a nice series of numbers from 0.0 to 1.0. These can be used to calculate how strongly we want to affect something… it’s basically a falloff curve. The trick is not to use these as actual transformation values, but as a reference for how much of the “end” result should be reached. So if you’re translating something 20 units, and the value retrieved is 0.4… the selection should be translated 20 * 0.4 units at this point in time.
Here’s a slightly usefull example
import bpy, math, mathutils
# Kappa is the position of the handle on a circular curve
kappa = ((math.sqrt(2)-1)/3)*4
# A series of nice 2D bezier curves
beziers = {
'linear': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.5,0.5)),
'p2':mathutils.Vector((0.5,0.5)),
'p3':mathutils.Vector((1.0,1.0))
},
'increasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector((1.0,(1.0-kappa))),
'p3':mathutils.Vector((1.0,1.0))
},
'decreasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.0,kappa)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0))
},
'swoop': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0))
},
}
# Find a point along a bezier curve
def findBezierPoint(r, curve):
c = 3 * (curve['p1'] - curve['p0'])
b = 3 * (curve['p2'] - curve['p1']) - c
a = curve['p3'] - curve['p0'] - c - b
r2 = r * r
r3 = r2 * r
return a * r3 + b * r2 + c * r + curve['p0']
# Now lets do something usefull!
# Lets say we want to scale something * 5.0 in 10 steps
# So we add a cube to do this to
bpy.ops.mesh.primitive_cube_add(view_align=False, enter_editmode=False, location=(0.0, 0.0, 0.0), rotation=(0, 0, 0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
# Pick a bezier curve
bezier = beziers['increasing']
# The number of steps we want to do this in
iterations = 10
# The stepsize is how far along the curve we move in each step
# This starts at 0.0 and ends at 1.0
stepSize = (1.0 / iterations)
# The size we start with is always 1 because we do it relative
startSize = 1.0
# The size at the end of the process should be the start multiplied by this
scaleUp = 5.0
# We get the difference in scale so we can figure out each step
scaleDif = scaleUp - startSize
# This is here to see if the progression is correct
checkSize = startSize
# Lets do 10 steps
for i in range(10):
# Before we do anything we find out where we should be at this point in time
previousStep = i
# Before we do anything we find out where we should be at this point in time
previousPoint = stepSize * i
# Find the point on the curve for the previous iteration in the loop
b = findBezierPoint(previousPoint, bezier)
# This should tell us what the current size of the object is
currentSize = (scaleDif * b[1]) + startSize
# Now lets find out how much bigger we need to make it
# We do i+1 because then we start with 1 and end with 10 (0 is nothing anyway)
step = (i+1)
# The point along the curve is always a nr between 0.0 and 1.0
# So we divide 1 by the number of iterations to find the stepsize
curvePoint = stepSize * step
# Find the point on the curve for this iteration in the loop
b = findBezierPoint(curvePoint, bezier)
# This should be the size we want to achieve
newSize = (scaleDif * b[1]) + startSize
scaleFactor = newSize / currentSize
checkSize *= scaleFactor
# Lets print out some values to check
print('step',step)
print(' currentSize',currentSize)
print(' newSize', newSize)
print(' scaleFactor', scaleFactor)
print(' checkSize',checkSize)
# lets add some meshes so we can see the effect
b*=20
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":True, "mode":1}, TRANSFORM_OT_translate={"value":(0, 0, 0), "constraint_axis":(False, False, False), "constraint_orientation":'GLOBAL', "mirror":False, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "release_confirm":False})
bpy.context.active_object.location = (b[0],0.0,b[1])
bpy.ops.transform.resize(value=(scaleFactor, scaleFactor, scaleFactor), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False, snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0), release_confirm=False)
Display clean python code for copying
import bpy, math, mathutils
# Kappa is the position of the handle on a circular curve
kappa = ((math.sqrt(2)-1)/3)*4
# A series of nice 2D bezier curves
beziers = {
'linear': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.5,0.5)),
'p2':mathutils.Vector((0.5,0.5)),
'p3':mathutils.Vector((1.0,1.0))
},
'increasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector((1.0,(1.0-kappa))),
'p3':mathutils.Vector((1.0,1.0))
},
'decreasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.0,kappa)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0))
},
'swoop': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0))
},
}
# Find a point along a bezier curve
def findBezierPoint(r, curve):
c = 3 * (curve['p1'] - curve['p0'])
b = 3 * (curve['p2'] - curve['p1']) - c
a = curve['p3'] - curve['p0'] - c - b
r2 = r * r
r3 = r2 * r
return a * r3 + b * r2 + c * r + curve['p0']
# Now lets do something usefull!
# Lets say we want to scale something * 5.0 in 10 steps
# So we add a cube to do this to
bpy.ops.mesh.primitive_cube_add(view_align=False, enter_editmode=False, location=(0.0, 0.0, 0.0), rotation=(0, 0, 0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
# Pick a bezier curve
bezier = beziers['increasing']
# The number of steps we want to do this in
iterations = 10
# The stepsize is how far along the curve we move in each step
# This starts at 0.0 and ends at 1.0
stepSize = (1.0 / iterations)
# The size we start with is always 1 because we do it relative
startSize = 1.0
# The size at the end of the process should be the start multiplied by this
scaleUp = 5.0
# We get the difference in scale so we can figure out each step
scaleDif = scaleUp - startSize
# This is here to see if the progression is correct
checkSize = startSize
# Lets do 10 steps
for i in range(10):
# Before we do anything we find out where we should be at this point in time
previousStep = i
# Before we do anything we find out where we should be at this point in time
previousPoint = stepSize * i
# Find the point on the curve for the previous iteration in the loop
b = findBezierPoint(previousPoint, bezier)
# This should tell us what the current size of the object is
currentSize = (scaleDif * b[1]) + startSize
# Now lets find out how much bigger we need to make it
# We do i+1 because then we start with 1 and end with 10 (0 is nothing anyway)
step = (i+1)
# The point along the curve is always a nr between 0.0 and 1.0
# So we divide 1 by the number of iterations to find the stepsize
curvePoint = stepSize * step
# Find the point on the curve for this iteration in the loop
b = findBezierPoint(curvePoint, bezier)
# This should be the size we want to achieve
newSize = (scaleDif * b[1]) + startSize
scaleFactor = newSize / currentSize
checkSize *= scaleFactor
# Lets print out some values to check
print('step',step)
print(' currentSize',currentSize)
print(' newSize', newSize)
print(' scaleFactor', scaleFactor)
print(' checkSize',checkSize)
# lets add some meshes so we can see the effect
b*=20
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":True, "mode":1}, TRANSFORM_OT_translate={"value":(0, 0, 0), "constraint_axis":(False, False, False), "constraint_orientation":'GLOBAL', "mirror":False, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "release_confirm":False})
bpy.context.active_object.location = (b[0],0.0,b[1])
bpy.ops.transform.resize(value=(scaleFactor, scaleFactor, scaleFactor), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False, snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0), release_confirm=False)
And another version that allows for stringing together multiple curves
import bpy, math, mathutils
print('-- starting --')
# Kappa is the position of the handle on a circular curve
kappa = ((math.sqrt(2)-1)/3)*4
# A series of nice 2D bezier curves
beziers = {
'linear': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.5,0.5)),
'p2':mathutils.Vector((0.5,0.5)),
'p3':mathutils.Vector((1.0,1.0)),
},
'increasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector((1.0,(1.0-kappa))),
'p3':mathutils.Vector((1.0,1.0)),
},
'decreasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.0,kappa)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0)),
},
'swoosh': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0)),
},
}
# Find a point along a bezier curve
def findBezierPoint(r, curve):
c = 3 * (curve['p1'] - curve['p0'])
b = 3 * (curve['p2'] - curve['p1']) - c
a = curve['p3'] - curve['p0'] - c - b
r2 = r * r
r3 = r2 * r
return a * r3 + b * r2 + c * r + curve['p0']
# Lets make a curve go from 1.0 to 0.0
def invertCurve(curve):
bezier = {
'p0': mathutils.Vector(((1.0-curve['p3'][0]),curve['p3'][1])),
'p1':mathutils.Vector(((1.0-curve['p2'][0]),curve['p2'][1])),
'p2':mathutils.Vector(((1.0-curve['p1'][0]),curve['p1'][1])),
'p3': mathutils.Vector(((1.0-curve['p0'][0]),curve['p0'][1])),
}
return bezier
# Make the intensity of a curve bigger or smaller
# Intensity has to be between 0.0 and 2.0 (1.0 is default)
def intensifyCurve(curve, intensity=1.0):
bezier = {
'p0': curve['p0'],
'p1':curve['p1'],
'p2':curve['p2'],
'p3': curve['p3'],
}
if intensity > 1.0:
if bezier['p1'][0]:
dif = 1.0 - bezier['p1'][0]
dif *= (intensity - 1.0)
bezier['p1'][0] += dif
if bezier['p1'][1]:
dif = 1.0 - bezier['p1'][1]
dif *= (intensity - 1.0)
bezier['p1'][1] += dif
if bezier['p2'][0] != 1.0:
dif = bezier['p2'][0]
dif *= (intensity - 1.0)
bezier['p2'][0] -= dif
if bezier['p2'][1] != 1.0:
dif = bezier['p1'][1]
dif *= (intensity - 1.0)
bezier['p1'][1] -= dif
elif intensity < 1.0:
if bezier['p1'][0]:
bezier['p1'][0] *= intensity
if bezier['p1'][1]:
bezier['p1'][1] *= intensity
if bezier['p2'][0] != 1.0:
dif = 1.0 - bezier['p2'][0]
dif *= intensity
bezier['p2'][0] += dif
if bezier['p2'][1] != 1.0:
dif = 1.0 - bezier['p2'][1]
dif *= intensity
bezier['p2'][1] += dif
return bezier
# Now lets do something usefull!
# Lets say we want to scale something * 5.0 in 10 steps
# So we add a cube to do this to
bpy.ops.mesh.primitive_cube_add(view_align=False, enter_editmode=False, location=(0.0, 0.0, 0.0), rotation=(0, 0, 0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
# So now lets do multiple bezier curves
curves = [beziers['decreasing'],beziers['decreasing']]
intensities = [1.0,1.0]
# The number of steps we want to do this in
iterations = 10
# The size we start with is always 1 because we do it relative
startSize = 1.0
# The size at the end of the process should be the start multiplied by this
scaleUp = 5.0
# We get the difference in scale so we can figure out each step
scaleDif = scaleUp - startSize
# This is here to see if the progression is correct
checkSize = startSize
# The number of iterations per curve
split = iterations / len(curves)
stepSize = (1.0/iterations) * len(curves)
# Lets do 10 steps
for i in range(iterations):
# Before we do anything we find out where we should be at this point in time
step = i
# Figure out what curve we need to use
curveId = math.floor(step/split)
curve = curves[curveId]
# Set the intensity for a curve
curve = intensifyCurve(curve, intensities[curveId])
# Make sure each curve is interpreted start to finish
step -= (curveId * split)
# Find out if we're even or odd!
# To make transistions nice we invert the even curves
odd = curveId % 2
if odd:
curve = invertCurve(curve)
# Before we do anything we find out where we should be at this point in time
curvePoint = stepSize * step
# Find the point on the curve for the previous iteration in the loop
currentPoint = findBezierPoint(curvePoint, curve)
# This should tell us what the current size of the object is
currentOffset = (scaleDif * currentPoint[1]) + startSize
# Now lets find out how much bigger we need to make it
# We do i+1 because then we start with 1 and end with 10 (0 is nothing anyway)
step += 1
# The point along the curve is always a nr between 0.0 and 1.0
# So we divide 1 by the number of iterations to find the stepsize
curvePoint = stepSize * step
# Find the point on the curve for this iteration in the loop
newPoint = findBezierPoint(curvePoint, curve)
# This should be the size we want to achieve
newOffset = (scaleDif * newPoint[1]) + startSize
scaleFactor = newOffset / currentOffset
checkSize *= scaleFactor
# Lets print out some values to check
print('step',step)
print(' stepSize',stepSize)
print(' curve',curveId)
print(' currentPoint', currentPoint[1])
print(' newPoint', newPoint[1])
print(' currentOffset',currentOffset)
print(' newOffset', newOffset)
print(' scaleFactor', scaleFactor)
print(' checkSize',checkSize)
# lets add some meshes so we can see the effect
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":True, "mode":1}, TRANSFORM_OT_translate={"value":(0, 0, 0), "constraint_axis":(False, False, False), "constraint_orientation":'GLOBAL', "mirror":False, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "release_confirm":False})
bpy.context.active_object.location = (((newPoint[0]*iterations*2)+(curveId*iterations*2)),0.0,(newPoint[1]*iterations*2))
bpy.ops.transform.resize(value=(scaleFactor, scaleFactor, scaleFactor), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False, snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0), release_confirm=False)
Display clean python code for copying
import bpy, math, mathutils
print('-- starting --')
# Kappa is the position of the handle on a circular curve
kappa = ((math.sqrt(2)-1)/3)*4
# A series of nice 2D bezier curves
beziers = {
'linear': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.5,0.5)),
'p2':mathutils.Vector((0.5,0.5)),
'p3':mathutils.Vector((1.0,1.0)),
},
'increasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector((1.0,(1.0-kappa))),
'p3':mathutils.Vector((1.0,1.0)),
},
'decreasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.0,kappa)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0)),
},
'swoosh': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0)),
},
}
# Find a point along a bezier curve
def findBezierPoint(r, curve):
c = 3 * (curve['p1'] - curve['p0'])
b = 3 * (curve['p2'] - curve['p1']) - c
a = curve['p3'] - curve['p0'] - c - b
r2 = r * r
r3 = r2 * r
return a * r3 + b * r2 + c * r + curve['p0']
# Lets make a curve go from 1.0 to 0.0
def invertCurve(curve):
bezier = {
'p0': mathutils.Vector(((1.0-curve['p3'][0]),curve['p3'][1])),
'p1':mathutils.Vector(((1.0-curve['p2'][0]),curve['p2'][1])),
'p2':mathutils.Vector(((1.0-curve['p1'][0]),curve['p1'][1])),
'p3': mathutils.Vector(((1.0-curve['p0'][0]),curve['p0'][1])),
}
return bezier
# Make the intensity of a curve bigger or smaller
# Intensity has to be between 0.0 and 2.0 (1.0 is default)
def intensifyCurve(curve, intensity=1.0):
bezier = {
'p0': curve['p0'],
'p1':curve['p1'],
'p2':curve['p2'],
'p3': curve['p3'],
}
if intensity > 1.0:
if bezier['p1'][0]:
dif = 1.0 - bezier['p1'][0]
dif *= (intensity - 1.0)
bezier['p1'][0] += dif
if bezier['p1'][1]:
dif = 1.0 - bezier['p1'][1]
dif *= (intensity - 1.0)
bezier['p1'][1] += dif
if bezier['p2'][0] != 1.0:
dif = bezier['p2'][0]
dif *= (intensity - 1.0)
bezier['p2'][0] -= dif
if bezier['p2'][1] != 1.0:
dif = bezier['p1'][1]
dif *= (intensity - 1.0)
bezier['p1'][1] -= dif
elif intensity < 1.0:
if bezier['p1'][0]:
bezier['p1'][0] *= intensity
if bezier['p1'][1]:
bezier['p1'][1] *= intensity
if bezier['p2'][0] != 1.0:
dif = 1.0 - bezier['p2'][0]
dif *= intensity
bezier['p2'][0] += dif
if bezier['p2'][1] != 1.0:
dif = 1.0 - bezier['p2'][1]
dif *= intensity
bezier['p2'][1] += dif
return bezier
# Now lets do something usefull!
# Lets say we want to scale something * 5.0 in 10 steps
# So we add a cube to do this to
bpy.ops.mesh.primitive_cube_add(view_align=False, enter_editmode=False, location=(0.0, 0.0, 0.0), rotation=(0, 0, 0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
# So now lets do multiple bezier curves
curves = [beziers['decreasing'],beziers['decreasing']]
intensities = [1.0,1.0]
# The number of steps we want to do this in
iterations = 10
# The size we start with is always 1 because we do it relative
startSize = 1.0
# The size at the end of the process should be the start multiplied by this
scaleUp = 5.0
# We get the difference in scale so we can figure out each step
scaleDif = scaleUp - startSize
# This is here to see if the progression is correct
checkSize = startSize
# The number of iterations per curve
split = iterations / len(curves)
stepSize = (1.0/iterations) * len(curves)
# Lets do 10 steps
for i in range(iterations):
# Before we do anything we find out where we should be at this point in time
step = i
# Figure out what curve we need to use
curveId = math.floor(step/split)
curve = curves[curveId]
# Set the intensity for a curve
curve = intensifyCurve(curve, intensities[curveId])
# Make sure each curve is interpreted start to finish
step -= (curveId * split)
# Find out if we're even or odd!
# To make transistions nice we invert the even curves
odd = curveId % 2
if odd:
curve = invertCurve(curve)
# Before we do anything we find out where we should be at this point in time
curvePoint = stepSize * step
# Find the point on the curve for the previous iteration in the loop
currentPoint = findBezierPoint(curvePoint, curve)
# This should tell us what the current size of the object is
currentOffset = (scaleDif * currentPoint[1]) + startSize
# Now lets find out how much bigger we need to make it
# We do i+1 because then we start with 1 and end with 10 (0 is nothing anyway)
step += 1
# The point along the curve is always a nr between 0.0 and 1.0
# So we divide 1 by the number of iterations to find the stepsize
curvePoint = stepSize * step
# Find the point on the curve for this iteration in the loop
newPoint = findBezierPoint(curvePoint, curve)
# This should be the size we want to achieve
newOffset = (scaleDif * newPoint[1]) + startSize
scaleFactor = newOffset / currentOffset
checkSize *= scaleFactor
# Lets print out some values to check
print('step',step)
print(' stepSize',stepSize)
print(' curve',curveId)
print(' currentPoint', currentPoint[1])
print(' newPoint', newPoint[1])
print(' currentOffset',currentOffset)
print(' newOffset', newOffset)
print(' scaleFactor', scaleFactor)
print(' checkSize',checkSize)
# lets add some meshes so we can see the effect
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":True, "mode":1}, TRANSFORM_OT_translate={"value":(0, 0, 0), "constraint_axis":(False, False, False), "constraint_orientation":'GLOBAL', "mirror":False, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "release_confirm":False})
bpy.context.active_object.location = (((newPoint[0]*iterations*2)+(curveId*iterations*2)),0.0,(newPoint[1]*iterations*2))
bpy.ops.transform.resize(value=(scaleFactor, scaleFactor, scaleFactor), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False, snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0), release_confirm=False)
Another addition, having the 3rd and 4th curve go negative
import bpy, math, mathutils
print('-- starting --')
# Kappa is the position of the handle on a circular curve
kappa = ((math.sqrt(2)-1)/3)*4
# A series of nice 2D bezier curves
beziers = {
'linear': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.5,0.5)),
'p2':mathutils.Vector((0.5,0.5)),
'p3':mathutils.Vector((1.0,1.0)),
},
'increasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector((1.0,(1.0-kappa))),
'p3':mathutils.Vector((1.0,1.0)),
},
'decreasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.0,kappa)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0)),
},
'swoosh': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0)),
},
}
# Find a point along a bezier curve
def findBezierPoint(r, curve):
c = 3 * (curve['p1'] - curve['p0'])
b = 3 * (curve['p2'] - curve['p1']) - c
a = curve['p3'] - curve['p0'] - c - b
r2 = r * r
r3 = r2 * r
return a * r3 + b * r2 + c * r + curve['p0']
# Lets make a curve go from 1.0 to 0.0
def reverseCurve(curve):
bezier = {
'p0': mathutils.Vector(((1.0-curve['p3'][0]),curve['p3'][1])),
'p1': mathutils.Vector(((1.0-curve['p2'][0]),curve['p2'][1])),
'p2': mathutils.Vector(((1.0-curve['p1'][0]),curve['p1'][1])),
'p3': mathutils.Vector(((1.0-curve['p0'][0]),curve['p0'][1])),
}
return bezier
# Negate a curve
def negateCurve(curve):
bezier = {
'p0': mathutils.Vector((curve['p0'][0],-curve['p0'][1])),
'p1': mathutils.Vector((curve['p1'][0],-curve['p1'][1])),
'p2': mathutils.Vector((curve['p2'][0],-curve['p2'][1])),
'p3': mathutils.Vector((curve['p3'][0],-curve['p3'][1])),
}
return bezier
# Make the intensity of a curve bigger or smaller
# Intensity has to be between 0.0 and 2.0 (1.0 is default)
def intensifyCurve(curve, intensity=1.0):
bezier = {
'p0': curve['p0'],
'p1': curve['p1'],
'p2': curve['p2'],
'p3': curve['p3'],
}
if intensity > 1.0:
if bezier['p1'][0]:
dif = 1.0 - bezier['p1'][0]
dif *= (intensity - 1.0)
bezier['p1'][0] += dif
if bezier['p1'][1]:
dif = 1.0 - bezier['p1'][1]
dif *= (intensity - 1.0)
bezier['p1'][1] += dif
if bezier['p2'][0] != 1.0:
dif = bezier['p2'][0]
dif *= (intensity - 1.0)
bezier['p2'][0] -= dif
if bezier['p2'][1] != 1.0:
dif = bezier['p1'][1]
dif *= (intensity - 1.0)
bezier['p1'][1] -= dif
elif intensity < 1.0:
if bezier['p1'][0]:
bezier['p1'][0] *= intensity
if bezier['p1'][1]:
bezier['p1'][1] *= intensity
if bezier['p2'][0] != 1.0:
dif = 1.0 - bezier['p2'][0]
dif *= intensity
bezier['p2'][0] += dif
if bezier['p2'][1] != 1.0:
dif = 1.0 - bezier['p2'][1]
dif *= intensity
bezier['p2'][1] += dif
return bezier
# Now lets do something usefull!
# Lets say we want to scale something * 5.0 in 10 steps
# So we add a cube to do this to
bpy.ops.mesh.primitive_cube_add(view_align=False, enter_editmode=False, location=(0.0, 0.0, 0.0), rotation=(0, 0, 0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
# So now lets do multiple bezier curves
curves = [beziers['swoosh'],beziers['decreasing'],beziers['increasing'],beziers['increasing']]
intensities = [1.0,1.0,1.0,1.0]
# The number of steps we want to do this in
iterations = 40
# The size we start with is always 1 because we do it relative
startSize = 1.0
# The size at the end of the process should be the start multiplied by this
scaleUp = 5.0
# We get the difference in scale so we can figure out each step
scaleDif = scaleUp - startSize
# This is here to see if the progression is correct
checkSize = startSize
# The number of iterations per curve
split = iterations / len(curves)
stepSize = (1.0/iterations) * len(curves)
# Lets do 10 steps
for i in range(iterations):
# Before we do anything we find out where we should be at this point in time
step = i
# Figure out what curve we need to use
curveId = math.floor(step/split)
curve = curves[curveId]
# Set the intensity for a curve
curve = intensifyCurve(curve, intensities[curveId])
# Make sure each curve is interpreted start to finish
step -= (curveId * split)
# The current curve number for reversing and inversing should be 1-2-3-4
# 1 is regular
curveNr = (curveId +1) - (curveId//4)
# The second and third curves are reversed (the third makes things smaller)
if curveNr is 2 or curveNr is 3:
curve = reverseCurve(curve)
# Before we do anything we find out where we should be at this point in time
curvePoint = stepSize * step
# Find the point on the curve for the previous iteration in the loop
currentPoint = findBezierPoint(curvePoint, curve)
# This should tell us what the current size of the object is
currentOffset = (scaleDif * currentPoint[1]) + startSize
# Now lets find out how much bigger we need to make it
# We do i+1 because then we start with 1 and end with 10 (0 is nothing anyway)
step += 1
# The point along the curve is always a nr between 0.0 and 1.0
# So we divide 1 by the number of iterations to find the stepsize
curvePoint = stepSize * step
# Find the point on the curve for this iteration in the loop
newPoint = findBezierPoint(curvePoint, curve)
# This should be the size we want to achieve
newOffset = (scaleDif * newPoint[1]) + startSize
scaleFactor = newOffset / currentOffset
checkSize *= scaleFactor
# Lets print out some values to check
print('')
print((i+1),step,'curve',curveNr)
#print(' stepSize',stepSize)
print(' currentPoint', round(currentPoint[1],5),'newPoint',round(newPoint[1]))
#print(' currentOffset',currentOffset)
#print(' newOffset', newOffset)
print(' scaleFactor', scaleFactor)
print(' checkSize',checkSize)
# lets add some meshes so we can see the effect
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":True, "mode":1}, TRANSFORM_OT_translate={"value":(0, 0, 0), "constraint_axis":(False, False, False), "constraint_orientation":'GLOBAL', "mirror":False, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "release_confirm":False})
xPos = ((newPoint[0]*iterations*2)+(curveId*iterations*2))
zPos = (newPoint[1]*iterations*2)
if curveNr == 3 or curveNr == 4:
zPos -= iterations*2
bpy.context.active_object.location = (xPos,0.0,zPos)
bpy.ops.transform.resize(value=(scaleFactor, scaleFactor, scaleFactor), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False, snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0), release_confirm=False)
Display clean python code for copying
import bpy, math, mathutils
print('-- starting --')
# Kappa is the position of the handle on a circular curve
kappa = ((math.sqrt(2)-1)/3)*4
# A series of nice 2D bezier curves
beziers = {
'linear': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.5,0.5)),
'p2':mathutils.Vector((0.5,0.5)),
'p3':mathutils.Vector((1.0,1.0)),
},
'increasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector((1.0,(1.0-kappa))),
'p3':mathutils.Vector((1.0,1.0)),
},
'decreasing': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((0.0,kappa)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0)),
},
'swoosh': {
'p0': mathutils.Vector((0.0,0.0)),
'p1':mathutils.Vector((kappa,0.0)),
'p2':mathutils.Vector(((1.0-kappa),1.0)),
'p3':mathutils.Vector((1.0,1.0)),
},
}
# Find a point along a bezier curve
def findBezierPoint(r, curve):
c = 3 * (curve['p1'] - curve['p0'])
b = 3 * (curve['p2'] - curve['p1']) - c
a = curve['p3'] - curve['p0'] - c - b
r2 = r * r
r3 = r2 * r
return a * r3 + b * r2 + c * r + curve['p0']
# Lets make a curve go from 1.0 to 0.0
def reverseCurve(curve):
bezier = {
'p0': mathutils.Vector(((1.0-curve['p3'][0]),curve['p3'][1])),
'p1': mathutils.Vector(((1.0-curve['p2'][0]),curve['p2'][1])),
'p2': mathutils.Vector(((1.0-curve['p1'][0]),curve['p1'][1])),
'p3': mathutils.Vector(((1.0-curve['p0'][0]),curve['p0'][1])),
}
return bezier
# Negate a curve
def negateCurve(curve):
bezier = {
'p0': mathutils.Vector((curve['p0'][0],-curve['p0'][1])),
'p1': mathutils.Vector((curve['p1'][0],-curve['p1'][1])),
'p2': mathutils.Vector((curve['p2'][0],-curve['p2'][1])),
'p3': mathutils.Vector((curve['p3'][0],-curve['p3'][1])),
}
return bezier
# Make the intensity of a curve bigger or smaller
# Intensity has to be between 0.0 and 2.0 (1.0 is default)
def intensifyCurve(curve, intensity=1.0):
bezier = {
'p0': curve['p0'],
'p1': curve['p1'],
'p2': curve['p2'],
'p3': curve['p3'],
}
if intensity > 1.0:
if bezier['p1'][0]:
dif = 1.0 - bezier['p1'][0]
dif *= (intensity - 1.0)
bezier['p1'][0] += dif
if bezier['p1'][1]:
dif = 1.0 - bezier['p1'][1]
dif *= (intensity - 1.0)
bezier['p1'][1] += dif
if bezier['p2'][0] != 1.0:
dif = bezier['p2'][0]
dif *= (intensity - 1.0)
bezier['p2'][0] -= dif
if bezier['p2'][1] != 1.0:
dif = bezier['p1'][1]
dif *= (intensity - 1.0)
bezier['p1'][1] -= dif
elif intensity < 1.0:
if bezier['p1'][0]:
bezier['p1'][0] *= intensity
if bezier['p1'][1]:
bezier['p1'][1] *= intensity
if bezier['p2'][0] != 1.0:
dif = 1.0 - bezier['p2'][0]
dif *= intensity
bezier['p2'][0] += dif
if bezier['p2'][1] != 1.0:
dif = 1.0 - bezier['p2'][1]
dif *= intensity
bezier['p2'][1] += dif
return bezier
# Now lets do something usefull!
# Lets say we want to scale something * 5.0 in 10 steps
# So we add a cube to do this to
bpy.ops.mesh.primitive_cube_add(view_align=False, enter_editmode=False, location=(0.0, 0.0, 0.0), rotation=(0, 0, 0), layers=(True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False))
# So now lets do multiple bezier curves
curves = [beziers['swoosh'],beziers['decreasing'],beziers['increasing'],beziers['increasing']]
intensities = [1.0,1.0,1.0,1.0]
# The number of steps we want to do this in
iterations = 40
# The size we start with is always 1 because we do it relative
startSize = 1.0
# The size at the end of the process should be the start multiplied by this
scaleUp = 5.0
# We get the difference in scale so we can figure out each step
scaleDif = scaleUp - startSize
# This is here to see if the progression is correct
checkSize = startSize
# The number of iterations per curve
split = iterations / len(curves)
stepSize = (1.0/iterations) * len(curves)
# Lets do 10 steps
for i in range(iterations):
# Before we do anything we find out where we should be at this point in time
step = i
# Figure out what curve we need to use
curveId = math.floor(step/split)
curve = curves[curveId]
# Set the intensity for a curve
curve = intensifyCurve(curve, intensities[curveId])
# Make sure each curve is interpreted start to finish
step -= (curveId * split)
# The current curve number for reversing and inversing should be 1-2-3-4
# 1 is regular
curveNr = (curveId +1) - (curveId//4)
# The second and third curves are reversed (the third makes things smaller)
if curveNr is 2 or curveNr is 3:
curve = reverseCurve(curve)
# Before we do anything we find out where we should be at this point in time
curvePoint = stepSize * step
# Find the point on the curve for the previous iteration in the loop
currentPoint = findBezierPoint(curvePoint, curve)
# This should tell us what the current size of the object is
currentOffset = (scaleDif * currentPoint[1]) + startSize
# Now lets find out how much bigger we need to make it
# We do i+1 because then we start with 1 and end with 10 (0 is nothing anyway)
step += 1
# The point along the curve is always a nr between 0.0 and 1.0
# So we divide 1 by the number of iterations to find the stepsize
curvePoint = stepSize * step
# Find the point on the curve for this iteration in the loop
newPoint = findBezierPoint(curvePoint, curve)
# This should be the size we want to achieve
newOffset = (scaleDif * newPoint[1]) + startSize
scaleFactor = newOffset / currentOffset
checkSize *= scaleFactor
# Lets print out some values to check
print('')
print((i+1),step,'curve',curveNr)
#print(' stepSize',stepSize)
print(' currentPoint', round(currentPoint[1],5),'newPoint',round(newPoint[1]))
#print(' currentOffset',currentOffset)
#print(' newOffset', newOffset)
print(' scaleFactor', scaleFactor)
print(' checkSize',checkSize)
# lets add some meshes so we can see the effect
bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked":True, "mode":1}, TRANSFORM_OT_translate={"value":(0, 0, 0), "constraint_axis":(False, False, False), "constraint_orientation":'GLOBAL', "mirror":False, "proportional":'DISABLED', "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "release_confirm":False})
xPos = ((newPoint[0]*iterations*2)+(curveId*iterations*2))
zPos = (newPoint[1]*iterations*2)
if curveNr == 3 or curveNr == 4:
zPos -= iterations*2
bpy.context.active_object.location = (xPos,0.0,zPos)
bpy.ops.transform.resize(value=(scaleFactor, scaleFactor, scaleFactor), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False, snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0), release_confirm=False)