Wednesday, 13 November 2013

Tutorial: Penrose Tiling in Blender




Building tiling patterns in Blender is pretty easy if you know how to use the array modifier... at least, it's usually easy if the tiling pattern repeats itself over space. For example, you can use the array modifier to construct practically any pattern from one of the wallpaper groups.

But that's not what I'm talking about today. I want to talk about Penrose tiling. Penrose tiling is a non-periodic tiling pattern. This means means it does not repeat itself as you move through space, and Blender's array modifier therefore can't be used to construct the pattern. Instead, I used Blender's scripting tools to do Penrose tiling.
(Edit: Penrose tiling is pentagonally symmetric, and you actually can use the array modifier speed up the process of making pentagonally symmetric patterns- however- this process would still require that you do most of the work by hand. Working by hand is slow, and it's very easy to make a mistake early in the process which later results in the pattern containing "hole")

Check out what I made:







I was surprised that I couldn't find anything online about Penrose tiling in Blender, so I decided to write something up myself.  The scripts I used are adapted from the algorithm described in this blog post I found about Penrose tiling. Please check out the link if you're curious about how the script works. If you want to skip this tutorial and just try out my Blender-adapted version of the script, I pasted it at the end of this post.

The steps of my process:

  • Generate a Penrose patterned mesh with the script
  • Use selection tricks to isolate specific patterns of edges in the mesh
  • Dissolve those edges to make new shapes (stars, diamonds etc.)
  • Edge split the shapes into islands. Separate the mesh into different objects according to shape.
  • The rest. (Solidify, bevel, light, animate, render etc.) 


Start by opening a new blender scene, open up the text editor, copy and paste my script, and press the Run Script button.  The script should generate a ten-sided grey disc in the 3D viewport.  Boring. 



Look at the disc in edit mode (right click to select it, and hit tab to enter edit mode). The disc is actually an interesting pattern of triangles. Less boring!

This is our base pattern. From here, I found a way to pull out more interesting patterns of shapes. Try mine out. If you come up with your own, please show me how you did it!

Go into edge mode (ctrl-tab then 2). If you look, the mesh has edges of three different lengths. Select a short edge, then go to the menu Select -> Similar -> Length.

Due to roundoff error, not all of the short edges are exactly the same length. Because of this, the select similar length command might not get the edges you want. If you play with it's threshold value (see picture below), you should be able to get all of the short edges in the mesh, resulting in a selection that looks like this:

Hit x -> Dissolve Edges to make all the small edges disappear. The mesh should now look like this:


Do the same thing to the long edges. Select all of them and dissolve them.


That's the pattern at the top of this post! Cool!

So, at this point, we can start trying to pull shapes out of the pattern using some selection trickery. Warning: do exactly as I say in the order I say it, or it might not work.



Go into face-select mode (ctrl-tab then 3), select a thin diamond, and select -> similar -> area. You should get this:

 Switch to vertex mode (ctrl-tab then 1), and invert the selection (ctrl-i). Deselect the loop of vertices on the edge of the mesh.



You should get this. The center of all the star shapes are selected. Dissolve them! (x -> Dissolve Vertices).


Oo, now you have stars. See? 

We can stillget some more shapes.



Switch to face select mode, and select all the thin diamond faces again. Invert the selection. Switch to edge mode. Invert the selection. Dissolve the edges that remain selected. This trick should merge adjacent pairs of thin diamonds into an angular pac-man-like shape.

One more trick.
There are a few places in the mesh where four fat diamonds make an incomplete star. In vertex mode, select the center vertex of an incomplete star.

Now go Select->Similar->Amount of connecting edges, switch to edge mode, and dissolve the selected edges.




The hard part is over, and the resulting pattern should look like this ->

If you're familiar with Blender, the rest of what I did was pretty straightforward, so I'll be brief.

We don't need to keep all the shapes as a single mesh anymore. Selecting all the vertices (ctrl-a), and edge split everything (ctrl-e->Edge split). Now if you select the stars (and only the stars... remember, use Select->Similar->Area), you can split that set of polygons into a new object (p->Selection). In this way, separate each set of shapes into their own mesh object.

Now we can select between shapes in object mode, and assign different materials (with different colours) depending on the shape.






Above, in this last image, you can see the modifiers I used and their settings.

I put in lights, built in a looping animation, and rendered.  So, that's pretty much it!

One last comment: as you may have observed, Penrose tiling is pentagonally symmetric. Interestingly enough, this is the reason why Penrose tiling is non-periodic. The crystallographic restriction theorem proves that any regularly spaced 2D pattern can't be pentatonally symmetric. Just try it!  If you attempt to create a tiling pattern of regular pentagons, you will fail!.

Now, you might be wondering why the python subdivision algorithm is using the golden ratio, φ. Well, I did some reading, and apparently φ (the golden ratio) appears a lot when you're working with pentagonal symmetry.  This happens because the cosine of 72°( which is one fifth of 360°) is φ/2.


Happy tiling!
~ CG From Space






My Blender-adapted script:



import bpy
import bmesh
import math
from mathutils import Vector

subDivIterations = 6

goldenRatio = (1 + math.sqrt(5)) / 2

def subdivide(triangles):
    result = []
    for color, A, B, C in triangles:
        if color == 0:
            # Subdivide red triangle
            P = A + (B - A) / goldenRatio
            result += [(0, C, P, B), (1, P, C, A)]
        else:
            # Subdivide blue triangle
            Q = B + (A - B) / goldenRatio
            R = B + (C - B) / goldenRatio
            result += [(1, R, C, A), (1, Q, R, B), (0, R, Q, A)]
    return result

# Create wheel of red triangles around the origin
def createWheel():
   
    triangles = []
  
    for i in range(10):
        theta_i = i*2.0*math.pi/10.0
        theta_ip1 = (i+1)*2.0*math.pi/10.0
      
        x_i = math.cos(theta_i)
        x_ip1 = math.cos(theta_ip1)
      
        y_i = math.sin(theta_i)
        y_ip1 = math.sin(theta_ip1)
      
        A = Vector((0.0,0.0,0.0))
        B = Vector((x_i,y_i,0.0))
        C = Vector((x_ip1,y_ip1,0.0))
      
        if i % 2 == 0:
            B, C = C, B  # Make sure to mirror every second triangle
        triangles.append((0, A, B, C))
    return triangles
  


# Generate map of tiling
listTriangles = createWheel()
for x in range(subDivIterations):
    listTriangles = subdivide(listTriangles)

# Construct the lists necessary for generation of geometry
listVertices = []
listFaces = []
for triangle in listTriangles:
    # write the vertex coords to the list, and remember the vertex indices
    # In Blender, the mesh data stores each vertex as a tuple of 3 floats.
    newVertex1 = (triangle[1][0],triangle[1][1],triangle[1][2])
    newVertex2 = (triangle[2][0],triangle[2][1],triangle[2][2])
    newVertex3 = (triangle[3][0],triangle[3][1],triangle[3][2])
  
    listVertices.append(newVertex1)
    newVertex1_i = len(listVertices) - 1
    listVertices.append(newVertex2)
    newVertex2_i = len(listVertices) - 1
    listVertices.append(newVertex3)
    newVertex3_i = len(listVertices) - 1
    # Define the faces by index numbers. Each faces is defined by a list of the vertex indices belonging to the face.
    # For triangles you need to repeat the first vertex also in the fourth position.
    newFace = (newVertex1_i,newVertex2_i,newVertex3_i)
    listFaces.append(newFace)
  

# Build the mesh in Blender's API

mesh = bpy.data.meshes.new("penroseMesh")   # create a new mesh

ob = bpy.data.objects.new("Pyramid", mesh)      # create an object with that mesh
ob.location = Vector((0,0,0)) #by.context.scene.cursor_location   # position object at 3d-cursor
bpy.context.scene.objects.link(ob)                # Link object to scene

# Fill the mesh with verts, edges, faces
mesh.from_pydata(listVertices,[],listFaces)   # edges or faces should be [], or you ask for problems
mesh.validate(True)
#mesh.update(calc_edges=True)    # Update mesh with new data

bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bmesh.ops.reverse_faces(bm, faces=bm.faces)
bm.to_mesh(mesh)
mesh.update()

17 comments:

  1. Cool stuff! Thanks for sharing!

    ReplyDelete
  2. Very interesting, and the video you made is fascinating to watch.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. With the operator "tris to quad" you have yet another pattern
      thanks

      Delete
  4. I have problems following the last selection steps: once the vertex of the incomplete stars are selected with "Amount of connecting edges" they are single vertex and switching to edge mode loose the selection.
    I've tried growing the selection and dissolving the edges but I got a different pattern. How you make that last selection?

    ReplyDelete
    Replies
    1. It should work if you've already done the bit where you merge a number of the thin diamonds. If you follow my instructions out of order it won't work.

      Delete
  5. Hey thx for this great info

    ReplyDelete
  6. Robin, great script! I made some minor modifications and converted it to a Sverchok (blender addon) script for ScriptNode. If you are interested: https://github.com/nortikin/sverchok/issues/85#issuecomment-41896469

    ReplyDelete
    Replies
    1. Hey, that's cool!
      What is Sverchok? I watched some videos, and it looks like a node based ... particle system ... or something?

      Have you seen the other patterns from my newer post?
      http://cgfromspace.blogspot.ca/2013/12/non-periodic-tiling-in-blender-chapter-2.html

      Delete
    2. I read a bit more:
      That project looks really exciting! I'll be trying it out this weekend :-)

      Delete
    3. sverchok - is programming of low-level-data as digits, vectors etc with nodes - height level tool. we are open to involve new progers while it version alpha. many job to be done.

      Delete
  7. gracias... funciono perfecto en 2.74

    ReplyDelete
  8. I see this is a few years old - I tried to run the script and got an error that seemed to point to line 86.

    This is my first time using blender so I have no clue what I'm doing - I just really wanted the model of the lovely pattern you made. Is there any way just to download that and save myself some agony?

    ReplyDelete
    Replies
    1. For me, the issue was line 83. The API was changed in version 2.80, I think. Line 83 should now be:
      bpy.context.scene.collection.objects.link(ob) # Link object to scene

      Delete
    2. Also, line 87 should just be mesh.validate with no (True).

      Delete
    3. the corrections above worked for me as well on version 3.4.1. To reiterate:

      83 bpy.context.collection.objects.link(ob) # Link object to scene
      87 mesh.validate

      can't wait to 3D print this! Thank you :)

      Delete