import turtle
t = turtle.Turtle()
def vary(shape,dup):
while not out_of_bound():
shape
dup
def out_of_bound():#returns True if turtle is out of screen
out = False
height = (turtle.window_height()-30)/2
width = (turtle.window_width()-30)/2
if abs(t.xcor())>width or abs(t.ycor())>height:
out = True
return out
def linear(direction,distance):#for linear duplication
try:
origin_head = t.heading()
t.seth(direction)
t.up()
t.fd(distance)
t.seth(origin_head)#ensures duplication remains linear
except:
print('Invalid input.')
def circle(radius,steps=None,orient=None,circle_color=None,shape_color=None):#circle or shape in circle, orientation in degrees
try:
t.down()
t.circle(-radius)
t.up()
t.circle(-radius,orient)#set orientation of the shape in circle
t.down()
t.circle(-radius,steps=steps)#draw shape in the circle
t.up()
t.lt(180)
t.circle(radius,orient)#return to default position
t.seth(0)
except:
print('Invalid input.')`enter code here`
above code is my attempt to use the turtle library to create patterns, in this case, in a linear manner. when I call the function, vary(circle(50,4,45),linear(0,100)) the while loop only draws one shape and stops, while the code continues to run. please help.
Calling vary like that will not pass circle and linear as parameters to vary. What vary will receive in this case are the return values of those functions (in this case None for both). If you want to pass a function as a parameter, do not add the parentheses - they result in calling the function, which returns its return value.
Because of the above, the following code:
while not out_of_bound():
shape
dup
Is equivalent to this:
while not out_of_bound():
None
None
which obviously does nothing.
The following code should achieve your goal:
def vary(shape, shape_args, dup, dup_args):
while not out_of_bound():
shape(*shape_args)
dup(*dup_args)
Then call it like this: vary(circle, (50,4,45), linear, (0,100)).
Related
I write a simple app, While drag or scale the MainView, The PartView rubberband will show scene area in PartView.But sometime the rubber-band become a line, and sometime the rubberband disappear.So How to aviod this phenomenon appear?And sometime I want the rubberband only show it's border-line, not contain it's light-blue rectangle,So how can I write code ?
My Code
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import random
import math
r = lambda : random.randint(0, 255)
r255 = lambda : (r(), r(), r())
class Scene(QGraphicsScene):
def __init__(self):
super().__init__()
for i in range(1000):
item = QGraphicsEllipseItem()
item.setRect(0, 0, r(), r())
item.setBrush(QColor(*r255()))
item.setPos(r()*100, r()*100)
self.addItem(item)
class MainView(QGraphicsView):
sigExposeRect = pyqtSignal(QRectF)
def drawBackground(self, painter: QPainter, rect: QRectF) -> None:
super().drawBackground(painter, rect)
self.sigExposeRect.emit(rect)
def wheelEvent(self, event: QWheelEvent) -> None:
factor = math.pow(2.7, event.angleDelta().y()/360)
self.scale(factor, factor)
class PartView(QGraphicsView):
def __init__(self):
super().__init__()
self.r = QRubberBand(QRubberBand.Rectangle, self)
self.r.setWindowOpacity(1)
self.r.show()
class View(QSplitter):
def __init__(self):
super().__init__()
self.m = MainView()
self.m.setMouseTracking(True)
self.m.setDragMode(QGraphicsView.ScrollHandDrag)
self.m.sigExposeRect.connect(self.onExposeRect)
self.p = PartView()
self.m.setScene(Scene())
self.p.setScene(self.m.scene())
self.p.fitInView(self.m.scene().itemsBoundingRect())
self.addWidget(self.m)
self.addWidget(self.p)
def onExposeRect(self, rect: QRectF):
prect = self.p.mapFromScene(rect).boundingRect()
self.p.r.setGeometry(prect)
app = QApplication([])
v = View()
v.show()
app.exec()
My Result
I think the problem is that the qrect passed to the drawBackground method is only includes the portion of the background that wasn't previously in the viewport. Not positive about that though.
Either way I was able to achieve your goal of avoiding only a section of the rubber band being drawn, by sending the area for the entire viewport to the onExposeRect slot.
class MainView(QGraphicsView):
sigExposeRect = pyqtSignal(QRectF)
def drawBackground(self, painter: QPainter, rect: QRectF) -> None:
# Adding this next line was the only change I made
orect = self.mapToScene(self.viewport().geometry()).boundingRect()
super().drawBackground(painter, rect)
self.sigExposeRect.emit(orect) # and passing it to the slot.
def wheelEvent(self, event: QWheelEvent) -> None:
factor = math.pow(2.7, event.angleDelta().y()/360)
self.scale(factor, factor)
A fundamental aspect about Graphics View is its high performance in drawing even thousands of elements.
To achieve this, one of the most important optimization is updating only the portions of the scene that really need redrawing, similar to what item views do, as they normally only redraw the items that actually require updates, instead of always painting the whole visible area, which can be a huge bottleneck.
This is the reason for which overriding drawBackground is ineffective: sometimes, only a small portion of the scene is updated (and, in certain situations, even no update is done at all), and the rect argument of drawBackground only includes that portion, not the whole visible area. The result is that in these situations, the signal will emit a rectangle that will not be consistent with the visible area.
Since the visible area is relative to the viewport of the scroll area, the only safe way to receive updates about that area is to connect to the horizontal and vertical scroll bars (which always work even if they are hidden).
A further precaution is to ensure that the visible rectangle is also updated whenever the scene rect is changed (since that change might not be reflected by the scroll bars), by connecting to the sceneRectChanged signal and also overriding the setSceneRect() of the source view. Considering that the changes in vertical and scroll bars might coincide, it's usually a good idea to delay the signal with a 0-delay QTimer, so that it's only sent once when more changes to the visible area happen at the same time.
Note that since you're not actually using the features of QRubberBand, there's little use in its usage, especially if you also need custom painting. Also, since the rubber band is a child of the view, it will always keep its position even if the preview view is scrolled.
In the following example I'll show two ways of drawing the "fake" rubber band (but choose only one of them, either comment one or the other to test them) that will always be consistent with both the source and target views.
class MainView(QGraphicsView):
sigExposeRect = pyqtSignal(QRectF)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.signalDelay = QTimer(self, singleShot=True, interval=0,
timeout=self.emitExposeRect)
# signals might have arguments that collide with the start(interval)
# override of QTimer, let's use a basic lambda that ignores them
self.delayEmit = lambda *args: self.signalDelay.start()
self.verticalScrollBar().valueChanged.connect(self.delayEmit)
self.horizontalScrollBar().valueChanged.connect(self.delayEmit)
def emitExposeRect(self):
topLeft = self.mapToScene(self.viewport().geometry().topLeft())
bottomRight = self.mapToScene(self.viewport().geometry().bottomRight())
self.sigExposeRect.emit(QRectF(topLeft, bottomRight))
def setScene(self, scene):
if self.scene() == scene:
return
if self.scene():
try:
self.scene().sceneRectChanged.disconnect(self.delayEmit)
except TypeError:
pass
super().setScene(scene)
if scene:
scene.sceneRectChanged.connect(self.delayEmit)
def setSceneRect(self, rect):
super().setSceneRect(rect)
self.delayEmit()
def wheelEvent(self, event: QWheelEvent) -> None:
factor = math.pow(2.7, event.angleDelta().y()/360)
self.scale(factor, factor)
class PartView(QGraphicsView):
exposeRect = None
def updateExposeRect(self, rect):
if self.exposeRect != rect:
self.exposeRect = rect
self.viewport().update()
def paintEvent(self, event):
super().paintEvent(event)
if not self.exposeRect:
return
rect = self.mapFromScene(self.exposeRect).boundingRect()
# use either *one* of the following:
# 1. QStyle implementation, imitates QRubberBand
qp = QStylePainter(self.viewport())
opt = QStyleOptionRubberBand()
opt.initFrom(self)
opt.rect = rect
qp.drawControl(QStyle.CE_RubberBand, opt)
# 2. basic QPainter
qp = QPainter(self.viewport())
color = self.palette().highlight().color()
qp.setPen(self.palette().highlight().color())
# for background
bgd = QColor(color)
bgd.setAlpha(40)
qp.setBrush(bgd)
qp.drawRect(rect)
class View(QSplitter):
def __init__(self):
super().__init__()
self.m = MainView()
self.m.setMouseTracking(True)
self.m.setDragMode(QGraphicsView.ScrollHandDrag)
self.p = PartView()
self.m.setScene(Scene())
self.p.setScene(self.m.scene())
self.p.fitInView(self.m.scene().itemsBoundingRect())
self.addWidget(self.m)
self.addWidget(self.p)
self.m.sigExposeRect.connect(self.p.updateExposeRect)
PS: please use single letter variables when they actually make sense (common variables, coordinates, loop placeholders, etc.), not for complex objects, and especially for attributes: there's no benefit in using self.m or self.p, and the only result you get is to make code less readable to you and others.
I'm building an application to view real time data based on the examples for scrolling plots. My x-axis should display time as a formated string. The x-values added to the plot are timestamp floats in seconds. Here is a simplified version of my plot window code.
Everything works in real time and I have no problem showing the values i want to plot, but the labels for my x-axis are only the timestamps not the formated strings. I know that the function formatTimestampToString(val) in the AxisItem overload returns a good string value.
import pyqtgraph as pg
class NewLegend(pg.LegendItem):
def __init__(self, size=None, offset=None):
pg.LegendItem.__init__(self, size, offset)
def paint(self, p, *args):
p.setPen(pg.mkPen(0,0,0)) # outline
p.setBrush(pg.mkBrush(255,255,255)) # background
p.drawRect(self.boundingRect())
class DateAxis(pg.AxisItem):
def tickStrings(self, values, scale, spacing):
strings = []
for val in values:
strings.append(formatTimestampToString(val))
return strings
class PlotWindow(QtWidgets.QWidget):
def __init__(self, iof, num):
QtWidgets.QWidget.__init__(self)
self.setWindowTitle('Plot Window ')
self.resize(1000, 800)
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
"""
... other stuff ...
"""
# Externally updated dict with data
self.data = {}
self.curves = {}
self.plotWidget = pg.GraphicsWindow("Graph Window")
axis = DateAxis(orientation='bottom')
self.plot = self.plotWidget.addPlot(axisItem={'bottom': axis})
self.plot.setAutoPan(x=True)
self.legend = NewLegend(size=(100, 60), offset=(70, 30))
self.legend.setParentItem(self.plot.graphicsItem())
def updatePlots(self):
#update data for the different curves
for data_name, curve in self.curves.items():
if curve != 0:
curve.setData(y=self.data[data_name].values[:], x=self.data[data_name].timestamps[:])
def addCurve(self, data_name):
if data_name not in self.curves:
self.curves[data_name] = self.plot.plot(y=self.data[data_name].values[:], x=self.data[data_name].timestamps[:], pen=pg.mkPen(color=self.randomColor(),width=3))
self.legend.addItem(self.curves[data_name], name=data_name)
def removeCurve(self, data_name):
if data_name in self.curves:
self.plot.removeItem(self.curves[data_name])
self.legend.removeItem(data_name)
del self.curves[data_name]
Am i doing something wrong? Is there a better way to overload the AxisItem? I have also tried to overload the AxisItem with a simple numerical formula just to see if it has any effect on my axis.
Another problem i have is with the LegendItem: I can add and subtract labels with no problem, but it only updates the size of the legend box when adding labels. This means that when I add and remove curves/data in my plot the legend grows, but never shrinks down again. I have tried calling the LegendItem.updateSize() function after removing labels, but nothing happens.
I hope you can help! Thanks
So I found the problem. In self.plot = self.plotWidget.addPlot(axisItem={'bottom': axis}) its supposed to say axisItems, not axisItem.
I just got into OOP-style in python and was playing around with classes. For this particular problem I want my class Circle to be a subclass of my class Point. But it is also important to have a equality check for a certain tolerance. However, I just do not know how to declare the circle variables as follows __init__(self, circle, radius). On top of that I do get a tuple error due to the equality function (I know this is due to tuples not being mutable). I have tried a myriad of ways, but I'll just paste the simplest form, just for the sake of echoing the idea more clearly:
class Point:
def __init__(self, x, y):
self.x= x
self.y= y
def __eq__(self, other):
if abs(self.x-other.x)<0.00001 and abs(self.y-other.y)< 0.00001:
return True
else:
return False
class Circle(Point):
def __init__(self, centre, radius):
self.centre = Point(x,y)
self.radius= radius
def equals(self, other):
return Point.__eq__(other)<0.00001 and abs(self.radius-other.radius)<0.00001
Is there some fundamental misunderstand on my part, or is the thing I am aiming for just impossible/not smart? Any sort of help is appreciated. Cheers.
One immediate problem lies here:
def __init__(self, centre, radius):
self.centre = Point(x,y)
You pass in centre (presumably a Point type) but reference the non-existing variables x and y.
In any case, I'm not convinced it's correct to consider a circle as being a type of point - it doesn't seem to fit with real-world modelling.
It may seem logical at first glance, since a circle does generally have a centre and radius, but think of what that would mean for other shapes such as a rectangle. A rectangle that was derived from a point would also have to have other points to specify the other three corners (as the simplest implementation) and it really makes no sense to treat one of those points as special.
I think it would be better to have a common base class of both point and circle (and every other shape that you need) and modify Circle so that it has-a point rather than is-a point.
That could be as simple as:
class Shape:
def __eq__(self, other):
return False
def type(self):
return "Shape"
class Point(Shape):
def __init__(self, x, y):
self.x = x
self.y = y
def type(self):
return "Point"
def __eq__(self, other):
if other.type() != self.type(): return False
return abs(self.x - other.x) < 0.00001 and abs(self.y - other.y) < 0.00001
class Circle(Shape):
def __init__(self, x, y, radius):
self.centre = Point(x,y)
self.radius = radius
def type(self):
return "Circle"
def __eq__(self, other):
if other.type() != self.type():
return False
return self.centre == other.centre and abs(self.radius - other.radius) < 0.00001
Note the default behaviour of the common base class which assumes equality is always false, even for itself, so don't think you can get any rational results if you try to compare shapes :-). Each sub-class then overrides that to check that:
the types are compatible(a); and
the relevant fields are matching (to some degree of accuaracy as per your original code).
(a) At the moment, this is checking that the types are identical, but you could equally modify it so that a point and a zero-radius circle (or a rectangle with four identical corner points or any other zero-dimensional "real shape") are considered identical.
Or a rectangle that is actually a square may be considered equal to a square , and so on.
I am creating a game and unable to detect turtle position in this list
I am using python3 and turtle.
the objective of this code is to create a filled shape when turtle intersect with its's own path
import turtle
t=turtle.Turtle()
t.fillcolor("red")
t.begin_fill()
s=turtle.Screen()
t.penup()
status=True
penstatus=False
t.speed(1)
x=[]
def go1():
t.left(-(t.heading()))
t.left(90)
def go2():
t.left(-(t.heading()))
# t.left(90)
def go3():
t.left(-(t.heading()))
t.left(270)
def go4():
t.left(-(t.heading()))
t.left(180)
def paint():
global penstatus
if penstatus==False:
penstatus=True
t.down()
else:
t.up()
def detect():
global status
a=t.position()
if a in x:
status=False
print("yes")
while status:
global x
s.onkeypress(go1, "Up")
s.onkeypress(go2, "Right")
s.onkeypress(go3, "Down")
s.onkeypress(go4, "Left")
s.onkeypress(paint,"space")
s.listen()
x.append(t.position())
t.fd(5)
detect()
t.end_fill()
s.mainloop()
it works sometimes but result of filling also gets wrong
There are two reasons you're having trouble detecting if the current position is in your list of past positions. The first is you "hop" five pixels at a time so you are potentially crossing the line at a "filled in" segment, not one you were actually positioned on.
The second is that turtle positions are floating point numbers and can be very slightly different when you come back to the same spot. We can fix both problems by not comparing directly but asking if the distance between points is less than our "hop" distance.
My rework of your code below implements this approach. It also changes how your keys work slightly; changes the logic to only include visible lines in the filled graphic; and is completely event-based. It also has a reset, "r", key to start a new drawing. You can back out any changes you don't like, the back position detection is still applicable:
from turtle import Turtle, Screen
DISTANCE = 3
def go_up():
turtle.setheading(90)
def go_right():
turtle.setheading(0)
def go_down():
turtle.setheading(270)
def go_left():
turtle.setheading(180)
def toggle_pen():
if turtle.isdown():
turtle.penup()
else:
turtle.pendown()
turtle.begin_fill() # ignore pending begin_fill, start anew
def reset_drawing():
global positions
turtle.reset()
turtle.fillcolor('red')
turtle.speed('fastest')
turtle.penup()
positions = []
move()
def move():
for position in positions:
if turtle.distance(position) < DISTANCE:
turtle.end_fill()
return
if turtle.isdown():
positions.append(turtle.position())
turtle.forward(DISTANCE)
screen.ontimer(move, 100)
screen = Screen()
screen.onkeypress(go_up, 'Up')
screen.onkeypress(go_right, 'Right')
screen.onkeypress(go_down, 'Down')
screen.onkeypress(go_left, 'Left')
screen.onkeypress(toggle_pen, 'space')
screen.onkeypress(reset_drawing, 'r')
screen.listen()
turtle = Turtle()
positions = None # make sure global is defined
reset_drawing()
screen.mainloop()
I am creating a game where the player is a ship that has to dodge meteors that fall. I have 2 classes, the ship and the meteors. The meteors "fall" by having their canvas objects moved down their y axis and their y coordinates are subtracted by the number as the move function. I have an if statement that detects whether the meteors have fallen passed the border of the canvas, and it deletes those meteors, and creates new meteors at the top of the screen, thus making it seem that there are multiple meteors that are falling. The ship class has a similar function that detects if the ship has gone passed the sides of the canvas, which triggers the death function. I have a function in the meteor class that detects whether it is overlapping, with the help from Sneaky Turtle's answer. Now, it's almost finished, I just have one problem. After 3 "rounds" the meteors should get faster. How I implemented this was by having an if statement that checks if a variable is over 3. If not, the variable adds 1. When it reaches 3, it resets and adds (speed amount) to the speed attribute of the meteor,which is used when it moves. the problem is it only works on the first "wave" after that, the speed attribute stays the same. All the sound functions are commented off so that I don't have to upload the files.
Code:
from random import *
from tkinter import *
from time import *
print('''****Meteor Run****
Don't let the meteors hit you!
A-Left D-Right ''')
sleep(1.25)
#from game_sounds import*
root=Tk()
c = Canvas(width=800,height=600,bg="#37061a")
c.pack()
m1=0
m2=0
m3=0
m4=0
m5=0
m6=0
m7=0
m8=0
direction=0
speed=0
score = 0
cont=True
class ship:
def __init__(self,x1,y1,x2,y2):
self.x1=x1
self.y1=y1
self.x2=x2
self.y2=y2
self.hitbox3=387.5 + x1
self.shape=c.create_polygon(353+x1,380+y1,387.5+x1,310+y1,
420+x1,380+y1,fill="Blue")
def move(self):
global direction
if direction=="L":
self.x1 = self.x1-10
self.hitbox3 = self.hitbox3-10
c.move(self.shape,-10,0)
sleep(0.001)
root.update()
if direction=="R":
self.x1 = self.x1+10
self.hitbox3 = self.hitbox3+10
c.move(self.shape,10,0)
root.update()
self.test_lost_in_space()
sleep(0.001)
def death(self):
root.destroy()
print("You Lost!")
print("Score:",score)
# death_sound()
def test_lost_in_space(self):
if self.hitbox3<=0:
self.death()
if self.hitbox3 >=800:
self.death()
def ship_explode(self):
overlap = c.find_overlapping(353+self.x1,380+self.y1,420+self.x1,310+self.y1)
if overlap != (self.shape,):
self.death()
class meteor:
def __init__(self,x1,y1):
self.x1=x1
self.y1=y1
self.hitbox=89+x1
self.speed=.75
self.shape =c.create_polygon(1+x1,50+y1,34+x1,23+y1,67+x1,23+y1,
89+x1,57+y1,64+x1,71+y1,27+x1,71+y1,fill="brown")
def meteor_return(self):
global m1
global m2
global m3
global m4
global m5
global m6
global m7
global m8
global speed
global score
if self.y1 >=600:
c.delete(self)
m1=meteor(randrange(0,700),randrange(6,12))
m2=meteor(randrange(0,700),randrange(6,12))
m3=meteor(randrange(0,700),randrange(6,12))
m4=meteor(randrange(0,700),randrange(6,12))
m5=meteor(randrange(0,700),randrange(6,12))
m6=meteor(randrange(0,700),randrange(6,12))
m7=meteor(randrange(0,700),randrange(6,12))
m8=meteor(randrange(0,700),randrange(6,12))
if speed!=3:
speed=speed +1
score = score + 1
# lvl_up()
if speed==3:
speed=0
self.speed= self.speed + .5
print(self.speed)
score = score + 5
# lvl_up_2()
def meteor_fall(self):
global speed
self.y1 = self.y1 + self.speed
c.move(self.shape,0,self.speed)
root.update()
self.meteor_return()
# ship1.ship_explode()
def ship_move(event):
global direction
if event.keysym=="a":
direction="L"
ship1.move()
if event.keysym=="d":
direction="R"
ship1.move()
ship1 =ship(0,0,0,0)
m1=meteor(randrange(0,200),randrange(6,12))
m2=meteor(randrange(200,400),randrange(6,12))
m3 =meteor(randrange(400,600),randrange(6,12))
m4=meteor(randrange(600,800),randrange(6,12))
m5 =meteor(randrange(400,600),randrange(6,12))
m6=meteor(randrange(600,800),randrange(6,12))
m7 =meteor(randrange(400,600),randrange(6,12))
m8=meteor(randrange(600,800),randrange(6,12))
c.bind_all("<KeyPress-a>",ship_move)
c.bind_all("<KeyPress-d>",ship_move)
while cont ==True:
m1.meteor_fall()
m2.meteor_fall()
m3.meteor_fall()
m4.meteor_fall()
m5.meteor_fall()
m6.meteor_fall()
m7.meteor_fall()
m8.meteor_fall()
c.bind_all("<KeyPress-a>",ship_move)
c.bind_all("<KeyPress-d>",ship_move)
ship1.death()
Instead of comparing the x and y coordinates of each meteor and seeing whether they are within the bounds of the ships co-ordinates, I would use find_overlapping to detect what actually overlaps the Ship.
If you have nothing on your canvas except the meteors and ship, you could implement something like:
ship_coords = c.coords(self.shape)
overlap = c.find_overlapping(*ship_coords)
if overlap != (self.shape, ):
#Code to run when the meteors collide with the ship.
...
Where (self.shape, ) is the tuple returned from the coordinates you pass to find_overlapping. I recommend reading documentation on the Tkinter canvas, it seems like you have just started learning! Hopefully this helps for the moment however.
If you need to specifically detect what items are overlapping with your ship, then there are plenty of other questions and answers on Stack Overflow about find_overlapping.