#!/usr/bin/env python # Rotatable polygon object classes for Tk Canvas # author: Matt McCredie import sys import math import Tkinter as tk def _rotate(point,angle): try: dx,dy = angle h = math.sqrt(dx**2 + dy**2) s = dy/h c = dx/h except TypeError: s = math.sin(angle) c = math.cos(angle) px,py = point return px*c + py*s, -px*s + py*c def mean(seq): return sum(seq)/len(seq) class ResizableCanvas(tk.Canvas): def __init__(self,*args,**kwargs): tk.Canvas.__init__(self,*args,**kwargs) self.bind('',self.resize) def resize(self,e): # calculate border width bw = ( int(self['bd']) + int(self['highlightthickness']) ) * 2 # calculate the new width and height newwidth = e.width - bw newheight = e.height - bw # Don't reduce either dimension to 0, it can cause issues. if newwidth < 1: newwidth = 1 if newheight < 1: newheight = 1 # get the old width and height oldwidth = int(self['width']) oldheight = int(self['height']) # if the dimensions have changed... if (newwidth,newheight) != (oldwidth,oldheight): self.configure(width=newwidth,height=newheight) class DragRotateMixin(object): # The class that inherits this must have tag and canvas attributes # similar to Die. def __init__(self): self.canvas.tag_bind(self.tag, "", self.ondown,"+") self.canvas.tag_bind(self.tag, "", self.onrot) self.canvas.tag_bind(self.tag, "", self.ondown,"+") self.canvas.tag_bind(self.tag, "", self.onmove) self._moved = False def ondown(self,e): self.mx, self.my = e.x, e.y self.canvas.lift(self.tag) def onmove(self,e): self.canvas.move(self.tag,e.x-self.mx,e.y-self.my) self.ondown(e) def onrot(self,e): coords = self.canvas.coords(self.tag) xs = coords[::2] ys = coords[1::2] ox,oy = mean(xs), mean(ys) rmx = self.mx - ox rmy = self.my - oy rx = e.x - ox ry = e.y - oy try: ang = math.atan(float(rmy)/rmx) - math.atan(float(ry)/rx) except ZeroDivisionError: ang = 0.0 self.rotate(ang) self.ondown(e) class RotatablePoly(object): def __init__(self,canvas,*args,**kwargs): self.canvas = canvas self.defargs = args self.defkwargs = kwargs self.tag = "RotatablePoly%d"%id(self) def draw(self,*args,**kwargs): if self.tag: self.canvas.delete(self.tag) if args: useargs = args else: useargs = self.defargs[:] usekwargs = dict(self.defkwargs) usekwargs.update(kwargs) tag = self.canvas.create_polygon(*useargs, **usekwargs) self.canvas.addtag_withtag(self.tag, tag) def rotate(self,ang): coords = self.canvas.coords(self.tag) xs = coords[::2] ys = coords[1::2] ox,oy = mean(xs), mean(ys) rps = [(x-ox, y-oy) for x,y in zip(xs,ys)] newcrds = [] for pnt in rps: rx,ry = _rotate(pnt, ang) rx,ry = rx+ox, ry+oy newcrds.extend([rx,ry]) self.canvas.coords(self.tag,*newcrds) def animaterotation(self,radpersec): self.radpersec = radpersec self.canvas.master.after(30,self.updaterot) def updaterot(self): self.rotate(0.03 * self.radpersec) self.canvas.master.after(30,self.updaterot) class DragToRotatePoly(RotatablePoly, DragRotateMixin): def __init__(self, *args, **kwargs): RotatablePoly.__init__(self,*args,**kwargs) DragRotateMixin.__init__(self) class App(object): def __init__(self, master=None): if master is None: self.master = tk.Tk() else: self.master = master self.buildGui() def buildGui(self): can = ResizableCanvas(self.master) can.pack(expand=True,fill=tk.BOTH) DragToRotatePoly(can,0,0,0,230,230,230,230,0, fill="black").draw() DragToRotatePoly(can,10,10,10,110,110,110,110,10, fill="blue").draw() DragToRotatePoly(can,120,120,120,220,220,220,220,120, fill="red").draw() DragToRotatePoly(can,120,10,120,110,220,110,220,10, fill="green").draw() DragToRotatePoly(can,10,120,110,120,110,220,10,220, fill="yellow").draw() #r.animaterotation(2*math.pi) def main(self, args=None): if args is None: args = sys.argv[1:] self.master.mainloop() return 0 if __name__ == "__main__": sys.exit(App().main())