PyQT5 setRect moves origin point in a QGraphicsScene - python

Whenever i try to move a rectangle in a scene, the origin point of the rectangle appears to change to the location where the rectangle was before updating the location.
So if i create the rectangle at (0,0) and move it using rect.setRect(x, y) then returning the position would yield (0,0) instead of (x, y).
If you would move it in the QGraphicsScene using your mouse it would return the correct (x, y).
The code i use to create the rectangle is as following:
class placeableObject:
def __init__(self, index, scene, QPen, QBrush, width=100, height=100):
"""Parent class for placeable objects"""
self.width = float(width)
self.height = float(height)
self.index = index
self.rect = scene.addRect(0, 0, int(width), int(height), QPen, QBrush)
self.rect.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
To move this rectangle i have the following embedded function and a function to return the position:
def getPos(self):
"""Returns a list with the x,y position of the object in the scene"""
return [self.rect.scenePos().x(), self.rect.scenePos().y()]
def move(self, x, y):
"""Moves the object in the editor view to coordinatex x,y"""
self.rect.setRect(x, y, self.width, self.height)

You are forgetting an important aspect of graphics items: their [scene] position is not always the top left corner of the object that is actually shown to the user.
This is clear when adding items using scene.add*() (this was already explained on this question).
As the documentation explains:
Note that the item's geometry is provided in item coordinates, and its position is initialized to (0, 0). For example, if a QRect(50, 50, 100, 100) is added, its top-left corner will be at (50, 50) relative to the origin in the item's coordinate system.
The item position is not the rectangle position, so when you use setRect you are not moving the item, but setting a new rectangle at the specified position, while leaving the item at (0, 0) coordinates of its system; note that this also means that if the item has no parent, scenePos() will be the same as pos(), otherwise it's relative to the parent.
If you want to know the actual position of the top left corner of the rectangle, you could use one of the following:
item.sceneBoundingRect().topLeft()
item.scenePos() + item.rect().topLeft()
If you are always adding the rectangle at (0, 0), then you can just use setPos(), but if you need to compute the position based on the current, actual rectangle position, you have to use one of the functions above.
Note that rectangles can also have negative sizes, so if you need the top left corner of the visible rectangle, you need to normalize it:
item.scenePos() + item.rect().normalized().topLeft()

It seems that i have figured it out.
I changed the move function to the following:
def move(self, x, y):
"""Moves the object in the editor view to coordinatex x,y"""
self.rect.setPos(x, y)
That returns the correct positions within the scene for me!
Thanks anyway :)

Related

Rendering a surface if it's within the bounds of it's parent [duplicate]

This question already has an answer here:
Is there a way to restrict the title crawl to certain portion of screen?
(1 answer)
Closed 2 years ago.
I'm adding support for scroll-panels for a GUI library I'm working on. The UI Components that are added to the scroll panel should only be rendered if they exist within the bounds of their parent.
Component
class Component(pg.sprite.Sprite):
def __init__(self):
self.original_image = pg.Surface((10, 10), pg.SRCALPHA)
self.image = self.original_image.copy()
self.image.fill((0, 0, 0))
self.rect = self.image.get_rect()
I thought about iterating over all of the elements in the scroll container, and then finding the elements that lie outside of the scroll box. If they do, then iterate over its pixels and set it to be transparent if the pixel lies outside of the parent.
Basic idea:
class ScrollBox(Container):
def __init__(self):
self.elements = []
def add_scroll_element(self, scrl_element):
self.elements.append(scrl_element)
# Called whenever there has been a scroll-up or scroll-down
def redraw_children(self):
for element in self.elements:
child_x, child_y = element.rect.x, element.rect.y
parent_x, parent_y = element.parent_screen.rect.x, element.parent_screen.rect.y
# Check if element is within bounds
if (child_y + element.rect.height) >= (parent_y + element.parent_screen.rect.height):
"""
Extra logic should be added here to determine if the pixel is within it's bounds
"""
for x_px in range(element.rect.width):
for y_px in range(element.rect.height):
element.change_pixel(x_px, y_px, (0, 0, 0, 0))
This solution would work, but it's not very good on performance because there are so many pixels that are being looped through.
I'm not very familiar with masks or colour keys in pygame, so I'm looking for a better way to achieve this behaviour, perhaps using one of those concepts.
Use pygame.Surface.clip() to set a rectangular clipping region on the target surface when you render an element and reset the clipping region after rendering the children:
class ScrollBox(Container):
# [...]
def redraw_children(self):
for element in self.elements:
window.clip(element.parent_screen.rect) # set clipping region for this element
# render the element
# [...]
window.clip(None) # reset clipping region

How to remove QRect from QGraphicsScene

