# Converting the Clifford Attractor equation into python to animate it in blender 2.8

I'm trying to re-create and animate a "Whispy Clifford Attractors" this http://paulbourke.net/fractals/clifford/5.jpg from this site http://paulbourke.net/fractals/clifford/

I can create the equations and animate the a variable in Octave / Matlab (but as you can see it's not a great image see animated gif below).

I'm having trouble re-creating and animating the image in Bender 2.8

I've adapted some python code but I'm having trouble with the math syntax to get it to work in Blender 2.8

Clifford formula
xn+1 = sin(a*yn) + c cos(a*xn)
yn+1 = sin(b*xn) + d cos(b*yn)
where a, b, c, d are variables that define each attractor.


When I run my script and click on -Add- -Curve- -Test Attactors- -Lorenz- This one works

When I click on -Add- -Curve- -Test Attactors- -Clifford- I get a single curve line.

I believe the issue is in these lines but I'm not sure how to fix it. I tried different for and while loops but no luck.

def iterate(self, x, y, z):

#while itr < 100:
#for i in range (10):
#add for loop find out npoints in while loop
dx = math.sin(self.a*y) + self.c*math.cos(self.a*x)
dy = math.sin(self.b*x) + self.d*math.cos(self.b*y)
dz = math.sin(self.e*x) + self.f*math.cos(self.e*z)
return (dx, dy, dz)


Here's the entire python code:

import bpy
import math

from bpy.props import BoolProperty
from bpy.props import FloatProperty
from bpy.props import FloatVectorProperty
from bpy.props import IntProperty

#print(IndentationError().msg)

bl_info = {
"author": "[email protected]",
"blender": (2, 80, 0),
"description": "Creates a strange attractor curve",
"location": "View3D > Add > Curve > Attractors",
"name": "Test Attractors",
"version": (0, 1),
"warning": "",
"wiki_url": ""
}

def get_prop(cls, name, default):
return bpy.props.FloatProperty(attr=cls + "_" + name,
name=name,
description="",
default=default)

def get_int_prop(cls, name, default):
return bpy.props.IntProperty(attr=cls + "_" + name,
name=name,
description="",
default=default)
def get_npoints(default=10000):
return IntProperty(attr="npoints",
name="Vertices",
description="",
min=1, soft_min=1, default=default)

class Object_OT_Attractor(bpy.types.Operator):
bl_idname= "object.attractor"
bl_label= "Label Attractor"
view_align= BoolProperty(name="Align to View",default=False)
location= FloatVectorProperty(name="Location",subtype="TRANSLATION")
rotation= FloatVectorProperty(name="Rotation",subtype="EULER")

def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, "npoints", text="Vertices")

col = layout.column(align=True)
for param in self.params:
col.prop(self, param, text=param)

col2 = layout.column(align=True)
col2.prop(self, "x", text="x")
col2.prop(self, "y", text="y")
col2.prop(self, "z", text="z")
col2.prop(self, "dt", text="dt")

def execute(self, context):
curvedata = bpy.data.curves.new(name="Curve", type="CURVE")
curvedata.dimensions = "3D"

objectdata = bpy.data.objects.new("ObjCurve", curvedata)
objectdata.location = (0,0,0)

polyline = curvedata.splines.new("POLY")

x = self.x
y = self.y
z = self.z

n = 0
while n < self.npoints:
dx, dy, dz = self.iterate(x, y, z)
x = x + self.dt * dx
y = y + self.dt * dy
z = z + self.dt * dz
polyline.points[n].co = (x,y,z,math.sqrt(dx*dx+dy*dy+dz*dz))
n += 1

return {"FINISHED"}

class CliffordAttractor(Object_OT_Attractor):
bl_label = "Clifford Attractor"
bl_options = {"REGISTER", "UNDO"}

npoints = get_npoints()
params = ["a", "b", "c", "d", "e", "f"]
a= get_prop("clifford", "a", -1.24458)
b= get_prop("clifford", "b", -1.25191)
c= get_prop("clifford", "c", -1.815908)
d= get_prop("clifford", "d", -1.90866)
e= get_prop("clifford", "e", -0.20)
f= get_prop("clifford", "f",  0.50)

