Welcome, guest | Sign In | My Account | Store | Cart
```# This progam creates a non periodic tiling of the plane using 'Penrose tiles',
# wich were invented by Roger Penrose.
# Each time it runs it will generate a new random pattern.
# There are an infinite number of them.
# Unlike a periodic tesselation the assembly requires forward planning and
# hence the program searches through various combinations of parts, often
# comming to a dead end and having to backtrack.
# The program has three phases, the first generating the initial layout with
# a simple sharks tooth design
# The second fills in the tiles completely
# The third phase reveals an inner fractal pattern by the process of deflation
# then deflating again over and over
# You must close the progam to stop it.
from __future__ import division
from Tkinter import *
from math import cos, sin, atan2, sqrt, pi
from random import random, shuffle, seed
from numpy import array, dot, transpose
from copy import deepcopy
from sys import getrecursionlimit, setrecursionlimit

nW = 800.0 # change these to make the screen bigger or smaller
nH = 600.0
canvas = Canvas( width = nW, height = nH, bg = 'darkred')
canvas.pack(expand = YES, fill = BOTH)

# setrecursionlimit(10000)
nRecLim = getrecursionlimit()
nScale = 30.0 # sqrt(nW * nH / nRecLim) / nFib #25.0 # change this to make the tile bigger or smaller
nFib   = (1.0 + sqrt(5.0))/2.0 # Fibonacci's golden ratio
nSmall = 5 # the smallest possible gap should be 18`
n36    = 36.0 * pi / 180.0
nSin36 = sin(n36)
nCos36 = cos(n36)
n72    = 72.0 * pi / 180.0
nSin72 = sin(n72)
nCos72 = cos(n72)
nSelfX = nScale * nFib * nSin36
nSelfY = nScale * nFib * nCos36
nW2 = nW / 2
nH2 = nH / 2
nTag = 0
nRatioSmall = nScale * nFib / (nFib + 1)
nRatioBig   = nScale * nFib**2 / (nFib + 1)
lAlternate = False
def NewTag():
global nTag
nTag += 1
return "TAG%d" %(nTag)

def Scalar(midx, midy, nR, x0, y0):
midx -= x0
midy -= y0
midR = sqrt(midx**2 + midy**2)
if midR == 0:
return [x0, y0]
midx *= nR / midR
midy *= nR / midR
midx += x0
midy += y0
return [midx,midy]

def MidArc(p1, p2, p0, nR):
x0 = p0[0]
y0 = p0[1]
x1 = p1[0]
y1 = p1[1]
x2 = p2[0]
y2 = p2[1]
midx = (x1 + x2) / 2
midy = (y1 + y2) / 2
return Scalar(midx, midy, nR, x0, y0)

def Perimeter(aP, lComplete = False):
points = []
for i in aP:
points += (i[0], i[1])
if lComplete:
points += (aP[0][0], aP[0][1])
return points

def linearTranslation(a1, points, r, a2):
nL = len(points)
p1 = array([a1] * nL, 'f')
q = points - p1
q = transpose(q)
q = dot(r, q)
q = transpose( q)
p2 = array([a2] * nL, 'f')
q = q + p2
return q

