3d Printing Fractals (1)

3d Printing Fractals (1)

This blog post goes alongside the video:

3d printing fractals

So going back a little bit to the fractal slicing blog here I wanted to move to generating 3d printed fractals. The first step is to 3d print a 2d fractal - so we can use the 'escape' level as the height, and it produces a very good 3 dimensional object, even if that is only an alternative representation of the 'colour' of the fractal.

To do this, I generated a point cloud, and the code to do this is relatively trivial - the code to work out if a point is inside or outside the fractal is as follows (where input is a complex number)

def frac_one(input, c):
    return input * input + c

def calc_iterations(location, c, limit=10, circle=2):
    if abs(location) > circle:
        return 0
    for i in range(limit):
        location = frac_one(location, c)
        if abs(location)> circle:
            break
    return(i+1)

There are multiple python libraries which will convert a coordinates into an .stl or equivalent and then save this nicely, but you can also just save it as a .xyz file and import it into something like meshlab to then turn into a surface, and an .stl and send it to your printer for slicing. Not perfect because the slicing process does loose resolution - i.e. not as good as generating gcode from the direct points, but still a good progress of 'code generated structures'.

For my first iteration, I used numpy-stl which was relatively straight forwards, but I had to generate the vertices as well as the points. I did this relatively straight forwards because I knew it was going to be a plane with no overhangs, so by offsetting each line by 1/2 a unit, it ended up with a net set of triangles:

data = []

limit = 15
divisor = 40
offset = (-75,-50)

size = 100

const = complex(0,0)
for i in range(size):
    data.append([])
    for j in range(size):
        point = complex((i+offset[0])/divisor, (j+offset[1])/divisor)
        counter = calc_iterations(const, point, limit=limit)
        data[i].append(counter)

So this is a big single dimension array of heights, and then the following makes an array of vertices neatly - I could possibly have done this in one loop with the above code, but hindsight is always easy. Note here that I offset every other line slightly. The values are all hard coded as a space of 1, because this is experimental art code, not enterprise stuff.

vertices = []

for j in range(size):
    for i in range(size):
        if j%2==0
            coord = [i,j, data[i][j]]
        else:
            coord = [i+0.5,j, data[i][j]]
        vertices.append(coord)
        
vertices = np.array(vertices)       

Okay, so now we need to make faces:

faces = []

for j in range(size):
    for i in range(size-1):
        this_point = j*size + i
        if j != 0:
            if j % 2 == 0:
                face = [this_point, this_point+1, this_point-size+1]
            else:
                face = [this_point, this_point+1, this_point-size]
            faces.append(face)
        if j != size-1:
            if j % 2 == 0:
                face = [this_point, this_point+1, this_point+size+1]
            else:
                face = [this_point, this_point+1, this_point+size]
            faces.append(face)
            
faces=np.array(faces)

The +1's are what make it index the mid point of the next line, and finally we can make the object:

object = mesh.Mesh(np.zeros(faces.shape[0], dtype=mesh.Mesh.dtype))
for i, f in enumerate(faces):
    for j in range(3):
        cube.vectors[i][j] = vertices[f[j],:]
        
object.save('object.stl')

The outcome is a combination of a file which could certainly be optimised, but is a flat surface - a little bit of work either to make it a closed mesh either in code, or part of the 3d printing toolchain (e.g. meshlab or fusion 360) is still required to make it a printable object with thickness.

And am I satisfied? No, because the issue here is that this is a 3d representation of a 2d object, and I know I can do better.

Mastodon