I am trying to create turtle objects with a class for my project which is a game. Each "Plane" object consists of:
plane3 = RawTurtle(screen)
plane3.ht()
plane3.color("red")
plane3.shape("plane.gif")
plane3.penup()
plane3.speed('fastest')
plane3.setposition(-270, 200)
plane3.setheading(360)
When putting this into a class and looking at other stack overflows questions to find out what to do, i threw together the following code:
class planes():
def __init__(self):
self.RawTurtle = RawTurtle(screen)
#self.hideturtle()
self.color = "red"
self.shape = ("plane.gif")
#self.penup()
self.speed = "fastest"
self.setposition = (-270, 100)
self.setheading = 360
Plane4 = planes()
When the code is run the turtle takes no shape or colour and is just a black triangle even though it causes no errors. However, errors do occur with the plane.hideturtle and plane.penup() functions which is why they are commented out.
File "C:/Users/marco/Desktop/Trooper shooter/TrooperShooter.py", line 694, in init
self.hideturtle()
AttributeError: 'planes' object has no attribute 'hideturtle'
Planes outside the class work perfectly and all planes are exactly identical. Any help is appreciated!
hideturtle() and penup() are both methods for the RawTurtle class, you haven't defined them for your planes class. So instead of this:
self.hideturtle()
self.penup()
you should have this:
self.RawTurtle.hideturtle()
self.RawTurtle.penup()
I believe your real problem is that your designed your plane class such that it has a turtle instead of designing it such that it is a turtle.
Taking the has a approach, every time you want to enable some additional turtle feature on your plane, you have to add a method to pass the call through to the contained turtle. Taking the is a approach, all turtle methods are in play:
from turtle import RawTurtle, TurtleScreen
from tkinter import Tk, Canvas, RIGHT
class Plane(RawTurtle):
def __init__(self):
super().__init__(screen)
self.hideturtle()
self.color('red')
self.shape('plane.gif')
# self.speed('fastest') # commented out while debugging
self.penup()
self.setposition(-270, 100)
self.setheading(0)
self.showturtle()
root = Tk()
canvas = Canvas(root, width=600, height=400)
canvas.pack(side=RIGHT)
screen = TurtleScreen(canvas)
screen.register_shape('plane.gif')
plane4 = Plane()
plane4.forward(400)
screen.mainloop()
I have an object "surface" which is stored in a "cell" as a list. You can think of a cube which is made of surfaces. Despite I can change surface properties using the "cell" object, there are times when is more convenient for me to change it directly on the "surface". Is it possible to change a property in "surface" object and then propagate it automatically to the same surface within "cell"? An example:
class Surface(object):
def __init__ (self, color):
self.color = color
class Cell(object):
def __init__(self, surfaces)
self.surface = surfaces
surfaces = []
surfaces.append(Surfaces(color='blue'))
surfaces.append(Surfaces(color='blue'))
surfaces.append(Surfaces(color='blue'))
surfaces.append(Surfaces(color='blue'))
surfaces.append(Surfaces(color='blue'))
surfaces.append(Surfaces(color='blue'))
cell = Cell(surfaces)
surface[0].color = 'red'
print(cell.surface[0].color)
'red'
When changing the surface color I would like it to be propagated to the same child within the "cell" object. Is this possible?
Appreciate the help!
I am trying to create a simple drawing application using Python, GTK3 and cairo. The tool should have different brushes and some kind of a highlighter pen.
I figured I can use the alpha property of the stroke to create it. However,
the connecting points are created overlapping and that creates a weird effect.
Here is the code responsible for this red brush and the highlighter mode:
def draw_brush(widget, x, y, odata, width=2.5, r=1, g=0, b=0, alpha=1):
cr = cairo.Context(widget.surface)
cr.set_source_rgba(r, g, b, alpha)
cr.set_line_width(width)
cr.set_line_cap(1)
cr.set_line_join(0)
for stroke in odata:
for i, point in enumerate(stroke):
if len(stroke) == 1:
radius = 2
cr.arc(point['x'], point['y'], radius, 0, 2.0 * math.pi)
cr.fill()
cr.stroke()
elif i != 0:
cr.move_to(stroke[i - 1]['x'], stroke[i - 1]['y'])
cr.line_to(point['x'], point['y'])
cr.stroke()
cr.save()
The code that draws on mouse click:
def motion_notify_event_cb(self, widget, event):
point = {'x': event.x, 'y': event.y, 'time': time.time()}
if self.odata:
self.odata[-1].append(point)
if widget.surface is None:
return False
if event.state & Gdk.EventMask.BUTTON_PRESS_MASK:
if self.buttons['current'] == 'freehand':
draw_brush(widget, event.x, event.y, self.odata)
if self.buttons['current'] == 'highlight':
draw_brush(widget, event.x, event.y, self.odata, width=12.5,
r=220/255, g=240/255, b=90/255, alpha=0.10)
widget.queue_draw()
return True
Can someone point out a way to prevent the overlapping points in this curve?
Update
Uli's solution seems to offer a partial remedy, but the stroke is still not good looking, it seems that it's redrawn over and over:
Update with partially working code
I still have not succeeded in creating a highlighter pen with cairo.
The closest I can get is in the following gist.
The application shutter, has a similar functionality but it's written in Perl on top of the libgoocanvas which is not maintained anymore.
I hope a bounty here will change the situation ...
update
available operators (Linux, GTK+3):
In [3]: [item for item in dir(cairo) if item.startswith("OPERATOR")]
Out[3]:
['OPERATOR_ADD',
'OPERATOR_ATOP',
'OPERATOR_CLEAR',
'OPERATOR_DEST',
'OPERATOR_DEST_ATOP',
'OPERATOR_DEST_IN',
'OPERATOR_DEST_OUT',
'OPERATOR_DEST_OVER',
'OPERATOR_IN',
'OPERATOR_OUT',
'OPERATOR_OVER',
'OPERATOR_SATURATE',
'OPERATOR_SOURCE',
'OPERATOR_XOR']
First, sorry for causing all of that confusion in the comments to your question. It turns out that I was complicating the problem for (partially) no reason! Here is my (heavily-modified) code:
#!/usr/bin/python
from __future__ import division
import math
import time
import cairo
import gi; gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
from gi.repository.GdkPixbuf import Pixbuf
import random
class Brush(object):
def __init__(self, width, rgba_color):
self.width = width
self.rgba_color = rgba_color
self.stroke = []
def add_point(self, point):
self.stroke.append(point)
class Canvas(object):
def __init__(self):
self.draw_area = self.init_draw_area()
self.brushes = []
def draw(self, widget, cr):
da = widget
cr.set_source_rgba(0, 0, 0, 1)
cr.paint()
#cr.set_operator(cairo.OPERATOR_SOURCE)#gets rid over overlap, but problematic with multiple colors
for brush in self.brushes:
cr.set_source_rgba(*brush.rgba_color)
cr.set_line_width(brush.width)
cr.set_line_cap(1)
cr.set_line_join(cairo.LINE_JOIN_ROUND)
cr.new_path()
for x, y in brush.stroke:
cr.line_to(x, y)
cr.stroke()
def init_draw_area(self):
draw_area = Gtk.DrawingArea()
draw_area.connect('draw', self.draw)
draw_area.connect('motion-notify-event', self.mouse_move)
draw_area.connect('button-press-event', self.mouse_press)
draw_area.connect('button-release-event', self.mouse_release)
draw_area.set_events(draw_area.get_events() |
Gdk.EventMask.BUTTON_PRESS_MASK |
Gdk.EventMask.POINTER_MOTION_MASK |
Gdk.EventMask.BUTTON_RELEASE_MASK)
return draw_area
def mouse_move(self, widget, event):
if event.state & Gdk.EventMask.BUTTON_PRESS_MASK:
curr_brush = self.brushes[-1]
curr_brush.add_point((event.x, event.y))
widget.queue_draw()
def mouse_press(self, widget, event):
if event.button == Gdk.BUTTON_PRIMARY:
rgba_color = (random.random(), random.random(), random.random(), 0.5)
brush = Brush(12, rgba_color)
brush.add_point((event.x, event.y))
self.brushes.append(brush)
widget.queue_draw()
elif event.button == Gdk.BUTTON_SECONDARY:
self.brushes = []
def mouse_release(self, widget, event):
widget.queue_draw()
class DrawingApp(object):
def __init__(self, width, height):
self.width = width
self.height = height
self.window = Gtk.Window()
self.window.set_border_width(8)
self.window.set_default_size(self.width, self.height)
self.window.connect('destroy', self.close)
self.box = Gtk.Box(spacing=6)
self.window.add(self.box)
self.canvas = Canvas()
self.box.pack_start(self.canvas.draw_area, True, True, 0)
self.window.show_all()
def close(self, window):
Gtk.main_quit()
if __name__ == "__main__":
DrawingApp(400, 400)
Gtk.main()
Here are the list of changes I made:
Replaced the inheritance in your code with a composition-based approach. That is, instead of inheriting from Gtk.Window or Gtk.DrawingArea, I created Brush, Canvas, and DrawingApp objects that contain these Gtk elements. The idea of this is to allow more flexibility in creating relevant classes to our application and hides all of the nasty Gtk internals as much as possible in setup functions. Hopefully this makes the code a bit clearer. I have no idea why all the tutorials for Gtk insist on using inheritance.
Speaking of the Brush class, there is now a Brush class! Its purpose is simple: it just contains information about the coordinates draw for a given stroke, its line width, and its color. A list of brush strokes making the drawing is stored as a property of DrawingApp. This is convenient because...
... all of the rendering is contained within the draw function of the Canvas class! All this does is draw the black screen, followed by rendering the brush strokes one by one as individual paths to the screen. This solves the problem with the code provided by #UliSchlachter. While the idea of a single connected path was right (and I used that here), all of the iterations of that path were being accumulated and drawn on top of each other. This explains your update image, where the start of each stroke was more opaque due to accumulating the most incomplete strokes.
For the sake of color variety, I made the app generate random highlighter colors every time you click with the left mouse button!
Note that the last point illustrates an issue with the blending. Try drawing multiple overlapping strokes and see what happens! You will find that the more overlaps there are, the more opaque it gets. You can use the cairo.OPERATOR_SOURCE setting to counteract this, but I don't think this is an ideal solution as I believe it overwrites the content underneath. Let me know if this solution is fine or if this also needs to be corrected. Here is a picture of the final result, for your reference:
Hope this helps!
Each move_to() creates a new sub-path that is drawn separately. What you want is a single, connected path.
As far as I know, cairo turns a line_to()-call into a move_to() if there is no current point yet, so the following should work:
def draw_brush(widget, x, y, odata, width=2.5, r=1, g=0, b=0, alpha=1):
cr = cairo.Context(widget.surface)
cr.set_source_rgba(r, g, b, alpha)
cr.set_line_width(width)
cr.set_line_cap(1)
cr.set_line_join(0)
for stroke in odata:
cr.new_path()
for i, point in enumerate(stroke):
if len(stroke) == 1:
radius = 2
cr.arc(point['x'], point['y'], radius, 0, 2.0 * math.pi)
cr.fill()
else:
cr.line_to(point['x'], point['y'])
cr.stroke()
cr.save() # What's this for?
Note that I removed the cr.stroke() after the cr.fill(), because it doesn't do anything. The fill just cleared the path, so there is nothing to stroke.
I recently created a program that will create QgraphicsEllipseItems whenever the mouse is clicked. That part works! However, it's not in the exact place where my cursor is. It seems to be slightly higher than where my mouse cursor is. I do have a QGraphicsRectItem created so maybe the two items are clashing with each other and moving off of one another? How can I get these circles to be placed on top of the rectangle item? Here's the code
class MyView(QtGui.QGraphicsView):
def __init__(self):
QtGui.QGraphicsView.__init__(self)
self.scene = QtGui.QGraphicsScene(self)
self.item = QtGui.QGraphicsRectItem(400, 400, 400, 400)
self.scene.addItem(self.item)
self.setScene(self.scene)
def paintMarkers(self):
self.cursor = QtGui.QCursor()
self.x = self.cursor.pos().x()
self.y = self.cursor.pos().y()
self.circleItem = QtGui.QGraphicsEllipseItem(self.x,self.y,10,10)
self.scene.addItem(self.circleItem)
self.circleItem.setPen(QtGui.QPen(QtCore.Qt.red, 1.5))
self.setScene(self.scene)
class Window(QtGui.QMainWindow):
def __init__(self):
#This initializes the main window or form
super(Window,self).__init__()
self.setGeometry(50,50,1000,1000)
self.setWindowTitle("Pre-Alignment system")
self.view = MyView()
self.setCentralWidget(self.view)
def mousePressEvent(self,QMouseEvent):
self.view.paintMarkers()
Much thanks!
There are two issues with the coordinates you are using to place the QGraphics...Items. The first is that the coordinates from QCursor are global screen coordinates, so you need to use self.mapFromGlobal() to convert them to coordinates relative to the QGraphicsView.
Secondly, you actually want the coordinates relative to the current QGraphicsScene, as this is where you are drawing the item. This is because the scene can be offset from the view (for example panning around a scene that is bigger than a view). To do this, you use self.mapToScene() on the coordinates relative to the QGraphicsView.
I would point out that typically you would draw something on the QGraphicsScene in response to some sort of mouse event in the QGraphicsView, which requires reimplementing things like QGraphicsView.mouseMoveEvent or QGraphicsView.mousePressEvent. These event handlers are passed a QEvent which contains the mouse coordinates relative to the view, and so you don't need to do the global coordinates transformation I mentioned in the first paragraph in these cases.
Update
I've just seen your other question, and now understand some of the issue a bit better. You shouldn't be overriding the mouse event in the main window. Instead override it in the view. For example:
class MyView(QtGui.QGraphicsView):
def __init__(self):
QtGui.QGraphicsView.__init__(self)
self.scene = QtGui.QGraphicsScene(self)
self.item = QtGui.QGraphicsRectItem(400, 400, 400, 400)
self.scene.addItem(self.item)
self.setScene(self.scene)
def paintMarkers(self, event):
# event position is in coordinates relative to the view
# so convert them to scene coordinates
p = self.mapToScene(event.x(), event.y())
self.circleItem = QtGui.QGraphicsEllipseItem(0,0,10,10)
self.circleItem.setPos(p.x()-self.circleItem.boundingRect().width()/2.0,
p.y()-self.circleItem.boundingRect().height()/2.0)
self.scene.addItem(self.circleItem)
self.circleItem.setPen(QtGui.QPen(QtCore.Qt.red, 1.5))
# self.setScene(self.scene) # <-- this line should not be needed here
# Note, I've renamed the second argument `event`. Otherwise you locally override the QMouseEvent class
def mousePressEvent(self, event):
self.paintMarkers(event)
# you may want to preserve the default mouse press behaviour,
# in which case call the following
return QGraphicsView.mousePressEvent(self, event)
Here we have not needed to use QWidget.mapFromGlobal() (what I covered in the first paragraph) because we use a mouse event from the QGraphicsView which returns coordinates relative to that widget only.
Update 2
Note: I've updated how the item is created/placed in the above code based on this answer.
I have managed to make a vertical text label inside a QGraphicsView which is imported from showGui.UI. Everything is exactly how I want it except I can't figure out to change the text color. setStyleSheet doesn't work with QGraphicsViews. I made another attempt with QPainter but i couldn't get that to roate correctly or inside of my graphics view. All the documentation I have found is in C++ and that leaves me bewildered. I think adding:
self.trans_graphicsView_cat.drawForeground(QPainter(setPen(QtGui.QColor(168, 34, 3)))
into the createScene Function will do it but I haven't been able to crack it.
My Code:
class MainDialog(QtGui.QMainWindow, showGui.Ui_MainWindow):
dbPath = appDataPath + "barter.db"
dbConn = sqlite3.connect(dbPath)
dbConn.text_factory = str
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.setupUi(self)
self.text = self.tr("Categories")
self.createScene()
def createScene(self):
scene = QtGui.QGraphicsScene()
self.trans_graphicsView_cat.setScene(scene)
item = scene.addText(self.text, QtGui.QFont('Arial Black', 15, QtGui.QFont.Light))
item.rotate(270)
The addText method returns a QGraphicsTextItem. This class has a setDefaultTextColor method which should allow you to change the colour of the text.
Alternatively, you could create your own instance of QGraphicsTextItem and then use addItem to add it to the scene. Note that the QGraphicsTextItem class has a setHtml method which should give you even greater control over the formatting of the text.