class shape:
def GetPoints(self):
pass
def __init__(self, cTag = None):
if cTag == None:
cTag = NewTag()
self.length = [2,1,1,2]
self.GetPoints()
self.points   = array(self.points, 'f')
self.vertices = self.points
self.tag = cTag
def draw(self):
v = Perimeter(self.vertices)
v.append(v[0])
v.append(v[1])
canvas.create_line(v, fill = 'orange', tag = self.tag, width = 1)
def drawOutline(self, x, y, n0, nC):
cosTheta = cos(n0)
sinTheta = sin(n0)
r     = array([[cosTheta, -sinTheta],[sinTheta,cosTheta]], 'f')
q     = linearTranslation(self.points[nC], self.points, r, [x, y])
self.vertices = q
self.draw()
def drawFill(self):
points = Perimeter(self.vertices)
canvas.create_polygon(points, fill = self.colour, tag = self.tag)
def drawComplete(self):
self.drawFill()
self.redArc()
self.blueArc()
def alignWith(self, nodeobj, nCnr2):
p1 = nodeobj.p1
p2 = nodeobj.p2
dx1 = p2[0] - p1[0]
dy1 = p2[1] - p1[1]
nTheta2 = atan2(dy1,dx1)
q1 = self.vertices[nCnr2]
q2 = self.vertices[(nCnr2 - 1)%4]
dx2 = q2[0] - q1[0]
dy2 = q2[1] - q1[1]
nTheta1 = atan2(dy2,dx2)
self.drawOutline(p1[0], p1[1], nTheta2 - nTheta1,nCnr2)

class kite(shape):
def GetPoints(self, cColour = 'brown'):
self.points  = [[0,0]
,[nSelfX,nSelfY]
,[0,nScale * nFib]
,[-nSelfX,nSelfY]]
self.colour = cColour
self.angles = [72,72,144,72]
self.type   = 'kite'
# shape, corner
self.viable = [[[dart,1],[kite, 0]]
,[[dart,2],[kite, 3]]
,[[dart,3],[kite, 2]]
,[[dart,0],[kite, 1]]]
def draw(self):
pass
def redArc(self):
nR = nRatioBig
p0 = self.vertices[0]
p1 = Scalar(self.vertices[3][0],self.vertices[3][1], nR, p0[0],p0[1])
p5 = Scalar(self.vertices[1][0],self.vertices[1][1], nR, p0[0],p0[1])
p3 = MidArc(p1,p5,p0,nR)
p2 = MidArc(p1,p3,p0,nR)
p4 = MidArc(p3,p5,p0,nR)
aR = Perimeter([p1,p2,p3,p4,p5])
canvas.create_line(aR, fill = 'darkgrey', tag = self.tag, width = 1)
def blueArc(self):
nR = 1 / (1 / nFib + 1) * nScale
p0 = self.vertices[2]
p1 = Scalar(self.vertices[3][0],self.vertices[3][1], nR, p0[0],p0[1])
p9 = Scalar(self.vertices[1][0],self.vertices[1][1], nR, p0[0],p0[1])
p5 = MidArc(p1,p9,p0,nR)
p3 = MidArc(p1,p5,p0,nR)
p7 = MidArc(p5,p9,p0,nR)
p2 = MidArc(p1,p3,p0,nR)
p4 = MidArc(p3,p5,p0,nR)
p6 = MidArc(p5,p7,p0,nR)
p8 = MidArc(p7,p9,p0,nR)
aR = Perimeter([p1,p2,p3,p4,p5,p6,p7,p8,p9])
canvas.create_line(aR, fill = 'cyan', tag = self.tag, width = 1)
def subShapes(self):
subs = []
p0 = list(self.vertices[0])
p1 = list(self.vertices[1])
p2 = list(self.vertices[2])
p3 = list(self.vertices[3])
p4 = Scalar(p3[0],p3[1], nRatioSmall, p0[0],p0[1])
p5 = Scalar(p1[0],p1[1], nRatioSmall, p0[0],p0[1])
p6 = Scalar(p2[0],p2[1], nRatioBig  , p0[0],p0[1])
subs.append(SubKiteRight(p3,p6,p2))
subs.append(SubKiteLeft(p3,p6,p4))
subs.append(SubKiteRight(p1,p6,p5))
subs.append(SubKiteLeft(p1,p6,p2))
subs.append(SubDartRight(p6,p0,p5))
subs.append(SubDartLeft(p6,p0,p4))
return subs

