6
$\begingroup$

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/ <image>

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).

<Octave animation> 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 <insert animation>

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

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": "rt_and_mike.tyka@gmail.com",
  "blender": (2, 80, 0),
  "category": "Add Curve",
  "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) 
    #bpy.context.scene.objects.link(objectdata)
    bpy.context.scene.collection.objects.link(objectdata)

    polyline = curvedata.splines.new("POLY")
    polyline.points.add(self.npoints-1)

    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_idname = "curve.clifford_attractor_add"
  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_idname = "curve.lorenz_attractor_add"
  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)



class Menu_MT_AttractorMenu(bpy.types.Menu):
  #bl_idname = "INFO_MT_curve_attractor_add"
  bl_idname= "menu.attractormenu"
  bl_label= "Test Attractors"

  def draw(self, context):
    layout = self.layout
    layout.operator_context = "INVOKE_REGION_WIN"
    #layout.label(text="Hello First Menu!", icon='WORLD_DATA')

    layout.operator("curve.clifford_attractor_add", text="Clifford")
    layout.operator("curve.lorenz_attractor_add",text="Lorenz")

def menu_func(self, context):
  #self.layout.menu("INFO_MT_curve_attractor_add", icon="PLUGIN")
  self.layout.menu(Menu_MT_AttractorMenu.bl_idname, icon="PLUGIN")
  #self.layout.operator(AttractorMenu.bl_idname)


#needed as workaround
bpy.types.VIEW3D_MT_curve_add.append(menu_func)

#All classes used  
classes = (
            Object_OT_Attractor,
            Menu_MT_AttractorMenu,
            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()
$\endgroup$
  • $\begingroup$ 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. $\endgroup$ – lemon Sep 14 at 7:43
4
$\begingroup$

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')
    polyline.bezier_points.add(self.npoints-1)

    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

    context.scene.collection.objects.link(objectdata)
    # 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.

enter image description here

$\endgroup$
  • $\begingroup$ 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. $\endgroup$ – Rick T Sep 14 at 11:04
  • 1
    $\begingroup$ I've rearranged a, b, c, d, e, f that may be the cause... $\endgroup$ – lemon Sep 14 at 11:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.