chiark / gitweb /
Goto the next wizard page if the skip button is pressed.
[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
255
256 class MachineSelectPage(InfoPage):
257         def __init__(self, parent):
258                 super(MachineSelectPage, self).__init__(parent, "Select your machine")
259                 self.AddText('What kind of machine do you have:')
260
261                 self.UltimakerRadio = self.AddRadioButton("Ultimaker", style=wx.RB_GROUP)
262                 self.UltimakerRadio.SetValue(True)
263                 self.UltimakerRadio.Bind(wx.EVT_RADIOBUTTON, self.OnUltimakerSelect)
264                 self.OtherRadio = self.AddRadioButton("Other (Ex: RepRap)")
265                 self.OtherRadio.Bind(wx.EVT_RADIOBUTTON, self.OnOtherSelect)
266                 self.AddSeperator()
267                 self.AddText('The collection of anonymous usage information helps with the continued improvement of Cura.')
268                 self.AddText('This does NOT submit your models online nor gathers any privacy related information.')
269                 self.SubmitUserStats = self.AddCheckbox('Submit anonymous usage information:')
270                 self.AddText('For full details see: http://wiki.ultimaker.com/Cura:stats')
271                 self.SubmitUserStats.SetValue(True)
272
273         def OnUltimakerSelect(self, e):
274                 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().ultimakerFirmwareUpgradePage)
275
276         def OnOtherSelect(self, e):
277                 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().repRapInfoPage)
278
279         def StoreData(self):
280                 if self.UltimakerRadio.GetValue():
281                         profile.putPreference('machine_width', '205')
282                         profile.putPreference('machine_depth', '205')
283                         profile.putPreference('machine_height', '200')
284                         profile.putPreference('machine_type', 'ultimaker')
285                         profile.putPreference('machine_center_is_zero', 'False')
286                         profile.putProfileSetting('nozzle_size', '0.4')
287                 else:
288                         profile.putPreference('machine_width', '80')
289                         profile.putPreference('machine_depth', '80')
290                         profile.putPreference('machine_height', '60')
291                         profile.putPreference('machine_type', 'reprap')
292                         profile.putPreference('startMode', 'Normal')
293                         profile.putProfileSetting('nozzle_size', '0.5')
294                 profile.putProfileSetting('wall_thickness', float(profile.getProfileSetting('nozzle_size')) * 2)
295                 if self.SubmitUserStats.GetValue():
296                         profile.putPreference('submit_slice_information', 'True')
297                 else:
298                         profile.putPreference('submit_slice_information', 'False')
299
300 class SelectParts(InfoPage):
301         def __init__(self, parent):
302                 super(SelectParts, self).__init__(parent, "Select upgraded parts you have")
303                 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.')
304                 self.AddSeperator()
305                 self.springExtruder = self.AddCheckbox('Extruder drive upgrade')
306                 self.heatedBed = self.AddCheckbox('Heated printer bed (self built)')
307                 self.dualExtrusion = self.AddCheckbox('Dual extrusion (experimental)')
308                 self.AddSeperator()
309                 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.')
310                 self.AddText('This upgrade can be bought from the Ultimaker webshop\nor found on thingiverse as thing:26094')
311                 self.springExtruder.SetValue(True)
312
313         def StoreData(self):
314                 profile.putPreference('ultimaker_extruder_upgrade', str(self.springExtruder.GetValue()))
315                 profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))
316                 if self.dualExtrusion.GetValue():
317                         profile.putPreference('extruder_amount', '2')
318                 else:
319                         profile.putPreference('extruder_amount', '1')
320                 if profile.getPreference('ultimaker_extruder_upgrade') == 'True':
321                         profile.putProfileSetting('retraction_enable', 'True')
322                 else:
323                         profile.putProfileSetting('retraction_enable', 'False')
324
325
326 class FirmwareUpgradePage(InfoPage):
327         def __init__(self, parent):
328                 super(FirmwareUpgradePage, self).__init__(parent, "Upgrade Ultimaker Firmware")
329                 self.AddText(
330                         '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.')
331                 self.AddHiddenSeperator()
332                 self.AddText(
333                         'The firmware shipping with new Ultimakers works, but upgrades\nhave been made to make better prints, and make calibration easier.')
334                 self.AddHiddenSeperator()
335                 self.AddText(
336                         '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.')
337                 upgradeButton, skipUpgradeButton = self.AddDualButton('Upgrade to Marlin firmware', 'Skip upgrade')
338                 upgradeButton.Bind(wx.EVT_BUTTON, self.OnUpgradeClick)
339                 skipUpgradeButton.Bind(wx.EVT_BUTTON, self.OnSkipClick)
340                 self.AddHiddenSeperator()
341                 self.AddText('Do not upgrade to this firmware if:')
342                 self.AddText('* You have an older machine based on ATMega1280')
343                 self.AddText('* Have other changes in the firmware')
344                 button = self.AddButton('Goto this page for a custom firmware')
345                 button.Bind(wx.EVT_BUTTON, self.OnUrlClick)
346
347         def AllowNext(self):
348                 return False
349
350         def OnUpgradeClick(self, e):
351                 if firmwareInstall.InstallFirmware():
352                         self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
353
354         def OnSkipClick(self, e):
355                 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
356                 self.GetParent().ShowPage(self.GetNext())
357
358         def OnUrlClick(self, e):
359                 webbrowser.open('http://daid.mine.nu/~daid/marlin_build/')
360
361
362 class UltimakerCheckupPage(InfoPage):
363         def __init__(self, parent):
364                 super(UltimakerCheckupPage, self).__init__(parent, "Ultimaker Checkup")
365
366                 self.checkBitmap = wx.Bitmap(getPathForImage('checkmark.png'))
367                 self.crossBitmap = wx.Bitmap(getPathForImage('cross.png'))
368                 self.unknownBitmap = wx.Bitmap(getPathForImage('question.png'))
369                 self.endStopNoneBitmap = wx.Bitmap(getPathForImage('endstop_none.png'))
370                 self.endStopXMinBitmap = wx.Bitmap(getPathForImage('endstop_xmin.png'))
371                 self.endStopXMaxBitmap = wx.Bitmap(getPathForImage('endstop_xmax.png'))
372                 self.endStopYMinBitmap = wx.Bitmap(getPathForImage('endstop_ymin.png'))
373                 self.endStopYMaxBitmap = wx.Bitmap(getPathForImage('endstop_ymax.png'))
374                 self.endStopZMinBitmap = wx.Bitmap(getPathForImage('endstop_zmin.png'))
375                 self.endStopZMaxBitmap = wx.Bitmap(getPathForImage('endstop_zmax.png'))
376
377                 self.AddText(
378                         '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.')
379                 b1, b2 = self.AddDualButton('Run checks', 'Skip checks')
380                 b1.Bind(wx.EVT_BUTTON, self.OnCheckClick)
381                 b2.Bind(wx.EVT_BUTTON, self.OnSkipClick)
382                 self.AddSeperator()
383                 self.commState = self.AddCheckmark('Communication:', self.unknownBitmap)
384                 self.tempState = self.AddCheckmark('Temperature:', self.unknownBitmap)
385                 self.stopState = self.AddCheckmark('Endstops:', self.unknownBitmap)
386                 self.AddSeperator()
387                 self.infoBox = self.AddInfoBox()
388                 self.machineState = self.AddText('')
389                 self.temperatureLabel = self.AddText('')
390                 self.errorLogButton = self.AddButton('Show error log')
391                 self.errorLogButton.Show(False)
392                 self.AddSeperator()
393                 self.endstopBitmap = self.AddBitmap(self.endStopNoneBitmap)
394                 self.comm = None
395                 self.xMinStop = False
396                 self.xMaxStop = False
397                 self.yMinStop = False
398                 self.yMaxStop = False
399                 self.zMinStop = False
400                 self.zMaxStop = False
401
402                 self.Bind(wx.EVT_BUTTON, self.OnErrorLog, self.errorLogButton)
403
404         def __del__(self):
405                 if self.comm is not None:
406                         self.comm.close()
407
408         def AllowNext(self):
409                 self.endstopBitmap.Show(False)
410                 return False
411
412         def OnSkipClick(self, e):
413                 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
414                 self.GetParent().ShowPage(self.GetNext())
415
416         def OnCheckClick(self, e=None):
417                 self.errorLogButton.Show(False)
418                 if self.comm is not None:
419                         self.comm.close()
420                         del self.comm
421                         self.comm = None
422                         wx.CallAfter(self.OnCheckClick)
423                         return
424                 self.infoBox.SetBusy('Connecting to machine.')
425                 self.commState.SetBitmap(self.unknownBitmap)
426                 self.tempState.SetBitmap(self.unknownBitmap)
427                 self.stopState.SetBitmap(self.unknownBitmap)
428                 self.checkupState = 0
429                 self.comm = machineCom.MachineCom(callbackObject=self)
430
431         def OnErrorLog(self, e):
432                 printWindow.LogWindow('\n'.join(self.comm.getLog()))
433
434         def mcLog(self, message):
435                 pass
436
437         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
438                 if not self.comm.isOperational():
439                         return
440                 if self.checkupState == 0:
441                         self.tempCheckTimeout = 20
442                         if temp > 70:
443                                 self.checkupState = 1
444                                 wx.CallAfter(self.infoBox.SetInfo, 'Cooldown before temperature check.')
445                                 self.comm.sendCommand('M104 S0')
446                                 self.comm.sendCommand('M104 S0')
447                         else:
448                                 self.startTemp = temp
449                                 self.checkupState = 2
450                                 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
451                                 self.comm.sendCommand('M104 S200')
452                                 self.comm.sendCommand('M104 S200')
453                 elif self.checkupState == 1:
454                         if temp < 60:
455                                 self.startTemp = temp
456                                 self.checkupState = 2
457                                 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
458                                 self.comm.sendCommand('M104 S200')
459                                 self.comm.sendCommand('M104 S200')
460                 elif self.checkupState == 2:
461                         #print "WARNING, TEMPERATURE TEST DISABLED FOR TESTING!"
462                         if temp > self.startTemp + 40:
463                                 self.checkupState = 3
464                                 wx.CallAfter(self.infoBox.SetAttention, 'Please make sure none of the endstops are pressed.')
465                                 wx.CallAfter(self.endstopBitmap.Show, True)
466                                 wx.CallAfter(self.Layout)
467                                 self.comm.sendCommand('M104 S0')
468                                 self.comm.sendCommand('M104 S0')
469                                 self.comm.sendCommand('M119')
470                                 wx.CallAfter(self.tempState.SetBitmap, self.checkBitmap)
471                         else:
472                                 self.tempCheckTimeout -= 1
473                                 if self.tempCheckTimeout < 1:
474                                         self.checkupState = -1
475                                         wx.CallAfter(self.tempState.SetBitmap, self.crossBitmap)
476                                         wx.CallAfter(self.infoBox.SetError, 'Temperature measurement FAILED!', 'http://wiki.ultimaker.com/Cura:_Temperature_measurement_problems')
477                                         self.comm.sendCommand('M104 S0')
478                                         self.comm.sendCommand('M104 S0')
479                 elif self.checkupState >= 3 and self.checkupState < 10:
480                         self.comm.sendCommand('M119')
481                 wx.CallAfter(self.temperatureLabel.SetLabel, 'Head temperature: %d' % (temp))
482
483         def mcStateChange(self, state):
484                 if self.comm is None:
485                         return
486                 if self.comm.isOperational():
487                         wx.CallAfter(self.commState.SetBitmap, self.checkBitmap)
488                         wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
489                 elif self.comm.isError():
490                         wx.CallAfter(self.commState.SetBitmap, self.crossBitmap)
491                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
492                         wx.CallAfter(self.endstopBitmap.Show, False)
493                         wx.CallAfter(self.machineState.SetLabel, '%s' % (self.comm.getErrorString()))
494                         wx.CallAfter(self.errorLogButton.Show, True)
495                         wx.CallAfter(self.Layout)
496                 else:
497                         wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
498
499         def mcMessage(self, message):
500                 if self.checkupState >= 3 and self.checkupState < 10 and ('_min' in message or '_max' in message):
501                         for data in message.split(' '):
502                                 if ':' in data:
503                                         tag, value = data.split(':', 1)
504                                         if tag == 'x_min':
505                                                 self.xMinStop = (value == 'H' or value == 'TRIGGERED')
506                                         if tag == 'x_max':
507                                                 self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
508                                         if tag == 'y_min':
509                                                 self.yMinStop = (value == 'H' or value == 'TRIGGERED')
510                                         if tag == 'y_max':
511                                                 self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
512                                         if tag == 'z_min':
513                                                 self.zMinStop = (value == 'H' or value == 'TRIGGERED')
514                                         if tag == 'z_max':
515                                                 self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
516                         if ':' in message:
517                                 tag, value = map(str.strip, message.split(':', 1))
518                                 if tag == 'x_min':
519                                         self.xMinStop = (value == 'H' or value == 'TRIGGERED')
520                                 if tag == 'x_max':
521                                         self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
522                                 if tag == 'y_min':
523                                         self.yMinStop = (value == 'H' or value == 'TRIGGERED')
524                                 if tag == 'y_max':
525                                         self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
526                                 if tag == 'z_min':
527                                         self.zMinStop = (value == 'H' or value == 'TRIGGERED')
528                                 if tag == 'z_max':
529                                         self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
530                         if 'z_max' in message:
531                                 self.comm.sendCommand('M119')
532
533                         if self.checkupState == 3:
534                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
535                                         self.checkupState = 4
536                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the right X endstop.')
537                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMaxBitmap)
538                         elif self.checkupState == 4:
539                                 if not self.xMinStop and self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
540                                         self.checkupState = 5
541                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the left X endstop.')
542                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMinBitmap)
543                         elif self.checkupState == 5:
544                                 if 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 = 6
546                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the front Y endstop.')
547                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMinBitmap)
548                         elif self.checkupState == 6:
549                                 if not self.xMinStop and not self.xMaxStop and self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
550                                         self.checkupState = 7
551                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the back Y endstop.')
552                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMaxBitmap)
553                         elif self.checkupState == 7:
554                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and self.yMaxStop and not self.zMinStop and not self.zMaxStop:
555                                         self.checkupState = 8
556                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the top Z endstop.')
557                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMinBitmap)
558                         elif self.checkupState == 8:
559                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and self.zMinStop and not self.zMaxStop:
560                                         self.checkupState = 9
561                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the bottom Z endstop.')
562                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMaxBitmap)
563                         elif self.checkupState == 9:
564                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and self.zMaxStop:
565                                         self.checkupState = 10
566                                         self.comm.close()
567                                         wx.CallAfter(self.infoBox.SetInfo, 'Checkup finished')
568                                         wx.CallAfter(self.infoBox.SetReadyIndicator)
569                                         wx.CallAfter(self.endstopBitmap.Show, False)
570                                         wx.CallAfter(self.stopState.SetBitmap, self.checkBitmap)
571                                         wx.CallAfter(self.OnSkipClick, None)
572
573         def mcProgress(self, lineNr):
574                 pass
575
576         def mcZChange(self, newZ):
577                 pass
578
579
580 class UltimakerCalibrationPage(InfoPage):
581         def __init__(self, parent):
582                 super(UltimakerCalibrationPage, self).__init__(parent, "Ultimaker Calibration")
583
584                 self.AddText("Your Ultimaker requires some calibration.")
585                 self.AddText("This calibration is needed for a proper extrusion amount.")
586                 self.AddSeperator()
587                 self.AddText("The following values are needed:")
588                 self.AddText("* Diameter of filament")
589                 self.AddText("* Number of steps per mm of filament extrusion")
590                 self.AddSeperator()
591                 self.AddText("The better you have calibrated these values, the better your prints\nwill become.")
592                 self.AddSeperator()
593                 self.AddText("First we need the diameter of your filament:")
594                 self.filamentDiameter = self.AddTextCtrl(profile.getProfileSetting('filament_diameter'))
595                 self.AddText(
596                         "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.")
597                 self.AddText("Note: This value can be changed later at any time.")
598
599         def StoreData(self):
600                 profile.putProfileSetting('filament_diameter', self.filamentDiameter.GetValue())
601
602
603 class UltimakerCalibrateStepsPerEPage(InfoPage):
604         def __init__(self, parent):
605                 super(UltimakerCalibrateStepsPerEPage, self).__init__(parent, "Ultimaker Calibration")
606
607                 #if profile.getPreference('steps_per_e') == '0':
608                 #       profile.putPreference('steps_per_e', '865.888')
609
610                 self.AddText("Calibrating the Steps Per E requires some manual actions.")
611                 self.AddText("First remove any filament from your machine.")
612                 self.AddText("Next put in your filament so the tip is aligned with the\ntop of the extruder drive.")
613                 self.AddText("We'll push the filament 100mm")
614                 self.extrudeButton = self.AddButton("Extrude 100mm filament")
615                 self.AddText("Now measure the amount of extruded filament:\n(this can be more or less then 100mm)")
616                 self.lengthInput, self.saveLengthButton = self.AddTextCtrlButton('100', 'Save')
617                 self.AddText("This results in the following steps per E:")
618                 self.stepsPerEInput = self.AddTextCtrl(profile.getPreference('steps_per_e'))
619                 self.AddText("You can repeat these steps to get better calibration.")
620                 self.AddSeperator()
621                 self.AddText(
622                         "If you still have filament in your printer which needs\nheat to remove, press the heat up button below:")
623                 self.heatButton = self.AddButton("Heatup for filament removal")
624
625                 self.saveLengthButton.Bind(wx.EVT_BUTTON, self.OnSaveLengthClick)
626                 self.extrudeButton.Bind(wx.EVT_BUTTON, self.OnExtrudeClick)
627                 self.heatButton.Bind(wx.EVT_BUTTON, self.OnHeatClick)
628
629         def OnSaveLengthClick(self, e):
630                 currentEValue = float(self.stepsPerEInput.GetValue())
631                 realExtrudeLength = float(self.lengthInput.GetValue())
632                 newEValue = currentEValue * 100 / realExtrudeLength
633                 self.stepsPerEInput.SetValue(str(newEValue))
634                 self.lengthInput.SetValue("100")
635
636         def OnExtrudeClick(self, e):
637                 threading.Thread(target=self.OnExtrudeRun).start()
638
639         def OnExtrudeRun(self):
640                 self.heatButton.Enable(False)
641                 self.extrudeButton.Enable(False)
642                 currentEValue = float(self.stepsPerEInput.GetValue())
643                 self.comm = machineCom.MachineCom()
644                 if not self.comm.isOpen():
645                         wx.MessageBox(
646                                 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
647                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
648                         self.heatButton.Enable(True)
649                         self.extrudeButton.Enable(True)
650                         return
651                 while True:
652                         line = self.comm.readline()
653                         if line == '':
654                                 return
655                         if 'start' in line:
656                                 break
657                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
658                 time.sleep(3)
659
660                 self.sendGCommand('M302') #Disable cold extrusion protection
661                 self.sendGCommand("M92 E%f" % (currentEValue))
662                 self.sendGCommand("G92 E0")
663                 self.sendGCommand("G1 E100 F600")
664                 time.sleep(15)
665                 self.comm.close()
666                 self.extrudeButton.Enable()
667                 self.heatButton.Enable()
668
669         def OnHeatClick(self, e):
670                 threading.Thread(target=self.OnHeatRun).start()
671
672         def OnHeatRun(self):
673                 self.heatButton.Enable(False)
674                 self.extrudeButton.Enable(False)
675                 self.comm = machineCom.MachineCom()
676                 if not self.comm.isOpen():
677                         wx.MessageBox(
678                                 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
679                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
680                         self.heatButton.Enable(True)
681                         self.extrudeButton.Enable(True)
682                         return
683                 while True:
684                         line = self.comm.readline()
685                         if line == '':
686                                 self.heatButton.Enable(True)
687                                 self.extrudeButton.Enable(True)
688                                 return
689                         if 'start' in line:
690                                 break
691                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
692                 time.sleep(3)
693
694                 self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.
695                 wx.MessageBox(
696                         'Wait till you can remove the filament from the machine, and press OK.\n(Temperature is set to 200C)',
697                         'Machine heatup', wx.OK | wx.ICON_INFORMATION)
698                 self.sendGCommand('M104 S0')
699                 time.sleep(1)
700                 self.comm.close()
701                 self.heatButton.Enable(True)
702                 self.extrudeButton.Enable(True)
703
704         def sendGCommand(self, cmd):
705                 self.comm.sendCommand(cmd) #Disable cold extrusion protection
706                 while True:
707                         line = self.comm.readline()
708                         if line == '':
709                                 return
710                         if line.startswith('ok'):
711                                 break
712
713         def StoreData(self):
714                 profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())
715
716
717 class configWizard(wx.wizard.Wizard):
718         def __init__(self):
719                 super(configWizard, self).__init__(None, -1, "Configuration Wizard")
720
721                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
722                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
723
724                 self.firstInfoPage = FirstInfoPage(self)
725                 self.machineSelectPage = MachineSelectPage(self)
726                 self.ultimakerSelectParts = SelectParts(self)
727                 self.ultimakerFirmwareUpgradePage = FirmwareUpgradePage(self)
728                 self.ultimakerCheckupPage = UltimakerCheckupPage(self)
729                 self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)
730                 self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)
731                 self.bedLevelPage = bedLevelWizardMain(self)
732                 self.repRapInfoPage = RepRapInfoPage(self)
733
734                 wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)
735                 wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerSelectParts)
736                 wx.wizard.WizardPageSimple.Chain(self.ultimakerSelectParts, self.ultimakerFirmwareUpgradePage)
737                 wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)
738                 wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.bedLevelPage)
739                 #wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)
740
741                 self.FitToPage(self.firstInfoPage)
742                 self.GetPageAreaSizer().Add(self.firstInfoPage)
743
744                 self.RunWizard(self.firstInfoPage)
745                 self.Destroy()
746
747         def OnPageChanging(self, e):
748                 e.GetPage().StoreData()
749
750         def OnPageChanged(self, e):
751                 if e.GetPage().AllowNext():
752                         self.FindWindowById(wx.ID_FORWARD).Enable()
753                 else:
754                         self.FindWindowById(wx.ID_FORWARD).Disable()
755                 self.FindWindowById(wx.ID_BACKWARD).Disable()
756
757 class bedLevelWizardMain(InfoPage):
758         def __init__(self, parent):
759                 super(bedLevelWizardMain, self).__init__(parent, "Bed leveling wizard")
760
761                 self.AddText('This wizard will help you in leveling your printer bed')
762                 self.AddSeperator()
763                 self.AddText('It will do the following steps')
764                 self.AddText('* Move the printer head to each corner')
765                 self.AddText('  and let you adjust the height of the bed to the nozzle')
766                 self.AddText('* Print a line around the bed to check if it is level')
767                 self.AddSeperator()
768
769                 self.connectButton = self.AddButton('Connect to printer')
770                 self.comm = None
771
772                 self.infoBox = self.AddInfoBox()
773                 self.resumeButton = self.AddButton('Resume')
774                 self.resumeButton.Enable(False)
775
776                 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
777                 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
778
779         def OnConnect(self, e = None):
780                 if self.comm is not None:
781                         self.comm.close()
782                         del self.comm
783                         self.comm = None
784                         wx.CallAfter(self.OnConnect)
785                         return
786                 self.connectButton.Enable(False)
787                 self.comm = machineCom.MachineCom(callbackObject=self)
788                 self.infoBox.SetBusy('Connecting to machine.')
789                 self._wizardState = 0
790
791         def AllowNext(self):
792                 return True
793
794         def OnResume(self, e):
795                 feedZ = profile.getProfileSettingFloat('max_z_speed') * 60
796                 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
797                 if self._wizardState == 2:
798                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back left corner...')
799                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
800                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, profile.getPreferenceFloat('machine_depth'), feedTravel))
801                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
802                         self.comm.sendCommand('M400')
803                         self._wizardState = 3
804                 elif self._wizardState == 4:
805                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back right corner...')
806                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
807                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth') - 25, feedTravel))
808                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
809                         self.comm.sendCommand('M400')
810                         self._wizardState = 5
811                 elif self._wizardState == 6:
812                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to front right corner...')
813                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
814                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width'), 20, feedTravel))
815                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
816                         self.comm.sendCommand('M400')
817                         self._wizardState = 7
818                 elif self._wizardState == 8:
819                         wx.CallAfter(self.infoBox.SetBusy, 'Heating up printer...')
820                         self.comm.sendCommand('G1 Z15 F%d' % (feedZ))
821                         self.comm.sendCommand('M104 S%d' % (profile.getProfileSettingFloat('print_temperature')))
822                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, 0, feedTravel))
823                         self._wizardState = 9
824                 self.resumeButton.Enable(False)
825
826         def mcLog(self, message):
827                 print 'Log:', message
828
829         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
830                 if self._wizardState == 1:
831                         self._wizardState = 2
832                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front left screw of your printer bed\nSo the nozzle just hits the bed.')
833                         wx.CallAfter(self.resumeButton.Enable, True)
834                 elif self._wizardState == 3:
835                         self._wizardState = 4
836                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back left screw of your printer bed\nSo the nozzle just hits the bed.')
837                         wx.CallAfter(self.resumeButton.Enable, True)
838                 elif self._wizardState == 5:
839                         self._wizardState = 6
840                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back right screw of your printer bed\nSo the nozzle just hits the bed.')
841                         wx.CallAfter(self.resumeButton.Enable, True)
842                 elif self._wizardState == 7:
843                         self._wizardState = 8
844                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front right screw of your printer bed\nSo the nozzle just hits the bed.')
845                         wx.CallAfter(self.resumeButton.Enable, True)
846                 elif self._wizardState == 9:
847                         if temp < profile.getProfileSettingFloat('print_temperature') - 5:
848                                 wx.CallAfter(self.infoBox.SetInfo, 'Heating up printer: %d/%d' % (temp, profile.getProfileSettingFloat('print_temperature')))
849                         else:
850                                 self._wizardState = 10
851                                 wx.CallAfter(self.infoBox.SetInfo, 'Printing a square on the printer bed at 0.3mm height.')
852                                 feedZ = profile.getProfileSettingFloat('max_z_speed') * 60
853                                 feedPrint = profile.getProfileSettingFloat('print_speed') * 60
854                                 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
855                                 w = profile.getPreferenceFloat('machine_width')
856                                 d = profile.getPreferenceFloat('machine_depth')
857                                 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
858                                 filamentArea = math.pi * filamentRadius * filamentRadius
859                                 ePerMM = (profile.calculateEdgeWidth() * 0.3) / filamentArea
860                                 eValue = 0.0
861
862                                 gcodeList = [
863                                         'G1 Z2 F%d' % (feedZ),
864                                         'G92 E0',
865                                         'G1 X%d Y%d F%d' % (5, 5, feedTravel),
866                                         'G1 Z0.3 F%d' % (feedZ)]
867                                 eValue += 5;
868                                 gcodeList.append('G1 E%f F%d' % (eValue, profile.getProfileSettingFloat('retraction_speed') * 60))
869
870                                 for i in xrange(0, 3):
871                                         dist = 5.0 + 0.4 * i
872                                         eValue += (d - 2*dist) * ePerMM
873                                         gcodeList.append('G1 X%d Y%d E%f F%d' % (dist, d - dist, eValue, feedPrint))
874                                         eValue += (w - 2*dist) * ePerMM
875                                         gcodeList.append('G1 X%d Y%d E%f F%d' % (w - dist, d - dist, eValue, feedPrint))
876                                         eValue += (d - 2*dist) * ePerMM
877                                         gcodeList.append('G1 X%d Y%d E%f F%d' % (w - dist, dist, eValue, feedPrint))
878                                         eValue += (w - 2*dist) * ePerMM
879                                         gcodeList.append('G1 X%d Y%d E%f F%d' % (dist, dist, eValue, feedPrint))
880
881                                 gcodeList.append('M400')
882                                 self.comm.printGCode(gcodeList)
883
884         def mcStateChange(self, state):
885                 if self.comm is None:
886                         return
887                 if self.comm.isOperational():
888                         if self._wizardState == 0:
889                                 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer...')
890                                 self.comm.sendCommand('M105')
891                                 self.comm.sendCommand('G28')
892                                 self._wizardState = 1
893                         elif self._wizardState == 10 and not self.comm.isPrinting():
894                                 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('max_z_speed') * 60))
895                                 self.comm.sendCommand('G92 E0')
896                                 self.comm.sendCommand('G1 E-10 F%d' % (profile.getProfileSettingFloat('retraction_speed') * 60))
897                                 self.comm.sendCommand('M104 S0')
898                                 wx.CallAfter(self.infoBox.SetInfo, 'Calibration finished.\nThe squares on the bed should slightly touch each other.')
899                                 wx.CallAfter(self.infoBox.SetReadyIndicator)
900                                 wx.CallAfter(self.GetParent().FindWindowById(wx.ID_FORWARD).Enable)
901                                 wx.CallAfter(self.connectButton.Enable, True)
902                                 self._wizardState = 11
903                 elif self.comm.isError():
904                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
905
906         def mcMessage(self, message):
907                 pass
908
909         def mcProgress(self, lineNr):
910                 pass
911
912         def mcZChange(self, newZ):
913                 pass
914
915 class bedLevelWizard(wx.wizard.Wizard):
916         def __init__(self):
917                 super(bedLevelWizard, self).__init__(None, -1, "Bed leveling wizard")
918
919                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
920                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
921
922                 self.mainPage = bedLevelWizardMain(self)
923
924                 self.FitToPage(self.mainPage)
925                 self.GetPageAreaSizer().Add(self.mainPage)
926
927                 self.RunWizard(self.mainPage)
928                 self.Destroy()
929
930         def OnPageChanging(self, e):
931                 e.GetPage().StoreData()
932
933         def OnPageChanged(self, e):
934                 if e.GetPage().AllowNext():
935                         self.FindWindowById(wx.ID_FORWARD).Enable()
936                 else:
937                         self.FindWindowById(wx.ID_FORWARD).Disable()
938                 self.FindWindowById(wx.ID_BACKWARD).Disable()