class dart(shape):
def GetPoints(self, cColour = 'darkgreen'):
self.points = [[0,0]
,[nSelfX,nSelfY]
,[0,nScale]
,[-nSelfX,nSelfY]]
self.angles = [72,36,216,36]
self.type   = 'dart'
self.viable = [[[dart, 0],[kite, 1]]
,[[kite, 2]]
,[[kite, 3]]
,[[dart, 1],[kite, 0]]]
self.colour = cColour
def redArc(self):
nR = nRatioSmall
p0 = self.vertices[0]
p1 = Scalar(self.vertices[3][0],self.vertices[3][1], nR, p0[0],p0[1])
p5 = Scalar(self.vertices[1][0],self.vertices[1][1], nR, p0[0],p0[1])
p3 = MidArc(p1,p5,p0,nR)
p2 = MidArc(p1,p3,p0,nR)
p4 = MidArc(p3,p5,p0,nR)
aR = Perimeter([p1,p2,p3,p4,p5])
canvas.create_line(aR, fill = 'darkgrey', tag = self.tag, width = 1)
def blueArc(self):
nR = 1 / nFib / (1 / nFib + 1) * nScale
p0 = self.vertices[2]
p1 = Scalar(self.vertices[3][0],self.vertices[3][1], nR, p0[0],p0[1])
p9 = Scalar(self.vertices[1][0],self.vertices[1][1], nR, p0[0],p0[1])
p5 = MidArc(p1,p9,p0,nR)
p5[0] = 2 * p0[0] - p5[0] # great arc
p5[1] = 2 * p0[1] - p5[1] # great arc
p3 = MidArc(p1,p5,p0,nR)
p7 = MidArc(p5,p9,p0,nR)
p2 = MidArc(p1,p3,p0,nR)
p4 = MidArc(p3,p5,p0,nR)
p6 = MidArc(p5,p7,p0,nR)
p8 = MidArc(p7,p9,p0,nR)
aR = Perimeter([p1,p2,p3,p4,p5,p6,p7,p8,p9])
canvas.create_line(aR, fill = 'cyan', tag = self.tag, width = 1)
def subShapes(self):
subs = []
p0 = list(self.vertices[0])
p1 = list(self.vertices[1])
p2 = list(self.vertices[2])
p3 = list(self.vertices[3])
p4 = Scalar(p3[0],p3[1], nRatioBig, p0[0],p0[1])
p5 = Scalar(p1[0],p1[1], nRatioBig, p0[0],p0[1])
subs.append(SubKiteRight(p0,p2,p4))
subs.append(SubKiteLeft(p0,p2,p5))
subs.append(SubDartRight(p2,p3,p4))
subs.append(SubDartLeft(p2,p1,p5))
return subs

def AlterBorder(aBorder, temp, nNook, nAligned, nDel, nIns):
cnr1 = nNook
cnr2 = nAligned
if nDel < 3:
# replacement
ang1 = aBorder[cnr1].angle
aBorder[cnr1]= NodeObject(temp, cnr2)
aBorder[cnr1].angle = ang1
aBorder[cnr1].prioritise()
if cnr1 == len(aBorder) - 1:
lClocked = True
else:
lClocked = False
# deletion
cnr3 =(cnr1 + 1) % len(aBorder)
for z in range(nDel):
if cnr3 < len(aBorder) - 1:
aBorder = aBorder[:cnr3] + aBorder[cnr3 + 1:]
elif cnr3 == len(aBorder): # delete first element
aBorder = aBorder[1:]
else: # around the clock
aBorder = aBorder[:cnr3]
# insertion
for nD in range(nIns):
cnr4 = (cnr1 + nD + nAdjust) % len(aBorder)
if lClocked:
cnr4 = nD
node = NodeObject(temp, (cnr2 + nD + 1) % 4)
aBorder.insert(cnr4, node)
return aBorder

