Undraw all objects within a certain region Python Graphics - python

I needed to know whether it is possible to clear all objects within a given set of coordinates regardless of the name of the object. I don't have any code for this at the moment as I've been trying to brainstorm this for a while now but have come up with nothing. An example I can give is that there are squares and circles within the coordinates of (200,100) and (300,200) how would I delete everything within these coordinates?

Yes, this can be done. First, the GraphWin object keeps track of items drawn on it but doesn't officially export this list. So you should keep track of what you draw onto the window.
The items you want (e.g. Rectangle and Circle) belong to the class _BBox which has a getCenter() method you can use to manipulate objects within your boundaries:
from random import randrange
from graphics import *
win = GraphWin("My Example", 400, 400)
boundary = Rectangle(Point(200, 100), Point(300, 200))
boundary.setOutline('blue')
boundary.draw(win)
graphics = []
for _ in range(150):
circle = Circle(Point(randrange(400), randrange(400)), 5)
circle.setOutline('green')
circle.draw(win)
graphics.append(circle)
x, y = randrange(400), randrange(400)
rectangle = Rectangle(Point(x, y), Point(x + 10, y + 10))
rectangle.setOutline('orange')
rectangle.draw(win)
graphics.append(rectangle)
for graphic in graphics:
center = graphic.getCenter()
if 200 < center.getX() < 300 and 100 < center.getY() < 200:
graphic.setFill('red')
graphic.undraw()
win.getMouse()
win.close()

Related

Pygame doesn't draw rectangles on the screen after a specific threshold

I'm trying to make a visualisation for various sorting algorithms and I was playing around with Pygame to try and draw the rectangles that I need.
In the below code, the user is given multiples inputs: the lowest value of the list to be sorted, the highest value, and the number of elements the list is going to have. The elements are going to be randomly generated.
Then I'm getting the user's screen size so that I can have an appropriate window for the visualisation. Based on the visualisation window and the user's input, I'm setting up the width and height of the rectangles, so that each rectangle has the same width and that they are scaled based on the highest value.
Almost everything is nice and fine with this approach, but there's one thing that I can't figure out. It seems that setting the number of elements (n, in the code below) too high, the rectangles are not being drawn.
My asumption is that after a specific threshold, RECT_W, which is the width of the rectangles, becomes to small for Pygame to draw it.
What options do I have to solve it, except of having the number of elements smaller than a specific value?
import random
import pygame
import color_constants as colors
import ctypes
import copy
from pygame.locals import *
# Read data based on user's input
def readData():
listOfNumbers = []
data = dict()
print("Lowest Value: ")
numLow = int(input())
print("Highest Value: ")
numHigh = int(input())
print("Length of list: ")
n = int(input())
for i in range(0, n):
listOfNumbers.append(random.randint(numLow, numHigh))
origLst = copy.copy(listOfNumbers)
data.update({'lst': origLst})
data.update({'numLow': numLow})
data.update({'numHigh': numHigh})
data.update({'n': n})
data.update({'sorted': listOfNumbers})
return data
if __name__ == "__main__":
data = readData()
# Getting the user's screen size
user32 = ctypes.windll.user32
SCREENSIZE = user32.GetSystemMetrics(0)-100, user32.GetSystemMetrics(1)-100
SCREEN_W = SCREENSIZE[0]
SCREEN_H = SCREENSIZE[1]
# Setting and scaling the size of rectangle based on the number of elements (n)
# and the highest number (numHigh)
RECT_W = SCREEN_W // data['n']
RECT_H = SCREEN_H / (data['numHigh'])
# Setting up the color literals
RED = (255, 255, 255)
GRAY = (0, 0, 0)
pygame.init()
screen = pygame.display.set_mode(SCREENSIZE)
running = True
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
screen.fill(GRAY)
for i in range(data['n']):
rect = Rect(i*RECT_W, 0, RECT_W, RECT_H * data['lst'][i])
rect.bottom = SCREEN_H
pygame.draw.rect(screen, RED, rect)
pygame.display.flip()
If data['n'] is greater than SCREEN_W, RECT_W is 0. A coordinate is truncated when drawing. You cannot draw a fraction of a pixel. The size of the rectangle can only be integral (0, 1, 2 ...). Hence, you cannot draw a rectangle with a size less than 1.
You can draw the rectangles on a large surface and scale down the surface. However, the rectangles get blurred. So, this is no good option.

