-
Notifications
You must be signed in to change notification settings - Fork 80
/
pycxsimulator.py
305 lines (255 loc) · 13.1 KB
/
pycxsimulator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
## "pycxsimulator.py"
## Dynamic, interactive simulation GUI for PyCX
##
## Project website:
## https://github.com/hsayama/PyCX
##
## Initial development by:
## Chun Wong
##
## Revisions by:
## Hiroki Sayama
##
## Copyright 2012 Chun Wong
## Copyright 2012-2019 Hiroki Sayama
##
## Simulation control & GUI extensions
## Copyright 2013 Przemyslaw Szufel & Bogumil Kaminski
## {pszufe, bkamins}@sgh.waw.pl
##
## Fixing errors due to "the grid and pack problem" by:
## Toshihiro Tanizawa
## began at 2016-06-15(Wed) 17:10:17
## fixed grid() and pack() problem on 2016-06-21(Tue) 18:29:40
##
## various bug fixes and updates by Steve Morgan on 3/28/2020
import matplotlib
#System check added by Steve Morgan
import platform #SM 3/28/2020
if platform.system() == 'Windows': #SM 3/28/2020
backend = 'TkAgg' #SM 3/28/2020
else: #SM 3/28/2020
backend = 'Qt5Agg' #SM 3/28/2020
matplotlib.use(backend) #SM 3/28/2020
import matplotlib.pyplot as plt #SM 3/28/2020
## version check added by Hiroki Sayama on 01/08/2019
import sys
if sys.version_info[0] == 3: # Python 3
from tkinter import *
from tkinter.ttk import Notebook
else: # Python 2
from Tkinter import *
from ttk import Notebook
## suppressing matplotlib deprecation warnings (especially with subplot) by Hiroki Sayama on 06/29/2020
import warnings
warnings.filterwarnings("ignore", category = matplotlib.cbook.MatplotlibDeprecationWarning)
class GUI:
# Constructor
def __init__(self, title='PyCX Simulator', interval=0, stepSize=1, parameterSetters=[]):
## all GUI variables moved to inside constructor by Hiroki Sayama 10/09/2018
self.titleText = title
self.timeInterval = interval
self.stepSize = stepSize
self.parameterSetters = parameterSetters
self.varEntries = {}
self.statusStr = ""
self.running = False
self.modelFigure = None
self.currentStep = 0
# initGUI() removed by Hiroki Sayama 10/09/2018
#create root window
self.rootWindow = Tk()
self.statusText = StringVar(self.rootWindow, value=self.statusStr) # at this point, statusStr = ""
# added "self.rootWindow" above by Hiroki Sayama 10/09/2018
self.setStatusStr("Simulation not yet started")
self.rootWindow.wm_title(self.titleText) # titleText = 'PyCX Simulator'
self.rootWindow.protocol('WM_DELETE_WINDOW', self.quitGUI)
self.rootWindow.geometry('450x300')
self.rootWindow.columnconfigure(0, weight=1)
self.rootWindow.rowconfigure(0, weight=1)
self.notebook = Notebook(self.rootWindow)
# self.notebook.grid(row=0,column=0,padx=2,pady=2,sticky='nswe') # commented out by toshi on 2016-06-21(Tue) 18:30:25
self.notebook.pack(side=TOP, padx=2, pady=2)
# added "self.rootWindow" by Hiroki Sayama 10/09/2018
self.frameRun = Frame(self.rootWindow)
self.frameSettings = Frame(self.rootWindow)
self.frameParameters = Frame(self.rootWindow)
self.frameInformation = Frame(self.rootWindow)
self.notebook.add(self.frameRun,text="Run")
self.notebook.add(self.frameSettings,text="Settings")
self.notebook.add(self.frameParameters,text="Parameters")
self.notebook.add(self.frameInformation,text="Info")
self.notebook.pack(expand=NO, fill=BOTH, padx=5, pady=5 ,side=TOP)
# self.notebook.grid(row=0, column=0, padx=5, pady=5, sticky='nswe') # commented out by toshi on 2016-06-21(Tue) 18:31:02
self.status = Label(self.rootWindow, width=40,height=3, relief=SUNKEN, bd=1, textvariable=self.statusText)
# self.status.grid(row=1,column=0,padx=5,pady=5,sticky='nswe') # commented out by toshi on 2016-06-21(Tue) 18:31:17
self.status.pack(side=TOP, fill=X, padx=5, pady=5, expand=NO)
# -----------------------------------
# frameRun
# -----------------------------------
# buttonRun
self.runPauseString = StringVar(self.rootWindow) # added "self.rootWindow" by Hiroki Sayama 10/09/2018
self.runPauseString.set("Run")
self.buttonRun = Button(self.frameRun,width=30,height=2,textvariable=self.runPauseString,command=self.runEvent)
self.buttonRun.pack(side=TOP, padx=5, pady=5)
self.showHelp(self.buttonRun,"Runs the simulation (or pauses the running simulation)")
# buttonStep
self.buttonStep = Button(self.frameRun,width=30,height=2,text='Step Once',command=self.stepOnce)
self.buttonStep.pack(side=TOP, padx=5, pady=5)
self.showHelp(self.buttonStep,"Steps the simulation only once")
# buttonReset
self.buttonReset = Button(self.frameRun,width=30,height=2,text='Reset',command=self.resetModel)
self.buttonReset.pack(side=TOP, padx=5, pady=5)
self.showHelp(self.buttonReset,"Resets the simulation")
# -----------------------------------
# frameSettings
# -----------------------------------
can = Canvas(self.frameSettings)
lab = Label(can, width=25,height=1,text="Step size ", justify=LEFT, anchor=W,takefocus=0)
lab.pack(side='left')
self.stepScale = Scale(can,from_=1, to=50, resolution=1,command=self.changeStepSize,orient=HORIZONTAL, width=25,length=150)
self.stepScale.set(self.stepSize)
self.showHelp(self.stepScale,"Skips model redraw during every [n] simulation steps\nResults in a faster model run.")
self.stepScale.pack(side='left')
can.pack(side='top')
can = Canvas(self.frameSettings)
lab = Label(can, width=25,height=1,text="Step visualization delay in ms ", justify=LEFT, anchor=W,takefocus=0)
lab.pack(side='left')
self.stepDelay = Scale(can,from_=0, to=max(2000,self.timeInterval),
resolution=10,command=self.changeStepDelay,orient=HORIZONTAL, width=25,length=150)
self.stepDelay.set(self.timeInterval)
self.showHelp(self.stepDelay,"The visualization of each step is delays by the given number of milliseconds.")
self.stepDelay.pack(side='left')
can.pack(side='top')
# --------------------------------------------
# frameInformation
# --------------------------------------------
scrollInfo = Scrollbar(self.frameInformation)
self.textInformation = Text(self.frameInformation, width=45,height=13,bg='lightgray',wrap=WORD,font=("Courier",10))
scrollInfo.pack(side=RIGHT, fill=Y)
self.textInformation.pack(side=LEFT,fill=BOTH,expand=YES)
scrollInfo.config(command=self.textInformation.yview)
self.textInformation.config(yscrollcommand=scrollInfo.set)
# --------------------------------------------
# ParameterSetters
# --------------------------------------------
for variableSetter in self.parameterSetters:
can = Canvas(self.frameParameters)
lab = Label(can, width=25,height=1,text=variableSetter.__name__+" ",anchor=W,takefocus=0)
lab.pack(side='left')
ent = Entry(can, width=11)
ent.insert(0, str(variableSetter()))
if variableSetter.__doc__ != None and len(variableSetter.__doc__) > 0:
self.showHelp(ent,variableSetter.__doc__.strip())
ent.pack(side='left')
can.pack(side='top')
self.varEntries[variableSetter]=ent
if len(self.parameterSetters) > 0:
self.buttonSaveParameters = Button(self.frameParameters,width=50,height=1,
command=self.saveParametersCmd,text="Save parameters to the running model",state=DISABLED)
self.showHelp(self.buttonSaveParameters,
"Saves the parameter values.\nNot all values may take effect on a running model\nA model reset might be required.")
self.buttonSaveParameters.pack(side='top',padx=5,pady=5)
self.buttonSaveParametersAndReset = Button(self.frameParameters,width=50,height=1,
command=self.saveParametersAndResetCmd,text="Save parameters to the model and reset the model")
self.showHelp(self.buttonSaveParametersAndReset,"Saves the given parameter values and resets the model")
self.buttonSaveParametersAndReset.pack(side='top',padx=5,pady=5)
# <<<<< Init >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def setStatusStr(self,newStatus):
self.statusStr = newStatus
self.statusText.set(self.statusStr)
# model control functions for changing parameters
def changeStepSize(self,val):
self.stepSize = int(val)
def changeStepDelay(self,val):
self.timeInterval= int(val)
def saveParametersCmd(self):
for variableSetter in self.parameterSetters:
variableSetter(float(self.varEntries[variableSetter].get()))
self.setStatusStr("New parameter values have been set")
def saveParametersAndResetCmd(self):
self.saveParametersCmd()
self.resetModel()
# <<<< runEvent >>>>>
# This event is envoked when "Run" button is clicked.
def runEvent(self):
self.running = not self.running
if self.running:
self.rootWindow.after(self.timeInterval,self.stepModel)
self.runPauseString.set("Pause")
self.buttonStep.configure(state=DISABLED)
self.buttonReset.configure(state=DISABLED)
if len(self.parameterSetters) > 0:
self.buttonSaveParameters.configure(state=NORMAL)
self.buttonSaveParametersAndReset.configure(state=DISABLED)
else:
self.runPauseString.set("Continue Run")
self.buttonStep.configure(state=NORMAL)
self.buttonReset.configure(state=NORMAL)
if len(self.parameterSetters) > 0:
self.buttonSaveParameters.configure(state=NORMAL)
self.buttonSaveParametersAndReset.configure(state=NORMAL)
def stepModel(self):
if self.running:
self.modelStepFunc()
self.currentStep += 1
self.setStatusStr("Step "+str(self.currentStep))
self.status.configure(foreground='black')
if (self.currentStep) % self.stepSize == 0:
self.drawModel()
self.rootWindow.after(int(self.timeInterval*1.0/self.stepSize),self.stepModel)
def stepOnce(self):
self.running = False
self.runPauseString.set("Continue Run")
self.modelStepFunc()
self.currentStep += 1
self.setStatusStr("Step "+str(self.currentStep))
self.drawModel()
if len(self.parameterSetters) > 0:
self.buttonSaveParameters.configure(state=NORMAL)
def resetModel(self):
self.running = False
self.runPauseString.set("Run")
self.modelInitFunc()
self.currentStep = 0;
self.setStatusStr("Model has been reset")
self.drawModel()
def drawModel(self):
plt.ion() #SM 3/26/2020
if self.modelFigure == None or self.modelFigure.canvas.manager.window == None:
self.modelFigure = plt.figure() #SM 3/26/2020
self.modelDrawFunc()
self.modelFigure.canvas.manager.window.update()
plt.show() # bug fix by Hiroki Sayama in 2016 #SM 3/26/2020
def start(self,func=[]):
if len(func)==3:
self.modelInitFunc = func[0]
self.modelDrawFunc = func[1]
self.modelStepFunc = func[2]
if (self.modelStepFunc.__doc__ != None and len(self.modelStepFunc.__doc__)>0):
self.showHelp(self.buttonStep,self.modelStepFunc.__doc__.strip())
if (self.modelInitFunc.__doc__ != None and len(self.modelInitFunc.__doc__)>0):
self.textInformation.config(state=NORMAL)
self.textInformation.delete(1.0, END)
self.textInformation.insert(END, self.modelInitFunc.__doc__.strip())
self.textInformation.config(state=DISABLED)
self.modelInitFunc()
self.drawModel()
self.rootWindow.mainloop()
def quitGUI(self):
self.running = False # HS 06/29/2020
self.rootWindow.quit()
plt.close('all') # HS 06/29/2020
self.rootWindow.destroy()
def showHelp(self, widget,text):
def setText(self):
self.statusText.set(text)
self.status.configure(foreground='blue')
def showHelpLeave(self):
self.statusText.set(self.statusStr)
self.status.configure(foreground='black')
widget.bind("<Enter>", lambda e : setText(self))
widget.bind("<Leave>", lambda e : showHelpLeave(self))