def FitsBorder(aBorder, nNook, aV, temp):
nAligned = aV[1]
nL = len(aBorder)
for nBack in range(4):
btest = aBorder[(nNook - nBack) % nL]
angle1 = temp.angles[(nAligned + nBack) % 4]
t = btest.angle - angle1
if t < -nSmall: # conflict
return False
elif t > nSmall: # still room
break
# tight angle fit, test lengths
nTestBorder = (nNook - nBack - 1) % nL
nTestShape  = (nAligned + nBack) % 4
btest       = aBorder[nTestBorder]
if btest.length <> temp.length[nTestShape]: # length mismatch
return False
for nForward in range(4):
btest = aBorder[(nNook + nForward + 1) % nL]
angle1 = temp.angles[(nAligned - nForward - 1) % 4]
t = btest.angle - angle1
if t < -nSmall: # conflict
return False
elif t > nSmall: # still room
break
# tight angle fit, test lengths
nTestBorder = (nNook + nForward + 1) % nL
nTestShape  = (nAligned - nForward - 2) % 4
btest       = aBorder[nTestBorder]
if btest.length <> temp.length[nTestShape]: # length mismatch
return False
aBorder[nNook].nBack    = nBack
aBorder[nNook].nForward = nForward
return True

nAligned = aV[1]
nL = len(aBorder)
nBack    = aBorder[ nNook].nBack
nForward = aBorder[ nNook].nForward
del aBorder[ nNook].nBack
del aBorder[ nNook].nForward
nTest    = (nNook - nBack) % nL
btest    = aBorder[ nTest]
nAlign   = (nAligned + nBack) % 4
btest.angle -= angle1
btest.prioritise()
btest       = aBorder[(nNook + nForward + 1) % nL]
angle1      = AddTile.angles[(nAligned - nForward - 1) % 4]
btest.angle -= angle1
btest.prioritise()
nDel = min(nForward + nBack, 3)
nIns = max(2 - nDel, 0)
aBorder = AlterBorder(aBorder, AddTile, nTest, nAlign, nDel, nIns)
return aBorder

class NodeObject:
def __init__(self, shape, edge):
self.angle  = 360 - shape.angles[edge]
self.length = shape.length[edge]
self.p1     = shape.vertices[edge]
self.p2     = shape.vertices[(edge + 1) % 4]
self.edge   = edge
self.x      = shape.vertices[edge][0]
self.y      = shape.vertices[edge][1]
self.dist   = sqrt((nW2 - self.x)**2 + (nH2 -self.y)**2)
self.viable = shape.viable[edge]
self.prioritise()
self.shape = shape
def prioritise(self):
self.priority = self.angle + self.dist / nW

def PauseMessage(cText):
global lFinished
lFinished = True
canvas.create_text(nW/2,20,text = cText,fill = 'white', tag = 'pause')
raw_input()
canvas.delete('pause')

def FillPlane(aBorder, level):
# a small delay allows the screen to show
print level, ' of ', nRecLim # when level gets too high the stack will overload
if level >= nRecLim - 50:
print 'The stack has maxed out!'
PauseMessage('The initial pattern is complete, press ENTER to advance.')
return {}
aBorderCopy = deepcopy(aBorder)
nMin = 10000
k    = 'all filled'
j    = k
for i in aBorderCopy:
if i.x <  nW * 0.05 or i.x > nW * 0.95: # dont go off the screen
continue
elif  i.y <  nH * 0.1 or i.y > nH * 0.9:
continue
elif i.priority < nMin:
j = i
nMin = i.priority
# by choosing the most sensible place to add to
# there is not such a need for conflict testing.
if  j == k: # no more space to fill
print 'complete'
PauseMessage('The initial pattern is complete, press ENTER to advance.')
return {}
b = j
i = aBorderCopy.index(b)
viable = b.viable
shuffle(viable)
while len(viable) > 0:
v = viable[0]
viable = viable[1:]
attempt = v[0]()
if FitsBorder(aBorderCopy, i, v, attempt):
aBorderCopy = addToBorder(aBorderCopy, i, b, v, attempt)
oShapesDict = FillPlane(aBorderCopy, level + 1)
canvas.delete(idtag)
if lFinished:
for n in aBorder:
oShapesDict[n.shape.tag] = n.shape
return oShapesDict
# it might be more efficient to do some sort of clean up
# but this is eaisier
aBorderCopy = deepcopy(aBorder)

