I want to move around objects in python tkinter, specifically polygons. The problem is in is_click function. I can't seem to figure out how to determine if I clicked the object. The code is not 100% complete yet, and moving around needs still need to be finished but I need to figure this out for now. I also have similar class where you can move around Circles and Rectangles, where is_click function is working, but as polygon has from 3 to 4 coordinates it is a bit more complicated. Run The classes for yourself to see what they are doing.
My code for polygons:
import tkinter
class Polygon:
def __init__(self, ax, ay, bx, by, cx, cy, dx=None, dy=None, color=None):
self.ax = ax
self.ay = ay
self.bx = bx
self.by = by
self.cx = cx
self.cy = cy
self.dx = dx
self.dy = dy
self.color = color
def is_click(self, event_x, event_y):
pass
def paint(self, g):
self.g = g
if self.dx is None:
self.id = self.g.create_polygon(self.ax,self.ay,
self.bx,self.by,
self.cx,self.cy,
fill=self.color)
else:
self.id = self.g.create_polygon(self.ax,self.ay,
self.bx,self.by,
self.cx,self.cy,
self.dx,self.dy,
fill=self.color)
def move(self, d_ax=0, d_ay=0, d_bx=0, d_by=0, d_cx=0, d_cy=0, d_dx=None, d_dy=None):
if d_dx is None:
self.ax += d_ax
self.ay += d_ay
self.bx += d_bx
self.by += d_by
self.cx += d_cx
self.cy += d_cy
self.g.move(self.id, d_ax, d_ay, d_bx, d_by, d_cx, d_cy)
else:
self.ax += d_ax
self.ay += d_ay
self.bx += d_bx
self.by += d_by
self.cx += d_cx
self.cy += d_cy
self.dx += d_dx
self.dy += d_dy
self.g.move(self.id, d_ax, d_ay, d_bx, d_by, d_cx, d_cy, d_dx, d_dy)
class Tangram:
def __init__(self):
self.array = []
self.g = tkinter.Canvas(width=800,height=800)
self.g.pack()
#all objects
self.add(Polygon(500,300,625,175,750,300, color='SeaGreen'))
self.add(Polygon(750,50,625,175,750,300, color='Tomato'))
self.add(Polygon(500,175,562.6,237.5,500,300, color='SteelBlue'))
self.add(Polygon(500,175,562.5,237.5,625,175,562.5,112.5, color='FireBrick'))
self.add(Polygon(562.5,112.5,625,175,687.5,112.5, color='DarkMagenta'))
self.add(Polygon(500,50,500,175,625,50, color='Gold'))
self.add(Polygon(562.5,112.5,687.5,112.5,750,50,625,50, color='DarkTurquoise'))
#end of all objects
self.g.bind('<Button-1>', self.event_move_start)
def add(self, Object):
self.array.append(Object)
Object.paint(self.g)
def event_move_start(self, event):
ix = len(self.array) - 1
while ix >= 0 and not self.array[ix].is_click(event.x, event.y):
ix -= 1
if ix < 0:
self.Object = None
return
self.Object = self.array[ix]
self.ex, self.ey = event.x, event.y
self.g.bind('<B1-Motion>', self.event_move)
self.g.bind('<ButtonRelease-1>', self.event_release)
def event_move(self):
pass
def event_release(self):
pass
Tangram()
and code for Circle and Rectangle moving:
import tkinter, random
class Circle:
def __init__(self, x, y, r, color='red'):
self.x = x
self.y = y
self.r = r
self.color = color
def is_click(self, x, y):
return (self.x-x)**2+(self.y-y)**2 < self.r**2
def paint(self, g):
self.g = g
self.id = self.g.create_oval(self.x-self.r,self.y-self.r,
self.x+self.r,self.y+self.r,
fill=self.color)
def move(self, dx=0, dy=0):
self.g.delete(self.id)
self.x += dx
self.y += dy
self.paint(self.g)
class Rectangle:
def __init__(self, x, y, width, height, color='red'):
self.x = x
self.y = y
self.width = width
self.height = height
self.color = color
def is_click(self, x, y):
return self.x<=x<self.x+self.width and self.y<=y<self.y+self.height
def paint(self, g):
self.g = g
self.id = self.g.create_rectangle(self.x,self.y,
self.x+self.width,self.y+self.height,
fill=self.color)
def move(self, dx=0, dy=0):
self.x += dx
self.y += dy
self.g.move(self.id, dx, dy)
class Program:
def __init__(self):
self.array = []
self.g = tkinter.Canvas(bg='white', width=400, height=400)
self.g.pack()
for i in range(20):
if random.randrange(2):
self.add(Circle(random.randint(50, 350),random.randint(50, 350), 20, 'blue'))
else:
self.add(Rectangle(random.randint(50, 350),random.randint(50, 350), 40, 30))
self.g.bind('<Button-1>', self.event_move_start)
def add(self, Object):
self.array.append(Object)
Object.paint(self.g)
def event_move_start(self, e):
ix = len(self.array)-1
while ix >= 0 and not self.array[ix].is_click(e.x, e.y):
ix -= 1
if ix < 0:
self.Object = None
return
self.Object = self.array[ix]
self.ex, self.ey = e.x, e.y
self.g.bind('<B1-Motion>', self.event_move)
self.g.bind('<ButtonRelease-1>', self.event_release)
def event_move(self, e):
self.Object.move(e.x-self.ex, e.y-self.ey)
self.ex, self.ey = e.x, e.y
def event_release(self, e):
self.g.unbind('<B1-Motion>')
self.g.unbind('<ButtonRelease-1>')
self.Object = None
Program()
This is not the full anwser to your questions, but its too long for a comment. One way, that I would centrally consider, is to change/amend your code so that it uses find_closes method. With this method, you can determine which widget (i.e. polygon) is clicked very easily. As a quick prof of concept, you can make the following changes in the Tangram and ploygon class:
def event_move_start(self, event):
ix = len(self.array) - 1
while ix >= 0 and not self.array[ix].is_click(event, self.g, event.x, event.y): # add event and canvas to argumetns
In polygon:
def is_click(self, event, g, event_x, event_y):
widget_id = event.widget.find_closest(event.x, event.y)
print(widget_id)
g.move(widget_id, 1, 1) # just dummy move for a clicked widget
pass
This will print the ids of clicked widgets/polygons and slightly move the clicked polygon. This can be used to select which object was clicked, and having this id object, you can for moving or whatever.
p.s. the full code that I used to test it is here.
Hope this helps.
Related
This question already has answers here:
Create trails of particles for the bullets
(1 answer)
Pygame change particle color
(1 answer)
Closed 5 months ago.
I am a beginner with pygame. I want to include a few "Emitter" classes to "Particle" class to show points. It doesn't work and I don't know how to fix it. I think the problem is with gfxdraw. In the "Emitter" class, it initializes the system window. This can also be a problem. I don't know how to transfer this to the "Particle" class. Would it help at all? I used many combinations for repair. Nothing works. Please help. Does anyone have any idea?
import random
import sys
import pygame
from pygame.locals import *
import pygame.gfxdraw
class ParticleSystem:
def __init__(self, id, pos, a,b, width, height, radius, color):
self.id = id
self.width = width
self.height = height
self.radius = radius
self.color = color
self.g = 9.81
self.pos = [random.randint(a,b), 0]
self.enabled = True
self.df = 0
def update(self, time, collision):
if self.enabled:
v = 1 / float(time)
if not self.collision_detect(collision):
self.pos[1] += self.g*v
else:
self.enabled = False
if self.df != 0:
F = (self.g*v)/9
if self.df < 0:
F = -F
self.pos[0] += F
self.df -= F
def collision_detect(self, collision):
x = int(self.pos[0])
y = self.pos[1]
r = self.radius
points = collision[x-r:x+r]
for p in points:
if y + r >= p:
for i in range(x-r, x+r):
if i >= 0 and i < self.width:
collision[i] = y
return True
if self.pos[1] >= self.height:
return True
else:
return False
def draw(self, surface):
pygame.gfxdraw.filled_circle(surface, int(self.pos[0]), int(self.pos[1]), self.radius, self.color)
pygame.gfxdraw.aacircle(surface, int(self.pos[0]), int(self.pos[1]), self.radius, self.color)
class Emitter:
def __init__(self, a, b, height, width):
self.a = a
self.b = b
self.timer = 60
self.width = width
self.height = height
self.color = (255, 255, 255)
self.background_color = (0, 0, 0)
self.pos = [0,0]
self.particles = []
self.counter = 0
self.freq = 5
self.size = 4
self.collision = [height] * width
self.df_c = 1
self.df_f = 100
self.begin()
def begin(self):
self.screen = pygame.display.set_mode((self.width, self.height))
self.clock = pygame.time.Clock()
while 1:
time = self.clock.get_time()
self.update(time)
self.clock.tick(self.timer)
self.render(time)
def update(self, time):
if self.counter > self.freq:
self.counter = 0
particle = ParticleSystem(len(self.particles), self.pos, self.a, self.b, self.width, self.height, self.size, self.color)
self.particles.append(particle)
else:
self.counter += 1
df_c = random.randint(0, 100)
df_f = 0
if df_c <= self.df_c:
df_f = random.randint(-self.df_f, self.df_f)
for part in self.particles:
if part.enabled:
if df_f != 0:
part.df = df_f
part.update(time, self.collision)
def render(self, time):
surface = pygame.Surface(self.screen.get_size())
surface.convert()
surface.fill(self.background_color)
for part in self.particles:
part.draw(surface)
self.screen.blit(surface, (0, 0))
pygame.display.flip()
class Particle:
def __init__(self, width, height, caption):
self.width = width
self.height = height
self.caption = caption
self.initialization()
def initialization(self):
pygame.init()
pygame.display.set_caption(self.caption)
#Emitter(0, self.width, self.height, self.width)
Emitter(0, 125, self.height, self.width)
Emitter(200, 500, self.height, self.width) #how to show the second Emitter
self.input(pygame.event.get())
def input(self, events):
for event in events:
if event.type == pygame.QUIT:
sys.exit(0)
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
sys.exit(0)
if __name__ == "__main__":
particle = Particle(800, 600, "Particle System - Christmas Time")
I have been trying to build a Terrain Visualizer from OpenGL and using a height map from simplex noise. I have the generator all sorted and it produces both colored and non-colored images, I only want to visualized the colored ones. However there is this weird bump thing, that I believe is a result of GL_TRIANGLE_STRIP.
Picture of the artifact, oddities outlined in red:
My best guess is GL_TRIANGLE_STRIP attaching the first and last vertex, but I do not know.
Here is my code:
import pyglet
from pyglet.gl import *
from pyglet.window import key
import math
from PIL import Image
class Model:
def get_points_in_list(self, fp):
img = Image.open(fp)
self.points = [[0 for x in range(img.width)] for y in range(img.height)]
for y in range(img.height):
for x in range(img.width):
self.points[y][x] = ((x, img.getpixel((x, y))[3], y), img.getpixel((x, y)))
def add_points_to_batch(self):
ysiz = len(self.points)
xsiz = len(self.points[0])
# color1[0], color1[1], color1[2], color2[0], color2[1], color2[2]
for yy in range(ysiz-1):
for xx in range(xsiz):
pos = self.points[yy][xx][0]
x, y, z = pos[0], pos[1], pos[2]
pos1 = self.points[yy+1][xx][0]
X, Y, Z = pos1[0], pos1[1], pos1[2]
color1 = (self.points[yy][xx][1][0], self.points[yy][xx][1][1], self.points[yy][xx][1][2])
color2 = (self.points[yy+1][xx][1][0], self.points[yy+1][xx][1][1], self.points[yy+1][xx][1][2])
self.batch.add(2, GL_TRIANGLE_STRIP, None,
('v3f', (x, y, z, X, Y, Z)),
('c3B', (color1[0], color1[1], color1[2], color2[0], color2[1], color2[2]))
)
def __init__(self):
self.batch = pyglet.graphics.Batch()
self.points = None
self.get_points_in_list('Output_colored.png')
self.add_points_to_batch()
def draw(self):
self.batch.draw()
class Player:
def __init__(self, pos=(0, 0, 0), rot=(0, 0)):
self.pos = list(pos)
self.rot = list(rot)
def mouse_motion(self, dx, dy):
dx /= 8
dy /= 8
self.rot[0] += dy
self.rot[1] -= dx
if self.rot[0]>90:
self.rot[0] = 90
elif self.rot[0] < -90:
self.rot[0] = -90
def update(self,dt,keys):
sens = 1
s = dt*100
rotY = -self.rot[1]/180*math.pi
dx, dz = s*math.sin(rotY), math.cos(rotY)
if keys[key.W]:
self.pos[0] += dx*sens
self.pos[2] -= dz*sens
if keys[key.S]:
self.pos[0] -= dx*sens
self.pos[2] += dz*sens
if keys[key.A]:
self.pos[0] -= dz*sens
self.pos[2] -= dx*sens
if keys[key.D]:
self.pos[0] += dz*sens
self.pos[2] += dx*sens
if keys[key.SPACE]:
self.pos[1] += s
if keys[key.LSHIFT]:
self.pos[1] -= s
class Window(pyglet.window.Window):
def push(self,pos,rot):
glPushMatrix()
rot = self.player.rot
pos = self.player.pos
glRotatef(-rot[0],1,0,0)
glRotatef(-rot[1],0,1,0)
glTranslatef(-pos[0], -pos[1], -pos[2])
def Projection(self):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
def Model(self):
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def set2d(self):
self.Projection()
gluPerspective(0, self.width, 0, self.height)
self.Model()
def set3d(self):
self.Projection()
gluPerspective(70, self.width/self.height, 0.05, 1000)
self.Model()
def setLock(self, state):
self.lock = state
self.set_exclusive_mouse(state)
lock = False
mouse_lock = property(lambda self:self.lock, setLock)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_minimum_size(300,200)
self.keys = key.KeyStateHandler()
self.push_handlers(self.keys)
pyglet.clock.schedule(self.update)
self.model = Model()
self.player = Player((0.5,1.5,1.5),(-30,0))
def on_mouse_motion(self,x,y,dx,dy):
if self.mouse_lock: self.player.mouse_motion(dx,dy)
def on_key_press(self, KEY, _MOD):
if KEY == key.ESCAPE:
self.close()
elif KEY == key.E:
self.mouse_lock = not self.mouse_lock
def update(self, dt):
self.player.update(dt, self.keys)
def on_draw(self):
self.clear()
self.set3d()
self.push(self.player.pos,self.player.rot)
self.model.draw()
glPopMatrix()
if __name__ == '__main__':
window = Window(width=400, height=300, caption='Terrain Viewer', resizable=True)
glClearColor(0, 0, 0, 1)
glEnable(GL_DEPTH_TEST)
pyglet.app.run()
Why is my program slow while rendering 128 particles? I think that's not enough to get less than 30 fps.
All I do is rendering 128 particles and giving them some basic gravitation
on_draw function
def on_draw(self, time=None):
glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
self.particles.append(Particle())
for particle in self.particles:
particle.draw()
if particle.is_dead:
self.particles.remove(particle)
Particle class
class Particle:
def __init__(self, **kwargs):
self.acceleration = Vector2(0, 0.05)
self.velocity = Vector2(random.uniform(-1, 1), random.uniform(-1, 0))
self.position = Vector2()
self.time_to_live = 255
self.numpoints = 50
self._vertices = []
for i in range(self.numpoints):
angle = math.radians(float(i) / self.numpoints * 360.0)
x = 10 * math.cos(angle) + self.velocity[0] + 300
y = 10 * math.sin(angle) + self.velocity[1] + 400
self._vertices += [x, y]
def update(self, time=None):
self.velocity += self.acceleration
self.position -= self.velocity
self.time_to_live -= 2
def draw(self):
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glPushMatrix()
glTranslatef(self.position[0], self.position[1], 0)
pyglet.graphics.draw(self.numpoints, GL_TRIANGLE_FAN, ('v2f', self._vertices), ('c4B', self.color))
glPopMatrix()
self.update()
#property
def is_dead(self):
if self.time_to_live <= 0:
return True
return False
#property
def color(self):
return tuple(color for i in range(self.numpoints) for color in (255, 255, 255, self.time_to_live))
I'm not overly happy about using GL_TRIANGLE_FAN because it's caused a lot of odd shapes when using batched rendering. So consider moving over to GL_TRIANGLES instead and simply add all the points to the object rather than leaning on GL to close the shape for you.
That way, you can easily move over to doing batched rendering:
import pyglet
from pyglet.gl import *
from collections import OrderedDict
from time import time, sleep
from math import *
from random import randint
key = pyglet.window.key
class CustomGroup(pyglet.graphics.Group):
def set_state(self):
#pyglet.gl.glLineWidth(5)
#glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
#glColor4f(1, 0, 0, 1) #FFFFFF
#glLineWidth(1)
#glEnable(texture.target)
#glBindTexture(texture.target, texture.id)
pass
def unset_state(self):
glLineWidth(1)
#glDisable(texture.target)
class Particle():
def __init__(self, x, y, batch, particles):
self.batch = batch
self.particles = particles
self.group = CustomGroup()
self.add_point(x, y)
def add_point(self, x, y):
colors = ()#255,0,0
sides = 50
radius = 25
deg = 360/sides
points = ()#x, y # Starting point is x, y?
prev = None
for i in range(sides):
n = ((deg*i)/180)*pi # Convert degrees to radians
point = int(radius * cos(n)) + x, int(radius * sin(n)) + y
if prev:
points += x, y
points += prev
points += point
colors += (255, i*int(255/sides), 0)*3 # Add a color pair for each point (r,g,b) * points[3 points added]
prev = point
points += x, y
points += prev
points += points[2:4]
colors += (255, 0, 255)*3
self.particles[len(self.particles)] = self.batch.add(int(len(points)/2), pyglet.gl.GL_TRIANGLES, self.group, ('v2i/stream', points), ('c3B', colors))
class main(pyglet.window.Window):
def __init__ (self, demo=False):
super(main, self).__init__(800, 600, fullscreen = False, vsync = True)
#print(self.context.config.sample_buffers)
self.x, self.y = 0, 0
self.sprites = OrderedDict()
self.batches = OrderedDict()
self.batches['default'] = pyglet.graphics.Batch()
self.active_batch = 'default'
for i in range(1000):
self.sprites[len(self.sprites)] = Particle(randint(0, 800), randint(0, 600), self.batches[self.active_batch], self.sprites)
self.alive = True
self.fps = 0
self.last_fps = time()
self.fps_label = pyglet.text.Label(str(self.fps) + ' fps', font_size=12, x=3, y=self.height-15)
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
#self.bg.draw()
self.batches[self.active_batch].draw()
self.fps += 1
if time()-self.last_fps > 1:
self.fps_label.text = str(self.fps) + ' fps'
self.fps = 0
self.last_fps = time()
self.fps_label.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main(demo=True)
x.run()
Bare in mind, on my nVidia 1070 I managed to get roughly 35fps out of this code, which isn't mind blowing. But it is 1000 objects * sides, give or take.
What I've changed is essentially this:
self.batch.add(int(len(points)/2), pyglet.gl.GL_TRIANGLES, self.group, ('v2i/stream', points), ('c3B', colors))
and in your draw loop, you'll do:
self.batch.draw()
Instead of calling Particle.draw() for each particle object.
What this does is that it'll send all the objects to the graphics card in one gigantic batch rather than having to tell the graphics card what to render object by object.
As #thokra pointed out, your code is more CPU intensive than GPU intensive.
Hopefully this fixes it or gives you a few pointers.
Most of this code is taking from a LAN project I did with a good friend of mine a while back:
https://github.com/Torxed/pyslither/blob/master/main.py
Because I didn't have all your code, mainly the main loop. I applied your problem to my own code and "solved " it by tweaking it a bit. Again, hope it helps and steal ideas from that github project if you need to. Happy new year!
I've got two three nested widget which handle the on_touch_down event, clicking on the first parent, the event is sensed to the second and then the third. With this click the third widget should draw on it's canvas but this it's not happening.
I understand the dynamic of the canvas object and it's groups. So I don't really understand why this is happening
class Spline_guide(DragBehavior, Button):
def __init__(self, **kw):
super(Spline_guide, self).__init__(**kw)
self.bind(pos = self.update_spline , size = self.update_spline)
def update_spline(self, *l):
self.pos = self.parent.pos
return self
def show_spline(self,*l):
print 'show spline'
with self.canvas:
Color(0,1,0)
######## THIS ISTRUCTION DOESN'T WORK
Ellipse(pos = (100,100), size = (100,100))
return self
def hide_spline(self, *l):
print 'hide spline'
self.canvas.clear()
return self
class Editable_point(DragBehavior, Widget):
def __init__(self, name,x,y, **kw):
super(Editable_point, self).__init__(**kw)
self.drag_rectangle = (0,0 ,800,300)
self.drag_timeout = 10000000
self.drag_distance = 0
self.name = name
self.pos = (x - DOT_DIMENSION / 2,y - DOT_DIMENSION / 2)
self.size = (DOT_DIMENSION, DOT_DIMENSION)
self.spline = Spline_guide()
self.add_widget(self.spline)
self.SHOW_SPLINE = False
self.bind(pos = self.check_pos)
def check_pos(self, *l):
self.area = self.parent.parent
self.x = self.x if self.x > self.area.x else self.area.x
self.y = self.y if self.y > self.area.y else self.area.y
return self
def draw_point(self):
self.area = self.parent.parent
self.drag_rectangle = (self.area.x, self.area.y, self.area.width, self.area.height)
self.canvas.clear()
with self.canvas:
Color(1,0,0)
Ellipse(pos=self.pos, size=self.size)
return self
def on_enter(self, pop):
def wrap(l):
if l.text in ['start','stop']:
print 'you can not use start or stop as name'
pop.dismiss()
return wrap
if self.name in ['start','stop']:
pop.dismiss()
print 'you can not edit the name of start or stop point'
return wrap
self.name = l.text
pop.dismiss()
return wrap
def show_info(self, *l):
graph = self.parent.parent.graph
X_OFFSET = graph._plot_area.x + self.parent.x - DOT_DIMENSION / 2
Y_OFFSET = graph._plot_area.y + self.parent.y - DOT_DIMENSION / 2
_x = self.x - X_OFFSET
_y = self.y - Y_OFFSET
x, y = normalize(graph.xmin,graph.ymin,graph.xmax,graph.ymax,graph._plot_area.width,graph._plot_area.height, _x, _y)
point_name = TextInput(text=self.name, multiline = False)
point_info = Popup(title ="X: {} Y: {}".format(x,y), content = point_name, size_hint=(None,None), size=(200,100))
point_name.bind(on_text_validate=self.on_enter(point_info))
point_info.open()
return self
def on_touch_down(self, *l):
if self.SHOW_SPLINE:
self.SHOW_SPLINE = False
self.spline.hide_spline()
else:
self.SHOW_SPLINE = True
self.spline.show_spline()
return self
class Editable_line(Widget):
def __init__(self, **kw):
super(Editable_line, self).__init__(**kw)
self._old_ = [100,100]
self.old_pos = [0,0]
self.bind(pos=self.update_line, size=self.update_line)
def replace_start_stop(self, new_elem):
elem = filter(lambda x: x.name == new_elem.name, [ i for i in self.children if hasattr(i,'name')])
if elem: self.remove_widget(elem[0])
self.add_widget(new_elem)
return self
def update_points(self):
r_x = float(Window.size[0]) / float(self._old_[0])
r_y = float(Window.size[1]) / float(self._old_[1])
for p in [ i for i in self.children if hasattr(i,'name')]:
new_x = p.x * r_x
new_y = p.y * r_y
p.x = new_x
p.y = new_y
p.size = (DOT_DIMENSION, DOT_DIMENSION)
return self
def update_line(self, *a):
self.pos = self.parent.pos
self.size = self.parent.size
self.parent.graph.pos = self.pos
self.parent.graph.size = self.size
self.parent.graph._redraw_size()
#Coordinate per start e stop personalizzare sul graph
y = self.parent.graph._plot_area.y + self.y
x = self.parent.graph._plot_area.x + self.x
h = self.parent.graph._plot_area.height
w = self.parent.graph._plot_area.width
self.replace_start_stop(Editable_point('start', x, y + h / 2))
self.replace_start_stop(Editable_point('stop', x + w, y + h / 2))
self.update_points()
self._old_ = Window.size
return self.draw_line()
def sort_points(self):
self.children = sorted(self.children , key=attrgetter('x'))
return self
def control_presence(self,coordinates):
x = int(coordinates.x)
y = int(coordinates.y)
x_range = range(x-DOT_DIMENSION, x+DOT_DIMENSION)
y_range = range(y-DOT_DIMENSION, y+DOT_DIMENSION)
for p in [ i for i in self.children if hasattr(i,'name')]:
if int(p.x) in x_range and int(p.y) in y_range: return p
return False
def on_touch_down(self,coordinates):
#add points
p = self.control_presence(coordinates)
if p:
if not coordinates.is_double_tap:
return p.on_touch_down(coordinates)
return p.show_info(coordinates)
x = int(coordinates.x)
y = int(coordinates.y)
p = Editable_point('new point', x, y)
p.size = (DOT_DIMENSION, DOT_DIMENSION)
self.add_widget(p)
return self.draw_line()
def remove_point(self,coordinates):
p = self.control_presence(coordinates)
if p:
if p.name in ['start','stop']: print 'you can\'t delete start or stop point' else: self.remove_widget(p)
return self.draw_line()
def draw_line(self):
self.sort_points()
self.canvas.before.clear()
_l = list()
for p in [ i for i in self.children if hasattr(i,'name')]:
_l.append(p.x + DOT_DIMENSION/2)
_l.append(p.y + DOT_DIMENSION/2)
p.draw_point()
with self.canvas.before:
Color(0,0.8,1)
Line(points=_l, witdth = LINE_WIDTH)
return self
def on_touch_move(self, coordinates):
p = self.control_presence(coordinates)
if p:
if p.name in ['start','stop']:
return self.parent.on_touch_up(coordinates)
p.on_touch_move(coordinates)
self.draw_line()
return self
This is a piece of my code and show how the classes are linked. The Spline_guide doesn't draw anything. Ideas?
Widgets which are added to another Widget are drawn in the latter Widget's canvas. When you call self.canvas.clear() (i.e. in Editable_point) you are removing all the child canvases.
You could use canvas.before or canvas.after for your drawing instead, or you can save the instructions and modify them later:
def __init__(self, **kwargs):
...
with self.canvas:
self.draw_color = Color(0, 1, 0, 1)
self.draw_ellipse = Ellipse(pos=self.pos, size=self.size)
def draw_point(self):
...
self.draw_ellipse.pos = self.pos
self.draw_ellipse.size = self.size
Then you never need to clear the canvas at all. This is the preferred solution as it offers the best performance.
I'm drawing little circles on a canvas with these functions :
This is the function that will draw the circles :
class Fourmis:
def __init__(self, can, posx, posy, name, radius):
self.can = can
self.largeur_can = int(self.can.cget("width"))
self.hauteur_can = int(self.can.cget("height"))
self.posx = posx
self.posy = posy
self.name = name
self.radius = radius
self.ball1 = self.can.create_oval(self.posy, self.posx, self.posy+radius, self.posx+radius, outline=self.name, fill=self.name, width=2)
self.nx = randrange(-10,10,1)
self.nx /= 2.0
self.ny = randrange(-10,10,1)
self.ny /= 2.0
#self.can.bind("<Motion>", self.destruction, add="+")
self.statut = True
self.move()
def move(self):
if self.statut == True :
self.pos_ball = self.can.coords(self.ball1)
self.posx_ball = self.pos_ball[0]
self.posy_ball = self.pos_ball[1]
if self.posx_ball < 0 or (self.posx_ball + self.radius) > self.largeur_can:
self.nx = -self.nx
if self.posy_ball < 0 or (self.posy_ball + self.radius) > self.hauteur_can:
self.ny = -self.ny
self.can.move(self.ball1, self.nx, self.ny)
self.can.after(10, self.move)
this one creates the canvas and the circles :
class App(Frame):
def __init__(self):
self.root=Tk()
self.can=Canvas(self.root,width=800,height=600,bg="black")
self.can.pack()
self.create(50, "green")
self.create(50, "purple")
def mainloop(self):
self.root.mainloop()
def create(self, i, name):
for x in range(i):
self.x=Fourmis(self.can,100,400, name,0)
I call these lines to run the project :
jeu = App()
jeu.mainloop()
What is the correct way to execute self.create(50, "green") and self.create(50, "purple") in different threads?
I have tried the following, but could not get it to work.:
class FuncThread(threading.Thread):
def __init__(self, i, name):
self.i = i
self.name = name
threading.Thread.__init__(self)
def run(self):
App.create(self, self.i, self.name)
Is someone able to tell me how to run these threads?
When this functionality is needed, what you do is schedule the events you wish to perform by putting them in a queue shared by the threads. This way, in a given thread you specify that you want to run "create(50, ...)" by queueing it, and the main thread dequeue the event and perform it.
Here is a basic example for creating moving balls in a second thread:
import threading
import Queue
import random
import math
import time
import Tkinter
random.seed(0)
class App:
def __init__(self, queue, width=400, height=300):
self.width, self.height = width, height
self.canvas = Tkinter.Canvas(width=width, height=height, bg='black')
self.canvas.pack(fill='none', expand=False)
self._oid = []
self.canvas.after(10, self.move)
self.queue = queue
self.canvas.after(50, self.check_queue)
def check_queue(self):
try:
x, y, rad, outline = self.queue.get(block=False)
except Queue.Empty:
pass
else:
self.create_moving_ball(x, y, rad, outline)
self.canvas.after(50, self.check_queue)
def move(self):
width, height = self.width, self.height
for i, (oid, r, angle, speed, (x, y)) in enumerate(self._oid):
sx, sy = speed
dx = sx * math.cos(angle)
dy = sy * math.sin(angle)
if y + dy + r> height or y + dy - r < 0:
sy = -sy
self._oid[i][3] = (sx, sy)
if x + dx + r > width or x + dx - r < 0:
sx = -sx
self._oid[i][3] = (sx, sy)
nx, ny = x + dx, y + dy
self._oid[i][-1] = (nx, ny)
self.canvas.move(oid, dx, dy)
self.canvas.update_idletasks()
self.canvas.after(10, self.move)
def create_moving_ball(self, x=100, y=100, rad=20, outline='white'):
oid = self.canvas.create_oval(x - rad, y - rad, x + rad, y + rad,
outline=outline)
oid_angle = math.radians(random.randint(1, 360))
oid_speed = random.randint(2, 5)
self._oid.append([oid, rad, oid_angle, (oid_speed, oid_speed), (x, y)])
def queue_create(queue, running):
while running:
if random.random() < 1e-6:
print "Create a new moving ball please"
x, y = random.randint(100, 150), random.randint(100, 150)
color = random.choice(['green', 'white', 'yellow', 'blue'])
queue.put((x, y, random.randint(10, 30), color))
time.sleep(0) # Effectively yield this thread.
root = Tkinter.Tk()
running = [True]
queue = Queue.Queue()
app = App(queue)
app.create_moving_ball()
app.canvas.bind('<Destroy>', lambda x: (running.pop(), x.widget.destroy()))
thread = threading.Thread(target=queue_create, args=(queue, running))
thread.start()
root.mainloop()