chiark / gitweb /
Merge branch 'master' of github.com:daid/Cura
[cura.git] / Cura / gui / configWizard.py
1 from __future__ import absolute_import\r
2 import __init__\r
3 \r
4 import wx, os, platform, types, webbrowser, threading, time, re\r
5 import wx.wizard\r
6 \r
7 from gui import machineCom\r
8 from util import profile\r
9 \r
10 class InfoPage(wx.wizard.WizardPageSimple):\r
11         def __init__(self, parent, title):\r
12                 wx.wizard.WizardPageSimple.__init__(self, parent)\r
13 \r
14                 sizer = wx.BoxSizer(wx.VERTICAL)\r
15                 self.sizer = sizer\r
16                 self.SetSizer(sizer)\r
17 \r
18                 title = wx.StaticText(self, -1, title)\r
19                 title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))\r
20                 sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 5)\r
21                 sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 5)\r
22         \r
23         def AddText(self,info):\r
24                 text = wx.StaticText(self, -1, info)\r
25                 self.GetSizer().Add(text, 0, wx.LEFT|wx.RIGHT, 5)\r
26                 return text\r
27         \r
28         def AddSeperator(self):\r
29                 self.GetSizer().Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 5)\r
30         \r
31         def AddHiddenSeperator(self):\r
32                 self.AddText('')\r
33         \r
34         def AddRadioButton(self, label, style = 0):\r
35                 radio = wx.RadioButton(self, -1, label, style=style)\r
36                 self.GetSizer().Add(radio, 0, wx.EXPAND|wx.ALL, 5)\r
37                 return radio\r
38         \r
39         def AddButton(self, label):\r
40                 button = wx.Button(self, -1, label)\r
41                 self.GetSizer().Add(button, 0, wx.LEFT, 5)\r
42                 return button\r
43         \r
44         def AddDualButton(self, label1, label2):\r
45                 p = wx.Panel(self)\r
46                 p.SetSizer(wx.BoxSizer(wx.HORIZONTAL))\r
47                 button1 = wx.Button(p, -1, label1)\r
48                 p.GetSizer().Add(button1, 0, wx.RIGHT, 8)\r
49                 button2 = wx.Button(p, -1, label2)\r
50                 p.GetSizer().Add(button2, 0)\r
51                 self.GetSizer().Add(p, 0, wx.LEFT, 5)\r
52                 return button1, button2\r
53         \r
54         def AllowNext(self):\r
55                 return True\r
56         \r
57         def StoreData(self):\r
58                 pass\r
59 \r
60 class FirstInfoPage(InfoPage):\r
61         def __init__(self, parent):\r
62                 super(FirstInfoPage, self).__init__(parent, "First time run wizard")\r
63                 self.AddText('Welcome, and thanks for trying Cura!')\r
64                 self.AddSeperator()\r
65                 self.AddText('This wizard will help you with the following steps:')\r
66                 self.AddText('* Configure Cura for your machine')\r
67                 self.AddText('* Upgrade your firmware')\r
68                 self.AddText('* Calibrate your machine')\r
69                 #self.AddText('* Do your first print')\r
70 \r
71 class RepRapInfoPage(InfoPage):\r
72         def __init__(self, parent):\r
73                 super(RepRapInfoPage, self).__init__(parent, "RepRap information")\r
74                 self.AddText('Sorry, but this wizard will not help you with\nconfiguring and calibrating your RepRap.')\r
75                 self.AddSeperator()\r
76                 self.AddText('You will have to manually install Marlin or Sprinter firmware\nand configure Cura.')\r
77 \r
78 class MachineSelectPage(InfoPage):\r
79         def __init__(self, parent):\r
80                 super(MachineSelectPage, self).__init__(parent, "Select your machine")\r
81                 self.AddText('What kind of machine do you have:')\r
82 \r
83                 self.UltimakerRadio = self.AddRadioButton("Ultimaker", style=wx.RB_GROUP)\r
84                 self.UltimakerRadio.SetValue(True)\r
85                 self.UltimakerRadio.Bind(wx.EVT_RADIOBUTTON, self.OnUltimakerSelect)\r
86                 self.OtherRadio = self.AddRadioButton("Other (Ex: RepRap)")\r
87                 self.OtherRadio.Bind(wx.EVT_RADIOBUTTON, self.OnOtherSelect)\r
88                 \r
89         def OnUltimakerSelect(self, e):\r
90                 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().ultimakerFirmwareUpgradePage)\r
91                 \r
92         def OnOtherSelect(self, e):\r
93                 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().repRapInfoPage)\r
94         \r
95         def StoreData(self):\r
96                 if self.UltimakerRadio.GetValue():\r
97                         profile.putPreference('machine_width', '205')\r
98                         profile.putPreference('machine_depth', '205')\r
99                         profile.putPreference('machine_height', '200')\r
100                         profile.putProfileSetting('nozzle_size', '0.4')\r
101                         profile.putProfileSetting('machine_center_x', '100')\r
102                         profile.putProfileSetting('machine_center_y', '100')\r
103                 else:\r
104                         profile.putPreference('machine_width', '80')\r
105                         profile.putPreference('machine_depth', '80')\r
106                         profile.putPreference('machine_height', '60')\r
107                         profile.putProfileSetting('nozzle_size', '0.5')\r
108                         profile.putProfileSetting('machine_center_x', '40')\r
109                         profile.putProfileSetting('machine_center_y', '40')\r
110                 profile.putProfileSetting('wall_thickness', float(profile.getProfileSetting('nozzle_size')) * 2)\r
111 \r
112 class FirmwareUpgradePage(InfoPage):\r
113         def __init__(self, parent):\r
114                 super(FirmwareUpgradePage, self).__init__(parent, "Upgrade Ultimaker Firmware")\r
115                 self.AddText('Firmware is the piece of software running directly on your 3D printer.\nThis firmware controls the step motors, regulates the temperature\nand ultimately makes your printer work.')\r
116                 self.AddHiddenSeperator()\r
117                 self.AddText('The firmware shipping with new Ultimakers works, but upgrades\nhave been made to make better prints, and make calibration easier.')\r
118                 self.AddHiddenSeperator()\r
119                 self.AddText('Cura requires these new features and thus\nyour firmware will most likely need to be upgraded.\nYou will get the chance to do so now.')\r
120                 upgradeButton, skipUpgradeButton = self.AddDualButton('Upgrade to Marlin firmware', 'Skip upgrade')\r
121                 upgradeButton.Bind(wx.EVT_BUTTON, self.OnUpgradeClick)\r
122                 skipUpgradeButton.Bind(wx.EVT_BUTTON, self.OnSkipClick)\r
123                 self.AddHiddenSeperator()\r
124                 self.AddText('Do not upgrade to this firmware if:')\r
125                 self.AddText('* You have an older machine based on ATMega1280')\r
126                 self.AddText('* Using an LCD panel')\r
127                 self.AddText('* Have other changes in the firmware')\r
128                 button = self.AddButton('Goto this page for a custom firmware')\r
129                 button.Bind(wx.EVT_BUTTON, self.OnUrlClick)\r
130         \r
131         def AllowNext(self):\r
132                 return False\r
133         \r
134         def OnUpgradeClick(self, e):\r
135                 if machineCom.InstallFirmware(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../firmware/default.hex")):\r
136                         self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()\r
137                 \r
138         def OnSkipClick(self, e):\r
139                 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()\r
140         \r
141         def OnUrlClick(self, e):\r
142                 webbrowser.open('http://daid.mine.nu/~daid/marlin_build/')\r
143 \r
144 class UltimakerCheckupPage(InfoPage):\r
145         def __init__(self, parent):\r
146                 super(UltimakerCheckupPage, self).__init__(parent, "Ultimaker Checkup")\r
147                 self.AddText('It is a good idea to do a few sanity checks now on your Ultimaker.\nYou can skip these if you know your machine is functional.')\r
148                 b1, b2 = self.AddDualButton('Run checks', 'Skip checks')\r
149                 b1.Bind(wx.EVT_BUTTON, self.OnCheckClick)\r
150                 b2.Bind(wx.EVT_BUTTON, self.OnSkipClick)\r
151                 self.AddSeperator();\r
152                 self.checkPanel = None\r
153         \r
154         def AllowNext(self):\r
155                 return False\r
156         \r
157         def OnSkipClick(self, e):\r
158                 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()\r
159         \r
160         def OnCheckClick(self, e):\r
161                 if self.checkPanel != None:\r
162                         self.checkPanel.Destroy()\r
163                 self.checkPanel = wx.Panel(self)\r
164                 self.checkPanel.SetSizer(wx.BoxSizer(wx.VERTICAL))\r
165                 self.GetSizer().Add(self.checkPanel, 0, wx.LEFT|wx.RIGHT, 5)\r
166                 threading.Thread(target=self.OnRun).start()\r
167 \r
168         def AddProgressText(self, info):\r
169                 text = wx.StaticText(self.checkPanel, -1, info)\r
170                 self.checkPanel.GetSizer().Add(text, 0)\r
171                 self.checkPanel.Layout()\r
172                 self.Layout()\r
173         \r
174         def OnRun(self):\r
175                 wx.CallAfter(self.AddProgressText, "Connecting to machine...")\r
176                 self.comm = machineCom.MachineCom()\r
177 \r
178                 wx.CallAfter(self.AddProgressText, "Checking start message...")\r
179                 if self.DoCommCommandWithTimeout(None, 'start') == False:\r
180                         wx.CallAfter(self.AddProgressText, "Error: Missing start message.")\r
181                         self.comm.close()\r
182                         return\r
183                 \r
184                 #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.\r
185                 time.sleep(3)\r
186                 \r
187                 wx.CallAfter(self.AddProgressText, "Disabling step motors...")\r
188                 if self.DoCommCommandWithTimeout('M84') == False:\r
189                         wx.CallAfter(self.AddProgressText, "Error: Missing reply to Deactivate steppers (M84).")\r
190                         self.comm.close()\r
191                         return\r
192 \r
193                 if self.DoCommCommandWithTimeout("M104 S0") == False:\r
194                         wx.CallAfter(self.AddProgressText, "Failed to set temperature")\r
195                         self.comm.close()\r
196                         return\r
197 \r
198                 wx.MessageBox('Please move the printer head to the center of the machine\nalso move the platform so it is not at the highest or lowest position,\nand make sure the machine is powered on.', 'Machine check', wx.OK | wx.ICON_INFORMATION)\r
199                 \r
200                 idleTemp = self.readTemp()\r
201                 if idleTemp > 40:\r
202                         wx.CallAfter(self.AddProgressText, "Waiting for head to cool down before temperature test...")\r
203                         while idleTemp > 40:\r
204                                 idleTemp = self.readTemp()\r
205                                 time.sleep(1)\r
206                 \r
207                 wx.CallAfter(self.AddProgressText, "Checking heater and temperature sensor...")\r
208                 wx.CallAfter(self.AddProgressText, "(This takes about 30 seconds)")\r
209                 if self.DoCommCommandWithTimeout("M104 S100") == False:\r
210                         wx.CallAfter(self.AddProgressText, "Failed to set temperature")\r
211                         self.comm.close()\r
212                         return\r
213                 \r
214                 time.sleep(25)\r
215                 tempInc = self.readTemp() - idleTemp\r
216                 \r
217                 if self.DoCommCommandWithTimeout("M104 S0") == False:\r
218                         wx.CallAfter(self.AddProgressText, "Failed to set temperature")\r
219                         self.comm.close()\r
220                         return\r
221                 \r
222                 if tempInc < 15:\r
223                         wx.CallAfter(self.AddProgressText, "Your temperature sensor or heater is not working!")\r
224                         self.comm.close()\r
225                         return\r
226                 wx.CallAfter(self.AddProgressText, "Heater and temperature sensor working\nWarning: head might still be hot!")\r
227 \r
228                 wx.CallAfter(self.AddProgressText, "Checking endstops")\r
229                 if self.DoCommCommandWithTimeout('M119', 'x_min') != "x_min:L x_max:L y_min:L y_max:L z_min:L z_max:L":\r
230                         wx.CallAfter(self.AddProgressText, "Error: There is a problem in your endstops!\nOne of them seems to be pressed while it shouldn't\ncheck the cable connections and the switches themselfs.")\r
231                         self.comm.close()\r
232                         return\r
233                 wx.CallAfter(self.AddProgressText, "Please press the X end switch in the front left corner.")\r
234                 if not self.DoCommCommandAndWaitForReply('M119', 'x_min', "x_min:H x_max:L y_min:L y_max:L z_min:L z_max:L"):\r
235                         wx.CallAfter(self.AddProgressText, "Failed to check the x_min endstop!")\r
236                         self.comm.close()\r
237                         return\r
238                 wx.CallAfter(self.AddProgressText, "Please press the X end switch in the front right corner.")\r
239                 if not self.DoCommCommandAndWaitForReply('M119', 'x_min', "x_min:L x_max:H y_min:L y_max:L z_min:L z_max:L"):\r
240                         wx.CallAfter(self.AddProgressText, "Failed to check the x_max endstop!")\r
241                         self.comm.close()\r
242                         return\r
243                 wx.CallAfter(self.AddProgressText, "Please press the Y end switch in the front left corner.")\r
244                 if not self.DoCommCommandAndWaitForReply('M119', 'x_min', "x_min:L x_max:L y_min:H y_max:L z_min:L z_max:L"):\r
245                         wx.CallAfter(self.AddProgressText, "Failed to check the x_max endstop!")\r
246                         self.comm.close()\r
247                         return\r
248                 wx.CallAfter(self.AddProgressText, "Please press the Y end switch in the back left corner.")\r
249                 if not self.DoCommCommandAndWaitForReply('M119', 'x_min', "x_min:L x_max:L y_min:L y_max:H z_min:L z_max:L"):\r
250                         wx.CallAfter(self.AddProgressText, "Failed to check the x_max endstop!")\r
251                         self.comm.close()\r
252                         return\r
253                 wx.CallAfter(self.AddProgressText, "Please press the Z end switch in the top.")\r
254                 if not self.DoCommCommandAndWaitForReply('M119', 'x_min', "x_min:L x_max:L y_min:L y_max:L z_min:H z_max:L"):\r
255                         wx.CallAfter(self.AddProgressText, "Failed to check the x_max endstop!")\r
256                         self.comm.close()\r
257                         return\r
258                 wx.CallAfter(self.AddProgressText, "Please press the Z end switch in the bottom.")\r
259                 if not self.DoCommCommandAndWaitForReply('M119', 'x_min', "x_min:L x_max:L y_min:L y_max:L z_min:L z_max:H"):\r
260                         wx.CallAfter(self.AddProgressText, "Failed to check the x_max endstop!")\r
261                         self.comm.close()\r
262                         return\r
263                 wx.CallAfter(self.AddProgressText, "End stops are working.")\r
264 \r
265                 wx.CallAfter(self.AddProgressText, "Done!")\r
266                 wx.CallAfter(self.GetParent().FindWindowById(wx.ID_FORWARD).Enable)\r
267                 self.comm.close()\r
268                 \r
269         def readTemp(self):\r
270                 line = self.DoCommCommandWithTimeout("M105", "ok T:")\r
271                 if line == False:\r
272                         return -1\r
273                 return int(re.search('T:([0-9]*)', line).group(1))\r
274         \r
275         def DoCommCommandAndWaitForReply(self, cmd, replyStart, reply):\r
276                 while True:\r
277                         ret = self.DoCommCommandWithTimeout(cmd, replyStart)\r
278                         if ret == reply:\r
279                                 return True\r
280                         if ret == False:\r
281                                 return False\r
282                         time.sleep(1)\r
283         \r
284         def DoCommCommandWithTimeout(self, cmd = None, replyStart = 'ok'):\r
285                 if cmd != None:\r
286                         self.comm.sendCommand(cmd)\r
287                 t = threading.Timer(5, self.OnSerialTimeout)\r
288                 t.start()\r
289                 while True:\r
290                         line = self.comm.readline()\r
291                         if line == '':\r
292                                 self.comm.close()\r
293                                 return False\r
294                         print line\r
295                         if line.startswith(replyStart):\r
296                                 break\r
297                 t.cancel()\r
298                 return line.rstrip()\r
299         \r
300         def OnSerialTimeout(self):\r
301                 self.comm.close()\r
302 \r
303 class UltimakerCalibrationPage(InfoPage):\r
304         def __init__(self, parent):\r
305                 super(UltimakerCalibrationPage, self).__init__(parent, "Ultimaker Calibration")\r
306                 \r
307                 self.AddText("Your Ultimaker requires some calibration.");\r
308                 self.AddText("This calibration is needed for a proper extrusion amount.");\r
309                 self.AddSeperator()\r
310                 self.AddText("The following values are needed:");\r
311                 self.AddText("* Diameter of filament");\r
312                 self.AddText("* Number of steps per mm of filament extrusion");\r
313                 self.AddSeperator()\r
314                 self.AddText("The better you have calibrated these values, the better your prints\nwill become.");\r
315                 self.AddSeperator()\r
316                 self.AddText("First we need the diameter of your filament:");\r
317                 self.filamentDiameter = wx.TextCtrl(self, -1, profile.getProfileSetting('filament_diameter'))\r
318                 self.GetSizer().Add(self.filamentDiameter, 0, wx.LEFT, 5)\r
319                 self.AddText("If you do not own digital Calipers that can measure\nat least 2 digits then use 2.89mm.\nWhich is the average diameter of most filament.");\r
320                 self.AddText("Note: This value can be changed later at any time.");\r
321 \r
322         def StoreData(self):\r
323                 profile.putProfileSetting('filament_diameter', self.filamentDiameter.GetValue())\r
324 \r
325 class UltimakerCalibrateStepsPerEPage(InfoPage):\r
326         def __init__(self, parent):\r
327                 super(UltimakerCalibrateStepsPerEPage, self).__init__(parent, "Ultimaker Calibration")\r
328 \r
329                 if profile.getPreference('steps_per_e') == '0':\r
330                         profile.putPreference('steps_per_e', '865.888')\r
331                 \r
332                 self.AddText("Calibrating the Steps Per E requires some manual actions.")\r
333                 self.AddText("First remove any filament from your machine.")\r
334                 self.AddText("Next put in your filament so the tip is aligned with the\ntop of the extruder drive.")\r
335                 self.AddText("We'll push the filament 100mm")\r
336                 self.extrudeButton = self.AddButton("Extrude 100mm filament")\r
337                 self.AddText("Now measure the amount of extruded filament:\n(this can be more or less then 100mm)")\r
338                 p = wx.Panel(self)\r
339                 p.SetSizer(wx.BoxSizer(wx.HORIZONTAL))\r
340                 self.lengthInput = wx.TextCtrl(p, -1, '100')\r
341                 p.GetSizer().Add(self.lengthInput, 0, wx.RIGHT, 8)\r
342                 self.saveLengthButton = wx.Button(p, -1, 'Save')\r
343                 p.GetSizer().Add(self.saveLengthButton, 0)\r
344                 self.GetSizer().Add(p, 0, wx.LEFT, 5)\r
345                 self.AddText("This results in the following steps per E:")\r
346                 self.stepsPerEInput = wx.TextCtrl(self, -1, profile.getPreference('steps_per_e'))\r
347                 self.GetSizer().Add(self.stepsPerEInput, 0, wx.LEFT, 5)\r
348                 self.AddText("You can repeat these steps to get better calibration.")\r
349                 self.AddSeperator()\r
350                 self.AddText("If you still have filament in your printer which needs\nheat to remove, press the heat up button below:")\r
351                 self.heatButton = self.AddButton("Heatup for filament removal")\r
352                 \r
353                 self.saveLengthButton.Bind(wx.EVT_BUTTON, self.OnSaveLengthClick)\r
354                 self.extrudeButton.Bind(wx.EVT_BUTTON, self.OnExtrudeClick)\r
355                 self.heatButton.Bind(wx.EVT_BUTTON, self.OnHeatClick)\r
356         \r
357         def OnSaveLengthClick(self, e):\r
358                 currentEValue = float(self.stepsPerEInput.GetValue())\r
359                 realExtrudeLength = float(self.lengthInput.GetValue())\r
360                 newEValue = currentEValue * 100 / realExtrudeLength\r
361                 self.stepsPerEInput.SetValue(str(newEValue))\r
362                 self.lengthInput.SetValue("100")\r
363         \r
364         def OnExtrudeClick(self, e):\r
365                 threading.Thread(target=self.OnExtrudeRun).start()\r
366 \r
367         def OnExtrudeRun(self):\r
368                 self.heatButton.Enable(False)\r
369                 self.extrudeButton.Enable(False)\r
370                 currentEValue = float(self.stepsPerEInput.GetValue())\r
371                 self.comm = machineCom.MachineCom()\r
372                 while True:\r
373                         line = self.comm.readline()\r
374                         if line == '':\r
375                                 return\r
376                         if line.startswith('start'):\r
377                                 break\r
378                 #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.\r
379                 time.sleep(3)\r
380                 \r
381                 self.sendGCommand('M302') #Disable cold extrusion protection\r
382                 self.sendGCommand("M92 E%f" % (currentEValue));\r
383                 self.sendGCommand("G92 E0");\r
384                 self.sendGCommand("G1 E100 F600");\r
385                 time.sleep(15)\r
386                 self.comm.close()\r
387                 self.extrudeButton.Enable()\r
388                 self.heatButton.Enable()\r
389 \r
390         def OnHeatClick(self, e):\r
391                 threading.Thread(target=self.OnHeatRun).start()\r
392         \r
393         def OnHeatRun(self):\r
394                 self.comm = machineCom.MachineCom()\r
395                 while True:\r
396                         line = self.comm.readline()\r
397                         if line == '':\r
398                                 return\r
399                         if line.startswith('start'):\r
400                                 break\r
401                 #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.\r
402                 time.sleep(3)\r
403                 \r
404                 self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.\r
405                 wx.MessageBox('Wait till you can remove the filament from the machine, and press OK.\n(Temperature is set to 200C)', 'Machine heatup', wx.OK | wx.ICON_INFORMATION)\r
406                 self.sendGCommand('M104 S0')\r
407                 time.sleep(1)\r
408                 self.comm.close()\r
409         \r
410         def sendGCommand(self, cmd):\r
411                 self.comm.sendCommand(cmd) #Disable cold extrusion protection\r
412                 while True:\r
413                         line = self.comm.readline()\r
414                         if line == '':\r
415                                 return\r
416                         if line.startswith('ok'):\r
417                                 break\r
418         \r
419         def StoreData(self):\r
420                 profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())\r
421 \r
422 class configWizard(wx.wizard.Wizard):\r
423         def __init__(self):\r
424                 super(configWizard, self).__init__(None, -1, "Configuration Wizard")\r
425                 \r
426                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)\r
427                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)\r
428 \r
429                 self.firstInfoPage = FirstInfoPage(self)\r
430                 self.machineSelectPage = MachineSelectPage(self)\r
431                 self.ultimakerFirmwareUpgradePage = FirmwareUpgradePage(self)\r
432                 self.ultimakerCheckupPage = UltimakerCheckupPage(self)\r
433                 self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)\r
434                 self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)\r
435                 self.repRapInfoPage = RepRapInfoPage(self)\r
436 \r
437                 wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)\r
438                 wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerFirmwareUpgradePage)\r
439                 wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)\r
440                 wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.ultimakerCalibrationPage)\r
441                 wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)\r
442                 \r
443                 self.FitToPage(self.firstInfoPage)\r
444                 self.GetPageAreaSizer().Add(self.firstInfoPage)\r
445                 \r
446                 self.RunWizard(self.firstInfoPage)\r
447                 self.Destroy()\r
448 \r
449         def OnPageChanging(self, e):\r
450                 e.GetPage().StoreData()\r
451 \r
452         def OnPageChanged(self, e):\r
453                 if e.GetPage().AllowNext():\r
454                         self.FindWindowById(wx.ID_FORWARD).Enable() \r
455                 else:\r
456                         self.FindWindowById(wx.ID_FORWARD).Disable() \r
457                 self.FindWindowById(wx.ID_BACKWARD).Disable() \r