chiark / gitweb /
Update to the bed leveling wizard to allow insertion of filament, and fixing the...
[cura.git] / Cura / gui / configWizard.py
1 # coding=utf-8
2 from __future__ import absolute_import
3
4 import webbrowser
5 import threading
6 import time
7 import math
8
9 import wx
10 import wx.wizard
11
12 from Cura.gui import firmwareInstall
13 from Cura.gui import printWindow
14 from Cura.util import machineCom
15 from Cura.util import profile
16 from Cura.util.resources import getPathForImage
17
18 class InfoBox(wx.Panel):
19         def __init__(self, parent):
20                 super(InfoBox, self).__init__(parent)
21                 self.SetBackgroundColour('#FFFF80')
22
23                 self.sizer = wx.GridBagSizer(5, 5)
24                 self.SetSizer(self.sizer)
25
26                 self.attentionBitmap = wx.Bitmap(getPathForImage('attention.png'))
27                 self.errorBitmap = wx.Bitmap(getPathForImage('error.png'))
28                 self.readyBitmap = wx.Bitmap(getPathForImage('ready.png'))
29                 self.busyBitmap = [
30                         wx.Bitmap(getPathForImage('busy-0.png')),
31                         wx.Bitmap(getPathForImage('busy-1.png')),
32                         wx.Bitmap(getPathForImage('busy-2.png')),
33                         wx.Bitmap(getPathForImage('busy-3.png'))
34                 ]
35
36                 self.bitmap = wx.StaticBitmap(self, -1, wx.EmptyBitmapRGBA(24, 24, red=255, green=255, blue=255, alpha=1))
37                 self.text = wx.StaticText(self, -1, '')
38                 self.extraInfoButton = wx.Button(self, -1, 'i', style=wx.BU_EXACTFIT)
39                 self.sizer.Add(self.bitmap, pos=(0, 0), flag=wx.ALL, border=5)
40                 self.sizer.Add(self.text, pos=(0, 1), flag=wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, border=5)
41                 self.sizer.Add(self.extraInfoButton, pos=(0,2), flag=wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, border=5)
42                 self.sizer.AddGrowableCol(1)
43
44                 self.extraInfoButton.Show(False)
45
46                 self.extraInfoUrl = ''
47                 self.busyState = None
48                 self.timer = wx.Timer(self)
49                 self.Bind(wx.EVT_TIMER, self.doBusyUpdate, self.timer)
50                 self.Bind(wx.EVT_BUTTON, self.doExtraInfo, self.extraInfoButton)
51                 self.timer.Start(100)
52
53         def SetInfo(self, info):
54                 self.SetBackgroundColour('#FFFF80')
55                 self.text.SetLabel(info)
56                 self.extraInfoButton.Show(False)
57                 self.Refresh()
58
59         def SetError(self, info, extraInfoUrl):
60                 self.extraInfoUrl = extraInfoUrl
61                 self.SetBackgroundColour('#FF8080')
62                 self.text.SetLabel(info)
63                 self.extraInfoButton.Show(True)
64                 self.Layout()
65                 self.SetErrorIndicator()
66                 self.Refresh()
67
68         def SetAttention(self, info):
69                 self.SetBackgroundColour('#FFFF80')
70                 self.text.SetLabel(info)
71                 self.extraInfoButton.Show(False)
72                 self.SetAttentionIndicator()
73                 self.Layout()
74                 self.Refresh()
75
76         def SetBusy(self, info):
77                 self.SetInfo(info)
78                 self.SetBusyIndicator()
79
80         def SetBusyIndicator(self):
81                 self.busyState = 0
82                 self.bitmap.SetBitmap(self.busyBitmap[self.busyState])
83
84         def doExtraInfo(self, e):
85                 webbrowser.open(self.extraInfoUrl)
86
87         def doBusyUpdate(self, e):
88                 if self.busyState is None:
89                         return
90                 self.busyState += 1
91                 if self.busyState >= len(self.busyBitmap):
92                         self.busyState = 0
93                 self.bitmap.SetBitmap(self.busyBitmap[self.busyState])
94
95         def SetReadyIndicator(self):
96                 self.busyState = None
97                 self.bitmap.SetBitmap(self.readyBitmap)
98
99         def SetErrorIndicator(self):
100                 self.busyState = None
101                 self.bitmap.SetBitmap(self.errorBitmap)
102
103         def SetAttentionIndicator(self):
104                 self.busyState = None
105                 self.bitmap.SetBitmap(self.attentionBitmap)
106
107
108 class InfoPage(wx.wizard.WizardPageSimple):
109         def __init__(self, parent, title):
110                 wx.wizard.WizardPageSimple.__init__(self, parent)
111
112                 sizer = wx.GridBagSizer(5, 5)
113                 self.sizer = sizer
114                 self.SetSizer(sizer)
115
116                 title = wx.StaticText(self, -1, title)
117                 title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
118                 sizer.Add(title, pos=(0, 0), span=(1, 2), flag=wx.ALIGN_CENTRE | wx.ALL)
119                 sizer.Add(wx.StaticLine(self, -1), pos=(1, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
120                 sizer.AddGrowableCol(1)
121
122                 self.rowNr = 2
123
124         def AddText(self, info):
125                 text = wx.StaticText(self, -1, info)
126                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT)
127                 self.rowNr += 1
128                 return text
129
130         def AddSeperator(self):
131                 self.GetSizer().Add(wx.StaticLine(self, -1), pos=(self.rowNr, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
132                 self.rowNr += 1
133
134         def AddHiddenSeperator(self):
135                 self.AddText('')
136
137         def AddInfoBox(self):
138                 infoBox = InfoBox(self)
139                 self.GetSizer().Add(infoBox, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT | wx.EXPAND)
140                 self.rowNr += 1
141                 return infoBox
142
143         def AddRadioButton(self, label, style=0):
144                 radio = wx.RadioButton(self, -1, label, style=style)
145                 self.GetSizer().Add(radio, pos=(self.rowNr, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
146                 self.rowNr += 1
147                 return radio
148
149         def AddCheckbox(self, label, checked=False):
150                 check = wx.CheckBox(self, -1)
151                 text = wx.StaticText(self, -1, label)
152                 check.SetValue(checked)
153                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT | wx.RIGHT)
154                 self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1, 2), flag=wx.ALL)
155                 self.rowNr += 1
156                 return check
157
158         def AddButton(self, label):
159                 button = wx.Button(self, -1, label)
160                 self.GetSizer().Add(button, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT)
161                 self.rowNr += 1
162                 return button
163
164         def AddDualButton(self, label1, label2):
165                 button1 = wx.Button(self, -1, label1)
166                 self.GetSizer().Add(button1, pos=(self.rowNr, 0), flag=wx.RIGHT)
167                 button2 = wx.Button(self, -1, label2)
168                 self.GetSizer().Add(button2, pos=(self.rowNr, 1))
169                 self.rowNr += 1
170                 return button1, button2
171
172         def AddTextCtrl(self, value):
173                 ret = wx.TextCtrl(self, -1, value)
174                 self.GetSizer().Add(ret, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT)
175                 self.rowNr += 1
176                 return ret
177
178         def AddLabelTextCtrl(self, info, value):
179                 text = wx.StaticText(self, -1, info)
180                 ret = wx.TextCtrl(self, -1, value)
181                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT)
182                 self.GetSizer().Add(ret, pos=(self.rowNr, 1), span=(1, 1), flag=wx.LEFT)
183                 self.rowNr += 1
184                 return ret
185
186         def AddTextCtrlButton(self, value, buttonText):
187                 text = wx.TextCtrl(self, -1, value)
188                 button = wx.Button(self, -1, buttonText)
189                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT)
190                 self.GetSizer().Add(button, pos=(self.rowNr, 1), span=(1, 1), flag=wx.LEFT)
191                 self.rowNr += 1
192                 return text, button
193
194         def AddBitmap(self, bitmap):
195                 bitmap = wx.StaticBitmap(self, -1, bitmap)
196                 self.GetSizer().Add(bitmap, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT)
197                 self.rowNr += 1
198                 return bitmap
199
200         def AddCheckmark(self, label, bitmap):
201                 check = wx.StaticBitmap(self, -1, bitmap)
202                 text = wx.StaticText(self, -1, label)
203                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT | wx.RIGHT)
204                 self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1, 1), flag=wx.ALL)
205                 self.rowNr += 1
206                 return check
207
208         def AllowNext(self):
209                 return True
210
211         def StoreData(self):
212                 pass
213
214
215 class FirstInfoPage(InfoPage):
216         def __init__(self, parent):
217                 super(FirstInfoPage, self).__init__(parent, "First time run wizard")
218                 self.AddText('Welcome, and thanks for trying Cura!')
219                 self.AddSeperator()
220                 self.AddText('This wizard will help you with the following steps:')
221                 self.AddText('* Configure Cura for your machine')
222                 self.AddText('* Upgrade your firmware')
223                 self.AddText('* Check if your machine is working safely')
224                 self.AddText('* Level your printer bed')
225
226                 #self.AddText('* Calibrate your machine')
227                 #self.AddText('* Do your first print')
228
229
230 class RepRapInfoPage(InfoPage):
231         def __init__(self, parent):
232                 super(RepRapInfoPage, self).__init__(parent, "RepRap information")
233                 self.AddText(
234                         'RepRap machines are vastly different, and there is no\ndefault configuration in Cura for any of them.')
235                 self.AddText('If you like a default profile for your machine added,\nthen make an issue on github.')
236                 self.AddSeperator()
237                 self.AddText('You will have to manually install Marlin or Sprinter firmware.')
238                 self.AddSeperator()
239                 self.machineWidth = self.AddLabelTextCtrl('Machine width (mm)', '80')
240                 self.machineDepth = self.AddLabelTextCtrl('Machine depth (mm)', '80')
241                 self.machineHeight = self.AddLabelTextCtrl('Machine height (mm)', '60')
242                 self.nozzleSize = self.AddLabelTextCtrl('Nozzle size (mm)', '0.5')
243                 self.heatedBed = self.AddCheckbox('Heated bed')
244                 self.HomeAtCenter = self.AddCheckbox('Bed center is 0,0,0 (RoStock)')
245
246         def StoreData(self):
247                 profile.putPreference('machine_width', self.machineWidth.GetValue())
248                 profile.putPreference('machine_depth', self.machineDepth.GetValue())
249                 profile.putPreference('machine_height', self.machineHeight.GetValue())
250                 profile.putProfileSetting('nozzle_size', self.nozzleSize.GetValue())
251                 profile.putProfileSetting('wall_thickness', float(profile.getProfileSettingFloat('nozzle_size')) * 2)
252                 profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))
253                 profile.putPreference('machine_center_is_zero', str(self.HomeAtCenter.GetValue()))
254                 profile.putPreference('extruder_head_size_min_x', '0')
255                 profile.putPreference('extruder_head_size_min_y', '0')
256                 profile.putPreference('extruder_head_size_max_x', '0')
257                 profile.putPreference('extruder_head_size_max_y', '0')
258                 profile.putPreference('extruder_head_size_height', '0')
259
260
261 class MachineSelectPage(InfoPage):
262         def __init__(self, parent):
263                 super(MachineSelectPage, self).__init__(parent, "Select your machine")
264                 self.AddText('What kind of machine do you have:')
265
266                 self.UltimakerRadio = self.AddRadioButton("Ultimaker", style=wx.RB_GROUP)
267                 self.UltimakerRadio.SetValue(True)
268                 self.UltimakerRadio.Bind(wx.EVT_RADIOBUTTON, self.OnUltimakerSelect)
269                 self.OtherRadio = self.AddRadioButton("Other (Ex: RepRap)")
270                 self.OtherRadio.Bind(wx.EVT_RADIOBUTTON, self.OnOtherSelect)
271                 self.AddSeperator()
272                 self.AddText('The collection of anonymous usage information helps with the continued improvement of Cura.')
273                 self.AddText('This does NOT submit your models online nor gathers any privacy related information.')
274                 self.SubmitUserStats = self.AddCheckbox('Submit anonymous usage information:')
275                 self.AddText('For full details see: http://wiki.ultimaker.com/Cura:stats')
276                 self.SubmitUserStats.SetValue(True)
277
278         def OnUltimakerSelect(self, e):
279                 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().ultimakerFirmwareUpgradePage)
280
281         def OnOtherSelect(self, e):
282                 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().repRapInfoPage)
283
284         def StoreData(self):
285                 if self.UltimakerRadio.GetValue():
286                         profile.putPreference('machine_width', '205')
287                         profile.putPreference('machine_depth', '205')
288                         profile.putPreference('machine_height', '200')
289                         profile.putPreference('machine_type', 'ultimaker')
290                         profile.putPreference('machine_center_is_zero', 'False')
291                         profile.putProfileSetting('nozzle_size', '0.4')
292                         profile.putPreference('extruder_head_size_min_x', '75.0')
293                         profile.putPreference('extruder_head_size_min_y', '18.0')
294                         profile.putPreference('extruder_head_size_max_x', '18.0')
295                         profile.putPreference('extruder_head_size_max_y', '35.0')
296                         profile.putPreference('extruder_head_size_height', '60.0')
297                 else:
298                         profile.putPreference('machine_width', '80')
299                         profile.putPreference('machine_depth', '80')
300                         profile.putPreference('machine_height', '60')
301                         profile.putPreference('machine_type', 'reprap')
302                         profile.putPreference('startMode', 'Normal')
303                         profile.putProfileSetting('nozzle_size', '0.5')
304                 profile.putProfileSetting('wall_thickness', float(profile.getProfileSetting('nozzle_size')) * 2)
305                 if self.SubmitUserStats.GetValue():
306                         profile.putPreference('submit_slice_information', 'True')
307                 else:
308                         profile.putPreference('submit_slice_information', 'False')
309
310 class SelectParts(InfoPage):
311         def __init__(self, parent):
312                 super(SelectParts, self).__init__(parent, "Select upgraded parts you have")
313                 self.AddText('To assist you in having better default settings for your Ultimaker\nCura would like to know which upgrades you have in your machine.')
314                 self.AddSeperator()
315                 self.springExtruder = self.AddCheckbox('Extruder drive upgrade')
316                 self.heatedBed = self.AddCheckbox('Heated printer bed (self built)')
317                 self.dualExtrusion = self.AddCheckbox('Dual extrusion (experimental)')
318                 self.AddSeperator()
319                 self.AddText('If you have an Ultimaker bought after october 2012 you will have the\nExtruder drive upgrade. If you do not have this upgrade,\nit is highly recommended to improve reliability.')
320                 self.AddText('This upgrade can be bought from the Ultimaker webshop\nor found on thingiverse as thing:26094')
321                 self.springExtruder.SetValue(True)
322
323         def StoreData(self):
324                 profile.putPreference('ultimaker_extruder_upgrade', str(self.springExtruder.GetValue()))
325                 profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))
326                 if self.dualExtrusion.GetValue():
327                         profile.putPreference('extruder_amount', '2')
328                 else:
329                         profile.putPreference('extruder_amount', '1')
330                 if profile.getPreference('ultimaker_extruder_upgrade') == 'True':
331                         profile.putProfileSetting('retraction_enable', 'True')
332                 else:
333                         profile.putProfileSetting('retraction_enable', 'False')
334
335
336 class FirmwareUpgradePage(InfoPage):
337         def __init__(self, parent):
338                 super(FirmwareUpgradePage, self).__init__(parent, "Upgrade Ultimaker Firmware")
339                 self.AddText(
340                         '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.')
341                 self.AddHiddenSeperator()
342                 self.AddText(
343                         'The firmware shipping with new Ultimakers works, but upgrades\nhave been made to make better prints, and make calibration easier.')
344                 self.AddHiddenSeperator()
345                 self.AddText(
346                         '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.')
347                 upgradeButton, skipUpgradeButton = self.AddDualButton('Upgrade to Marlin firmware', 'Skip upgrade')
348                 upgradeButton.Bind(wx.EVT_BUTTON, self.OnUpgradeClick)
349                 skipUpgradeButton.Bind(wx.EVT_BUTTON, self.OnSkipClick)
350                 self.AddHiddenSeperator()
351                 self.AddText('Do not upgrade to this firmware if:')
352                 self.AddText('* You have an older machine based on ATMega1280')
353                 self.AddText('* Have other changes in the firmware')
354 #               button = self.AddButton('Goto this page for a custom firmware')
355 #               button.Bind(wx.EVT_BUTTON, self.OnUrlClick)
356
357         def AllowNext(self):
358                 return False
359
360         def OnUpgradeClick(self, e):
361                 if firmwareInstall.InstallFirmware():
362                         self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
363
364         def OnSkipClick(self, e):
365                 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
366                 self.GetParent().ShowPage(self.GetNext())
367
368         def OnUrlClick(self, e):
369                 webbrowser.open('http://daid.mine.nu/~daid/marlin_build/')
370
371
372 class UltimakerCheckupPage(InfoPage):
373         def __init__(self, parent):
374                 super(UltimakerCheckupPage, self).__init__(parent, "Ultimaker Checkup")
375
376                 self.checkBitmap = wx.Bitmap(getPathForImage('checkmark.png'))
377                 self.crossBitmap = wx.Bitmap(getPathForImage('cross.png'))
378                 self.unknownBitmap = wx.Bitmap(getPathForImage('question.png'))
379                 self.endStopNoneBitmap = wx.Bitmap(getPathForImage('endstop_none.png'))
380                 self.endStopXMinBitmap = wx.Bitmap(getPathForImage('endstop_xmin.png'))
381                 self.endStopXMaxBitmap = wx.Bitmap(getPathForImage('endstop_xmax.png'))
382                 self.endStopYMinBitmap = wx.Bitmap(getPathForImage('endstop_ymin.png'))
383                 self.endStopYMaxBitmap = wx.Bitmap(getPathForImage('endstop_ymax.png'))
384                 self.endStopZMinBitmap = wx.Bitmap(getPathForImage('endstop_zmin.png'))
385                 self.endStopZMaxBitmap = wx.Bitmap(getPathForImage('endstop_zmax.png'))
386
387                 self.AddText(
388                         '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.')
389                 b1, b2 = self.AddDualButton('Run checks', 'Skip checks')
390                 b1.Bind(wx.EVT_BUTTON, self.OnCheckClick)
391                 b2.Bind(wx.EVT_BUTTON, self.OnSkipClick)
392                 self.AddSeperator()
393                 self.commState = self.AddCheckmark('Communication:', self.unknownBitmap)
394                 self.tempState = self.AddCheckmark('Temperature:', self.unknownBitmap)
395                 self.stopState = self.AddCheckmark('Endstops:', self.unknownBitmap)
396                 self.AddSeperator()
397                 self.infoBox = self.AddInfoBox()
398                 self.machineState = self.AddText('')
399                 self.temperatureLabel = self.AddText('')
400                 self.errorLogButton = self.AddButton('Show error log')
401                 self.errorLogButton.Show(False)
402                 self.AddSeperator()
403                 self.endstopBitmap = self.AddBitmap(self.endStopNoneBitmap)
404                 self.comm = None
405                 self.xMinStop = False
406                 self.xMaxStop = False
407                 self.yMinStop = False
408                 self.yMaxStop = False
409                 self.zMinStop = False
410                 self.zMaxStop = False
411
412                 self.Bind(wx.EVT_BUTTON, self.OnErrorLog, self.errorLogButton)
413
414         def __del__(self):
415                 if self.comm is not None:
416                         self.comm.close()
417
418         def AllowNext(self):
419                 self.endstopBitmap.Show(False)
420                 return False
421
422         def OnSkipClick(self, e):
423                 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
424                 self.GetParent().ShowPage(self.GetNext())
425
426         def OnCheckClick(self, e=None):
427                 self.errorLogButton.Show(False)
428                 if self.comm is not None:
429                         self.comm.close()
430                         del self.comm
431                         self.comm = None
432                         wx.CallAfter(self.OnCheckClick)
433                         return
434                 self.infoBox.SetBusy('Connecting to machine.')
435                 self.commState.SetBitmap(self.unknownBitmap)
436                 self.tempState.SetBitmap(self.unknownBitmap)
437                 self.stopState.SetBitmap(self.unknownBitmap)
438                 self.checkupState = 0
439                 self.comm = machineCom.MachineCom(callbackObject=self)
440
441         def OnErrorLog(self, e):
442                 printWindow.LogWindow('\n'.join(self.comm.getLog()))
443
444         def mcLog(self, message):
445                 pass
446
447         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
448                 if not self.comm.isOperational():
449                         return
450                 if self.checkupState == 0:
451                         self.tempCheckTimeout = 20
452                         if temp > 70:
453                                 self.checkupState = 1
454                                 wx.CallAfter(self.infoBox.SetInfo, 'Cooldown before temperature check.')
455                                 self.comm.sendCommand('M104 S0')
456                                 self.comm.sendCommand('M104 S0')
457                         else:
458                                 self.startTemp = temp
459                                 self.checkupState = 2
460                                 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
461                                 self.comm.sendCommand('M104 S200')
462                                 self.comm.sendCommand('M104 S200')
463                 elif self.checkupState == 1:
464                         if temp < 60:
465                                 self.startTemp = temp
466                                 self.checkupState = 2
467                                 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
468                                 self.comm.sendCommand('M104 S200')
469                                 self.comm.sendCommand('M104 S200')
470                 elif self.checkupState == 2:
471                         #print "WARNING, TEMPERATURE TEST DISABLED FOR TESTING!"
472                         if temp > self.startTemp + 40:
473                                 self.checkupState = 3
474                                 wx.CallAfter(self.infoBox.SetAttention, 'Please make sure none of the endstops are pressed.')
475                                 wx.CallAfter(self.endstopBitmap.Show, True)
476                                 wx.CallAfter(self.Layout)
477                                 self.comm.sendCommand('M104 S0')
478                                 self.comm.sendCommand('M104 S0')
479                                 self.comm.sendCommand('M119')
480                                 wx.CallAfter(self.tempState.SetBitmap, self.checkBitmap)
481                         else:
482                                 self.tempCheckTimeout -= 1
483                                 if self.tempCheckTimeout < 1:
484                                         self.checkupState = -1
485                                         wx.CallAfter(self.tempState.SetBitmap, self.crossBitmap)
486                                         wx.CallAfter(self.infoBox.SetError, 'Temperature measurement FAILED!', 'http://wiki.ultimaker.com/Cura:_Temperature_measurement_problems')
487                                         self.comm.sendCommand('M104 S0')
488                                         self.comm.sendCommand('M104 S0')
489                 elif self.checkupState >= 3 and self.checkupState < 10:
490                         self.comm.sendCommand('M119')
491                 wx.CallAfter(self.temperatureLabel.SetLabel, 'Head temperature: %d' % (temp))
492
493         def mcStateChange(self, state):
494                 if self.comm is None:
495                         return
496                 if self.comm.isOperational():
497                         wx.CallAfter(self.commState.SetBitmap, self.checkBitmap)
498                         wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
499                 elif self.comm.isError():
500                         wx.CallAfter(self.commState.SetBitmap, self.crossBitmap)
501                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
502                         wx.CallAfter(self.endstopBitmap.Show, False)
503                         wx.CallAfter(self.machineState.SetLabel, '%s' % (self.comm.getErrorString()))
504                         wx.CallAfter(self.errorLogButton.Show, True)
505                         wx.CallAfter(self.Layout)
506                 else:
507                         wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
508
509         def mcMessage(self, message):
510                 if self.checkupState >= 3 and self.checkupState < 10 and ('_min' in message or '_max' in message):
511                         for data in message.split(' '):
512                                 if ':' in data:
513                                         tag, value = data.split(':', 1)
514                                         if tag == 'x_min':
515                                                 self.xMinStop = (value == 'H' or value == 'TRIGGERED')
516                                         if tag == 'x_max':
517                                                 self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
518                                         if tag == 'y_min':
519                                                 self.yMinStop = (value == 'H' or value == 'TRIGGERED')
520                                         if tag == 'y_max':
521                                                 self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
522                                         if tag == 'z_min':
523                                                 self.zMinStop = (value == 'H' or value == 'TRIGGERED')
524                                         if tag == 'z_max':
525                                                 self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
526                         if ':' in message:
527                                 tag, value = map(str.strip, message.split(':', 1))
528                                 if tag == 'x_min':
529                                         self.xMinStop = (value == 'H' or value == 'TRIGGERED')
530                                 if tag == 'x_max':
531                                         self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
532                                 if tag == 'y_min':
533                                         self.yMinStop = (value == 'H' or value == 'TRIGGERED')
534                                 if tag == 'y_max':
535                                         self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
536                                 if tag == 'z_min':
537                                         self.zMinStop = (value == 'H' or value == 'TRIGGERED')
538                                 if tag == 'z_max':
539                                         self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
540                         if 'z_max' in message:
541                                 self.comm.sendCommand('M119')
542
543                         if self.checkupState == 3:
544                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
545                                         self.checkupState = 4
546                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the right X endstop.')
547                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMaxBitmap)
548                         elif self.checkupState == 4:
549                                 if not self.xMinStop and self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
550                                         self.checkupState = 5
551                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the left X endstop.')
552                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMinBitmap)
553                         elif self.checkupState == 5:
554                                 if self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
555                                         self.checkupState = 6
556                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the front Y endstop.')
557                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMinBitmap)
558                         elif self.checkupState == 6:
559                                 if not self.xMinStop and not self.xMaxStop and self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
560                                         self.checkupState = 7
561                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the back Y endstop.')
562                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMaxBitmap)
563                         elif self.checkupState == 7:
564                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and self.yMaxStop and not self.zMinStop and not self.zMaxStop:
565                                         self.checkupState = 8
566                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the top Z endstop.')
567                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMinBitmap)
568                         elif self.checkupState == 8:
569                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and self.zMinStop and not self.zMaxStop:
570                                         self.checkupState = 9
571                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the bottom Z endstop.')
572                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMaxBitmap)
573                         elif self.checkupState == 9:
574                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and self.zMaxStop:
575                                         self.checkupState = 10
576                                         self.comm.close()
577                                         wx.CallAfter(self.infoBox.SetInfo, 'Checkup finished')
578                                         wx.CallAfter(self.infoBox.SetReadyIndicator)
579                                         wx.CallAfter(self.endstopBitmap.Show, False)
580                                         wx.CallAfter(self.stopState.SetBitmap, self.checkBitmap)
581                                         wx.CallAfter(self.OnSkipClick, None)
582
583         def mcProgress(self, lineNr):
584                 pass
585
586         def mcZChange(self, newZ):
587                 pass
588
589
590 class UltimakerCalibrationPage(InfoPage):
591         def __init__(self, parent):
592                 super(UltimakerCalibrationPage, self).__init__(parent, "Ultimaker Calibration")
593
594                 self.AddText("Your Ultimaker requires some calibration.")
595                 self.AddText("This calibration is needed for a proper extrusion amount.")
596                 self.AddSeperator()
597                 self.AddText("The following values are needed:")
598                 self.AddText("* Diameter of filament")
599                 self.AddText("* Number of steps per mm of filament extrusion")
600                 self.AddSeperator()
601                 self.AddText("The better you have calibrated these values, the better your prints\nwill become.")
602                 self.AddSeperator()
603                 self.AddText("First we need the diameter of your filament:")
604                 self.filamentDiameter = self.AddTextCtrl(profile.getProfileSetting('filament_diameter'))
605                 self.AddText(
606                         "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.")
607                 self.AddText("Note: This value can be changed later at any time.")
608
609         def StoreData(self):
610                 profile.putProfileSetting('filament_diameter', self.filamentDiameter.GetValue())
611
612
613 class UltimakerCalibrateStepsPerEPage(InfoPage):
614         def __init__(self, parent):
615                 super(UltimakerCalibrateStepsPerEPage, self).__init__(parent, "Ultimaker Calibration")
616
617                 #if profile.getPreference('steps_per_e') == '0':
618                 #       profile.putPreference('steps_per_e', '865.888')
619
620                 self.AddText("Calibrating the Steps Per E requires some manual actions.")
621                 self.AddText("First remove any filament from your machine.")
622                 self.AddText("Next put in your filament so the tip is aligned with the\ntop of the extruder drive.")
623                 self.AddText("We'll push the filament 100mm")
624                 self.extrudeButton = self.AddButton("Extrude 100mm filament")
625                 self.AddText("Now measure the amount of extruded filament:\n(this can be more or less then 100mm)")
626                 self.lengthInput, self.saveLengthButton = self.AddTextCtrlButton('100', 'Save')
627                 self.AddText("This results in the following steps per E:")
628                 self.stepsPerEInput = self.AddTextCtrl(profile.getPreference('steps_per_e'))
629                 self.AddText("You can repeat these steps to get better calibration.")
630                 self.AddSeperator()
631                 self.AddText(
632                         "If you still have filament in your printer which needs\nheat to remove, press the heat up button below:")
633                 self.heatButton = self.AddButton("Heatup for filament removal")
634
635                 self.saveLengthButton.Bind(wx.EVT_BUTTON, self.OnSaveLengthClick)
636                 self.extrudeButton.Bind(wx.EVT_BUTTON, self.OnExtrudeClick)
637                 self.heatButton.Bind(wx.EVT_BUTTON, self.OnHeatClick)
638
639         def OnSaveLengthClick(self, e):
640                 currentEValue = float(self.stepsPerEInput.GetValue())
641                 realExtrudeLength = float(self.lengthInput.GetValue())
642                 newEValue = currentEValue * 100 / realExtrudeLength
643                 self.stepsPerEInput.SetValue(str(newEValue))
644                 self.lengthInput.SetValue("100")
645
646         def OnExtrudeClick(self, e):
647                 threading.Thread(target=self.OnExtrudeRun).start()
648
649         def OnExtrudeRun(self):
650                 self.heatButton.Enable(False)
651                 self.extrudeButton.Enable(False)
652                 currentEValue = float(self.stepsPerEInput.GetValue())
653                 self.comm = machineCom.MachineCom()
654                 if not self.comm.isOpen():
655                         wx.MessageBox(
656                                 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
657                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
658                         self.heatButton.Enable(True)
659                         self.extrudeButton.Enable(True)
660                         return
661                 while True:
662                         line = self.comm.readline()
663                         if line == '':
664                                 return
665                         if 'start' in line:
666                                 break
667                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
668                 time.sleep(3)
669
670                 self.sendGCommand('M302') #Disable cold extrusion protection
671                 self.sendGCommand("M92 E%f" % (currentEValue))
672                 self.sendGCommand("G92 E0")
673                 self.sendGCommand("G1 E100 F600")
674                 time.sleep(15)
675                 self.comm.close()
676                 self.extrudeButton.Enable()
677                 self.heatButton.Enable()
678
679         def OnHeatClick(self, e):
680                 threading.Thread(target=self.OnHeatRun).start()
681
682         def OnHeatRun(self):
683                 self.heatButton.Enable(False)
684                 self.extrudeButton.Enable(False)
685                 self.comm = machineCom.MachineCom()
686                 if not self.comm.isOpen():
687                         wx.MessageBox(
688                                 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
689                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
690                         self.heatButton.Enable(True)
691                         self.extrudeButton.Enable(True)
692                         return
693                 while True:
694                         line = self.comm.readline()
695                         if line == '':
696                                 self.heatButton.Enable(True)
697                                 self.extrudeButton.Enable(True)
698                                 return
699                         if 'start' in line:
700                                 break
701                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
702                 time.sleep(3)
703
704                 self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.
705                 wx.MessageBox(
706                         'Wait till you can remove the filament from the machine, and press OK.\n(Temperature is set to 200C)',
707                         'Machine heatup', wx.OK | wx.ICON_INFORMATION)
708                 self.sendGCommand('M104 S0')
709                 time.sleep(1)
710                 self.comm.close()
711                 self.heatButton.Enable(True)
712                 self.extrudeButton.Enable(True)
713
714         def sendGCommand(self, cmd):
715                 self.comm.sendCommand(cmd) #Disable cold extrusion protection
716                 while True:
717                         line = self.comm.readline()
718                         if line == '':
719                                 return
720                         if line.startswith('ok'):
721                                 break
722
723         def StoreData(self):
724                 profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())
725
726
727 class configWizard(wx.wizard.Wizard):
728         def __init__(self):
729                 super(configWizard, self).__init__(None, -1, "Configuration Wizard")
730
731                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
732                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
733
734                 self.firstInfoPage = FirstInfoPage(self)
735                 self.machineSelectPage = MachineSelectPage(self)
736                 self.ultimakerSelectParts = SelectParts(self)
737                 self.ultimakerFirmwareUpgradePage = FirmwareUpgradePage(self)
738                 self.ultimakerCheckupPage = UltimakerCheckupPage(self)
739                 self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)
740                 self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)
741                 self.bedLevelPage = bedLevelWizardMain(self)
742                 self.repRapInfoPage = RepRapInfoPage(self)
743
744                 wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)
745                 wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerSelectParts)
746                 wx.wizard.WizardPageSimple.Chain(self.ultimakerSelectParts, self.ultimakerFirmwareUpgradePage)
747                 wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)
748                 wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.bedLevelPage)
749                 #wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)
750
751                 self.FitToPage(self.firstInfoPage)
752                 self.GetPageAreaSizer().Add(self.firstInfoPage)
753
754                 self.RunWizard(self.firstInfoPage)
755                 self.Destroy()
756
757         def OnPageChanging(self, e):
758                 e.GetPage().StoreData()
759
760         def OnPageChanged(self, e):
761                 if e.GetPage().AllowNext():
762                         self.FindWindowById(wx.ID_FORWARD).Enable()
763                 else:
764                         self.FindWindowById(wx.ID_FORWARD).Disable()
765                 self.FindWindowById(wx.ID_BACKWARD).Disable()
766
767 class bedLevelWizardMain(InfoPage):
768         def __init__(self, parent):
769                 super(bedLevelWizardMain, self).__init__(parent, "Bed leveling wizard")
770
771                 self.AddText('This wizard will help you in leveling your printer bed')
772                 self.AddSeperator()
773                 self.AddText('It will do the following steps')
774                 self.AddText('* Move the printer head to each corner')
775                 self.AddText('  and let you adjust the height of the bed to the nozzle')
776                 self.AddText('* Print a line around the bed to check if it is level')
777                 self.AddSeperator()
778
779                 self.connectButton = self.AddButton('Connect to printer')
780                 self.comm = None
781
782                 self.infoBox = self.AddInfoBox()
783                 self.resumeButton = self.AddButton('Resume')
784                 self.resumeButton.Enable(False)
785
786                 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
787                 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
788
789         def OnConnect(self, e = None):
790                 if self.comm is not None:
791                         self.comm.close()
792                         del self.comm
793                         self.comm = None
794                         wx.CallAfter(self.OnConnect)
795                         return
796                 self.connectButton.Enable(False)
797                 self.comm = machineCom.MachineCom(callbackObject=self)
798                 self.infoBox.SetBusy('Connecting to machine.')
799                 self._wizardState = 0
800
801         def AllowNext(self):
802                 return True
803
804         def OnResume(self, e):
805                 feedZ = profile.getProfileSettingFloat('print_speed') * 60
806                 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
807                 if self._wizardState == 2:
808                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back left corner...')
809                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
810                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, profile.getPreferenceFloat('machine_depth'), feedTravel))
811                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
812                         self.comm.sendCommand('M400')
813                         self._wizardState = 3
814                 elif self._wizardState == 4:
815                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back right corner...')
816                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
817                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width') - 5.0, profile.getPreferenceFloat('machine_depth') - 25, feedTravel))
818                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
819                         self.comm.sendCommand('M400')
820                         self._wizardState = 5
821                 elif self._wizardState == 6:
822                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to front right corner...')
823                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
824                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width') - 5.0, 20, feedTravel))
825                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
826                         self.comm.sendCommand('M400')
827                         self._wizardState = 7
828                 elif self._wizardState == 8:
829                         wx.CallAfter(self.infoBox.SetBusy, 'Heating up printer...')
830                         self.comm.sendCommand('G1 Z15 F%d' % (feedZ))
831                         self.comm.sendCommand('M104 S%d' % (profile.getProfileSettingFloat('print_temperature')))
832                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, 0, feedTravel))
833                         self._wizardState = 9
834                 elif self._wizardState == 10:
835                         self._wizardState = 11
836                         wx.CallAfter(self.infoBox.SetInfo, 'Printing a square on the printer bed at 0.3mm height.')
837                         feedZ = profile.getProfileSettingFloat('print_speed') * 60
838                         feedPrint = profile.getProfileSettingFloat('print_speed') * 60
839                         feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
840                         w = profile.getPreferenceFloat('machine_width')
841                         d = profile.getPreferenceFloat('machine_depth')
842                         filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
843                         filamentArea = math.pi * filamentRadius * filamentRadius
844                         ePerMM = (profile.calculateEdgeWidth() * 0.3) / filamentArea
845                         eValue = 0.0
846
847                         gcodeList = [
848                                 'G1 Z2 F%d' % (feedZ),
849                                 'G92 E0',
850                                 'G1 X%d Y%d F%d' % (5, 5, feedTravel),
851                                 'G1 Z0.3 F%d' % (feedZ)]
852                         eValue += 5.0
853                         gcodeList.append('G1 E%f F%d' % (eValue, profile.getProfileSettingFloat('retraction_speed') * 60))
854
855                         for i in xrange(0, 3):
856                                 dist = 5.0 + 0.4 * float(i)
857                                 eValue += (d - 2.0*dist) * ePerMM
858                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (dist, d - dist, eValue, feedPrint))
859                                 eValue += (w - 2.0*dist) * ePerMM
860                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (w - dist, d - dist, eValue, feedPrint))
861                                 eValue += (d - 2.0*dist) * ePerMM
862                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (w - dist, dist, eValue, feedPrint))
863                                 eValue += (w - 2.0*dist) * ePerMM
864                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (dist, dist, eValue, feedPrint))
865
866                         gcodeList.append('M400')
867                         self.comm.printGCode(gcodeList)
868                 self.resumeButton.Enable(False)
869
870         def mcLog(self, message):
871                 print 'Log:', message
872
873         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
874                 if self._wizardState == 1:
875                         self._wizardState = 2
876                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front left screw of your printer bed\nSo the nozzle just hits the bed.')
877                         wx.CallAfter(self.resumeButton.Enable, True)
878                 elif self._wizardState == 3:
879                         self._wizardState = 4
880                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back left screw of your printer bed\nSo the nozzle just hits the bed.')
881                         wx.CallAfter(self.resumeButton.Enable, True)
882                 elif self._wizardState == 5:
883                         self._wizardState = 6
884                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back right screw of your printer bed\nSo the nozzle just hits the bed.')
885                         wx.CallAfter(self.resumeButton.Enable, True)
886                 elif self._wizardState == 7:
887                         self._wizardState = 8
888                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front right screw of your printer bed\nSo the nozzle just hits the bed.')
889                         wx.CallAfter(self.resumeButton.Enable, True)
890                 elif self._wizardState == 9:
891                         if temp < profile.getProfileSettingFloat('print_temperature') - 5:
892                                 wx.CallAfter(self.infoBox.SetInfo, 'Heating up printer: %d/%d' % (temp, profile.getProfileSettingFloat('print_temperature')))
893                         else:
894                                 wx.CallAfter(self.infoBox.SetAttention, 'The printer is hot now. Please insert some PLA filament into the printer.')
895                                 wx.CallAfter(self.resumeButton.Enable, True)
896                                 self._wizardState = 10
897
898         def mcStateChange(self, state):
899                 if self.comm is None:
900                         return
901                 if self.comm.isOperational():
902                         if self._wizardState == 0:
903                                 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer...')
904                                 self.comm.sendCommand('M105')
905                                 self.comm.sendCommand('G28')
906                                 self._wizardState = 1
907                         elif self._wizardState == 11 and not self.comm.isPrinting():
908                                 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('print_speed') * 60))
909                                 self.comm.sendCommand('G92 E0')
910                                 self.comm.sendCommand('G1 E-10 F%d' % (profile.getProfileSettingFloat('retraction_speed') * 60))
911                                 self.comm.sendCommand('M104 S0')
912                                 wx.CallAfter(self.infoBox.SetInfo, 'Calibration finished.\nThe squares on the bed should slightly touch each other.')
913                                 wx.CallAfter(self.infoBox.SetReadyIndicator)
914                                 wx.CallAfter(self.GetParent().FindWindowById(wx.ID_FORWARD).Enable)
915                                 wx.CallAfter(self.connectButton.Enable, True)
916                                 self._wizardState = 12
917                 elif self.comm.isError():
918                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
919
920         def mcMessage(self, message):
921                 pass
922
923         def mcProgress(self, lineNr):
924                 pass
925
926         def mcZChange(self, newZ):
927                 pass
928
929 class bedLevelWizard(wx.wizard.Wizard):
930         def __init__(self):
931                 super(bedLevelWizard, self).__init__(None, -1, "Bed leveling wizard")
932
933                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
934                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
935
936                 self.mainPage = bedLevelWizardMain(self)
937
938                 self.FitToPage(self.mainPage)
939                 self.GetPageAreaSizer().Add(self.mainPage)
940
941                 self.RunWizard(self.mainPage)
942                 self.Destroy()
943
944         def OnPageChanging(self, e):
945                 e.GetPage().StoreData()
946
947         def OnPageChanged(self, e):
948                 if e.GetPage().AllowNext():
949                         self.FindWindowById(wx.ID_FORWARD).Enable()
950                 else:
951                         self.FindWindowById(wx.ID_FORWARD).Disable()
952                 self.FindWindowById(wx.ID_BACKWARD).Disable()