Using bezier math in Blender with python

Monday, January 31st, 2011

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.

  1. import bpy, math, mathutils
  2.  
  3. # Kappa is the position of the handle on a circular curve
  4. kappa = ((math.sqrt(2)-1)/3)*4
  5.  
  6. # A series of nice bezier curves
  7. beziers = {
  8.         'linear': {
  9.                 'p0': mathutils.Vector((0.0,0.0)),
  10.                 'p1':mathutils.Vector((0.5,0.5)),
  11.                 'p2':mathutils.Vector((0.5,0.5)),
  12.                 'p3':mathutils.Vector((1.0,1.0))
  13.                 },
  14.         'increasing': {
  15.                 'p0': mathutils.Vector((0.0,0.0)),
  16.                 'p1':mathutils.Vector((kappa,0.0)),
  17.                 'p2':mathutils.Vector((1.0,(1.0-kappa))),
  18.                 'p3':mathutils.Vector((1.0,1.0))
  19.                 },
  20.         'decreasing': {
  21.                 'p0': mathutils.Vector((0.0,0.0)),
  22.                 'p1':mathutils.Vector((0.0,kappa)),
  23.                 'p2':mathutils.Vector(((1.0-kappa),1.0)),
  24.                 'p3':mathutils.Vector((1.0,1.0))
  25.                 },
  26.         'swoop': {
  27.                 'p0': mathutils.Vector((0.0,0.0)),
  28.                 'p1':mathutils.Vector((0.5,0.0)),
  29.                 'p2':mathutils.Vector((0.5,1.0)),
  30.                 'p3':mathutils.Vector((1.0,1.0))
  31.                 },
  32.         }
  33.  
  34. # Find a point along a bezier curve
  35. def findBezierPoint(r, curve):
  36.         c = 3 * (curve['p1'] - curve['p0'])
  37.         b = 3 * (curve['p2'] - curve['p1']) - c
  38.         a = curve['p3'] - curve['p0'] - c - b
  39.  
  40.         r2 = r * r
  41.         r3 = r2 * r
  42.  
  43.         return a * r3 + b * r2 + c * r + curve['p0']
  44.  
  45. # Pick a bezier curve
  46. bezier = beziers['increasing']
  47.  
  48. # Loop through and find points along a bezier curve
  49. for i in range(11):
  50.  
  51.         b = findBezierPoint((i*0.1),bezier)
  52.  
  53.         print((i+1),b)
  54.  
  55.         b*=10
  56.  
  57.         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

  1. import bpy, math, mathutils
  2.  
  3. # Kappa is the position of the handle on a circular curve
  4. kappa = ((math.sqrt(2)-1)/3)*4
  5.  
  6. # A series of nice 2D bezier curves
  7. beziers = {
  8.         'linear': {
  9.                 'p0': mathutils.Vector((0.0,0.0)),
  10.                 'p1':mathutils.Vector((0.5,0.5)),
  11.                 'p2':mathutils.Vector((0.5,0.5)),
  12.                 'p3':mathutils.Vector((1.0,1.0))
  13.                 },
  14.         'increasing': {
  15.                 'p0': mathutils.Vector((0.0,0.0)),
  16.                 'p1':mathutils.Vector((kappa,0.0)),
  17.                 'p2':mathutils.Vector((1.0,(1.0-kappa))),
  18.                 'p3':mathutils.Vector((1.0,1.0))
  19.                 },
  20.         'decreasing': {
  21.                 'p0': mathutils.Vector((0.0,0.0)),
  22.                 'p1':mathutils.Vector((0.0,kappa)),
  23.                 'p2':mathutils.Vector(((1.0-kappa),1.0)),
  24.                 'p3':mathutils.Vector((1.0,1.0))
  25.                 },
  26.         'swoop': {
  27.                 'p0': mathutils.Vector((0.0,0.0)),
  28.                 'p1':mathutils.Vector((kappa,0.0)),
  29.                 'p2':mathutils.Vector(((1.0-kappa),1.0)),
  30.                 'p3':mathutils.Vector((1.0,1.0))
  31.                 },
  32.         }
  33.  
  34. # Find a point along a bezier curve
  35. def findBezierPoint(r, curve):
  36.         c = 3 * (curve['p1'] - curve['p0'])
  37.         b = 3 * (curve['p2'] - curve['p1']) - c
  38.         a = curve['p3'] - curve['p0'] - c - b
  39.  
  40.         r2 = r * r
  41.         r3 = r2 * r
  42.  
  43.         return a * r3 + b * r2 + c * r + curve['p0']
  44.  
  45. # Now lets do something usefull!
  46. # Lets say we want to scale something * 5.0 in 10 steps
  47.  
  48. # So we add a cube to do this to
  49. 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))
  50.  
  51. # Pick a bezier curve
  52. bezier = beziers['increasing']
  53.  
  54. # The number of steps we want to do this in
  55. iterations = 10
  56.  
  57. # The stepsize is how far along the curve we move in each step
  58. # This starts at 0.0 and ends at 1.0
  59. stepSize = (1.0 / iterations)
  60.  
  61. # The size we start with is always 1 because we do it relative
  62. startSize = 1.0
  63.  
  64. # The size at the end of the process should be the start multiplied by this
  65. scaleUp = 5.0
  66.  
  67. # We get the difference in scale so we can figure out each step
  68. scaleDif = scaleUp - startSize
  69.  
  70. # This is here to see if the progression is correct
  71. checkSize = startSize
  72.  
  73. # Lets do 10 steps
  74. for i in range(10):
  75.  
  76.         # Before we do anything we find out where we should be at this point in time
  77.         previousStep = i
  78.  
  79.         # Before we do anything we find out where we should be at this point in time
  80.         previousPoint = stepSize * i
  81.  
  82.         # Find the point on the curve for the previous iteration in the loop
  83.         b = findBezierPoint(previousPoint, bezier)
  84.  
  85.         # This should tell us what the current size of the object is
  86.         currentSize = (scaleDif * b[1]) + startSize
  87.  
  88.         # Now lets find out how much bigger we need to make it
  89.         # We do i+1 because then we start with 1 and end with 10 (0 is nothing anyway)
  90.         step = (i+1)
  91.  
  92.         # The point along the curve is always a nr between 0.0 and 1.0
  93.         # So we divide 1 by the number of iterations to find the stepsize
  94.         curvePoint =  stepSize * step
  95.  
  96.         # Find the point on the curve for this iteration in the loop
  97.         b = findBezierPoint(curvePoint, bezier)
  98.  
  99.         # This should be the size we want to achieve
  100.         newSize = (scaleDif * b[1]) + startSize
  101.  
  102.         scaleFactor = newSize / currentSize
  103.  
  104.         checkSize *= scaleFactor
  105.  
  106.         # Lets print out some values to check
  107.         print('step',step)
  108.         print('  currentSize',currentSize)
  109.         print('  newSize', newSize)
  110.         print('  scaleFactor', scaleFactor)
  111.         print('  checkSize',checkSize)
  112.  
  113.         # lets add some meshes so we can see the effect
  114.         b*=20
  115.         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})
  116.         bpy.context.active_object.location = (b[0],0.0,b[1])
  117.         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

  1. import bpy, math, mathutils
  2.  
  3. print('-- starting --')
  4.  
  5. # Kappa is the position of the handle on a circular curve
  6. kappa = ((math.sqrt(2)-1)/3)*4
  7.  
  8. # A series of nice 2D bezier curves
  9. beziers = {
  10.         'linear': {
  11.                 'p0': mathutils.Vector((0.0,0.0)),
  12.                 'p1':mathutils.Vector((0.5,0.5)),
  13.                 'p2':mathutils.Vector((0.5,0.5)),
  14.                 'p3':mathutils.Vector((1.0,1.0)),
  15.                 },
  16.         'increasing': {
  17.                 'p0': mathutils.Vector((0.0,0.0)),
  18.                 'p1':mathutils.Vector((kappa,0.0)),
  19.                 'p2':mathutils.Vector((1.0,(1.0-kappa))),
  20.                 'p3':mathutils.Vector((1.0,1.0)),
  21.                 },
  22.         'decreasing': {
  23.                 'p0': mathutils.Vector((0.0,0.0)),
  24.                 'p1':mathutils.Vector((0.0,kappa)),
  25.                 'p2':mathutils.Vector(((1.0-kappa),1.0)),
  26.                 'p3':mathutils.Vector((1.0,1.0)),
  27.                 },
  28.         'swoosh': {
  29.                 'p0': mathutils.Vector((0.0,0.0)),
  30.                 'p1':mathutils.Vector((kappa,0.0)),
  31.                 'p2':mathutils.Vector(((1.0-kappa),1.0)),
  32.                 'p3':mathutils.Vector((1.0,1.0)),
  33.                 },
  34.         }
  35.  
  36. # Find a point along a bezier curve
  37. def findBezierPoint(r, curve):
  38.         c = 3 * (curve['p1'] - curve['p0'])
  39.         b = 3 * (curve['p2'] - curve['p1']) - c
  40.         a = curve['p3'] - curve['p0'] - c - b
  41.  
  42.         r2 = r * r
  43.         r3 = r2 * r
  44.  
  45.         return a * r3 + b * r2 + c * r + curve['p0']
  46.  
  47. # Lets make a curve go from 1.0 to 0.0
  48. def invertCurve(curve):
  49.         bezier = {
  50.                 'p0': mathutils.Vector(((1.0-curve['p3'][0]),curve['p3'][1])),
  51.                 'p1':mathutils.Vector(((1.0-curve['p2'][0]),curve['p2'][1])),
  52.                 'p2':mathutils.Vector(((1.0-curve['p1'][0]),curve['p1'][1])),
  53.                 'p3': mathutils.Vector(((1.0-curve['p0'][0]),curve['p0'][1])),
  54.                 }
  55.         return bezier
  56.  
  57. # Make the intensity of a curve bigger or smaller
  58. # Intensity has to be between 0.0 and 2.0 (1.0 is default)
  59. def intensifyCurve(curve, intensity=1.0):
  60.  
  61.         bezier = {
  62.                 'p0': curve['p0'],
  63.                 'p1':curve['p1'],
  64.                 'p2':curve['p2'],
  65.                 'p3': curve['p3'],
  66.                 }
  67.  
  68.         if intensity > 1.0:
  69.                 if bezier['p1'][0]:
  70.                         dif = 1.0 - bezier['p1'][0]
  71.                         dif *= (intensity - 1.0)
  72.                         bezier['p1'][0] += dif
  73.  
  74.                 if bezier['p1'][1]:
  75.                         dif = 1.0 - bezier['p1'][1]
  76.                         dif *= (intensity - 1.0)
  77.                         bezier['p1'][1] += dif
  78.  
  79.                 if bezier['p2'][0] != 1.0:
  80.                         dif = bezier['p2'][0]
  81.                         dif *= (intensity - 1.0)
  82.                         bezier['p2'][0] -= dif
  83.  
  84.                 if bezier['p2'][1] != 1.0:
  85.                         dif = bezier['p1'][1]
  86.                         dif *= (intensity - 1.0)
  87.                         bezier['p1'][1] -= dif
  88.  
  89.         elif intensity < 1.0:
  90.                 if bezier['p1'][0]:
  91.                         bezier['p1'][0] *= intensity
  92.  
  93.                 if bezier['p1'][1]:
  94.                         bezier['p1'][1] *= intensity
  95.  
  96.                 if bezier['p2'][0] != 1.0:
  97.                         dif = 1.0 - bezier['p2'][0]
  98.                         dif *= intensity
  99.                         bezier['p2'][0] += dif
  100.  
  101.                 if bezier['p2'][1] != 1.0:
  102.                         dif = 1.0 - bezier['p2'][1]
  103.                         dif *= intensity
  104.                         bezier['p2'][1] += dif
  105.  
  106.         return bezier
  107.  
  108. # Now lets do something usefull!
  109. # Lets say we want to scale something * 5.0 in 10 steps
  110.  
  111. # So we add a cube to do this to
  112. 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))
  113.  
  114. # So now lets do multiple bezier curves
  115. curves = [beziers['decreasing'],beziers['decreasing']]
  116. intensities = [1.0,1.0]
  117.  
  118. # The number of steps we want to do this in
  119. iterations = 10
  120.  
  121. # The size we start with is always 1 because we do it relative
  122. startSize = 1.0
  123.  
  124. # The size at the end of the process should be the start multiplied by this
  125. scaleUp = 5.0
  126.  
  127. # We get the difference in scale so we can figure out each step
  128. scaleDif = scaleUp - startSize
  129.  
  130. # This is here to see if the progression is correct
  131. checkSize = startSize
  132.  
  133. # The number of iterations per curve
  134. split = iterations / len(curves)
  135. stepSize = (1.0/iterations) * len(curves)
  136.  
  137. # Lets do 10 steps
  138. for i in range(iterations):
  139.  
  140.         # Before we do anything we find out where we should be at this point in time
  141.         step = i
  142.  
  143.         # Figure out what curve we need to use
  144.         curveId = math.floor(step/split)
  145.         curve = curves[curveId]
  146.  
  147.         # Set the intensity for a curve
  148.         curve = intensifyCurve(curve, intensities[curveId])
  149.  
  150.         # Make sure each curve is interpreted start to finish
  151.         step -= (curveId * split)
  152.  
  153.         # Find out if we're even or odd!
  154.         # To make transistions nice we invert the even curves
  155.         odd = curveId % 2
  156.         if odd:
  157.                 curve = invertCurve(curve)
  158.  
  159.         # Before we do anything we find out where we should be at this point in time
  160.         curvePoint = stepSize * step
  161.  
  162.         # Find the point on the curve for the previous iteration in the loop
  163.         currentPoint = findBezierPoint(curvePoint, curve)
  164.  
  165.         # This should tell us what the current size of the object is
  166.         currentOffset = (scaleDif * currentPoint[1]) + startSize
  167.  
  168.         # Now lets find out how much bigger we need to make it
  169.         # We do i+1 because then we start with 1 and end with 10 (0 is nothing anyway)
  170.         step += 1
  171.  
  172.         # The point along the curve is always a nr between 0.0 and 1.0
  173.         # So we divide 1 by the number of iterations to find the stepsize
  174.         curvePoint =  stepSize * step
  175.  
  176.         # Find the point on the curve for this iteration in the loop
  177.         newPoint = findBezierPoint(curvePoint, curve)
  178.  
  179.         # This should be the size we want to achieve
  180.         newOffset = (scaleDif * newPoint[1]) + startSize
  181.  
  182.         scaleFactor = newOffset / currentOffset
  183.  
  184.         checkSize *= scaleFactor
  185.  
  186.         # Lets print out some values to check
  187.         print('step',step)
  188.         print('  stepSize',stepSize)
  189.         print('  curve',curveId)
  190.         print('  currentPoint', currentPoint[1])
  191.         print('  newPoint', newPoint[1])
  192.         print('  currentOffset',currentOffset)
  193.         print('  newOffset', newOffset)
  194.         print('  scaleFactor', scaleFactor)
  195.         print('  checkSize',checkSize)
  196.  
  197.         # lets add some meshes so we can see the effect
  198.         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})
  199.         bpy.context.active_object.location = (((newPoint[0]*iterations*2)+(curveId*iterations*2)),0.0,(newPoint[1]*iterations*2))
  200.         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

  1. import bpy, math, mathutils
  2.  
  3. print('-- starting --')
  4.  
  5. # Kappa is the position of the handle on a circular curve
  6. kappa = ((math.sqrt(2)-1)/3)*4
  7.  
  8. # A series of nice 2D bezier curves
  9. beziers = {
  10.         'linear': {
  11.                 'p0': mathutils.Vector((0.0,0.0)),
  12.                 'p1':mathutils.Vector((0.5,0.5)),
  13.                 'p2':mathutils.Vector((0.5,0.5)),
  14.                 'p3':mathutils.Vector((1.0,1.0)),
  15.                 },
  16.         'increasing': {
  17.                 'p0': mathutils.Vector((0.0,0.0)),
  18.                 'p1':mathutils.Vector((kappa,0.0)),
  19.                 'p2':mathutils.Vector((1.0,(1.0-kappa))),
  20.                 'p3':mathutils.Vector((1.0,1.0)),
  21.                 },
  22.         'decreasing': {
  23.                 'p0': mathutils.Vector((0.0,0.0)),
  24.                 'p1':mathutils.Vector((0.0,kappa)),
  25.                 'p2':mathutils.Vector(((1.0-kappa),1.0)),
  26.                 'p3':mathutils.Vector((1.0,1.0)),
  27.                 },
  28.         'swoosh': {
  29.                 'p0': mathutils.Vector((0.0,0.0)),
  30.                 'p1':mathutils.Vector((kappa,0.0)),
  31.                 'p2':mathutils.Vector(((1.0-kappa),1.0)),
  32.                 'p3':mathutils.Vector((1.0,1.0)),
  33.                 },
  34.         }
  35.  
  36. # Find a point along a bezier curve
  37. def findBezierPoint(r, curve):
  38.         c = 3 * (curve['p1'] - curve['p0'])
  39.         b = 3 * (curve['p2'] - curve['p1']) - c
  40.         a = curve['p3'] - curve['p0'] - c - b
  41.  
  42.         r2 = r * r
  43.         r3 = r2 * r
  44.  
  45.         return a * r3 + b * r2 + c * r + curve['p0']
  46.  
  47. # Lets make a curve go from 1.0 to 0.0
  48. def reverseCurve(curve):
  49.         bezier = {
  50.                 'p0': mathutils.Vector(((1.0-curve['p3'][0]),curve['p3'][1])),
  51.                 'p1': mathutils.Vector(((1.0-curve['p2'][0]),curve['p2'][1])),
  52.                 'p2': mathutils.Vector(((1.0-curve['p1'][0]),curve['p1'][1])),
  53.                 'p3': mathutils.Vector(((1.0-curve['p0'][0]),curve['p0'][1])),
  54.                 }
  55.         return bezier
  56.  
  57. # Negate a curve
  58. def negateCurve(curve):
  59.         bezier = {
  60.                 'p0': mathutils.Vector((curve['p0'][0],-curve['p0'][1])),
  61.                 'p1': mathutils.Vector((curve['p1'][0],-curve['p1'][1])),
  62.                 'p2': mathutils.Vector((curve['p2'][0],-curve['p2'][1])),
  63.                 'p3': mathutils.Vector((curve['p3'][0],-curve['p3'][1])),
  64.                 }
  65.         return bezier
  66.  
  67. # Make the intensity of a curve bigger or smaller
  68. # Intensity has to be between 0.0 and 2.0 (1.0 is default)
  69. def intensifyCurve(curve, intensity=1.0):
  70.  
  71.         bezier = {
  72.                 'p0': curve['p0'],
  73.                 'p1': curve['p1'],
  74.                 'p2': curve['p2'],
  75.                 'p3': curve['p3'],
  76.                 }
  77.  
  78.         if intensity > 1.0:
  79.                 if bezier['p1'][0]:
  80.                         dif = 1.0 - bezier['p1'][0]
  81.                         dif *= (intensity - 1.0)
  82.                         bezier['p1'][0] += dif
  83.  
  84.                 if bezier['p1'][1]:
  85.                         dif = 1.0 - bezier['p1'][1]
  86.                         dif *= (intensity - 1.0)
  87.                         bezier['p1'][1] += dif
  88.  
  89.                 if bezier['p2'][0] != 1.0:
  90.                         dif = bezier['p2'][0]
  91.                         dif *= (intensity - 1.0)
  92.                         bezier['p2'][0] -= dif
  93.  
  94.                 if bezier['p2'][1] != 1.0:
  95.                         dif = bezier['p1'][1]
  96.                         dif *= (intensity - 1.0)
  97.                         bezier['p1'][1] -= dif
  98.  
  99.         elif intensity < 1.0:
  100.                 if bezier['p1'][0]:
  101.                         bezier['p1'][0] *= intensity
  102.  
  103.                 if bezier['p1'][1]:
  104.                         bezier['p1'][1] *= intensity
  105.  
  106.                 if bezier['p2'][0] != 1.0:
  107.                         dif = 1.0 - bezier['p2'][0]
  108.                         dif *= intensity
  109.                         bezier['p2'][0] += dif
  110.  
  111.                 if bezier['p2'][1] != 1.0:
  112.                         dif = 1.0 - bezier['p2'][1]
  113.                         dif *= intensity
  114.                         bezier['p2'][1] += dif
  115.  
  116.         return bezier
  117.  
  118. # Now lets do something usefull!
  119. # Lets say we want to scale something * 5.0 in 10 steps
  120.  
  121. # So we add a cube to do this to
  122. 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))
  123.  
  124. # So now lets do multiple bezier curves
  125. curves = [beziers['swoosh'],beziers['decreasing'],beziers['increasing'],beziers['increasing']]
  126. intensities = [1.0,1.0,1.0,1.0]
  127.  
  128. # The number of steps we want to do this in
  129. iterations = 40
  130.  
  131. # The size we start with is always 1 because we do it relative
  132. startSize = 1.0
  133.  
  134. # The size at the end of the process should be the start multiplied by this
  135. scaleUp = 5.0
  136.  
  137. # We get the difference in scale so we can figure out each step
  138. scaleDif = scaleUp - startSize
  139.  
  140. # This is here to see if the progression is correct
  141. checkSize = startSize
  142.  
  143. # The number of iterations per curve
  144. split = iterations / len(curves)
  145. stepSize = (1.0/iterations) * len(curves)
  146.  
  147. # Lets do 10 steps
  148. for i in range(iterations):
  149.  
  150.         # Before we do anything we find out where we should be at this point in time
  151.         step = i
  152.  
  153.         # Figure out what curve we need to use
  154.         curveId = math.floor(step/split)
  155.         curve = curves[curveId]
  156.  
  157.         # Set the intensity for a curve
  158.         curve = intensifyCurve(curve, intensities[curveId])
  159.  
  160.         # Make sure each curve is interpreted start to finish
  161.         step -= (curveId * split)
  162.  
  163.         # The current curve number for reversing and inversing should be 1-2-3-4
  164.         # 1 is regular
  165.         curveNr = (curveId +1) - (curveId//4)
  166.  
  167.         # The second and third curves are reversed (the third makes things smaller)
  168.         if curveNr is 2 or curveNr is 3:
  169.                 curve = reverseCurve(curve)
  170.  
  171.         # Before we do anything we find out where we should be at this point in time
  172.         curvePoint = stepSize * step
  173.  
  174.         # Find the point on the curve for the previous iteration in the loop
  175.         currentPoint = findBezierPoint(curvePoint, curve)
  176.  
  177.         # This should tell us what the current size of the object is
  178.         currentOffset = (scaleDif * currentPoint[1]) + startSize
  179.  
  180.         # Now lets find out how much bigger we need to make it
  181.         # We do i+1 because then we start with 1 and end with 10 (0 is nothing anyway)
  182.         step += 1
  183.  
  184.         # The point along the curve is always a nr between 0.0 and 1.0
  185.         # So we divide 1 by the number of iterations to find the stepsize
  186.         curvePoint =  stepSize * step
  187.  
  188.         # Find the point on the curve for this iteration in the loop
  189.         newPoint = findBezierPoint(curvePoint, curve)
  190.  
  191.         # This should be the size we want to achieve
  192.         newOffset = (scaleDif * newPoint[1]) + startSize
  193.  
  194.         scaleFactor = newOffset / currentOffset
  195.  
  196.         checkSize *= scaleFactor
  197.  
  198.         # Lets print out some values to check
  199.         print('')
  200.         print((i+1),step,'curve',curveNr)
  201.         #print('  stepSize',stepSize)
  202.         print('  currentPoint', round(currentPoint[1],5),'newPoint',round(newPoint[1]))
  203.         #print('  currentOffset',currentOffset)
  204.         #print('  newOffset', newOffset)
  205.         print('  scaleFactor', scaleFactor)
  206.         print('  checkSize',checkSize)
  207.  
  208.         # lets add some meshes so we can see the effect
  209.         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})
  210.  
  211.         xPos = ((newPoint[0]*iterations*2)+(curveId*iterations*2))
  212.         zPos = (newPoint[1]*iterations*2)
  213.  
  214.         if curveNr == 3 or curveNr == 4:
  215.                 zPos -= iterations*2
  216.  
  217.         bpy.context.active_object.location = (xPos,0.0,zPos)
  218.         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)

