I wanted to check out python scripting in Blender, so I decided to implement the rose curve within Blender and augment it in a creative way.
Code for a basic rose curve:
import bpy
import math
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False, confirm=False)
theta = 0
r = 10
k = 6
while theta < 2 * math.pi:
x = r * (math.cos(k*theta) * math.cos(theta))
y = r * (math.cos(k*theta) * math.sin(theta))
bpy.ops.mesh.primitive_uv_sphere_add(radius=1, enter_editmode=False, align='WORLD', location=(x, y, 0), scale=(1, 1, 1))
theta += 0.01
first result - Cauliflower Curve
Used a constant of 1 for sphere radius and radius used in function (r)
+ bpy.ops.mesh.primitive_uv_sphere_add(radius=sphere_1, enter_editmode=False, align='WORLD', location=(x, y, 0), scale=(1, 1, 1))
- bpy.ops.mesh.primitive_uv_sphere_add(radius=r, enter_editmode=False, align='WORLD', location=(x, y, 0), scale=(1, 1, 1))
second result - now identifiable
This animates the growth in scale of the spheres, so the rose “grows” in size.
import bpy
import math
# Utility
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False, confirm=False)
bc = bpy.context
bd = bpy.data
# Start
sphere_r = 0.5
theta = 0
r = 10
k = 6
# draw flower
while theta < 2 * math.pi:
x = r * (math.cos(k*theta) * math.cos(theta))
y = r * (math.cos(k*theta) * math.sin(theta))
bpy.ops.mesh.primitive_uv_sphere_add(radius=sphere_r, enter_editmode=False, align='WORLD', location=(x, y, 0), scale=(1, 1, 1))
theta += 0.01
# set variable for accessing objects in collection
flower_nodes = bd.collections['Collection'].objects
# iterate over objects in collection
for i in flower_nodes:
# set initial state at frame 1
i.scale = [ 0, 0, 0]
i.keyframe_insert(data_path = 'scale', frame = 1)
i.scale = [ 5, 5, 5]
i.keyframe_insert(data_path = 'scale', frame = 150)
Trying now to figure out how to input the frame value into k
to animate moving through the different formations of rose curve. A quick google / youtube search didn’t solve this.
At the moment it seems like I need to implement it programatically into the for loop as so far I’ve only found how to set keyframes in the python script, rather than read values from the playbar.
I’m now perhaps seeing a limitation of scripting in blender which is that the script is run once at setup, rather than in a loop. But maybe there are ways around this.
The solution I found was to add a keyframe at each step, which I managed to do with a for loop but of course it pooled every sphere together into the same location.
all balls moving together
Got to this point with about 8 hours to go on the clock. There are two problems.
i.location = [x, y, z]
you can only assign individual indexes at a time i.location[0] = x
x and y not being mapped
/ ... /
# draw rose
while theta < 2 * math.pi:
x = r * (math.cos(k*theta) * math.cos(theta))
y = r * (math.cos(k*theta) * math.sin(theta))
bpy.ops.mesh.primitive_uv_sphere_add(radius=sphere_r, enter_editmode=False, align='WORLD', location=(x, y, 0), scale=(1, 1, 1))
theta += 0.01
# set variable for accessing objects in collection.
# needs to be set after sphere creation
flower_nodes = bd.collections['Collection'].objects
# for animation
# iterate over objects in collection
for i in flower_nodes:
# set initial state at frame 1
x = r * (math.cos(k * 0) * math.cos(0))
y = r * (math.cos(k * 0) * math.sin(0))
i.location = [x, y, 0]
i.keyframe_insert(data_path = 'location', frame = 1)
x = r * (math.cos(k * 6.28999999999991) * math.cos(6.28999999999991))
y = r * (math.cos(k * 6.28999999999991) * math.sin(6.28999999999991))
i.location = [x, y, 0]
i.keyframe_insert(data_path = 'location', frame = 50)
I think a solution to address each sphere on its own with its relative starting position relative to theta is to give the sphere an id on creation and to parametrically map that id to a starting point of theta. Could even give it the id of id_theta
within the while loop.
More than likely this could also be solved with filling each xyz coordinate into an array upon drawing each sphere, and then using that array within a for loop to define keyframes. This would take me more time than I would like and would only allow me to replicate the Houdini rose curve tutorial and not really “make it my own”. So I will put the remaining time into exploring more creative design approaches working with what I have functioning right now.
The obvious idea was to start integrating the z axis. Here drawing two iterations of the rose curve; one with k = 2 and the other with k = 3.
while theta < 2 * math.pi:
k = 2
x = r * (math.cos(k*theta) * math.cos(theta))
y = r * (math.cos(k*theta) * math.sin(theta))
z = r * (math.sin(k*theta) * math.tan(theta))
bpy.ops.mesh.primitive_uv_sphere_add(radius=sphere_r, enter_editmode=False, align='WORLD', location=(x, y, z), scale=(1, 1, 1))
k = 3
x = r * (math.cos(k*theta) * math.cos(theta))
y = r * (math.cos(k*theta) * math.sin(theta))
z = r * (math.sin(k*theta) * math.tan(theta))
bpy.ops.mesh.primitive_uv_sphere_add(radius=sphere_r, enter_editmode=False, align='WORLD', location=(x, y, z), scale=(1, 1, 1))
theta += 0.01
print('build stage')
zaxis-exp
Variation
Scaling in the outer rose curve resultent from tan function.
Adding lights to test
final gif
The rose curve generation and position animation of the spheres as well as point light animation is done procedurally with the below python script. The background seabed was added / animated via the Blender UI.
import bpy
import math
import random
bc = bpy.context
bco = bpy.context.object
bd = bpy.data
rose_nodes = bpy.data.collections['rose_curves'].objects
pointPower = [300000, 2000000]
pointKeyF = [0, 50, 100, 150, 200, 250, 300, 350, 400]
# Start
sphere_r = 0.5
theta = 0
r = 20
center = 0
# draw rose
while theta < 2 * math.pi:
k = 2
x = r * (math.cos(k*theta) * math.cos(theta))
y = r * (math.cos(k*theta) * math.sin(theta))
z = r * (math.sin(k*theta) * math.tan(theta))
bpy.ops.mesh.primitive_uv_sphere_add(radius=sphere_r, enter_editmode=False, align='WORLD', location=(x * 2, y * 2, z), scale=(1, 1, 1))
if theta > 0:
k = 3
x = r / (math.cos(k*theta) * math.cos(theta))
y = r / (math.cos(k*theta) * math.sin(theta))
z = r / (math.sin(k*theta) * math.tan(theta))
bpy.ops.mesh.primitive_uv_sphere_add(radius=sphere_r + 3, enter_editmode=False, align='WORLD', location=(x * 0.8, y * 0.8, z * 0.8), scale=(1, 1, 1))
k = 7
x = r * (math.cos(k*theta) * math.cos(theta))
y = r * (math.cos(k*theta) * math.sin(theta))
#z = r * (math.sin(k*theta) * math.tan(theta))
bpy.ops.mesh.primitive_uv_sphere_add(radius=sphere_r + 1, enter_editmode=False, align='WORLD', location=(x * 2, y * 2, z), scale=(1, 1, 1))
theta += 0.01
print('build stage')
print('rose curves drawn')
# set keyframes for sphere movement
for i in rose_nodes:
startXpos = i.location[0]
startYpos = i.location[1]
startZpos = i.location[2]
i.keyframe_insert(data_path = 'location', frame = 0)
#i.modifiers.new(name = 'wave', type = 'WAVE')
i.location[0] += random.randrange(-5, 5)
i.location[1] += random.randrange(-3, 3)
i.location[2] += random.randrange(-5, 5)
i.keyframe_insert(data_path = 'location', frame = 100)
i.location[0] += startXpos
i.location[1] += startYpos
i.location[2] += startZpos
i.keyframe_insert(data_path = 'location', frame = 200)
i.location[0] += random.randrange(-5, 5)
i.location[1] += random.randrange(-3, 3)
i.location[2] += random.randrange(-5, 5)
i.keyframe_insert(data_path = 'location', frame = 300)
i.location[0] = startXpos
i.location[1] = startYpos
i.location[2] = startZpos
i.keyframe_insert(data_path = 'location', frame = 400)
print('rose keyframes added')
# Add keyframes for point light oscillation
for i in range(8):
if (i % 2) == 0:
bpy.data.collections['others'].objects['Point'].data.energy = pointPower[0]
bpy.data.collections['others'].objects['Point'].data.keyframe_insert(data_path = 'energy', frame = pointKeyF[i])
else:
bpy.data.collections['others'].objects['Point'].data.energy = pointPower[1]
bpy.data.collections['others'].objects['Point'].data.keyframe_insert(data_path = 'energy', frame = pointKeyF[i])
print('point light keyframes added')