#Created by dan december 2011. www.danomagnum.com #from Tkinter import Tk, Frame, BOTH, RIGHT, LEFT, RAISED, Label, IntVar, BooleanVar, X, RAISED, Menu, Scrollbar,Y, Scrollbar, TclError, N, S from Tkinter import * #from ttk import Button, Style, Scale from ttk import Scale import ladder import time import pickle import copy import tkFileDialog import tkSimpleDialog import xmltoladder import datetime FORMATS = [('Python Ladder Logic','*.pll')] FORMATSXML = [('Python Ladder Logic XML files','*.xml')] class AutoScrollbar(Scrollbar): # a scrollbar that hides itself if it's not needed. only # works if you use the grid geometry manager. def set(self, lo, hi): if float(lo) <= 0.0 and float(hi) >= 1.0: # grid_remove is currently missing from Tkinter! self.tk.call("grid", "remove", self) else: self.grid() Scrollbar.set(self, lo, hi) def pack(self, **kw): raise TclError, "cannot use pack with this widget" def place(self, **kw): raise TclError, "cannot use place with this widget" class IOWindow(tkSimpleDialog.Dialog): def body(self, master): self.vartype = StringVar() Label(master, text="Type:").grid(row=0) Radiobutton(master, text="Boolean", variable=self.vartype, value='bool').grid(row=0,column=1) Radiobutton(master, text="Integer", variable=self.vartype, value='int').grid(row=0,column=2) Radiobutton(master, text="String", variable=self.vartype, value='str').grid(row=0,column=3) self.vartype.set('bool') Label(master, text="Name:").grid(row=1) self.name = Entry(master) self.name.grid(row=1, column=1) Label(master, text="Value:").grid(row=2) self.value = Entry(master) self.value.insert(0,0) self.value.grid(row=2, column=1) self.result = None return self.name # initial focus def apply(self): val = False vartype = self.vartype.get() print vartype val2 = self.value.get() if vartype == 'bool': val = bool(val2) elif vartype == 'int': val = int(val2) elif vartype == 'str': val = str(val2) print val self.result = [val, vartype,self.name.get()] #print first, second class MainWindow(Frame): def __init__(self, parent): Frame.__init__(self, parent, background="white") self.parent = parent self.rungs = [] self.exspeed = 100 self.paused = False self.pausedvar = BooleanVar() self.pausedvar.set(self.paused) self.initUI() self.updatesize = False self.calculating = False def initUI(self): #this sets up the interface, and then goes into the program part #self.parent.title("PyLadderLogic") #self.style = Style() #self.style.theme_use("default") #self.pack(fill=BOTH,expand=1) self.grid(row=0,column=0,sticky='news') ladder.fix_scroll = self.scrollsizeupdate ladder.remove_row = self.removerows #sc = Scrollbar(self) #sc.pack(side=RIGHT,fill=Y) topperfrm = Frame(self) topperfrm.pack(fill=X,expand=1) spdlbl = Label(topperfrm,text="Speed^-1") spdlbl.pack(side=LEFT) scale = Scale(topperfrm, from_=50, to=1000, command=self.onScale,value=100) scale.pack(side=LEFT) spdlbl = Checkbutton(topperfrm,text="Pause",command=self.playpause, variable=self.pausedvar,indicatoron=0) spdlbl.pack(side=RIGHT) button1 = Button(topperfrm,text="Run one loop",command=self.runone) button1.pack(side=RIGHT) button1 = Button(topperfrm,text="Update",command=self.toggleit) button1.pack(side=RIGHT) topfrm = Frame(self) topfrm.pack(fill=X,expand=1) self.popup = Menu(root,tearoff=0) self.popup.add_command(label='Add Rung',command=self.addrung) self.popup.add_command(label='Add IO',command=self.addIO) self.popup.add_command(label='Load XML',command=self.loadxml) self.popup.add_command(label='Save XML',command=self.savexml) root.config(menu=self.popup) #this section defines the "bits" in the "plc" and a few 10-bit words IO = ladder.value_word(0,"I0:0") IO.bits_display_new(topfrm) self.IO = IO.bits() self.IO[0].set(True) self.IO[1].set(True) self.IO[2].set(True) self.IO[6].set(True) ladder.itemlist += self.IO self.dataforms = [topfrm] def addrung(self): r = ladder.rung() self.rungs.append(r) r.display_new(self) def addIO(self): w = IOWindow(self) if w.result is not None: [value, vartype, name] = w.result r = ladder.IO(value, name, vartype) self.IO.append(r) r.display_new(self.dataforms[0]) ladder.itemlist = self.IO def toggleit(self): #this function is named this because I just kept reusing it, so no reason. #self.bv.set(random.randint(0,1023)) #self.bv.display_update() #self.blk1.display_update() #for r in self.rungs: #r.display_update() #self.update_idletasks() #canvas.config(scrollregion=canvas.bbox("all")) self.removedata() def runone(self): #do one run through the ladder self.paused = True self.pausedvar.set(self.paused) for r in self.rungs: r.run() r.display_update() def onScale(self, val): #set the speed speed = int(float(val)) self.exspeed = int(float(val)) def playpause(self): #toggle the play/pause self.paused = not self.paused self.pausedvar.set(self.paused) def scrollsizeupdate(self): #canvas.config(scrollregion=canvas.bbox("all")) self.updatesize=True def removerows(self,row): pausestate = self.paused self.paused = True self.pausedvar.set(self.paused) while self.calculating: time.sleep(.1) self.rungs.remove(row) row.__del__() self.paused = pausestate self.pausedvar.set(self.paused) def removedata(self): pausestate = self.paused self.paused = True self.pausedvar.set(self.paused) while self.calculating: time.sleep(.1) self.IO = [] ladder.itemlist = [] dfs = self.dataforms[:] for f in dfs: f.destroy() self.dataforms = [] self.paused = pausestate self.pausedvar.set(self.paused) def savexml(self): filename = tkFileDialog.asksaveasfilename(parent=root,filetypes=FORMATSXML,title="Save") if not filename: return False pausestate = self.paused self.paused = True self.pausedvar.set(self.paused) while self.calculating: time.sleep(.1) xmltoladder.create_xml(self.rungs,self.IO,filename) self.paused = pausestate self.pausedvar.set(self.paused) def loadxml(self): filename = tkFileDialog.askopenfilename(parent=root,filetypes=FORMATSXML,title="Save") if not filename: return False rows = self.rungs[:] for r in rows: self.removerows(r) self.removedata() f = open(filename,'r') [self.rungs, self.IO] = xmltoladder.create_ladder(f) f.close() self.reinit() self.updatesize=True def reinit(self): ladder.itemlist = [] topfrm = Frame(self) topfrm.pack(fill=X,expand=1) for i in self.IO: i.display_new(topfrm) ladder.itemlist += self.IO self.dataforms.append(topfrm) for r in self.rungs: r.display_new(self) r.display_update() def calculatron(*args): #this is what automatically executes the logic at whatever interval is set app = args[0] if not app.paused: app.calculating = True for r in app.rungs: r.run() r.display_update() app.calculating = False else: pass root.after(app.exspeed,calculatron,app) def scrollbarupdate(*args): app = args[0] if app.updatesize: canvas.config(scrollregion=canvas.bbox("all")) app.updatesize = False root.after(100,scrollbarupdate,app) def OnMouseWheel(event): d = 0 if event.num == 5 or event.delta == -120: d = 1 elif event.num == 4 or event.delta == 120: d = -1 canvas.yview("scroll", d, "units") root = Tk() vscrollbar = AutoScrollbar(root) vscrollbar.grid(row=0,column=1, sticky=N+S) hscrollbar = AutoScrollbar(root, orient=HORIZONTAL) hscrollbar.grid(row=1, column=0, sticky=E+W) canvas = Canvas(root, yscrollcommand=vscrollbar.set, xscrollcommand=hscrollbar.set,width=1000,height=300) canvas.grid(row=0, column=0, sticky=N+S+E+W) root.bind('', OnMouseWheel) root.bind('', OnMouseWheel) root.bind('', OnMouseWheel) vscrollbar.config(command=canvas.yview) hscrollbar.config(command=canvas.xview) root.grid_rowconfigure(0, weight=1) root.grid_columnconfigure(0,weight=1) frame = Frame(canvas) frame.rowconfigure(1, weight=1) frame.columnconfigure(1,weight=1) #rows = 5 #for i in range(1,rows): #for j in range(1,10): #button = Button(frame, padx=7, pady=7, text="[%d,%d]" % (i,j)) #button.grid(row=i, column=j, sticky='news') app = MainWindow(frame) canvas.create_window(0, 0, anchor=NW, window=frame) frame.update_idletasks() canvas.config(scrollregion=canvas.bbox("all")) app.update_idletasks() root.after(100,calculatron,app) root.after(100,scrollbarupdate,app) root.mainloop()