Using sine waves

Monday, January 24th, 2011

I’ve recently been using some sine wave math to get nice and smooth results in python for Blender 3D. This page is merely here so I don’t forget what I did, and why.

The basics

The math itself is really simple… as you can see in the image above… the trick is to use this stuff in a “math loop”.

A basic loop moving through a sine wave

  1. # Get the math module
  2. import math
  3.  
  4. # Loop twenty one times (21 because then it starts at 0 and ends with i as 20)
  5. for i in range(21):
  6.  
  7.         # Make a value between 0.0 and 2.0
  8.         x = i * 0.1
  9.  
  10.         # Get the y value
  11.         y = math.sin(math.pi * x)
  12.  
  13.         # Print it out
  14.         print(y)

Display clean python code for copying

# Get the math module
import math

# Loop twenty one times (21 because then it starts at 0 and ends with i as 20)
for i in range(21):

	# Make a value between 0.0 and 2.0
	x = i * 0.1

	# Get the y value
	y = math.sin(math.pi * x)

	# Print it out
	print(y)

A nice increasing curve

  1. # Get the math module
  2. import math
  3.  
  4. # Make a factor so we know how much of an increase each step is
  5. # in this case we want to increase the x value 0.5 in ten steps
  6. factor = (0.5 / 10)
  7.  
  8. # Loop twenty times (range should be 11 so we end up with i as 10)
  9. for i in range(11):
  10.  
  11.         # find out the x position for this value
  12.         x = i * factor
  13.  
  14.         # Multiply by pi
  15.         x = x * math.pi
  16.  
  17.         # but now we add 1.5 * pi because we want the last quarter of the curve
  18.         x = x + (1.5 * math.pi)
  19.  
  20.         # Get the y value
  21.         y = math.sin(x)
  22.  
  23.         # And because we don't want a curve from -1 to 0, but from 0 to 1 we add 1
  24.         y = y + 1
  25.  
  26.         # Print it out
  27.         print(y)

