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