Center a Rectangle in New Window - python

I am trying to center a rectangle in a new created window using Python 3.2
Here is my code so far:
from graphics import *
def plotSquare(win, side):
rect=Rectangle(Point(side//2,side//2), Point(side,side))
rect.setWidth(5)
rect.draw(win)
def main ():
win=GraphWin("My Window", 500, 500)
win.setCoords(0, 0, 500, 500)
win.width=500
win.height=500
side=eval(input("What is the size of one side of the square (0<n<500): "))
plotSquare(win, side)
win.getMouse()
win.close
main()
What function can I use to center the rectangle?

Rather than positioning the rectangle at side/2, take into account the size of the window, so the left boundary of the rectangle would be 500/2 - side/2.
Same idea for the top of the rectangle.

Related

Collision detection between circle and rectangle

I wrote some code to show a circle and a rectangle randomly on the screen with PyQt6. and I want to detect if these two objects have a collision then I make them red otherwise I make them green.
But how should I detect whether there is a collision or not?
here is my code
from random import randint
from sys import argv
from PyQt6.QtCore import QRect, QTimer, Qt, QMimeData
from PyQt6.QtGui import QColor, QKeyEvent, QMouseEvent, QPainter, QPen, QPaintEvent, QBrush, QDrag
from PyQt6.QtWidgets import QApplication, QVBoxLayout, QMainWindow, QPushButton
class Window(QMainWindow):
def __init__(self) -> None:
super().__init__()
screenWidth = 1920
screenHeight = 1080
self.isRunning = True
self.windowWidth = 1200
self.windowHeight = 800
self.clockCounterVariable = 0
self.milSec = 0
self.seconds = 0
self.minutes = 0
self.hours = 0
self.setWindowTitle("Smart rockets")
self.setGeometry((screenWidth - self.windowWidth) // 2, (screenHeight - self.windowHeight) // 2, self.windowWidth, self.windowHeight)
self.setLayout(QVBoxLayout())
self.setStyleSheet("background-color:rgb(20, 20, 20);font-size:20px;")
self.clock = QTimer(self)
self.clock.timeout.connect(self.clockCounter)
self.clock.start(10)
button = QPushButton("Refresh", self)
button.setGeometry(20,self.windowHeight - 60,self.windowWidth - 40,40)
button.setStyleSheet("background-color:rgb(80, 80, 80);font-size:20px;")
button.setCheckable(True)
button.clicked.connect(self.refreshRectAndCircle)
rectangleWidth = randint(50, 500)
rectangleHeight = randint(50, 500)
self.rectangle = QRect(randint(0, self.windowWidth - rectangleWidth), randint(0, self.windowHeight - rectangleHeight - 80), rectangleWidth, rectangleHeight)
circleRadius = randint(50, 200)
self.circle = QRect(randint(0, self.windowWidth - circleRadius), randint(0, self.windowHeight - circleRadius - 80), circleRadius, circleRadius)
self.show()
def dragEnterEvent(self, event) -> super:
event.accept()
def keyPressEvent(self, event: QKeyEvent) -> super:
key = QKeyEvent.key(event)
if key == 112 or key == 80: # P/p
if self.isRunning:
self.clock.stop()
print("pause process")
self.isRunning = False
else:
print("continue process")
self.isRunning = True
self.clock.start(10)
elif (key == 115) or (key == 83): # S/s
self.closeWindow()
return super().keyPressEvent(event)
def mousePressEvent(self, event: QMouseEvent) -> super:
if event.buttons() == Qt.MouseButton.LeftButton:
if self.isRunning:
self.clock.stop()
print("pause process")
self.isRunning = False
else:
print("continue process")
self.isRunning = True
self.clock.start(10)
return super().mousePressEvent(event)
def clockCounter(self) -> None:
self.clockCounterVariable += 1
self.update()
def paintEvent(self, a0: QPaintEvent) -> super:
painter = QPainter()
self.milSec = self.clockCounterVariable
self.seconds, self.milSec = divmod(self.milSec, 100)
self.minutes, self.seconds = divmod(self.seconds, 60)
self.hours, self.minutes = divmod(self.minutes, 60)
painter.begin(self)
painter.setPen(QPen(QColor(255, 128, 20), 1, Qt.PenStyle.SolidLine))
painter.drawText(QRect(35, 30, 400, 30), Qt.AlignmentFlag.AlignLeft, "{:02d} : {:02d} : {:02d} : {:02d}".format(self.hours, self.minutes, self.seconds, self.milSec))
if self.collided():
painter.setPen(QPen(QColor(255, 20, 20), 0, Qt.PenStyle.SolidLine))
painter.setBrush(QBrush(QColor(128, 20, 20), Qt.BrushStyle.SolidPattern))
else:
painter.setPen(QPen(QColor(20, 255, 20), 0, Qt.PenStyle.SolidLine))
painter.setBrush(QBrush(QColor(20, 128, 20), Qt.BrushStyle.SolidPattern))
painter.drawRect(self.rectangle)
painter.drawEllipse(self.circle)
painter.end()
return super().paintEvent(a0)
def refreshRectAndCircle(self) -> None:
rectangleWidth = randint(50, 500)
rectangleHeight = randint(50, 500)
self.rectangle = QRect(randint(0, self.windowWidth - rectangleWidth), randint(0, self.windowHeight - rectangleHeight - 80), rectangleWidth, rectangleHeight)
circleRadius = randint(50, 200)
self.circle = QRect(randint(0, self.windowWidth - circleRadius), randint(0, self.windowHeight - circleRadius - 80), circleRadius, circleRadius)
self.update()
def collided(self) -> bool:
# return True if collided and return False if not collided
circle = self.circle
rect = self.rectangle
if __name__ == "__main__":
App = QApplication(argv)
window = Window()
App.exec()
how should I detect whether there is a collision between the circle and the rectangle or not?
While you can achieve this with math functions, luckily Qt provides some useful functions that can make this much easier.
You can achieve this with three steps - or even just one (see the last section).
Check the center of the circle
If the center of the circle is within the boundaries of the rectangle, you can always assume that they collide. You're using a QRect, which is a rectangle that is always aligned to the axis, making things much easier.
Mathematically speaking you just need to ensure that the X of the center is between the smallest and biggest X of the left and right vertical lines of the rectangle, then the same for the Y.
Qt allows us to check if QRect.contains() the QRect.center() of the circle.
def collided(self) -> bool:
center = self.circle.center()
if self.rectangle.contains(center):
return True
Check the vertexes of the rectangle
If the length between the center of the circle and any of the vertexes of the rectangle is smaller than the radius, you can be sure that they are within the circle area.
Using the basic Pythagorean equation, you can know the hypotenuse created between the center and each of the vertexes of the rectangle, and if the hypotenuse is smaller than the radius, it means that they are within the circle.
With Qt we can use QLineF with the center and the vertexes (topLeft(), topRight(), bottomRight() and bottomLeft()), whenever any of the lengths is smaller than the radius, it means that the vertex is within the circle. Using QPolygonF we can easily iterate through all vertexes in a for loop.
# ...
center = QPointF(center)
radius = self.circle.width() / 2
corners = QPolygonF(QRectF(self.rectangle))[:4]
for corner in corners:
if QLineF(center, corner).length() < radius:
return True
Check the closest side of the rectangle
It is possible that the circle only collides with a side of the rectangle: the center of the circle is outside of the rectangle, and none of the vertexes are within the circle.
Consider this case:
In this situations, the collision always happens whenever the perpendicular line of the closest side of the rectangle is smaller than the radius:
Using math, we'll need to get the line perpendicular to the closest side, going toward the center of the circle, computing the angle between the side and the lines connecting the center with each vertex (shown in orange above), then with the help of some trigonometry, get the cathetus of one of the triangles (shown in red): if the length of that line is smaller than the radius, the shapes collide.
Luckily again, Qt can help us. We can get the two closest points using the lines created in the section "Check the vertexes of the rectangle" above, get the side of those points and compute a perpendicular angle that will be used to create a "diameter": starting from the center, we create two lines with opposite angles and the radius with the fromPolar(), then create the actual diameter with the external points of those lines. Finally, we check if that diameter intersects() with the side.
And this is the final function:
def collided(self) -> bool:
center = self.circle.center()
if self.rectangle.contains(center):
return True
# use floating point based coordinates
center = QPointF(center)
radius = self.circle.width() / 2
corners = QPolygonF(QRectF(self.rectangle))[:4]
lines = []
for corner in corners:
line = QLineF(center, corner)
if line.length() < radius:
return True
lines.append(line)
# sort lines by their lengths
lines.sort(key=lambda l: l.length())
# create the side of the closest points
segment = QLineF(lines[0].p2(), lines[1].p2())
# the perpendicular angle, intersecting with the center of the circle
perpAngle = (segment.angle() + 90) % 360
# the ends of the "diameter" per pendicular to the side
d1 = QLineF.fromPolar(radius, perpAngle).translated(center)
d2 = QLineF.fromPolar(radius, perpAngle + 180).translated(center)
# the actual diameter line
diameterLine = QLineF(d1.p2(), d2.p2())
# get the intersection type
intersection = diameterLine.intersects(segment, QPointF())
return intersection == QLineF.BoundedIntersection
Further considerations
when dealing with geometric shapes, you should consider using QPainterPath which actually makes the above extremely simpler:
def collided(self) -> bool:
circlePath = QPainterPath()
circlePath.addEllipse(QRectF(self.circle))
return circlePath.intersects(QRectF(self.rectangle))
Qt has a powerful (yet complex) Graphics View Framework that makes graphics and user interaction much more intuitive and effective; while the QPainter API is certainly easier for simpler cases, it may result in cumbersome (and difficult to debug) code as soon as your program requirements grow in complexity;
QMainWindow has its own, private and inaccessible layout manager, you cannot call setLayout() on it; use setCentralWidget() and set a layout to that widget eventually;
never use generic stylesheet properties for parent widgets (as you did for the main window) because it may result in awkward drawing of complex widgets like scroll areas; always use selector types for windows and containers instead;
unless you actually need to paint on the QMainWindow contents (which is a rare occurrence), you should always implement the paintEvent() on its central widget instead; otherwise, if you don't need QMainWindow features (menubar, statusbar, dock widgets and toolbars), just use a QWidget;
QTimer is not reliable for precise time measurement: if any function called while it's running requires more time than the timeout interval, the connected function will always be called afterwards; use QElapsedTimer instead;
in paintEvent() just use painter = QPainter(self), remove painter.begin(self) (it's implicit using the above) and painter.end() (unnecessary, since it's automatically destroyed when the function returns);
don't create unnecessary instance attributes (self.milSec, self.seconds, etc) that will be almost certainly overwritten sooner or later, and that you're not using elsewhere; the paint event must always return as soon as possible and must be always optimized as much as possible;

pyglet drawing primitive GL_POINT. A buffer issue?

Beginner in pyglet. I have an issue when drawing GL_POINT using pyglet.graphicss.draw(). I want this GL_POINT to be drawn after another on the next pixel buffer, but it seems the function does not keep the last GL_POINT to be drawn on the next pixel buffer.
import pyglet
from pyglet.gl import *
from pyglet.window import key # for key input, on_key_press
window = pyglet.window.Window(800, 600) # create a window object with the resolution of 800x600
window.set_caption('window title')
glClear(GL_COLOR_BUFFER_BIT)
#window.event
def on_key_press(symbol, modifiers): # keyboard input handler
if symbol == key.L: # Drawing a center point
print("DRAWING TEST A POINT (400, 300)")
pyglet.graphics.draw(
1, pyglet.gl.GL_POINTS,
('v2i', (400, 300))
)
elif symbol == key.K: # Drawing a bit further 100 more horizontally from center point
print("DRAWING TEST A POINT (500, 300)")
pyglet.graphics.draw(
1, pyglet.gl.GL_POINTS,
('v2i', (500, 300))
)
pyglet.app.run()
Pressing L would draw a center point.
Then pressing K would draw 100 more horizontally from the center point with the last center point gone.
Where is the bug? is there something wrong with my code? if not,
my guess would be, does pyglet.graphicss.draw() function actually redraw one after another primitive shape? How do I code to draw one after another?
The issue is caused by Double buffering. You can solve the issue by drawing the point to both buffers. Draw the point twice and swap the OpenGL front and back buffers in between by (flip).
pyglet.graphics.draw(
1, pyglet.gl.GL_POINTS,
('v2i', (400, 300))
)
window.flip()
pyglet.graphics.draw(
1, pyglet.gl.GL_POINTS,
('v2i', (400, 300))
)
But I recommend to add the points to a list and to draw the list. e.g.:
import pyglet
from pyglet.gl import *
from pyglet.window import key # for key input, on_key_press
points = []
window = pyglet.window.Window(800, 600) # create a window object with the resolution of 800x600
window.set_caption('window title')
glClear(GL_COLOR_BUFFER_BIT)
#window.event
def on_key_press(symbol, modifiers): # keyboard input handler
global points
if symbol == key.L: # Drawing a center point
print("DRAWING TEST A POINT (400, 300)")
points += [400, 300]
elif symbol == key.K: # Drawing a bit further 100 more horizontally from center point
print("DRAWING TEST A POINT (500, 300)")
points += [500, 300]
pyglet.graphics.draw(len(points) // 2, pyglet.gl.GL_POINTS, ('v2i', points))
pyglet.app.run()

Python Turtle, change visible part

On the canvas,I draw two dots,right one is at (100,0),left one is at (-1000,0).After initializing the program,the orginal screen location(visible part) is near the right dot,just like pic1 show
pic 1:[1]: https://i.stack.imgur.com/KtPRN.png
And now I wanna move the the screen(visible part) to the left dot using coordinate so that i can see it(pic2).What should I do?
pic 2:https://i.stack.imgur.com/Rtfrv.png
def drawDot(x):
penup()
goto(x, 0)
pendown()
dot('pink')
write(x)
b = -1000 #left dot(-1000,0)
a = 100 #right dot(100,0)
speed(0)
delay(0)
tracer(0, 0)
hideturtle()
screensize(500,500)
color('red')
bgcolor('black')
drawDot(a)
drawDot(b)
done()
I believe the following does what you describe. When the window opens, it's centered on (0, 0) and point a is visible off to the right and point b isn't visible at all. When you click on the window, it scrolls so that the window is centered on point b:
from turtle import Screen, Turtle
WINDOW_WIDTH, WINDOW_HEIGHT = 500, 500
CANVAS_WIDTH, CANVAS_HEIGHT = 3000, 1000
def drawDot(x):
turtle.penup()
turtle.setx(x)
turtle.dot('pink')
turtle.write(x)
def scrollToDot(x, y): # unused arguments
canvas = screen.getcanvas()
# tkinter has a different coordinate system
# we have to describe left edge of scrolled
# window as percentage in its coordinates:
screen_center = CANVAS_WIDTH / 2
dot_center = screen_center + b
left_edge = dot_center - screen.window_width() / 2
canvas.xview_moveto(left_edge / CANVAS_WIDTH) # percentage
a = 100 # right dot(100, 0)
b = -1000 # left dot(-1000, 0)
screen = Screen()
screen.setup(WINDOW_WIDTH, WINDOW_HEIGHT) # What we see
screen.screensize(CANVAS_WIDTH, CANVAS_HEIGHT) # What there is
screen.bgcolor('black')
turtle = Turtle()
turtle.hideturtle()
turtle.speed('fastest')
turtle.color('red')
drawDot(a)
drawDot(b)
screen.onclick(scrollToDot)
screen.mainloop()
To do this, we have to access the tkinter Canvas underpinnings of turtle. However, the Canvas coordinate system is different than turtle's, so we have to make an adjustment as noted in the code comments.

Tie circle radius to window size in Python Turtle

In Python turtle, when drawing objects on the screen, if there was a way to have a circle's radius connected to the window width or height, so that it can be resized by altering the window size?
Yes, it is possible.
You will need to create an event that the turtle will listen to: In the following example, if you click on the turtle, a circle of half the width of the canvas will be drawn.
If you resize the canvas, and click on the turtle again, a new circle of half the
new width will be redrawn.
import turtle
def start(dummy_a, dummy_b):
t.reset()
y, x = screen.window_height(), screen.window_width()
t.home()
t.circle(x/4)
if __name__ == '__main__':
screen = turtle.Screen()
t = turtle.Turtle()
t.onclick(start, add=True)
screen.listen()
turtle.done()
Here's my alternative click to adjust drawing to resized window solution:
from turtle import Turtle, Screen
def onResize(x=0, y=0):
screen.onclick(None) # disable events inside event handler
screen.setworldcoordinates(-1, -1, 1, 1)
screen.onclick(onResize)
screen = Screen()
onResize() # establish initial coordinate system
turtle = Turtle(visible=False)
turtle.penup()
turtle.sety(-0.5)
turtle.pendown()
turtle.circle(0.5, steps=30)
screen.mainloop()
Note that we're not redrawing anything, we're just readjusting our virtual coordinates (unit square in this example) and letting turtle redraw things. If we're willing to peek under the hood, we can take this one step further:
import tkinter as tk
from turtle import RawTurtle, TurtleScreen, ScrolledCanvas
class MyTurtleScreen(TurtleScreen):
def __init__(self, cv):
super().__init__(cv)
cv.bind('<Configure>', self.onResize)
def onResize(self, event=None):
self.setworldcoordinates(-1, -1, 1, 1)
root = tk.Tk()
canvas = ScrolledCanvas(root)
canvas.pack(fill=tk.BOTH, expand=tk.YES)
screen = MyTurtleScreen(canvas)
screen.onResize() # establish initial coordinate system
turtle = RawTurtle(screen, visible=False)
turtle.penup()
turtle.sety(-0.5)
turtle.pendown()
turtle.circle(0.5, steps=30)
screen.mainloop()
This is a generic embedding turtle in tkinter example except that I've customized TurtleScreen to accept a Configure event. Now, when you resize the window, the coordinate system trick from before kicks in automatically so you don't need to click on the window -- it just happens.

Turtle Graphics - how can background image in the center after setting the world cooridinates?

I have been typically setting the world coordinates to the bottom left hand corner when using turtle graphics.
import turtle
t=turtle.Pen()
turtle.setup(500,500)
turtle.setworldcoordinates(0, 0,500, 500)
The challenges is that when I insert a background image
turtle.bgpic("cat.gif")
It is also being moved the original origin (0,0) which is now in the bottom left hand corner of the screen. I need to move the center of the image to the center of my window. Is there to do this?
If you're willing to poke about a bit "beneath the shell", you can manipulate this at the tkinter level:
from turtle import Turtle, Screen
screen = Screen()
screen.setup(500, 500)
screen.setworldcoordinates(0, 0, 500, 500)
screen.bgpic("cat.gif")
canvas = screen.getcanvas()
canvas.itemconfig(screen._bgpic, anchor="sw") # pylint: disable=W0212
turtle = Turtle()
turtle.dot(100) # draw a large dot at (0, 0)
screen.mainloop()

Categories

Resources