chiark / gitweb /
Check all the hotends in the first-run-wizard for dual-extrusion.
[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[self.checkExtruderNr] > 70:
453                                 self.checkupState = 1
454                                 wx.CallAfter(self.infoBox.SetInfo, 'Cooldown before temperature check.')
455                                 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
456                                 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
457                         else:
458                                 self.startTemp = temp[self.checkExtruderNr]
459                                 self.checkupState = 2
460                                 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
461                                 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
462                                 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
463                 elif self.checkupState == 1:
464                         if temp < 60:
465                                 self.startTemp = temp[self.checkExtruderNr]
466                                 self.checkupState = 2
467                                 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
468                                 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
469                                 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
470                 elif self.checkupState == 2:
471                         #print "WARNING, TEMPERATURE TEST DISABLED FOR TESTING!"
472                         if temp[self.checkExtruderNr] > self.startTemp + 40:
473                                 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
474                                 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
475                                 if self.checkExtruderNr < int(profile.getPreference('extruder_amount')):
476                                         self.checkExtruderNr = 0
477                                         self.checkupState = 3
478                                         wx.CallAfter(self.infoBox.SetAttention, 'Please make sure none of the endstops are pressed.')
479                                         wx.CallAfter(self.endstopBitmap.Show, True)
480                                         wx.CallAfter(self.Layout)
481                                         self.comm.sendCommand('M119')
482                                         wx.CallAfter(self.tempState.SetBitmap, self.checkBitmap)
483                                 else:
484                                         self.checkupState = 0
485                                         self.checkExtruderNr += 1
486                         else:
487                                 self.tempCheckTimeout -= 1
488                                 if self.tempCheckTimeout < 1:
489                                         self.checkupState = -1
490                                         wx.CallAfter(self.tempState.SetBitmap, self.crossBitmap)
491                                         wx.CallAfter(self.infoBox.SetError, 'Temperature measurement FAILED!', 'http://wiki.ultimaker.com/Cura:_Temperature_measurement_problems')
492                                         self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
493                                         self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
494                 elif self.checkupState >= 3 and self.checkupState < 10:
495                         self.comm.sendCommand('M119')
496                 wx.CallAfter(self.temperatureLabel.SetLabel, 'Head temperature: %d' % (temp[self.checkExtruderNr]))
497
498         def mcStateChange(self, state):
499                 if self.comm is None:
500                         return
501                 if self.comm.isOperational():
502                         wx.CallAfter(self.commState.SetBitmap, self.checkBitmap)
503                         wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
504                 elif self.comm.isError():
505                         wx.CallAfter(self.commState.SetBitmap, self.crossBitmap)
506                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
507                         wx.CallAfter(self.endstopBitmap.Show, False)
508                         wx.CallAfter(self.machineState.SetLabel, '%s' % (self.comm.getErrorString()))
509                         wx.CallAfter(self.errorLogButton.Show, True)
510                         wx.CallAfter(self.Layout)
511                 else:
512                         wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
513
514         def mcMessage(self, message):
515                 if self.checkupState >= 3 and self.checkupState < 10 and ('_min' in message or '_max' in message):
516                         for data in message.split(' '):
517                                 if ':' in data:
518                                         tag, value = data.split(':', 1)
519                                         if tag == 'x_min':
520                                                 self.xMinStop = (value == 'H' or value == 'TRIGGERED')
521                                         if tag == 'x_max':
522                                                 self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
523                                         if tag == 'y_min':
524                                                 self.yMinStop = (value == 'H' or value == 'TRIGGERED')
525                                         if tag == 'y_max':
526                                                 self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
527                                         if tag == 'z_min':
528                                                 self.zMinStop = (value == 'H' or value == 'TRIGGERED')
529                                         if tag == 'z_max':
530                                                 self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
531                         if ':' in message:
532                                 tag, value = map(str.strip, message.split(':', 1))
533                                 if tag == 'x_min':
534                                         self.xMinStop = (value == 'H' or value == 'TRIGGERED')
535                                 if tag == 'x_max':
536                                         self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
537                                 if tag == 'y_min':
538                                         self.yMinStop = (value == 'H' or value == 'TRIGGERED')
539                                 if tag == 'y_max':
540                                         self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
541                                 if tag == 'z_min':
542                                         self.zMinStop = (value == 'H' or value == 'TRIGGERED')
543                                 if tag == 'z_max':
544                                         self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
545                         if 'z_max' in message:
546                                 self.comm.sendCommand('M119')
547
548                         if self.checkupState == 3:
549                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
550                                         self.checkupState = 4
551                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the right X endstop.')
552                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMaxBitmap)
553                         elif self.checkupState == 4:
554                                 if not self.xMinStop and self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
555                                         self.checkupState = 5
556                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the left X endstop.')
557                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMinBitmap)
558                         elif self.checkupState == 5:
559                                 if self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
560                                         self.checkupState = 6
561                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the front Y endstop.')
562                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMinBitmap)
563                         elif self.checkupState == 6:
564                                 if not self.xMinStop and not self.xMaxStop and self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
565                                         self.checkupState = 7
566                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the back Y endstop.')
567                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMaxBitmap)
568                         elif self.checkupState == 7:
569                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and self.yMaxStop and not self.zMinStop and not self.zMaxStop:
570                                         self.checkupState = 8
571                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the top Z endstop.')
572                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMinBitmap)
573                         elif self.checkupState == 8:
574                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and self.zMinStop and not self.zMaxStop:
575                                         self.checkupState = 9
576                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the bottom Z endstop.')
577                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMaxBitmap)
578                         elif self.checkupState == 9:
579                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and self.zMaxStop:
580                                         self.checkupState = 10
581                                         self.comm.close()
582                                         wx.CallAfter(self.infoBox.SetInfo, 'Checkup finished')
583                                         wx.CallAfter(self.infoBox.SetReadyIndicator)
584                                         wx.CallAfter(self.endstopBitmap.Show, False)
585                                         wx.CallAfter(self.stopState.SetBitmap, self.checkBitmap)
586                                         wx.CallAfter(self.OnSkipClick, None)
587
588         def mcProgress(self, lineNr):
589                 pass
590
591         def mcZChange(self, newZ):
592                 pass
593
594
595 class UltimakerCalibrationPage(InfoPage):
596         def __init__(self, parent):
597                 super(UltimakerCalibrationPage, self).__init__(parent, "Ultimaker Calibration")
598
599                 self.AddText("Your Ultimaker requires some calibration.")
600                 self.AddText("This calibration is needed for a proper extrusion amount.")
601                 self.AddSeperator()
602                 self.AddText("The following values are needed:")
603                 self.AddText("* Diameter of filament")
604                 self.AddText("* Number of steps per mm of filament extrusion")
605                 self.AddSeperator()
606                 self.AddText("The better you have calibrated these values, the better your prints\nwill become.")
607                 self.AddSeperator()
608                 self.AddText("First we need the diameter of your filament:")
609                 self.filamentDiameter = self.AddTextCtrl(profile.getProfileSetting('filament_diameter'))
610                 self.AddText(
611                         "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.")
612                 self.AddText("Note: This value can be changed later at any time.")
613
614         def StoreData(self):
615                 profile.putProfileSetting('filament_diameter', self.filamentDiameter.GetValue())
616
617
618 class UltimakerCalibrateStepsPerEPage(InfoPage):
619         def __init__(self, parent):
620                 super(UltimakerCalibrateStepsPerEPage, self).__init__(parent, "Ultimaker Calibration")
621
622                 #if profile.getPreference('steps_per_e') == '0':
623                 #       profile.putPreference('steps_per_e', '865.888')
624
625                 self.AddText("Calibrating the Steps Per E requires some manual actions.")
626                 self.AddText("First remove any filament from your machine.")
627                 self.AddText("Next put in your filament so the tip is aligned with the\ntop of the extruder drive.")
628                 self.AddText("We'll push the filament 100mm")
629                 self.extrudeButton = self.AddButton("Extrude 100mm filament")
630                 self.AddText("Now measure the amount of extruded filament:\n(this can be more or less then 100mm)")
631                 self.lengthInput, self.saveLengthButton = self.AddTextCtrlButton('100', 'Save')
632                 self.AddText("This results in the following steps per E:")
633                 self.stepsPerEInput = self.AddTextCtrl(profile.getPreference('steps_per_e'))
634                 self.AddText("You can repeat these steps to get better calibration.")
635                 self.AddSeperator()
636                 self.AddText(
637                         "If you still have filament in your printer which needs\nheat to remove, press the heat up button below:")
638                 self.heatButton = self.AddButton("Heatup for filament removal")
639
640                 self.saveLengthButton.Bind(wx.EVT_BUTTON, self.OnSaveLengthClick)
641                 self.extrudeButton.Bind(wx.EVT_BUTTON, self.OnExtrudeClick)
642                 self.heatButton.Bind(wx.EVT_BUTTON, self.OnHeatClick)
643
644         def OnSaveLengthClick(self, e):
645                 currentEValue = float(self.stepsPerEInput.GetValue())
646                 realExtrudeLength = float(self.lengthInput.GetValue())
647                 newEValue = currentEValue * 100 / realExtrudeLength
648                 self.stepsPerEInput.SetValue(str(newEValue))
649                 self.lengthInput.SetValue("100")
650
651         def OnExtrudeClick(self, e):
652                 threading.Thread(target=self.OnExtrudeRun).start()
653
654         def OnExtrudeRun(self):
655                 self.heatButton.Enable(False)
656                 self.extrudeButton.Enable(False)
657                 currentEValue = float(self.stepsPerEInput.GetValue())
658                 self.comm = machineCom.MachineCom()
659                 if not self.comm.isOpen():
660                         wx.MessageBox(
661                                 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
662                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
663                         self.heatButton.Enable(True)
664                         self.extrudeButton.Enable(True)
665                         return
666                 while True:
667                         line = self.comm.readline()
668                         if line == '':
669                                 return
670                         if 'start' in line:
671                                 break
672                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
673                 time.sleep(3)
674
675                 self.sendGCommand('M302') #Disable cold extrusion protection
676                 self.sendGCommand("M92 E%f" % (currentEValue))
677                 self.sendGCommand("G92 E0")
678                 self.sendGCommand("G1 E100 F600")
679                 time.sleep(15)
680                 self.comm.close()
681                 self.extrudeButton.Enable()
682                 self.heatButton.Enable()
683
684         def OnHeatClick(self, e):
685                 threading.Thread(target=self.OnHeatRun).start()
686
687         def OnHeatRun(self):
688                 self.heatButton.Enable(False)
689                 self.extrudeButton.Enable(False)
690                 self.comm = machineCom.MachineCom()
691                 if not self.comm.isOpen():
692                         wx.MessageBox(
693                                 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
694                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
695                         self.heatButton.Enable(True)
696                         self.extrudeButton.Enable(True)
697                         return
698                 while True:
699                         line = self.comm.readline()
700                         if line == '':
701                                 self.heatButton.Enable(True)
702                                 self.extrudeButton.Enable(True)
703                                 return
704                         if 'start' in line:
705                                 break
706                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
707                 time.sleep(3)
708
709                 self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.
710                 wx.MessageBox(
711                         'Wait till you can remove the filament from the machine, and press OK.\n(Temperature is set to 200C)',
712                         'Machine heatup', wx.OK | wx.ICON_INFORMATION)
713                 self.sendGCommand('M104 S0')
714                 time.sleep(1)
715                 self.comm.close()
716                 self.heatButton.Enable(True)
717                 self.extrudeButton.Enable(True)
718
719         def sendGCommand(self, cmd):
720                 self.comm.sendCommand(cmd) #Disable cold extrusion protection
721                 while True:
722                         line = self.comm.readline()
723                         if line == '':
724                                 return
725                         if line.startswith('ok'):
726                                 break
727
728         def StoreData(self):
729                 profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())
730
731
732 class configWizard(wx.wizard.Wizard):
733         def __init__(self):
734                 super(configWizard, self).__init__(None, -1, "Configuration Wizard")
735
736                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
737                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
738
739                 self.firstInfoPage = FirstInfoPage(self)
740                 self.machineSelectPage = MachineSelectPage(self)
741                 self.ultimakerSelectParts = SelectParts(self)
742                 self.ultimakerFirmwareUpgradePage = FirmwareUpgradePage(self)
743                 self.ultimakerCheckupPage = UltimakerCheckupPage(self)
744                 self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)
745                 self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)
746                 self.bedLevelPage = bedLevelWizardMain(self)
747                 self.repRapInfoPage = RepRapInfoPage(self)
748
749                 wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)
750                 wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerSelectParts)
751                 wx.wizard.WizardPageSimple.Chain(self.ultimakerSelectParts, self.ultimakerFirmwareUpgradePage)
752                 wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)
753                 wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.bedLevelPage)
754                 #wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)
755
756                 self.FitToPage(self.firstInfoPage)
757                 self.GetPageAreaSizer().Add(self.firstInfoPage)
758
759                 self.RunWizard(self.firstInfoPage)
760                 self.Destroy()
761
762         def OnPageChanging(self, e):
763                 e.GetPage().StoreData()
764
765         def OnPageChanged(self, e):
766                 if e.GetPage().AllowNext():
767                         self.FindWindowById(wx.ID_FORWARD).Enable()
768                 else:
769                         self.FindWindowById(wx.ID_FORWARD).Disable()
770                 self.FindWindowById(wx.ID_BACKWARD).Disable()
771
772 class bedLevelWizardMain(InfoPage):
773         def __init__(self, parent):
774                 super(bedLevelWizardMain, self).__init__(parent, "Bed leveling wizard")
775
776                 self.AddText('This wizard will help you in leveling your printer bed')
777                 self.AddSeperator()
778                 self.AddText('It will do the following steps')
779                 self.AddText('* Move the printer head to each corner')
780                 self.AddText('  and let you adjust the height of the bed to the nozzle')
781                 self.AddText('* Print a line around the bed to check if it is level')
782                 self.AddSeperator()
783
784                 self.connectButton = self.AddButton('Connect to printer')
785                 self.comm = None
786
787                 self.infoBox = self.AddInfoBox()
788                 self.resumeButton = self.AddButton('Resume')
789                 self.resumeButton.Enable(False)
790
791                 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
792                 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
793
794         def OnConnect(self, e = None):
795                 if self.comm is not None:
796                         self.comm.close()
797                         del self.comm
798                         self.comm = None
799                         wx.CallAfter(self.OnConnect)
800                         return
801                 self.connectButton.Enable(False)
802                 self.comm = machineCom.MachineCom(callbackObject=self)
803                 self.infoBox.SetBusy('Connecting to machine.')
804                 self._wizardState = 0
805
806         def AllowNext(self):
807                 return True
808
809         def OnResume(self, e):
810                 feedZ = profile.getProfileSettingFloat('print_speed') * 60
811                 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
812                 if self._wizardState == 2:
813                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back left corner...')
814                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
815                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, profile.getPreferenceFloat('machine_depth'), feedTravel))
816                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
817                         self.comm.sendCommand('M400')
818                         self._wizardState = 3
819                 elif self._wizardState == 4:
820                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back right corner...')
821                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
822                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width') - 5.0, profile.getPreferenceFloat('machine_depth') - 25, feedTravel))
823                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
824                         self.comm.sendCommand('M400')
825                         self._wizardState = 5
826                 elif self._wizardState == 6:
827                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to front right corner...')
828                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
829                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width') - 5.0, 20, feedTravel))
830                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
831                         self.comm.sendCommand('M400')
832                         self._wizardState = 7
833                 elif self._wizardState == 8:
834                         wx.CallAfter(self.infoBox.SetBusy, 'Heating up printer...')
835                         self.comm.sendCommand('G1 Z15 F%d' % (feedZ))
836                         self.comm.sendCommand('M104 S%d' % (profile.getProfileSettingFloat('print_temperature')))
837                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, 0, feedTravel))
838                         self._wizardState = 9
839                 elif self._wizardState == 10:
840                         self._wizardState = 11
841                         wx.CallAfter(self.infoBox.SetInfo, 'Printing a square on the printer bed at 0.3mm height.')
842                         feedZ = profile.getProfileSettingFloat('print_speed') * 60
843                         feedPrint = profile.getProfileSettingFloat('print_speed') * 60
844                         feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
845                         w = profile.getPreferenceFloat('machine_width')
846                         d = profile.getPreferenceFloat('machine_depth')
847                         filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
848                         filamentArea = math.pi * filamentRadius * filamentRadius
849                         ePerMM = (profile.calculateEdgeWidth() * 0.3) / filamentArea
850                         eValue = 0.0
851
852                         gcodeList = [
853                                 'G1 Z2 F%d' % (feedZ),
854                                 'G92 E0',
855                                 'G1 X%d Y%d F%d' % (5, 5, feedTravel),
856                                 'G1 Z0.3 F%d' % (feedZ)]
857                         eValue += 5.0
858                         gcodeList.append('G1 E%f F%d' % (eValue, profile.getProfileSettingFloat('retraction_speed') * 60))
859
860                         for i in xrange(0, 3):
861                                 dist = 5.0 + 0.4 * float(i)
862                                 eValue += (d - 2.0*dist) * ePerMM
863                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (dist, d - dist, eValue, feedPrint))
864                                 eValue += (w - 2.0*dist) * ePerMM
865                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (w - dist, d - dist, eValue, feedPrint))
866                                 eValue += (d - 2.0*dist) * ePerMM
867                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (w - dist, dist, eValue, feedPrint))
868                                 eValue += (w - 2.0*dist) * ePerMM
869                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (dist, dist, eValue, feedPrint))
870
871                         gcodeList.append('M400')
872                         self.comm.printGCode(gcodeList)
873                 self.resumeButton.Enable(False)
874
875         def mcLog(self, message):
876                 print 'Log:', message
877
878         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
879                 if self._wizardState == 1:
880                         self._wizardState = 2
881                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front left screw of your printer bed\nSo the nozzle just hits the bed.')
882                         wx.CallAfter(self.resumeButton.Enable, True)
883                 elif self._wizardState == 3:
884                         self._wizardState = 4
885                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back left screw of your printer bed\nSo the nozzle just hits the bed.')
886                         wx.CallAfter(self.resumeButton.Enable, True)
887                 elif self._wizardState == 5:
888                         self._wizardState = 6
889                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back right screw of your printer bed\nSo the nozzle just hits the bed.')
890                         wx.CallAfter(self.resumeButton.Enable, True)
891                 elif self._wizardState == 7:
892                         self._wizardState = 8
893                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front right screw of your printer bed\nSo the nozzle just hits the bed.')
894                         wx.CallAfter(self.resumeButton.Enable, True)
895                 elif self._wizardState == 9:
896                         if temp[0] < profile.getProfileSettingFloat('print_temperature') - 5:
897                                 wx.CallAfter(self.infoBox.SetInfo, 'Heating up printer: %d/%d' % (temp, profile.getProfileSettingFloat('print_temperature')))
898                         else:
899                                 wx.CallAfter(self.infoBox.SetAttention, 'The printer is hot now. Please insert some PLA filament into the printer.')
900                                 wx.CallAfter(self.resumeButton.Enable, True)
901                                 self._wizardState = 10
902
903         def mcStateChange(self, state):
904                 if self.comm is None:
905                         return
906                 if self.comm.isOperational():
907                         if self._wizardState == 0:
908                                 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer...')
909                                 self.comm.sendCommand('M105')
910                                 self.comm.sendCommand('G28')
911                                 self._wizardState = 1
912                         elif self._wizardState == 11 and not self.comm.isPrinting():
913                                 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('print_speed') * 60))
914                                 self.comm.sendCommand('G92 E0')
915                                 self.comm.sendCommand('G1 E-10 F%d' % (profile.getProfileSettingFloat('retraction_speed') * 60))
916                                 self.comm.sendCommand('M104 S0')
917                                 wx.CallAfter(self.infoBox.SetInfo, 'Calibration finished.\nThe squares on the bed should slightly touch each other.')
918                                 wx.CallAfter(self.infoBox.SetReadyIndicator)
919                                 wx.CallAfter(self.GetParent().FindWindowById(wx.ID_FORWARD).Enable)
920                                 wx.CallAfter(self.connectButton.Enable, True)
921                                 self._wizardState = 12
922                 elif self.comm.isError():
923                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
924
925         def mcMessage(self, message):
926                 pass
927
928         def mcProgress(self, lineNr):
929                 pass
930
931         def mcZChange(self, newZ):
932                 pass
933
934 class bedLevelWizard(wx.wizard.Wizard):
935         def __init__(self):
936                 super(bedLevelWizard, self).__init__(None, -1, "Bed leveling wizard")
937
938                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
939                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
940
941                 self.mainPage = bedLevelWizardMain(self)
942
943                 self.FitToPage(self.mainPage)
944                 self.GetPageAreaSizer().Add(self.mainPage)
945
946                 self.RunWizard(self.mainPage)
947                 self.Destroy()
948
949         def OnPageChanging(self, e):
950                 e.GetPage().StoreData()
951
952         def OnPageChanged(self, e):
953                 if e.GetPage().AllowNext():
954                         self.FindWindowById(wx.ID_FORWARD).Enable()
955                 else:
956                         self.FindWindowById(wx.ID_FORWARD).Disable()
957                 self.FindWindowById(wx.ID_BACKWARD).Disable()