Draw a sine wave from a revolving unit circle in blender

In Blender 3.2.1, I need a unit mesh circle at origin and path on the x-axis with a length of $$2\pi$$. Object constraint on circle to follow path. Keyframe on unit circle at 0 & 250 with $$360^{\circ}$$ z-rotation. How do I get a single vertex on the unit circle to trace the sine or cosine wave?

• could you pls tell us what your end goal is? Aug 1, 2022 at 11:15
• i want to trace the sine/cosine wave from the rotating unit circle. i just dont know how to get the vertex to trace the path. it is a step in a larger gear driven idea. Aug 1, 2022 at 11:19
• i still don't get it. you only get a sin or cos wave, if you would move the circle while following path. If the circle is fixed, you only get a right left or up down movement. So what do you want? Aug 1, 2022 at 11:23
• do you want that? [1]: i.sstatic.net/o8cI9.gif Aug 1, 2022 at 11:25
• Aug 1, 2022 at 11:35

1 Answer

Just copy the script and execute then press spacebar in the Timeline to play the animation or you can scrub through the timeline from frame 0 to 250.

import bpy
import math

REVOLUTION = 360
TOTAL_FRAMES = 250
SINEWAVE_RESOLUTION = 64
RADIUS = 1

bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = TOTAL_FRAMES
bpy.context.scene.frame_current = 1

def get_object(name):
objects = bpy.context.scene.objects
if name in objects:
return objects[name]
m = bpy.data.meshes.new(name + "-mesh")
o = bpy.data.objects.new(name, m)
#o.modifiers.new(name, 'SKIN')
bpy.context.collection.objects.link(o)
return o

def get_empty(name):
objects = bpy.context.scene.objects
if name in objects:
return objects[name]
bpy.ops.object.empty_add(type='SPHERE', align='WORLD', location=(0, 0, 0))
empty = bpy.context.object
empty.name = name
empty.scale = 0.1, 0.1, 0.1
return empty

# ==================================================================================================
# Descritpion: plot the sinwave with some points
# ==================================================================================================

def get_range(start, end, step = 2):
return [x * 0.1 for x in range(start * 10, end * 10, step)]

def get_sinewave_x(angle):
return RADIUS * math.cos(REVOLUTION - math.radians(angle))

def get_sinewave_y(angle):
return RADIUS * math.sin(math.radians(angle))

def get_sinewave_length():
return RADIUS * 4

def draw_sinwave(angle, id):
verts = []
xunits = get_sinewave_length()
for u in range(0, SINEWAVE_RESOLUTION + 1):
a = u / SINEWAVE_RESOLUTION * REVOLUTION
px = xunits / SINEWAVE_RESOLUTION * u
py = get_sinewave_y(a)
if (a <= angle):
verts.append([px, py, 0.0])

#verts.append([xunits, 0, 0.0])
edges = []
for i in range(len(verts)-1):
edges.append((i, i+1))

o = get_object("sinewave-" + id)
m = o.data
m.clear_geometry()
m.from_pydata(verts, edges, ())

# ==================================================================================================
# Create Circle
# ==================================================================================================

def create_circle(n):
o = get_object("circle")
exists = len(o.data.vertices) > 0
if exists:
return o

verts = []
for i in range(n):
verts.append((0.0, 0.0, 0.0))
edges = []
for i in range(len(verts)-1):
edges.append((i, i+1))
edges.append((i+1, 0))

m = o.data
m.clear_geometry()
m.from_pydata(verts, edges, ())
return o

def draw_circle(r, segments = 32):
o = create_circle(segments)
o.location = 0.0, 0.0, 0.0

for i in range(segments):
a = 2 * math.pi / segments * i
o.data.vertices[i].co = math.cos(a) * r, math.sin(a) * r, 0.0

# ==================================================================================================
# draw lines connecting circle and x-axis
# ==================================================================================================

def draw_line(x, y, px, py, id):
o = get_object("line-" + id)
m = o.data
verts = m.vertices
exists = len(verts) > 0

if exists:
verts[0].co = x, y, 0.0
verts[1].co = px, py, 0.0
return

verts = []
verts.append((x, y, 0.0))
verts.append((px, py, 0.0))

m.clear_geometry()
m.from_pydata(verts, [(0,1)], ())

# ==================================================================================================
# draw geometry
# ==================================================================================================

def draw_empties(x, y, px, py):
get_empty("empty-1").location = x, y, 0.0
get_empty("empty-2").location = px, py, 0.0

def draw_circle_angle(angle):
rad = math.radians(angle)
draw_circle(RADIUS)
l1x = RADIUS * math.cos(math.pi * 2 - rad)
l1y = RADIUS * math.sin(rad)
l2x = angle / REVOLUTION * get_sinewave_length()
l2y = get_sinewave_y(angle)
draw_line(0, 0, l1x, l1y, "1")
draw_line(l2x, 0, l2x, l2y, "2")
draw_empties(l2x, l2y, l1x, l1y)

# ==================================================================================================
# animation functions
# ==================================================================================================

def animate():
f = bpy.context.scene.frame_current
angle = REVOLUTION / TOTAL_FRAMES * f
draw_sinwave(angle, '1')
draw_circle_angle(angle)

# ==================================================================================================
# execute code
# ==================================================================================================

def on_enter_frame(a,b):
animate()

bpy.app.handlers.frame_change_post.clear()
bpy.app.handlers.frame_change_post.append(on_enter_frame)

draw_sinwave(REVOLUTION, '2')
on_enter_frame(None, None)

for o in bpy.context.scene.objects:
o.select_set(True)
bpy.context.view_layer.objects.active = get_object("sinewave-1")

bpy.context.scene.objects['sinewave-2'].select_set(False)