import WindowsBMP import CircleDrawer import LineDrawer import math import string import ColourMap # some types an_int = type (3) a_list = type ([]) a_tuple = type (()) #================================================================= # Some examples #================================================================= def example(which): """ Draw some example patterns. 0 => Draw the default pattern 1 => Pattern 0, but with rotation and shrinkage 2 => Higher resolution version of 1 3 => Keeps the symetry of the default pattern but changes the pattern 4 => Similar to 0, but changes the relative sizes of the wheels 5 => Changes the wheel sizes and rotates 6 => Changes the wheel sizes and rotates 7 => Adds in a third wheel 8 => Vary the phase of the third wheel 9 => Vary the phase of the 1st wheel in three wheel system 10 => Vary the size of the third wheel in the three wheel system 11 => An 8-fold symetric 4 wheel pattern 12 => Varying phase diagram for the previous 4 wheel pattern 13 => Like 0 but uses the drawCircles method 101 => Creates the images for an animation of the previous pattern """ s = Spirograph () if which < 100: fname = s.path + s.filename + string.zfill(which, 5) + '.bmp' print "Saving example to ", fname # 0 => Draw the default pattern if which == 0: s.draw () # 1 => Pattern 0, but with rotation and shrinkage elif which == 1: for i in range (0,30): s.draw () s.radius *= 0.98 s.phases = [x + math.pi/60 for x in s.phases] # 2 => Higher resolution version of 1 elif which == 2: s.setSize (1600) ; s.colour = ColourMap.black for i in range (0,30): s.draw () s.radius *= 0.98 s.phases = [x + math.pi/60 for x in s.phases] # 3 => Keeps the symetry of the default pattern but changes the pattern elif which == 3: s.setSize (640) ; s.colour = ColourMap.black sym = s.getSymetry () cols = [ColourMap.black,ColourMap.blue,ColourMap.magenta] for i in range (0,3): s.colour = cols [i] s.draw () s.speeds [1] += sym s.radius *= 0.95 # 4 => Similar to 0, but changes the relative sizes of the wheels elif which == 4: s.setSize (1600) ; s.colour = ColourMap.black sizes = [4,2] for i in range (0,15): s.draw () sizes [1] += 0.2 s.sizes = [x for x in sizes] # 5 => Changes the wheel sizes and rotates elif which == 5: s.setSize (1600) ; s.colour = ColourMap.black for i in range (0,30): s.draw () s.sizes [1] *= 0.98 s.radius *= 0.98 s.phases = [x + math.pi/60 for x in s.phases] # 6 => Changes the wheel sizes and rotates elif which == 6: s.setSize (1600) ; s.colour = ColourMap.black for i in range (0,30): s.draw () s.sizes [1] *= 1.02 s.radius *= 0.98 s.phases = [x + math.pi/60 for x in s.phases] # 7 => Adds in a third wheel elif which == 7: s.phases.append (0) s.speeds.append (13) s.sizes.append (1) s.draw () # 8 => Vary the phase of the third wheel elif which == 8: s.setSize (1600) ; s.colour = ColourMap.black s.phases.append (0) s.speeds.append (13) s.sizes.append (1) for i in range (0,30): s.draw () s.phases [2] += math.pi / 15 # 9 => Vary the phase of the 1st wheel elif which == 9: s.setSize (1600) ; s.colour = ColourMap.black s.phases = [0,0,0] s.speeds = [-5,2,9] s.sizes = [4,3,2] for i in range (0,20): s.draw () s.phases [0] += math.pi / 30 # 10 => Vary the size of the third wheel in the three wheel system elif which == 10: s.setSize (1600) ; s.colour = ColourMap.black s.phases = [0,0,0] s.speeds = [-5,2,9] s.sizes = [4,3,2] for i in range (0,10): s.draw () s.sizes [2] *= 1.1 # 11 => An 8-fold symetric 4 wheel pattern # note: very large pattern, suitable for printer elif which == 11: s.setSize (1600) ; s.points = 2000 s.colour = ColourMap.black s.phases = [0,0,0,0] s.speeds = [3, -13, 11, 19] s.sizes = [3,3,2,1] s.draw () # 12 => Varying phase diagram for the previous 4 wheel pattern # note: very large pattern, suitable for printer elif which == 12: s.setSize (3200) ; s.points = 2000 s.colour = ColourMap.black s.phases = [0,0,0,0] s.speeds = [3, -13, 11, 19] s.sizes = [3,3,2,1] for i in range (0,30): s.draw () s.phases [1] += math.pi / 15 # 13 => Like 0 but uses the drawCircles method elif which == 13: s.points = 300 s.colour = ColourMap.cyan s.draw () s.colour = ColourMap.blue s.drawCircles () # 101 => Creates the images for an animation of pattern 12 elif which == 101: s.setSize (400) ; s.points = 2000 s.colour = ColourMap.blue s.phases = [0,0,0,0] s.speeds = [3, -13, 11, 19] s.sizes = [3,3,2,1] for i in range (0,120): fname = s.path + "anim" + string.zfill(i, 3) + 'a.bmp' print "Saving animation frame", fname s.clear () s.draw () s.phases [1] += math.pi / 60 s.bitmap.writeFile (fname) else: help (example) return if which < 100: s.bitmap.writeFile (fname) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def hcfList (list): "Highest common factor of a list of integers" if len (list) == 0: return 1 h = list [0] for i in range (1, len(list)): h = hcf (h, list [i]) return h #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def hcf (n1, n2): "Highest common factor of two integers" n1 = abs (int (n1)) n2 = abs (int (n2)) while 1: if n1 > n2: t = n1 n1 = n2 n2 = t if n1 == 0: return n2 if n1 == 1: return 1 n2 = n2 % n1 #================================================================= # The Spirograph class #================================================================= class Spirograph: def __init__ (self): self.speeds = [-3, 2] # Rotation rates self.sizes = [4, 3] # Relative sizes of the wheels self.phases = [0, 0] # Starting angles of the wheels self.points = 500 # Number of points in the pattern self.colour = ColourMap.blue self.ccl_radius = 3 # used when drawing circles for points self.path = 'c:\\python22\\work\\' self.filename = 'spirograph' # Start off with a blank bitmap 400 x 400 self.setSize (400) ; #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def setSize (self, size): """ Resize the bitmap """ self.width = size self.radius = (size/2) - 2 - self.ccl_radius self.clear () #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def clear(self): """ Remove the old bitmap and create a new one """ self.bitmap = WindowsBMP.WindowsBMP () self.bitmap.create24Bit (self.width, self.width) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def draw (self): """ Draw the pattern. This doesn't erase the previous bitmap contents so you can superimpose multiple drawings. """ points = self.getPoints () if len (points) > 0: p0 = points [-1] for pt in points: line = LineDrawer.draw (p0, pt) self.bitmap.drawPointSet (line, self.colour) p0 = pt #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def drawCircles (self): """ Draw the pattern as a circle at each point """ points = self.getPoints () for pt in points: ccl = CircleDrawer.drawRadiusCircle (pt, self.ccl_radius) self.bitmap.drawPointSet (ccl, self.colour) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def getPoints (self): """ Returns the set of points defining the pattern """ self.fixWheels () # Initialise dt = [2 * math.pi * x / self.points for x in self.speeds] theta = self.phases xc = self.width / 2 yc = self.width / 2 wlist = range (0, len (self.speeds)) points = [] if len (self.speeds) < 1: print "*** No wheels ***" return points # Calculate the points for i in range (0, self.points): x = xc y = yc for j in wlist: x += self.sizes [j] * math.cos (theta [j]) y += self.sizes [j] * math.sin (theta [j]) theta [j] += dt [j] points.append ((int(x), int(y))) return points #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def fixWheels (self): """ Fix the wheel parameters. Assumes that the speeds are definitive and that the phases and sizes are dependent. """ # ensure all the arrays are the same size n = len (self.speeds) if len (self.sizes) < n: self.sizes += ([1] * (n - len (self.sizes))) if len (self.phases) < n: self.phases += ([0] * (n - len (self.phases))) sum = 0 # calculate the total size for i in range (0,n): sum += abs (self.sizes [i]) # Normalise factor = float (self.radius) / float (sum) for i in range (0,n): self.sizes [i] *= factor # Fix the speeds - if there is a common factor we just draw the same # patter multiple times, so we divide all the speeds by their highest # common factor h = hcfList (self.speeds) ; self.speeds = [int (x) / h for x in self.speeds] #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ def getSymetry (self): "Return the symetry of the current pattern" difs = [] for i in range (1, len (self.speeds)): difs.append (self.speeds [i] - self.speeds [i-1]) return hcfList (difs)