x= get_prop("clifford", "x",   0.10)
y= get_prop("clifford", "y",   0.00)
z= get_prop("clifford", "z",   0.00)
dt= get_prop("clifford", "dt", 0.01)

def iterate(self, x, y, z):
#global itr
#while itr < 100:
#for i in range (10):
#add for loop find out npoints in while loop
dx = math.sin(self.a*y) + self.c*math.cos(self.a*x)
dy = math.sin(self.b*x) + self.d*math.cos(self.b*y)
dz = math.sin(self.e*x) + self.f*math.cos(self.e*z)
return (dx, dy, dz)

class LorenzAttractor(Object_OT_Attractor):
bl_label = "Lorenz Attractor"
bl_options = {"REGISTER", "UNDO"}

npoints = get_npoints()
params = ["a", "b", "c"]
a = get_prop("lorenz", "a",  10.00)
b = get_prop("lorenz", "b",  28.00)
c = get_prop("lorenz", "c",  (8/3))

x = get_prop("lorenz", "x",   0.10)
y = get_prop("lorenz", "y",   0.00)
z = get_prop("lorenz", "z",   0.00)
dt = get_prop("lorenz", "dt", 0.01)

def iterate(self, x, y, z):
dx = self.a * (y - x)
dy = (x * (self.b - z) - y)
dz = (x * y - self.c * z)
return (dx, dy, dz)

bl_label= "Test Attractors"

def draw(self, context):
layout = self.layout
layout.operator_context = "INVOKE_REGION_WIN"

#needed as workaround

#All classes used
classes = (
Object_OT_Attractor,
LorenzAttractor,
CliffordAttractor,
)

#Register and unregister all classes
register, unregister = bpy.utils.register_classes_factory(classes)

# This allows you to run the script directly from Blender's Text editor
# to test the add-on without having to install it.
if __name__ == "__main__":
register()

• At least, should not be "x = x + self.dt * dx" but "x = self.dt * dx". Nonetheless this is not enough to have a nice curve. Sep 14, 2019 at 7:43

Some little enhancements in 'Object_OT_Attractor'.

Mainly the issue was due to x = x + ... instead of x = .... So have changed that but that means you'll have to adapt Lorenz the same way.

Have also used a Bezier curve with 'AUTOMATIC' handle type.

  def execute(self, context):
curvedata = bpy.data.curves.new(name="Curve", type='CURVE')
curvedata.dimensions = '3D'

objectdata = bpy.data.objects.new("ObjCurve", curvedata)
objectdata.location = (0,0,0)

# Changed to Bezier
polyline = curvedata.splines.new('BEZIER')

x = self.x
y = self.y
z = self.z

n = 0
while n < self.npoints:
dx, dy, dz = self.iterate(x, y, z)
# No more x = x +
x = self.dt * dx
y = self.dt * dy
z = self.dt * dz
polyline.bezier_points[n].co = (x,y,z)
n += 1

# Set object active
objectdata.select_set( True )
context.view_layer.objects.active = objectdata
bpy.ops.object.editmode_toggle()
bpy.ops.curve.select_all(action='SELECT')
# Automatic handle type
bpy.ops.curve.handle_type_set(type='AUTOMATIC')
bpy.ops.object.editmode_toggle()

return {"FINISHED"}


Note: I've used bpy.ops to set handle type as setting it directly in data is too slow.

• Thanks!! I replaced my def execute in the Object_OT_Attractor with yours but when I run the script and click on the Clifford or the Lorenz option the plugin works but the image is a extremely small single line curve hook similar to before. Nothing like your cool animated image. Sep 14, 2019 at 11:04
• I've rearranged a, b, c, d, e, f that may be the cause... Sep 14, 2019 at 11:14
• another way to get wispy effect youtu.be/rtPzXrZ23k0 Jan 24, 2022 at 12:25
• 2nd way to get wispy effect youtu.be/CrOboutVHH8 Jul 4, 2023 at 16:49