Python Ceramics
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.
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