Is there a way to make turtle-graphics object oriented? - python

class Paddle(turtle.Turtle):
def __init__(self, pos, color, shape, speed, shape_size):
self.turtle.pos = turtle.goto(pos)
self.turtle.color = turtle.color(color)
self.turtle.shape = turtle.shape(shape)
self.turtle.speed = turtle.speed(speed)
self.turtle.shapesize = turtle.shapesize(shapesize)
OOP Python turtle module
I have tried to make my game object oriented by trying this code block above, but I get the AttributeError: 'Paddle' object has no attribute 'turtle' traceback. Honestly I have never done OOP with inheritance and modules combined so I'm not quite sure what I'm supposed to do. If you have a resource where I can learn how inheritance works with modules, I'd greatly appreciate it!

Python turtle graphics is object-oriented. The question is, "Is there a way to make your program object-oriented?" We can implement the spirit of what your code is trying to do in an object-oriented fashion something like:
from turtle import Screen, Turtle
class Paddle(Turtle):
def __init__(self, position, color, shape, speed, stretch):
super().__init__(shape=shape, visible=False)
self.penup()
self.goto(position)
self.color(color)
self.shape(shape)
self.speed(speed)
self.shapesize(stretch, 1)
self.showturtle()
screen = Screen()
paddle1 = Paddle((100, 100), 'blue', 'square', 'fastest', 3)
paddle2 = Paddle((-100, 100), 'red', 'square', 'fastest', 3)
screen.exitonclick()
You were mixing the OOP concepts of isa and contains. The above solution only uses isa

Related

creating boxes using turtle

can someone please help me making few boxes, each on different axis co-ordinates using turtle,
P.S. trying to use class and objects in below code:
import turtle
from turtle import *
# window:
window = turtle.Screen()
window.bgcolor("white")
window.title("Process flow")
class a:
penup()
shape("square")
speed(0)
def __init__(self, reshape, color, location):
self.reshape = reshape
self.color = color
self.location = location
start_node1 = a(reshape=shapesize(stretch_wid=1, stretch_len=3), color=color("light blue"), location=goto(0, 300))
start_node2 = a(reshape=shapesize(stretch_wid=1, stretch_len=3), color=color("yellow"), location=goto(0, 270))
print(start_node1)
print(start_node2)
done()
You seem to have strung together random bits of code with the hope that it would work. E.g. argument calls like:
..., location=goto(0, 300)
pass None onto your initializer as that's what goto() returns. And importing turtle two different ways:
import turtle
from turtle import *
is a sign you're in conceptual trouble. Below is my rework of your code to display boxes of different colors and sizes at various locations that tries to retain as much of the "flavor" of your original code as possible:
from turtle import Screen, Turtle
class Box(Turtle):
def __init__(self, reshape, color, location):
super().__init__(shape='square', visible=False)
self.shapesize(**reshape)
self.color(color)
self.speed('fastest')
self.penup()
self.goto(location)
self.showturtle()
screen = Screen()
screen.title("Process flow")
start_node1 = Box(reshape={'stretch_wid':1, 'stretch_len':3}, color='light blue', location=(100, 300))
start_node2 = Box(reshape={'stretch_wid':2, 'stretch_len':4}, color='yellow', location=(-100, 270))
screen.exitonclick()

Make multiple shapes work together in Zelle Graphics

I'm a beginner in Python and I'm using the Zelle's Graphics library. I wish to draw multiple shapes and make them behave as one. For example, I might call a group of shapes 'x' and when I use the builtin move() method the entire collection of shapes move.
The solution I envision is a new GraphicsObject that is actually a group of graphics objects. Below is a skeletal example with just enough code to demonstrate the idea:
from graphics import *
class GraphicsGroup(GraphicsObject):
def __init__(self):
super().__init__(options=[])
self.components = []
def draw(self, graphwin):
for component in self.components:
component.draw(graphwin)
return self
def move(self, dx, dy):
for component in self.components:
component.move(dx, dy)
def add_component(self, component):
if isinstance(component, GraphicsObject):
self.components.append(component)
win = GraphWin("Group Test", 200, 200)
# Some example objects borrowed from graphics.py source docstrings
text = Text(Point(35, 35), "Centered Text")
polygon = Polygon(Point(10, 10), Point(50, 30), Point(20, 70))
circle = Circle(Point(50, 50), 10)
rectangle = Rectangle(Point(25, 60), Point(60, 25))
group = GraphicsGroup()
group.add_component(text)
group.add_component(polygon)
group.add_component(circle)
group.add_component(rectangle)
group.draw(win)
win.getMouse() # Pause to view result
group.move(100, 100)
win.getMouse() # Pause to view result
win.close()
Since GraphicsGroup isn't really a graphic object itself, we override draw and move instead of _draw and _move like a proper graphics entity.

Creating Turtle objects in a class with python

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()

Python/Psychopy: Instantiating multiple circle objects from a class

I've made a class that contains a circle (a Psychopy Circle object). I want to know how I can instantiate 2 circle objects using this class, each for example, with a different fill color.
class Circle(object):
def __init__(self):
self.circle = visual.Circle(win, units = 'deg', pos=(1,1),
radius=1, lineColor="black", fillColor="red")
self.components = [self.circle]
def draw(self):
[component.draw() for component in self.components]
circle=Circle() #red colour
circle2=Circle() #blue colour if possible
Is there a way for me to instantiate circle2 whilst accessing some of the visual.circle parameters e.g. to change it's position or fill color? This is my first use of classes. Currently, If I draw 'circle' and 'cirle2' to the screen, one simply overlays the other, as one is merely a copy of the other.
Cheers,
Jon
Based on your clarification in the comment, I assume you want something like this:
class Circle(psychopy.visual.circle.Circle):
def __init__(self, win, lineColor='black', fillColor='red'):
super(Circle, self).__init__(
win=win, lineColor=lineColor, fillColor=fillColor, units='deg',
pos=(1,1), radius=1)
Circle would then default to units='deg', pos=(1,1), and radius=1. You could, however, specify different lineColors and fillColors for each instance. Since Circle inherits from the PsychoPy visual.Circle class, it has all its features. The call to super() actually initializes the parent class. See e.g. this post for more information on the super() function.
Let's put this to work.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from psychopy import core, visual, monitors
import psychopy.visual.circle
class Circle(psychopy.visual.circle.Circle):
def __init__(self, win, lineColor='black', fillColor='red'):
super(Circle, self).__init__(
win=win, lineColor=lineColor, fillColor=fillColor, units='deg',
pos=(1,1), radius=1)
def main():
# Create a temporary monitor configuration.
monitor = monitors.Monitor('test_display')
monitor.setWidth(60)
monitor.setDistance(80)
monitor.setSizePix((1440, 900))
win = visual.Window(monitor=monitor)
colors = ('red', 'green', 'blue')
circles = [Circle(win=win, fillColor=color) for color in colors]
for circle in circles:
circle.draw()
win.flip()
core.wait(1)
core.quit()
if __name__ == '__main__':
main()
This code will create three Circles with different colors, and display them one after the other. I had to create a temporary monitor configuration or else PsychoPy would refuse to open a Window on my current computer.
You can change the constructor of the class (the __init__ method) adding an atribute color and in the fillColor change the value to the variable color. With the position you can do the same.

Cairo GTK draw a line with transparency (like a highlighter pen)

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.

Categories

Resources