Tillman Jex |||

Rose Curves in Blender

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.

Rose Curve Implementation

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 Curvefirst 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 identifiablesecond result - now identifiable

Animation

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 togetherall balls moving together

Got to this point with about 8 hours to go on the clock. There are two problems.

  1. The for loop is setting all spheres to the same position on frame 1 and frame 50 (360 degrees). This means that all balls only know two positions to interpolate between: 0 degrees and 360 degrees…
  2. But I only accidentally saw that the x and y parameters are actually not being saved as information into the animation keyframes… This is more of a problem right now.
    1. EDIT: Turns out in Blender, you can’t automate with a vector eg i.location = [x, y, z] you can only assign individual indexes at a time i.location[0] = x

x and y not being mappedx 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.

Creative Direction

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-expzaxis-exp

Variation divide-not-mult

Scaling in the outer rose curve resultent from tan function. rose curve 3

Adding lights to test rose curve 3 lights

The Final Product

final giffinal gif

Final Code

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')
Up next Collage Isolation Tombola Sequencer
Latest projects PDF Correlator with doc2vec Tombola Sequencer Rose Curves in Blender Collage Isolation WOMB Installation The Fog - Film Score “Shortcut” - First Term Masters Project Paradigm Shift In Search of Shadows Subsurface Oceans Op Voyager - The Ignition