Display clean python code for copying

# Get the math module
import math

# Make a factor so we know how much of an increase each step is
# in this case we want to increase the x value 0.5 in ten steps
factor = (0.5 / 10)

# Loop twenty times (range should be 11 so we end up with i as 10)
for i in range(11):

	# find out the x position for this value
	x = i * factor

	# Multiply by pi
	x = x * math.pi

	# but now we add 1.5 * pi because we want the last quarter of the curve
	x = x + (1.5 * math.pi)

	# Get the y value
	y = math.sin(x)

	# And because we don't want a curve from -1 to 0, but from 0 to 1 we add 1
	y = y + 1

	# Print it out
	print(y)

Normal smooth method

Wednesday, March 17th, 2010

On this post I’ll try to explain how the method for the Normal Smooth script works.


Concept

The idea is to reposition verticles so your mesh ends up nice and smooth. Of course there is already a function in Blender that does it, but that doesn’t take the actual “surface” of the mesh into account. So say you have a part of a perfect sphere selected, and you run the current internal function, it would flatten that selection, or even make it concave (hollow). That’s what we don’t want. In stead if you try to smooth a perfect sphere, it should not change anything. You can’t get smoother than a sphere!

See below here… that’s not nicely smoothed!


Smoothing the normal way

I’ll explain the concept in 2D. Lets say we want to smooth the position of vertex A and it’s connected to vertex B and C. Then we get the vertex normals for B and C. We then rotate those normal vectors 90 degrees towards A and make them half the length of the distance between that specific vert (B or C) and A. Once we have those, we find the points at the ends of those two vectors and place vert A at the midpoint between them.