I have a QGraphicsScene with a bunch of QRects. I'm using the QRects as an overlay over a QPixmap to show detected object. I need to clear all the rectangles from the scene, but can't figure out how.
What is the best way to clear all of the QRects from the QGraphicsScene without redrawing the image?
Right now I'm keeping a list of QRects as I add them to the scene and then to clear I try to loop through all the rects and use QGraphicsScene.removeItem(rect) to remove the rectangle, but that doesn't work. I get the following error:
TypeError: removeItem(self, QGraphicsItem): argument 1 has unexpected type 'QRectF'
I guess a QRect isn't a graphics item. But how should I remove it from the scene. Below is the code where I add or (try to) remove the Rects:
if self.is_viewing_tracks:
for track_id, positions in self.tracks.items():
for frameno, pos in positions.items():
if int(frameno) == self.frameno:
x, y = pos
rect = QRectF(x-5, y-5, 10, 10)
self.scene.addRect(rect)
text = self.scene.addText(str(track_id))
text.setPos(x, y)
self.rects.append(rect)
else:
for rect in self.rects:
self.scene.removeItem(rect)
What is the right way to remove QRects from a QGraphicsScene?
QRectFs are not added to the QGraphicsScene but are used to create QGraphicsRectItem through the addRect() method, so you must remove the QGraphicsRectItem and not the QRectF. The solution is to store the QGraphicsRectItems in self.rects:
if self.is_viewing_tracks:
for track_id, positions in self.tracks.items():
for frameno, pos in positions.items():
if int(frameno) == self.frameno:
x, y = pos
rect = QRectF(x - 5, y - 5, 10, 10)
rect_item = self.scene.addRect(rect)
text = self.scene.addText(str(track_id))
text.setPos(x, y)
self.rects.append(rect_item)
else:
for rect in self.rects:
self.scene.removeItem(rect)
self.rects = []

What are the pygame.Surface.get_rect() key arguments?

I'm learning to use pygame in my programming course, but I'm confused with the pygame.Surface.get_rect() method. I see that it can take some keyword arguments but I can't find anywhere what they are. I've seen get_rect(center=(num, num)) but I am trying to assign a top left corner coordinate. Is there a way of doing this?
The keyword arguments are documented as being applied to the rect's attributes. Those are documented in the pygame.Rect page:
The Rect object has several virtual attributes which can be used to
move and align the Rect:
x,y
top, left, bottom, right
topleft, bottomleft, topright, bottomright
midtop, midleft, midbottom, midright
center, centerx, centery
size, width, height
w,h
All of these attributes can be assigned to:
rect1.right = 10
rect2.center = (20,30)
Assigning to size, width or height changes the dimensions of the
rectangle; all other assignments move the rectangle without resizing
it. Notice that some attributes are integers and others are pairs of
integers.
So, if you want to get a rectangle the size of your image, with its top left at a certain point, you'd do:
my_surface.get_rect(topleft=some_point)
You could also pass separate top and left arguments (or x and y).

pygame drawing a filled rectangle overwrite border, but only sometimes?

I have a class in my pygame program that extends sprite, and effectively gives collision and whatnot to a rectangle. The __init__ of the class is as follows:
def __init__(self,topleft,size,label):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(size)
self.rect = self.image.get_rect()
self.rect.topleft = topleft
self.image.fill([128,128,128])
pygame.draw.rect(self.image, [200,200,200], self.rect, 5)
And this works great... for one rectangle. Problem is, after that rectangle, any others are solidly colored rectangles set to the fill color (128,128,128) instead of being a filled rectangle with a differently colored (200,200,200) border. I'm expecting this is some sort of issue with class variables as opposed to instance variables, but I'm unsure where the problem lies.
Okay, I figured it out. The Rect argument in the draw function is relative to the position of the image. Since I had set its rect to its location on the screen, it was drawing the rectangle way offset from the corner. It worked on my first one only because it happened to start at [0,0]. Here is the fixed draw code:
pygame.draw.rect(self.image, [200,200,200],pygame.Rect([0,0],size), 5)

How to change a sprite image in python based on mouse direction

I've seen several different posts about changing a sprite image.
For the current assignment, I have to construct a packman sprite, then it should follow the mouse. No problem there.
Here is that bit of code:
class Packman(games.Sprite):
"""Create the packman that is conrolled by the mouse"""
#load the packman image
image_right = games.load_image("snap_right.png") # initial value
image_left = games.load_image("snap_left.png")
show_image = image_right
def __init__(self, x=games.mouse.x, y = games.mouse.y):
"""Initialise packman"""
super(Packman, self).__init__(image = Packman.show_image, x = games.mouse.x, y = games.mouse.y)
def update(self):
"""Move packmans coordinates"""
self.x = games.mouse.x
self.y = games.mouse.y
if self.left < 0:
self.left = 0
if self.right > games.screen.width:
self.right = games.screen.width
#change Packman's direction that he is facing`
As you can see I try and load two images, but only one image at a time will be displayed.
(I reckon there is a way of just flipping one image horizontally, in stead of using two images.) As is, I can move Packman around. Now I need to add the bit that makes Packman face left/right depending on the direction the mouse is traveling in. My handbook gives me an example to rotate the image through 180deg with keypresses, which work, but then packman is just upside down, with his eye at the bottom.
Is there another way of flipping packman depending on mouse direction? (I only need horizontal flip, i.e. left and right)
Well I use Pygame for my sprites but the solution is pretty simple.
I basically use something like this. Two pointers pointing to initialized images. And then a pointer to pointers called image which will be what we use to draw the sprites.
# image is the pointer that points to the pointer that points to the image being currently used
image1 = pygame.image.load('left.png')
image2 = pygame.image.load('right.png')
image = image1
Then in draw I just do
screen.blit(image, position)
Now since you are using the mouse to track the position of pacman. What you have to do is this. At EACH frame, store the location of the mouse x and y in a class variable. Call it something like old_x and old_y. On the next frame, simply compare your mouse x position to your old_x. If the mouse position is greater, your pacman is trying to move to the right. image = image2 If your mouse position is less, then your pacman is moving to the left. image = image1 Apply the necessary state changes and change your image accordingly.

Categories