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