Python Ceramics

Fibonacci Bowls

Selection of fibonacci bowls

It's like found in nature dude . 

As I was watching a royal institution christmas lecture snippet, covering the appearance of the fibonacci sequence in nature, I thought to my self, 'I could pop that into a script in blender', and so I did

This first section is the same setup as I've used on other scripts, and basically just sets up the location of the meta balls and adds them. This and some of the incrementation section below was used to create the bowls without indentations in them.

This section of code creates a second set of meta balls which sit slightly outside where the initial set of meta balls are, the 'use_nagative = True' line at the end however gives this second set of meta balls a negative influence, which in turn causes the indentations seen in two of the bowls.

This final section of code controls the changes made to each row of meta objects

  • after each object is placed the calcAngle moves the position of the next object around by 137.5 degrees.

  • z just increments the z location of the objects up a set amount

  • dist moves the normal meta balls out from the center, dist_inv does the same for the negative meta balls

  • r decreases the radius of the meta balls as they increase in number, r_inv does the same for the negative meta balls

To explain the dist and dist_inv calculation(s) I've added the below gallery which shows how various calculations affect the way the bowl curves outward, going from a straight line to a concave or convex curve.

fibi (calm white + back light) 02.png

This was a little experiment that was done outside of the script, this is basically one of the bowls with negative metas converted to a mesh, then I deleted some faces and added a solidify modifier to the mesh.


import math
import bpy

# number of object objects at each z index
sequence = [3, 5, 8, 13, 21]
seq_inv = [5, 8, 13, 21]

# distance from the grid center
dist = 1.25
dist_inv = 3.45

# start points for rotation calculations
start = 0

calcAngle = 137.5

z = 0
z_inv = 1
r = 1
r_inv = 0.70

# Remove any pre-existing objects from the scene
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

for i in sequence:
    for i in range(i):

        angle = (start * 0.01745329252)

        # calculate the x location for main metaball objects
        xval = math.sin(angle)
        xcalc = (format(xval*dist, '.2f'))
        xcords = (float(xcalc))

        # calculate the y location for each object
        yval = math.cos(angle)
        ycalc = (format(yval*dist, '.2f'))
        ycords = (float(ycalc))

        # add objects
        mb = bpy.ops.object.metaball_add(
            type='BALL',
            radius=r,
            location=(xcords, ycords, z)
        )

        # calculate the x location for each object
        xval = math.sin(angle)
        xcalc = (format(xval*dist_inv, '.2f'))
        xcords = (float(xcalc))

        # calculate the y location for negative metaball objects
        yval = math.cos(angle)
        ycalc = (format(yval*dist_inv, '.2f'))
        ycords = (float(ycalc))

        # add objects
        mb = bpy.ops.object.metaball_add(
            type='BALL',
            radius=r_inv,
            location=(xcords, ycords, z)
        )
        bpy.context.active_object.data.elements[0].use_negative = True

        # rotate the start point for the next object
        start = start + calcAngle
    z = z + 0.95
    dist = dist + (1.90 / z)
    dist_inv = dist_inv + (1.225 / z)
    r = r - 0.080
    r_inv = r_inv - 0.090

Meta Funnel

Almost 42 years without needing to really know about or use sine and cosine, then I get the bright idea of scripting something with rotation in it and ruin my streak.

In addition to bpy, math was also imported for this script, below is where the control values for generating the object(s) are set.

Working backwards from the bottom, the add objects section sets the radius, location and local rotation of the meta ellipsoid.

The X and Y locations are calculated using sine and cosine, the angle is calculated by dividing 360 by the number of columns, this is then converted to radians and passed to the sine and cosine methods.

The local rotation of the ellipsoids is easier to calculate in that it is a straightforward conversion of an angle to radians.

After each object has been created these next two lines of code rotate the location around the centre point and local rotation of the next object by the same amount.

After a complete stack has been generated, 

  • the Z location is incremented (though it could be de-incremented)

  • Next the rotation of the next stacks start location can be modified, for straight or leaning columns

  • The distance from the centre point can also be changed so the columns can made to bend inwards, outwards or go straight upwards

  • Finally the radius of the the objects in this stack can be modified.

The material is based on a porcelain coated with a clear glaze. 

The base of each of the structure was made with a meta plain object, these however get thicker as well as wider as the radius increases, so in order to make the base of each structure look nice when sitting on the floor once I was happy with the settings I converted the meta objects to a single mesh. Once the structure was in a mesh form I deleted a whole bunch of vertices from the base plain, tidied it up and extruded a nice flat bottom for the structure (with a small bevel added to it). 


import math
import bpy

created = []

# number of object columns
columns = 10
# number of objects in each column
stacks = 6
# z location start position
z = 0
# distance from the grid center
dist = 4

# start points for rotation calculations
start = 360
startDegree = 0

# calculate the angle that each column around the grid center
calcAngle = start/columns