class SubShape:
def draw(self):
points = Perimeter(self.vertices)
canvas.create_polygon(points, fill = self.colour, tag = cDefTag)

class SubKiteRight(SubShape):
def __init__(self,p0,p1,p2):
self.vertices = deepcopy([p0,p1,p2])
self.colour = 'beige'
def subShapes(self):
subs = []
p0 = self.vertices[0]
p1 = self.vertices[1]
p2 = self.vertices[2]
p3 = Scalar(p2[0],p2[1], nRatioSmall, p0[0],p0[1])
p4 = Scalar(p1[0],p1[1], nRatioBig  , p0[0],p0[1])
subs.append(SubKiteRight(p2,p4,p1))
subs.append(SubKiteLeft(p2,p4,p3))
subs.append(SubDartLeft(p4,p0,p3))
return subs

class SubKiteLeft(SubShape):
global lAlternate
def __init__(self,p0,p1,p2):
self.vertices = deepcopy([p0,p1,p2])
self.colour = 'tan'

def subShapes(self):
subs = []
p0 = self.vertices[0]
p1 = self.vertices[1]
p2 = self.vertices[2]
p3 = Scalar(p2[0],p2[1], nRatioSmall, p0[0],p0[1])
p4 = Scalar(p1[0],p1[1], nRatioBig  , p0[0],p0[1])
subs.append(SubKiteRight(p2,p4,p3))
subs.append(SubKiteLeft(p2,p4,p1))
subs.append(SubDartRight(p4,p0,p3))
return subs

class SubDartRight(SubShape):
def __init__(self,p0,p1,p2):
self.vertices = deepcopy([p0,p1,p2])
self.colour = 'darkgrey'

def subShapes(self):
subs = []
p0 = self.vertices[0]
p1 = self.vertices[1]
p2 = self.vertices[2]
p3 = Scalar(p1[0],p1[1], nRatioSmall, p0[0],p0[1])
subs.append(SubKiteRight(p1,p2,p3))
subs.append(SubDartRight(p2,p0,p3))
return subs
class SubDartLeft(SubShape):
def __init__(self,p0,p1,p2):
self.vertices = deepcopy([p0,p1,p2])
self.colour = 'grey'

def subShapes(self):
subs = []
p0 = self.vertices[0]
p1 = self.vertices[1]
p2 = self.vertices[2]
p3 = Scalar(p1[0],p1[1], nRatioSmall, p0[0],p0[1])
subs.append(SubKiteLeft(p1,p2,p3))
subs.append(SubDartLeft(p2,p0,p3))
return subs

seed()
aBorder = []
lFinished = False
if random() > 0.5:
temp = kite('first')
else:
temp = dart('first')
temp.drawOutline(nW2, nH2, 0, 0)
for i in range(4):
aBorder.append(NodeObject(temp, i))
oShapesDict = FillPlane(aBorder, 1)
canvas.delete('first')
cDefTag = NewTag()
aSubShapes  = []
for s in oShapesDict:
print '#'
oShape = oShapesDict[s]
oShape.drawComplete()
aSubShapes += oShape.subShapes()

while True:
nRatioSmall *= nFib / (nFib + 1)
nRatioBig   *= nFib / (nFib + 1)
PauseMessage('press ENTER again to "DEFLATE" the pattern.')
cLastTag = cDefTag
cDefTag  = NewTag()
aNewSubs = []
for s in aSubShapes:
s.draw()
print '#'
t = s.subShapes()
aNewSubs += t
canvas.delete(cLastTag)
lAlternate = True
aSubShapes = deepcopy(aNewSubs)
```