I am trying to create a rectangle with width = 100 and length = 50 using graphic.py library. I am a bit confused on how to specify the length

I am trying to create a rectangle with width = 100 and length = 50 using graphics.py library. I am a bit confused on how to specify the length. Here's what I have so far:
main ():
win = GraphWin("window", 300, 300)
rec = Rectangle (Point (250,250), Point(200, 200))
rec.setWidth (50)
rec.draw(win)
In short, read the documentation. SetWidth changes the line thickness, not the rectangle size.
The rectangle dimensions are entirely determined by the two opposing corners you specify when you instantiate the object. I'll change your values to illustrate:
rec = Rectangle (Point(300, 200), Point(100, 50))
This defines a rectangle with opposite corners at the given points.
The width (x direction) is abs(300-100) = 200
The height (y direction) is abs(200-50) = 150
Does that clear up your confusion?

Pygame: How do I blit and rotate an image to connect two points on the screen?

Here is a test program. I started with two random dots and the line connecting them. Now, I want to take a given image (with x,y dimensions of 79 x 1080) and blit it on top of the guide line. I understand that arctan will give me the angle between the points on a cartesian grid, but because y is backwards the screen (x,y), I have to invert some values. I'm confused about the negating step.
If you run this repeatedly, you'll see the image is always parallel to the line, and sometimes on top, but not consistently.
import math
import pygame
import random
pygame.init()
screen = pygame.display.set_mode((600,600))
#target = (126, 270)
#start = (234, 54)
target = (random.randrange(600), random.randrange(600))
start = (random.randrange(600), random.randrange(600))
BLACK = (0,0,0)
BLUE = (0,0,128)
GREEN = (0,128,0)
pygame.draw.circle(screen, GREEN, start, 15)
pygame.draw.circle(screen, BLUE, target, 15)
pygame.draw.line(screen, BLUE, start, target, 5)
route = pygame.Surface((79,1080))
route.set_colorkey(BLACK)
BMP = pygame.image.load('art/trade_route00.png').convert()
(bx, by, bwidth, bheight) = route.get_rect()
route.blit(BMP, (0,0), area=route.get_rect())
# get distance within screen in pixels
dist = math.sqrt((start[0] - target[0])**2 + (start[1] - target[1])**2)
# scale to fit: use distance between points, and make width extra skinny.
route = pygame.transform.scale(route, (int(bwidth * dist/bwidth * 0.05), int( bheight * dist/bheight)))
# and rotate... (invert, as negative is for clockwise)
angle = math.degrees(math.atan2(-1*(target[1]-start[1]), target[0]-start[0]))
route = pygame.transform.rotate(route, angle + 90 )
position = route.get_rect()
HERE = (abs(target[0] - position[2]), target[1]) # - position[3]/2)
print(HERE)
screen.blit(route, HERE)
pygame.display.update()
print(start, target, dist, angle, position)
The main problem
The error is not due to the inverse y coordinates (0 at top, max at bottom) while rotating as you seems to think. That part is correct. The error is here:
HERE = (abs(target[0] - position[2]), target[1]) # - position[3]/2)
HERE must be the coordinates of the top-left corner of the rectangle inscribing your green and blue dots connected by the blue line. At those coordinates, you need to place the Surface route after rescaling.
You can get this vertex by doing:
HERE = (min(start[0], target[0]), min(start[1], target[1]))
This should solve the problem, and your colored dots should lay on the blue line.
A side note
Another thing you might wish to fix is the scaling parameter of route:
route = pygame.transform.scale(route, (int(bwidth * dist/bwidth * 0.05), int( bheight * dist/bheight)))
If my guess is correct and you want to preserve the original widht/height ratio in the rescaled route (since your original image is not a square) this should be:
route = pygame.transform.scale(route, (int(dist* bwidth/bheight), int(dist)))
assuming that you want height (the greater size in the original) be scaled to dist. So you may not need the 0.05, or maybe you can use a different shrinking parameter (probably 0.05 will shrink it too much).