But let me explain with a picture, which should help.


That’s just the basics

Of course there is a lot more that you can do with a script. Like looking further along the surface and using more normals. Also in 3d at times you have quads and you need to figure out what you want to do with the vert at the far end of the quad. Having non manifold meshes can be tricky too.

At least I hope this explains the idea a bit, and as ideas go…. it’s not too bad

Dolf

Mechanics math

Wednesday, March 17th, 2010

Here so I don’t have to search the internet to look for this stuff.

I’m working on some physics things for my swarm AI… tricky since I haven’t done any of this for ehm… 15 years or so.


Second law of Newton

F = m * a
a = F/m
m = F/a

F is force in newtons
m is mass in kilograms
a is acceleration in meters per second

Finding mesh angles

Wednesday, March 17th, 2010

Here I’ll try to explain a method for finding out the angle of a mesh at a specific point/edge in python.

This method can for instance be used to create effects like with the ma baker or ma self scripts;


Finding the angle between two faces in Blender

Now this isn’t really complicated.

Lets say you retrieve two faces from Blender’s python API that are connected by an edge.

Lets call them face1 and face2 and face1.no retrieves the face normal of face1.

Then we can simply do the following to find the angle:

  1. myAngle = Mathutils.AngleBetweenVecs(face1.no, face2.no)

