You see, I've stumbled across an excellent website, one Tiling Encyclopedia which archives almost two-hundred non-periodic patterns. I was delighted to discover that most (if not all) of the listed patterns can be generated using simple substitution algorithms. What's a substitution algorithm? Well, allow me to explain myself with an example: the Kite-Domino (discovered by D.Frettlöh and M. Baake). There's only two kinds of tiles: "Kites" and "Dominos".
These are the substitution rules which generate the Kite-Domino. As you can see, the algorithm is simple enough to summarize in a single image.
Any place you have a Kite prototile, replace it with the designated configuration of kite and domino prototiles. Likewise, the domino prototile has it's own substitution rule.
After several iterations of substitution, you can turn a single domino into an extremely complicated pattern.
Here's another one: the Goodman-Strauss 7-Fold Rhomb, named after it's discoverer, C. Goodman-Strauss.
I found this one particularly beautiful, and it's the one which initially motivated me to code my tiling script.
This time, there's three rhombus prototiles. I had to do a bit of math to figure out how to model them: it turns out that each rhombus has an angle 180/7.0 times 1, 2 and 3 respectively (hence "7-Fold Rhomb" I suppose?).
As you can see, this pattern uses the same kind of substitution rule. The only difference is that this time, you need to be a little more careful when building your substitutions because the orientation of the tile is important. Notice that little arrow markings were added to the shapes - these indicate the orientation of the tiles.
Most, if not all of the patterns documented in the Tiling Encyclopedia can be generated from a substitution rule. Naturally, I decided to find a way to build a set of substitution rules into Blender and use them to generate patterns. Read on to find out how this works.
Tutorial
My script is available at the end of this post.
Let's pick an interesting pattern to build ... say, the Pinwheel (discovered by J. Conway and C. Radin) ... it's apparently famous enough to have a wikipedia article. The prototile substitution rules are given on the right.
We need to:
- Model the prototiles
- Define the substitution rules
- Set the initial conditions
- Run the script
If an object has a name of the form [shapeName].###, my script will treat them as an ordinary prototile of type [shapeName].
In building the Pinwheel, I used two prototile types: Triangle and TriangleRev. Both are triangles with side lengths 1:2:√5, and mirror images of one another.
It should be possible to build the Pinwheel with only one prototile, but my script seems unable to properly mirror tiles (i.e. work with objects that have negative scale on one axis). Until this gets fixed, we can workaround by using a second prototile as the mirrored shape.
Build the triangle and it's mirror image as separate objects. I suggest you name them Triangle.000 and TriangleRev.000. The former is depicted above.
Model the triangles however you like, but make sure that the width of their base is double their height. I chose to model them aligned with the x-z plane, and with a height of 1. I also chose to build the narrow corner of both prototiles at their respective object's origin/center.
Define the substitution rules
A substitution rule will be represented by an object (named SubRule_[shapeName]) and the tiling of objects parented to it. The children, (named Sub_[shapeName].###) will inform my script where to put the new tiles when substituting the old one. We do this by scaling down the "Sub" tiles, and fitting them over top the "SubRule" object, filling its area. We basically are going to reconstruct the substitution rule diagrams, one for each prototile.
To build the SubRule objects, I duplicated the Triangle and TriangleRev prototiles, and re-named them as SubRule_Triangle and SubRule_TriangleRev respectively. If you like, you can store different SubRule objects on separate layers. When my script finds these two objects, it will understand that these objects are involved in defining substitution rules for tiles of type Triangle and TriangleRev. Save yourself the headache, and clear any scaling or rotation from these objects.
I built the Sub objects from more duplicates of the prototiles. These objects were named Sub_Triangle.000, Sub_Triangle.001 ... Sub_TriangleRev.000, Sub_TriangleRev.001 etc. I then scaled them down and started tiling them according to the substitution rule diagram. When positioning these tiles, make sure you're transforming the objects themselves (don't edit the mesh in any way), and make sure these objects are parented to the "SubRule" object.
On the right, I assembled the Triangle substitution rule. The red, dark orange, and green triangles are named Sub_TriangleRev.### and the yellow and light orange triangles are named Sub_Triangle.###.
A substitution rule will be represented by an object (named SubRule_[shapeName]) and the tiling of objects parented to it. The children, (named Sub_[shapeName].###) will inform my script where to put the new tiles when substituting the old one. We do this by scaling down the "Sub" tiles, and fitting them over top the "SubRule" object, filling its area. We basically are going to reconstruct the substitution rule diagrams, one for each prototile.
To build the SubRule objects, I duplicated the Triangle and TriangleRev prototiles, and re-named them as SubRule_Triangle and SubRule_TriangleRev respectively. If you like, you can store different SubRule objects on separate layers. When my script finds these two objects, it will understand that these objects are involved in defining substitution rules for tiles of type Triangle and TriangleRev. Save yourself the headache, and clear any scaling or rotation from these objects.
I built the Sub objects from more duplicates of the prototiles. These objects were named Sub_Triangle.000, Sub_Triangle.001 ... Sub_TriangleRev.000, Sub_TriangleRev.001 etc. I then scaled them down and started tiling them according to the substitution rule diagram. When positioning these tiles, make sure you're transforming the objects themselves (don't edit the mesh in any way), and make sure these objects are parented to the "SubRule" object.
On the right, I assembled the Triangle substitution rule. The red, dark orange, and green triangles are named Sub_TriangleRev.### and the yellow and light orange triangles are named Sub_Triangle.###.
You might be asking yourself, how do I transform the Sub tiles into place?
- Translation
- Scale
- Rotation
SubRule_TriangleRev was built in a similar manner.
The hardest is over. Now we only need to place a few tiles so that we can subdivide them. I duplicated the Triangle object twice and arranged them into a square. The tiles are red and green in my picture just so that you can tell them apart. Once they're substituted by my script, the new tiles will inherit the colours designated in the substitution rules.
Run the Script
My script will try to subdivide whatever objects you have selected. Open up the script in the text editor, and click the "Run Script" button. You might have noticed that the script starts getting pretty slow after two or three subdivisions.
The slowdown is another one of my script's shortcomings. I suspect the scene.update() command is the bottleneck, but I'm not sure yet. I have some ideas for optimization/speed-up, but if anyone has any suggestions, I would be delighted to hear from you.
Result
This is roughly what the result should look like. If you merge the tiles into a single mesh and mark all the edges as Freestyle edges, you can outline each triangle. I prefer this pattern without outlines however - it causes multiple triangles to get lumped into interesting shapes.
So that's it! Venture onwards! Endow your 3D artwork with the manifold bounties of non-periodic tiling! Try out my script. Show me cool stuff that you made! If you have any problems, please don't hesitate to send me questions.
import bpy
import bmesh
from math import *
from mathutils import Vector
# Some borrowed code (Apparently from Nick Keeline's "Cloud Generator") which I again borrowed.
def duplicateObject(scene, name, copyobj):
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Create new object associated with the mesh
ob_new = bpy.data.objects.new(name, mesh)
# Copy data block from the old object into the new object
ob_new.data = copyobj.data.copy()
ob_new.scale = copyobj.scale
ob_new.rotation_euler = copyobj.rotation_euler
ob_new.location = copyobj.location
# Link new object to the given scene and select it
scene.objects.link(ob_new)
ob_new.select = False
return ob_new
############
### MAIN ###
############
# Number of times to iterate substitution rules. Advice: it often gets very slow after 2-3 iterations.
numIterations = 1
currScene = bpy.data.scenes["Scene"]
# Get list of objects to substitute (from selection)
l_objToBeSubbed = bpy.context.selected_objects
# Initialize a list of objects that will substitute the above objects
l_objWhichSubbed = []
# Get the list of objects which contain the substitution rules.
l_subRuleObj = [item for item in bpy.data.objects if item.name.split("_")[0] == "SubRule"]
# Generate a list of the types of shapes that will be substituted.
l_shapeType = [item.name.split("_")[1] for item in bpy.data.objects if item.name.split("_")[0] == "SubRule"]
for iteration in range(numIterations):
# Iterate over each object that needs to be subdivided
for obj in l_objToBeSubbed:
# Determine the kind of substitution needed for the current tile
shapeType = obj.name.split(".")[0]
i_type = l_shapeType.index(shapeType)
# Get the object which contains the needed substitution rule
subRuleObj = l_subRuleObj[i_type]
print("Subbing "+obj.name+" using SubRule "+subRuleObj.name)
# Get the list of template tiles that will determine where to put the new tiles
l_objSubstitutes = subRuleObj.children
# Initialize a list of new shapes to be duplicated from the teplates.
l_newShapes = []
for subst in l_objSubstitutes:
print("Substitute object: "+subst.name)
newShapeType = subst.name.split("_")[1].split(".")[0]
newShape = duplicateObject(currScene,newShapeType,subst)
l_newShapes.append(newShape)
# Do this here to speed things up.
currScene.update()
# Duplicate the templated pattern of tile objects and place them into the tesselation
# according to the arrangement of templates stored in the substitution rule.
for newShape in l_newShapes:
# Find transforms if parented
# To optimize, try to use currScene.update() less frequently.
tMat = obj.matrix_world*newShape.matrix_world
newShape.location = tMat.to_translation()
newShape.rotation_euler = tMat.to_euler('XYZ')
newShape.scale = tMat.to_scale()
# # A different way ... it seems just as slow.
# newShape.parent = obj
# currScene.update() # update the matrix_world data
# loc = newShape.matrix_world.to_translation()
# rot = newShape.matrix_world.to_euler('XYZ')
# scl = newShape.matrix_world.to_scale()
# newShape.parent = None
# # Set transforms as though parented
# newShape.location = loc
# newShape.rotation_euler = rot
# newShape.scale = scl
# Track the new substitute objects
l_objWhichSubbed.append(newShape)
# Delete the objects I substituted
for obj in l_objToBeSubbed:
obj.select = True
bpy.ops.object.delete(use_global=False)
# If we are iterating again, we need to designate the newly created tiles
# as the objects we want to swap.
l_objToBeSubbed = l_objWhichSubbed
l_objWhichSubbed = []
# Select the newly created objects
for obj in l_objToBeSubbed:
obj.select = True
- CG From Space