# radius of object
metaRad = 0.75

# Remove any pre-existing objects from the scene
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()

for i in range(stacks):
    for i in range(columns):

        angle = (start * 0.01745329252)
        degree = startDegree * 0.01745329252

        # calculate the x location for each object
        xval = math.sin(angle)
        xcalc = (format(xval*dist, '.2f'))
        xcords = (float(xcalc))

        # calculate the y location for each object
        yval = math.cos(angle)
        ycalc = (format(yval*dist, '.2f'))
        ycords = (float(ycalc))

        # add objects
        mb = bpy.ops.object.metaball_add(
            type='ELLIPSOID',
            radius=metaRad,
            location=(xcords, ycords, z),
            rotation=(0, 0, degree)
        )

        # cube = bpy.ops.mesh.primitive_cube_add(
        #     size = 1,
        #     location = (xcords, ycords, z),
        #     rotation = (0, 0, degree)
        # )

        # rotate the start point for the next object
        start = start - calcAngle

        # rotate the local oriantation of each object
        startDegree = startDegree + calcAngle

    # after each stack has been created
    # increase (or decrease) the z location for the next stack
    z = z+0.95
    # rotate the start location(s) for the next stack by a set amount
    calcAngle = calcAngle-0.45
    # change the start distance from grid center to decrease or increase radius
    # of the stack
    # dist = dist - (0.15 * z)
    dist = dist + (0.225 * z)
    # increase or decrease the radius of the objects in each stack
    # (not radius of the stack)
    metaRad = metaRad + 0.03


bpy.ops.object.metaball_add(
    type='PLANE',
    radius=3,
    location=(0, 0, -3),
    rotation=(0, 0, 0)
    )

# apply a material to each object in the scene
# create material called 'neutral'
neutral = bpy.data.materials.new("material")
neutral.use_nodes = True

# clear any existing node setup
neutral.node_tree.nodes.clear()

# create nodes
cords = neutral.node_tree.nodes.new(type='ShaderNodeTexCoord')
vorOne = neutral.node_tree.nodes.new(type='ShaderNodeTexVoronoi')
vorTwo = neutral.node_tree.nodes.new(type='ShaderNodeTexVoronoi')
mathSub = neutral.node_tree.nodes.new(type='ShaderNodeMath')
invert = neutral.node_tree.nodes.new(type='ShaderNodeInvert')
bump = neutral.node_tree.nodes.new(type='ShaderNodeBump')
noise = neutral.node_tree.nodes.new(type='ShaderNodeTexNoise')
princip = neutral.node_tree.nodes.new(type='ShaderNodeBsdfPrincipled')
output = neutral.node_tree.nodes.new(type='ShaderNodeOutputMaterial')

vorOne.inputs[1].default_value = 7.000
vorTwo.inputs[1].default_value = 6.995

mathSub.operation = 'SUBTRACT'

invert.inputs[0].default_value = 0.950

bump.inputs[0].default_value = 1.0

noise.inputs[1].default_value = 2.0
noise.inputs[2].default_value = 2.0
noise.inputs[3].default_value = 2.0

princip.inputs[0].default_value = [0.863, 0.776, 0.638, 1.0]
princip.inputs[1].default_value = 0.6
princip.inputs[3].default_value = [0.8, 1.0, 1.0, 1.0]
princip.inputs[12].default_value = 1.0
princip.inputs[14].default_value = 1.504
princip.inputs[15].default_value = 0.2

# link the nodes
neutral.node_tree.links.new(
    cords.outputs['Object'],
    vorOne.inputs['Vector'])

neutral.node_tree.links.new(
    cords.outputs['Object'],
    vorTwo.inputs['Vector'])

neutral.node_tree.links.new(
    vorOne.outputs['Fac'],
    mathSub.inputs[0])

neutral.node_tree.links.new(
    vorTwo.outputs['Fac'],
    mathSub.inputs[1])

neutral.node_tree.links.new(
    mathSub.outputs['Value'],
    invert.inputs['Color'])

neutral.node_tree.links.new(
    invert.outputs['Color'],
    bump.inputs[2])

neutral.node_tree.links.new(
    bump.outputs['Normal'],
    princip.inputs['Normal'])

neutral.node_tree.links.new(
    noise.outputs['Fac'],
    princip.inputs['Roughness'])

neutral.node_tree.links.new(
    princip.outputs['BSDF'],
    output.inputs['Surface'])

# position the nodes
noise.location = (-200, 0)
bump.location = (-200, -200)
invert.location = (-375, -200)
mathSub.location = (-550, -200)
vorOne.location = (-745, -100)
vorTwo.location = (-745, -300)
cords.location = (-945, -200)
princip.location = (000, 0)
output.location = (200, -200)


for ob in bpy.data.objects:
    created.append(ob.name)

name = -1
for i in created:
    name = name + 1

    target = bpy.data.objects[created[name]]
    target.active_material = neutral