Python Zelles Graphics.py - how can I move a line similar to moving other GraphicsObjects like circles

255 views Asked by At

I have a script that creates Circle GraphicsObjects and applies newton's laws to them (gravitational pull), and moves each shape by its respective x,y velocity at each time step.

My goal is to draw a line between each of these circles and to move each line along with each circle every time step.

Circle.move(dx, dy) takes two inputs, but the same method for a line should have 4 inputs for each of the end points of the line. This method does not exist as far as I can tell.

The code in my project may be hard to digest so I added a sample of something similar. I would expect the code below to move the line like the hands of a clock (i.e., one point stays in the center, and the other end of the line rotates around the center in a circle).

I attempted to create a new version of the Line object and give it the move method, but this doesn't work as expected.

from graphics import *
import math, random, time

width = 500
height = 500
win = GraphWin("Example",width,height)
resolution = 10 # steps/lines per rotation
centerX = width/2
centerY = height/2
radius1 = 300
P1 = Point(centerX,centerY)

class newLine(Line):
    def move(self, dx1, dy1, dx2, dy2):
        self.p1.x = self.p1.x + dx1
        self.p1.y = self.p1.y + dy1
        self.p2.x = self.p2.x + dx2
        self.p2.y = self.p2.y + dy2

circleX = centerX+radius1*math.cos(2*math.pi*0/resolution)
circleY = centerY+radius1*math.sin(2*math.pi*0/resolution)
P2 = Point(circleX,circleY)
L1 = newLine(P1,P2)
L1.draw(win)

for i in range(resolution):
    circleX2 = centerX+radius1*math.cos(2*math.pi*(i+1)/resolution)
    circleY2 = centerY+radius1*math.sin(2*math.pi*(i+1)/resolution)
    dx = circleX2-circleX
    dy = circleY2-circleY
    L1.move(0,0,dx,dy)
    time.sleep(0.2)


win.getMouse()
win.close()
1

There are 1 answers

0
furas On

First idea:

Remove old line using undraw() and next create new Line and draw it.

It can be the only method - because it uses tkinter.Canvas and it can't rotate when it moves.

        l1.undraw()
        
        p2 = Point(circle_x, circle_y)
        l1 = Line(p1, p2)
        
        l1.draw(win)

Full working code

from graphics import *
import math
import random
import time   # PEP8: every module in separated line

# --- constansts ---  # PEP8: `UPPER_CASE_NAMES`

WIDTH = 500
HEIGHT = 500

# --- classes ---  # PEP8: `CamelCaseNames`

# empty

# --- main ----  # PEP8: `lower_case_names`

# PEP8: space after comma

win = GraphWin("Example", WIDTH, HEIGHT)

resolution = 10  # steps/lines per rotation   # PEP8: two spaces before #

center_x = WIDTH/2
center_y = HEIGHT/2
radius = 100

p1 = Point(center_x, center_y)

value = 0   # `2 * math.pi * 0 / resolution` gives `0`
circle_x = center_x + radius * math.cos(value)
circle_y = center_y + radius * math.sin(value)

p2 = Point(circle_x, circle_y)
l1 = Line(p1, p2)
l1.draw(win)
time.sleep(0.1)

for r in range(3):  # repeat 3 times
    for i in range(1, resolution+1):
        value = 2 * math.pi * (i / resolution)
        circle_x = center_x + radius * math.cos(value)
        circle_y = center_y + radius * math.sin(value)
    
        l1.undraw()
        
        p2 = Point(circle_x, circle_y)
        l1 = Line(p1, p2)
        
        l1.draw(win)
        
        time.sleep(0.1)
        
win.getMouse()
win.close()

PEP 8 -- Style Guide for Python Code


BTW:

You can get full path to source code

import graphics 

print( graphics.__file__ )

and you can check how move() works - maybe it use the same method.


EDIT:

Digging in source code I got another idea - use Polygon which has list of points and you can move last point - but it needs to calculate dx, dy - or simpler you can replace last point.

But both methods needs ot use undraw() + draw() to refresh screen.

Teoretically update() should refresh screen but it doesn't do it.

p2 = Point(circle_x, circle_y)
l1 = Polygon(p1, p2)
l1.draw(win)
time.sleep(0.1)

for i in range(1, resolution+1):
    
    value = 2 * math.pi * (i / resolution)
    new_circle_x = center_x + radius * math.cos(value)
    new_circle_y = center_y + radius * math.sin(value)

    dx = new_circle_x - circle_x
    dy = new_circle_y - circle_y

    l1.points[-1].move(dx, dy)

    circle_x = new_circle_x
    circle_y = new_circle_y

    # ---

    # force to redraw it    
    l1.undraw()
    l1.draw(win)
    #update()   # doesn't redraw it
    
    time.sleep(0.1)

or

p2 = Point(circle_x, circle_y)
l1 = Polygon(p1, p2)
l1.draw(win)
time.sleep(0.1)

for i in range(1, resolution+1):
    
    value = 2 * math.pi * (i / resolution)
    circle_x = center_x + radius * math.cos(value)
    circle_y = center_y + radius * math.sin(value)

    p2 = Point(circle_x, circle_y)
    
    l1.points[-1] = p2

    # ---
    
    # force to redraw it    
    l1.undraw()
    l1.draw(win)
    #update()   # doesn't redraw it
    
    time.sleep(0.1)

EDIT:

After digging in source code I created

class NewLine(Line):
    
    def move(self, dx1, dy1, dx2, dy2):
        self.p1.x += dx1
        self.p1.y += dy1
        self.p2.x += dx2
        self.p2.y += dy2

        canvas = self.canvas
        self.undraw()
        self.draw(canvas)

or

class NewLine(Line):
    
    def move(self, dx1, dy1, dx2, dy2):
        self.p1.x += dx1
        self.p1.y += dy1
        self.p2.x += dx2
        self.p2.y += dy2

        self.canvas.coords(self.id, self.p1.x, self.p1.y, self.p2.x, self.p2.y)
        self.canvas.update()
        #update()   # it needs `from graphics import *`

In similar way it can move end point.

class NewLine(Line):
    
    def _change(self):
        #canvas = self.canvas
        #self.undraw()
        #self.draw(canvas)

        # OR
        
        self.canvas.coords(self.id, self.p1.x, self.p1.y, self.p2.x, self.p2.y)
        self.canvas.update()
        #update()   # it needs `from graphics import *`

    def moveP2ToXY(self, x, y):
        self.p2.x = x
        self.p2.y = y

        self._change()        

    def moveP2ToPoint(self, point):
        self.p2.x = point.x
        self.p2.y = point.y
        
        self._change()        

    def move(self, dx1, dy1, dx2, dy2):
        self.p1.x += dx1
        self.p1.y += dy1
        self.p2.x += dx2
        self.p2.y += dy2

        self._change()        
        
    def moveTo(self, x1, y1, x2, y2):
        self.p1.x = x1
        self.p1.y = y1
        self.p2.x = x2
        self.p2.y = y2

        self._change()