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