Fractal using Spirograph in Python

17 Nov, 2020
Introduction
Spirograph toy that is used to produce complex patterns using plastic cogs and colored pens. A fractal is a curve, that is developed using a recurring pattern that repeats itself infinitely on a low scale. Fractals are used for modeling structures (such as snowflakes) or for describing partly chaotic phenomena.Spirograph can be used to draw various fractals. Some of them are given below
You can visit benice-equation-blogspot.in for more fractals design with there parametric equation. Some of them are given below
Mathematics behind the curtain
These are the two parametric equation to form a spirograph fractals, to understand these equations you have to consider a generalized figure of spirograph.
For the mathematics part you can refer to Wiki although i’ll try to explain a little of that mathematics in a short here. If we are interested behind the maths then you can check out the referred links. So as of now, these various curve can be drawn by using a parametric equation and varying some values of that equation we can get different fractals. So here’s the parametric equation:
where,
R is a scaling parameter and does not affect the structure of the Spirograph.
and,
So, now let’s try to implement this in code.
#importing the required librariesimport random, argparseimport mathimport turtlefrom PIL import Imagefrom datetime import datetime    from fractions import gcd  # A class that draws a spirographclass Spiro:    # constructor    def __init__(self, xc, yc, col, R, r, l):          # create own turtle        self.t = turtle.Turtle()        # set cursor shape        self.t.shape('turtle')        # set step in degrees        self.step = 5        # set drawing complete flag        self.drawingComplete = False          # set parameters        self.setparams(xc, yc, col, R, r, l)          # initiatize drawing        self.restart()      # set parameters    def setparams(self, xc, yc, col, R, r, l):        # spirograph parameters        self.xc = xc        self.yc = yc        self.R = int(R)        self.r = int(r)        self.l = l        self.col = col        # reduce r/R to smallest form by dividing with GCD        gcdVal = gcd(self.r, self.R)        self.nRot = self.r//gcdVal        # get ratio of radii        self.k = r/float(R)        # set color        self.t.color(*col)        # current angle        self.a = 0      # restart drawing    def restart(self):        # set flag        self.drawingComplete = False        # show turtle        self.t.showturtle()        # go to first point        self.t.up()        R, k, l = self.R, self.k, self.l        a = 0.0        x = R*((1-k)*math.cos(a) + l*k*math.cos((1-k)*a/k))        y = R*((1-k)*math.sin(a) - l*k*math.sin((1-k)*a/k))        self.t.setpos(self.xc + x, self.yc + y)        self.t.down()      # draw the whole thing    def draw(self):        # draw rest of points        R, k, l = self.R, self.k, self.l        for i in range(0, 360*self.nRot + 1, self.step):            a = math.radians(i)            x = R*((1-k)*math.cos(a) + l*k*math.cos((1-k)*a/k))            y = R*((1-k)*math.sin(a) - l*k*math.sin((1-k)*a/k))            self.t.setpos(self.xc + x, self.yc + y)        # done - hide turtle        self.t.hideturtle()          # update by one step    def update(self):        # skip if done        if self.drawingComplete:            return        # increment angle        self.a += self.step        # draw step        R, k, l = self.R, self.k, self.l        # set angle        a = math.radians(self.a)        x = self.R*((1-k)*math.cos(a) + l*k*math.cos((1-k)*a/k))        y = self.R*((1-k)*math.sin(a) - l*k*math.sin((1-k)*a/k))        self.t.setpos(self.xc + x, self.yc + y)        # check if drawing is complete and set flag        if self.a >= 360*self.nRot:            self.drawingComplete = True            # done - hide turtle            self.t.hideturtle()      # clear everything    def clear(self):        self.t.clear()  # A class for animating spirographsclass SpiroAnimator:    # constructor    def __init__(self, N):        # timer value in milliseconds        self.deltaT = 10        # get window dimensions        self.width = turtle.window_width()        self.height = turtle.window_height()        # create spiro objects        self.spiros = []        for i in range(N):            # generate random parameters            rparams = self.genRandomParams()            # set spiro params            spiro = Spiro(*rparams)            self.spiros.append(spiro)        # call timer        turtle.ontimer(self.update, self.deltaT)          # restart sprio drawing    def restart(self):        for spiro in self.spiros:            # clear            spiro.clear()            # generate random parameters            rparams = self.genRandomParams()            # set spiro params            spiro.setparams(*rparams)            # restart drawing            spiro.restart()      # generate random parameters    def genRandomParams(self):        width, height = self.width, self.height        R = random.randint(50, min(width, height)//2)        r = random.randint(10, 9*R//10)        l = random.uniform(0.1, 0.9)        xc = random.randint(-width//2, width//2)        yc = random.randint(-height//2, height//2)        col = (random.random(),               random.random(),               random.random())        return (xc, yc, col, R, r, l)      def update(self):        # update all spiros        nComplete = 0        for spiro in self.spiros:            # update            spiro.update()            # count completed ones            if spiro.drawingComplete:                nComplete+= 1        # if all spiros are complete, restart        if nComplete == len(self.spiros):            self.restart()        # call timer        turtle.ontimer(self.update, self.deltaT)      # toggle turtle on/off    def toggleTurtles(self):        for spiro in self.spiros:            if spiro.t.isvisible():                spiro.t.hideturtle()            else:                spiro.t.showturtle()              # save spiros to imagedef saveDrawing():    # hide turtle    turtle.hideturtle()    # generate unique file name    dateStr = (datetime.now()).strftime("%d%b%Y-%H%M%S")    fileName = 'spiro-' + dateStr     print('saving drawing to %s.eps/png' % fileName)    # get tkinter canvas    canvas = turtle.getcanvas()    # save postscipt image    canvas.postscript(file = fileName + '.eps')    # use PIL to convert to PNG    img = Image.open(fileName + '.eps')    img.save(fileName + '.png', 'png')    # show turtle    turtle.showturtle()  # main() functiondef main():    # use sys.argv if needed    print('generating spirograph...')    # create parser    descStr = """This program draws spirographs using the Turtle module.     When run with no arguments, this program draws random spirographs.          Terminology:      R: radius of outer circle.    r: radius of inner circle.    l: ratio of hole distance to r.    """    parser = argparse.ArgumentParser(description=descStr)        # add expected arguments    parser.add_argument('--sparams', nargs=3, dest='sparams', required=False,                         help="The three arguments in sparams: R, r, l.")                                # parse args    args = parser.parse_args()      # set to 80% screen width    turtle.setup(width=0.8)      # set cursor shape    turtle.shape('turtle')      # set title    turtle.title("Spirographs!")    # add key handler for saving images    turtle.onkey(saveDrawing, "s")    # start listening     turtle.listen()      # hide main turtle cursor    turtle.hideturtle()      # checks args and draw    if args.sparams:        params = [float(x) for x in args.sparams]        # draw spirograph with given parameters        # black by default        col = (0.0, 0.0, 0.0)        spiro = Spiro(0, 0, col, *params)        spiro.draw()    else:        # create animator object        spiroAnim = SpiroAnimator(4)        # add key handler to toggle turtle cursor        turtle.onkey(spiroAnim.toggleTurtles, "t")        # add key handler to restart animation        turtle.onkey(spiroAnim.restart, "space")      # start turtle main loop    turtle.mainloop()  # call mainif __name__ == '__main__':    main()
Output:


Video Playerhttps://media.geeksforgeeks.org/wp-content/uploads/2017-11-01-at-17-28-59.mp400:0000:0000:48Use Up/Down Arrow keys to increase or decrease volume.


The above program draws 4 different kinds of spirograph fractals, try to generate other fractals and then upload your github links in the comment. I’ll be happy to help you out if any error comes up.
This article is contributed by Subhajit Saha. If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
Fractal
GBlog
Python
Technical Scripter
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.