Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rephrase spirograph code into function

I'm writing a python spirograph program, and I need some help with converting part of it into a function. The code is attempting to reproduce the result illustrated in the video I found here. One line rotates around the origin, and then another rotates off the end of that, etc.

With a little bit of research into (what I think is) trigonometry, I put together a function rotate(point, angle, center=(0, 0)). The user inputs a point to be rotated, the angle (clockwise) that it is to be rotated by, and the centerpoint for it to be rotated around.

Then, I implemented an initial test, whereby one line rotates around the other. The end of the second line draws as if it were holding a pen. The code's a little messy, but it looks like this.

x, y = 0, 0
lines = []

while 1:

    point1 = rotate((0,50), x)
    point2 = map(sum,zip(rotate((0, 50), y), point1))
    if x == 0:
        oldpoint2 = point2
    else:
        canvas.create_line(oldpoint2[0], oldpoint2[1], point2[0], point2[1])
    lines.append( canvas.create_line(0, 0, point1[0], point1[1]) )
    lines.append( canvas.create_line(point1[0], point1[1], point2[0], point2[1]) )
    oldpoint2 = point2

    tk.update()

    x += 5
    if x > 360 and y > 360:
        x -= 360
        canvas.delete("all")
        time.sleep(1)
    y += 8.8
    if y > 360: y -= 360

    for line in lines:
        canvas.delete(line)
    lines = []

Great, works perfectly. My ultimate goal is what's in the video, however. In the video, the user can input any arbitrary number of arms, then define the length and angular velocity for each arm. Mine only works with two arms. My question, ultimately, is how to put the code I posted into a function that looks like drawSpiral(arms, lenlist, velocitylist). It would take the number of arms, a list of the velocities for each arm, and a list of the length of each arm as arguments.

What I've Tried

I've already attempted this several times. Initially, I had something that didn't work at all. I got some cool shapes, but definitely not the desired output. I've worked for a few hours, and the closest I could get was this:

def drawSpiral(arms, lenlist, velocitylist):
    if not arms == len(lenlist) == len(velocitylist):
        raise ValueError("The lists don't match the provided number of arms")

    iteration = 0
    while 1:

        tk.update()

        iteration += 1

        #Empty the list of points
        pointlist = []
        pointlist.append((0, 0))

        #Create a list of the final rotation degrees for each point
        rotations = []
        for vel in velocitylist:
            rotations.append(vel*iteration)


        for n in range(arms):
            point = tuple(map(sum,zip(rotate((0, lenlist[n]), rotations[n], pointlist[n]))))
            pointlist.append(point)

        for point in pointlist:
            create_point(point)
        for n in range(arms):
            print pointlist[n], pointlist[n+1]

This is fairly close to my solution, I feel, but not quite there. Calling drawSpiral(2, [50, 75], [1, 5]) looks like it might be producing some of the right points, but not connecting the right sets. Staring at it for about an hour and trying a few things, I haven't made any progress. I've also gotten pretty confused looking at my own code. I'm stuck! The point rotating around the center is attached to a point that is just flying diagonally across the screen and back. The line attached to the center is stretching back and forth. Can someone point me in the right direction?

Results of further tests

I've set up both functions to plot points at the ends of each arm, and found some interesting results. The first arm, in both cases, is rotating at a speed of 5, and the second at a speed of -3. The loop, outside of the function, is producing the pattern: Loop's result

The function, called with drawSpiral(2, [50, 50], [5, -3]), produces the result of Function's result

It seems to be stretching the top half. With both arms having a velocity of 5, the function would be expected to produce two circles, one larger than the other. However, it produces an upside-down cardioid shape, with the point connected to the center. enter image description here

Now there's more evidence, can anyone who understands math more than me help me?

like image 922
Luke Taylor Avatar asked May 09 '15 15:05

Luke Taylor


1 Answers

Your error is in

 for n in range(arms):
        point = tuple(map(sum,zip(rotate((0, lenlist[n]), rotations[n], pointlist[n]))))
        pointlist.append(point)

Specifically,

rotate((0, lenlist[n])

replace it with

 for n in range(arms):
        point = tuple(map(sum,zip(rotate((pointlist[n][0], lenlist[n]), rotations[n], pointlist[n]))))
        pointlist.append(point)

You go against the usual mathematical notation for polars (circular graphs) and that caused your confusion and eventual issues. As far as I can tell your function is plotting an (X,Y) point (0,length) and then finding the difference between that point and the center point (which is correctly defined as the last point you found) and rotating it around that center. The issue is that (0,length) is not 'length' away from the center. By replacing the (0,lenlist[n]) with (pointlist[n][0],lenlist[n]) makes the next point based upon the last point.

Also I would recommend editing your rotate function to be rotate(length,angle,centerpoint) which would simplify the inputs to a more traditional representation.

like image 170
Gavin Achtemeier Avatar answered Nov 05 '22 17:11

Gavin Achtemeier