In Python create a circle With two mouse clicks

I need to use Python to create an archery target with five rings(yellow,red,blue,black,white)
It needs to get the first mouse click to select where the center ring should go.
The second mouse click will indicate somewhere on the circumference of the inner circle.
The remaining rings (red,blue,black,white) width need top be the same as the inner circles radius.
I am having trouble figuring out how take information from the second mouse click and give it to the inner circle to find it's radius.
from graphics import *
def main():
win = GraphWin('Archery Target',300,300)
center = win.getMouse()
w = Circle(center, 100)
w.setFill('white')
w.draw(win)
bl = Circle(center, 80)
bl.setFill('black')
bl.draw(win)
b = Circle(center, 60)
b.setFill('blue')
b.draw(win)
r = Circle(center, 40)
r.setFill('red')
r.draw(win)
y = Circle(center, 20)
y.setFill('yellow')
y.draw(win)
win.getMouse() # pause for click in window
win.close()
main()
This code just lets you decide where the center will be placed, but has a single ring size.
final output
Assuming that the graphics module you load is the one of John Zelle (found e.g. here), what you need is another point
point = win.getMouse()
and then, as #Kevin suggested, the Pythagorean formula to calculate the distance between your points (needs from math import sqrt)
dx = point.getX() - center.getX()
dy = point.getY() - center.getY()
radius = sqrt(dx*dx + dy*dy)
You can then use radius to plot your circles.
If this doesn't solve your question, please give some more details.

creating a transparent surface to draw pixels to in pygame

I've created a surface that I've used pixel array to put pixels on, but i want to make the surface transparent but leaving the pixels opaque, I've tried making the surface transparent then drawing the pixels tot he surface but that just makes the pixels also transparent, any help or something I've missed?
-Edit- Hopefully this'll help in some way, this is the class object that creates the surface that is the galaxy
Also I have stated what I've tried, there's not much more to tell
class Galaxy(object):
def __init__(self,posx=0,posy=0,radius=0,depth=0):
radius = int(radius)
self.size = [radius*2,radius*2,depth]
self.posx = posx
self.posy = posy
self.radius = radius
#create array for stars
self.starArray = []
#create surface for stars
self.surface = pygame.Surface([radius*2,radius*2])
self.starPixel = pygame.PixelArray(self.surface)
#populate
for x in range(radius*2):
for y in range(radius*2):
#generate stars
num1 = noise.snoise2(x+posx,y+posy,repeatx=radius*10,repeaty=radius*10)
distance = math.sqrt(math.pow((x-radius),2)+math.pow((y-radius),2))
if distance < 0:
distance = distance * -1
#print(x,y,"is",distance,"from",radius,radius)
val = 5
#glaxy density algorithm
num = (num1 / ( ((distance+0.0001)/radius)*(val*10) )) * 10
#density
if num > (1/val):
#create star
self.starArray.append(Stars(x,y,seed=num1*100000,distance=distance))
#print(num*1000)
self.addPixels()
#adds all star pixels to pixel array on surface
def addPixels(self):
for i in self.starArray:
self.starPixel[i.x,i.y] = i.colour
del self.starPixel
#sends to screen to await rendering
def display(self):
screen.displaySurface(self.surface,[self.posx+camPosX,self.posy+camPosY])
Use MyGalaxy.set_colorkey(SomeUnusedRGB) to define a zero-alpha (invisible) background colour, fill MyGalaxy with that colour, then draw the pixels on top of that. You can use pixelArray functions to draw to that surface, but you're probably better to use MyGalaxy.set_at(pixelLocationXY, pixelColourRGB) instead, for reasons of managability and performance.
Make sure that SomeUnusedRGB is never the same as any pixelColourRGB, or those pixels won't appear (since pygame will interpret them as invisible). When you blit MyGalaxy to wherever you want it, it ought to only blit the non-SomeUnusedRGB-coloured pixels, leaving the rest unaltered.
(This is the best I can offer you without knowing more about your code; revise the question to include what you're already trying, and I'll update this answer.)

Categories

Resources