Display clean python code for copying

myAngle = Mathutils.AngleBetweenVecs(face1.no, face2.no)

The result though is only an angle between 0 and 90 degrees, to find out if that is positive or negative continue reading below.


Finding out whether the angle between two faces is convex or concave

A lot of the time you also want to know whether the angle is concave or convex (positive or negative).

To get that we get the vector from the midpoint of face1 to the midpoint of face2.

The midpoint of a face is retrieved by getting face1.cent.

Then we get the dot product of the face normal of face1 and the vector we just retrieved.

In python that could be:

  1. dotProduct = Mathutils.DotVecs(face1.no, (face2.cent - face1.cent))

Display clean python code for copying

dotProduct = Mathutils.DotVecs(face1.no, (face2.cent - face1.cent))

The resulting dot product will be either positive or negative depending on whether the angle is concave or convex.

Angle math

Wednesday, March 17th, 2010

This is just here so I don’t forget ;)

(small characters here refer to angles, capitals to lengths of sides)


An oblique triangle

A triangle without a 90 degree angle

Python equivalents of the above math are for instance (untested):

A = math.sin(a) / (math.sin(b)/B)

B = math.sin(b) / (math.sin(c)/C)

C = math.sin(c) / (math.sin(a)/A)


A right angle triangle

A triangle with a single 90 degree angle (a in this case)

Python equivalents of the above math are for instance:

c = math.asin(C/A)

c = math.acos(A/C)

c = math.atan(C/B)

A = math.sqrt(B*B+C*C)

Remember soscastoa!

click here to close

Help keep these files free,
and support further development!