chiark / gitweb /
Improvement on the bed leveling wizard
[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 bedLevelWizardMain(InfoPage):
723         def __init__(self, parent):
724                 super(bedLevelWizardMain, self).__init__(parent, "Bed leveling wizard")
725
726                 self.AddText('This wizard will help you in leveling your printer bed')
727                 self.AddSeperator()
728                 self.AddText('It will do the following steps')
729                 self.AddText('* Move the printer head to each corner')
730                 self.AddText('  and let you adjust the height of the bed to the nozzle')
731                 self.AddText('* Print a line around the bed to check if it is level')
732                 self.AddSeperator()
733
734                 self.connectButton = self.AddButton('Connect to printer')
735                 self.comm = None
736
737                 self.infoBox = self.AddInfoBox()
738                 self.resumeButton = self.AddButton('Resume')
739                 self.resumeButton.Enable(False)
740
741                 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
742                 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
743
744         def OnConnect(self, e = None):
745                 if self.comm is not None:
746                         self.comm.close()
747                         del self.comm
748                         self.comm = None
749                         wx.CallAfter(self.OnConnect)
750                         return
751                 self.connectButton.Enable(False)
752                 self.comm = machineCom.MachineCom(callbackObject=self)
753                 self.infoBox.SetBusy('Connecting to machine.')
754                 self._wizardState = 0
755
756         def AllowNext(self):
757                 return False
758
759         def OnResume(self, e):
760                 feedZ = profile.getProfileSettingFloat('max_z_speed') * 60
761                 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
762                 if self._wizardState == 2:
763                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back left corner...')
764                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
765                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, profile.getPreferenceFloat('machine_depth'), feedTravel))
766                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
767                         self.comm.sendCommand('M400')
768                         self._wizardState = 3
769                 elif self._wizardState == 4:
770                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back right corner...')
771                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
772                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth') - 20, feedTravel))
773                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
774                         self.comm.sendCommand('M400')
775                         self._wizardState = 5
776                 elif self._wizardState == 6:
777                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to front right corner...')
778                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
779                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width'), 20, feedTravel))
780                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
781                         self.comm.sendCommand('M400')
782                         self._wizardState = 7
783                 elif self._wizardState == 8:
784                         wx.CallAfter(self.infoBox.SetBusy, 'Heating up printer...')
785                         self.comm.sendCommand('G1 Z15 F%d' % (feedZ))
786                         self.comm.sendCommand('M104 S%d' % (profile.getProfileSettingFloat('print_temperature')))
787                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, 0, feedTravel))
788                         self._wizardState = 9
789                 self.resumeButton.Enable(False)
790
791         def mcLog(self, message):
792                 print 'Log:', message
793
794         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
795                 if self._wizardState == 1:
796                         self._wizardState = 2
797                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front left screw of your printer bed\nSo the nozzle just hits the bed.')
798                         wx.CallAfter(self.resumeButton.Enable, True)
799                 elif self._wizardState == 3:
800                         self._wizardState = 4
801                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back left screw of your printer bed\nSo the nozzle just hits the bed.')
802                         wx.CallAfter(self.resumeButton.Enable, True)
803                 elif self._wizardState == 5:
804                         self._wizardState = 6
805                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back right screw of your printer bed\nSo the nozzle just hits the bed.')
806                         wx.CallAfter(self.resumeButton.Enable, True)
807                 elif self._wizardState == 7:
808                         self._wizardState = 8
809                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front right screw of your printer bed\nSo the nozzle just hits the bed.')
810                         wx.CallAfter(self.resumeButton.Enable, True)
811                 elif self._wizardState == 9:
812                         if temp < profile.getProfileSettingFloat('print_temperature') - 5:
813                                 wx.CallAfter(self.infoBox.SetInfo, 'Heating up printer: %d/%d' % (temp, profile.getProfileSettingFloat('print_temperature')))
814                         else:
815                                 self._wizardState = 10
816                                 wx.CallAfter(self.infoBox.SetInfo, 'Printing a square on the printer bed at 0.3mm height.')
817                                 feedZ = profile.getProfileSettingFloat('max_z_speed') * 60
818                                 feedPrint = profile.getProfileSettingFloat('print_speed') * 60
819                                 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
820                                 w = profile.getPreferenceFloat('machine_width')
821                                 d = profile.getPreferenceFloat('machine_depth')
822                                 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
823                                 filamentArea = math.pi * filamentRadius * filamentRadius
824                                 ePerMM = (profile.calculateEdgeWidth() * 0.3) / filamentArea
825                                 eValue = 0.0
826
827                                 gcodeList = [
828                                         'G1 Z2 F%d' % (feedZ),
829                                         'G92 E0',
830                                         'G1 X%d Y%d F%d' % (5, 5, feedTravel),
831                                         'G1 Z0.3 F%d' % (feedZ)]
832                                 eValue += 5;
833                                 gcodeList.append('G1 E%f F%d' % (eValue, profile.getProfileSettingFloat('retraction_speed') * 60))
834
835                                 for i in xrange(0, 3):
836                                         dist = 5.0 + 0.4 * i
837                                         eValue += (d - 2*dist) * ePerMM
838                                         gcodeList.append('G1 X%d Y%d E%f F%d' % (dist, d - dist, eValue, feedPrint))
839                                         eValue += (w - 2*dist) * ePerMM
840                                         gcodeList.append('G1 X%d Y%d E%f F%d' % (w - dist, d - dist, eValue, feedPrint))
841                                         eValue += (d - 2*dist) * ePerMM
842                                         gcodeList.append('G1 X%d Y%d E%f F%d' % (w - dist, dist, eValue, feedPrint))
843                                         eValue += (w - 2*dist) * ePerMM
844                                         gcodeList.append('G1 X%d Y%d E%f F%d' % (dist, dist, eValue, feedPrint))
845
846                                 gcodeList.append('M400')
847                                 self.comm.printGCode(gcodeList)
848
849         def mcStateChange(self, state):
850                 if self.comm is None:
851                         return
852                 if self.comm.isOperational():
853                         if self._wizardState == 0:
854                                 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer...')
855                                 self.comm.sendCommand('G28')
856                                 self._wizardState = 1
857                         elif self._wizardState == 10 and not self.comm.isPrinting():
858                                 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('max_z_speed') * 60))
859                                 self.comm.sendCommand('G92 E0')
860                                 self.comm.sendCommand('G1 E-10 F%d' % (profile.getProfileSettingFloat('retraction_speed') * 60))
861                                 self.comm.sendCommand('M104 S0')
862                                 wx.CallAfter(self.infoBox.SetInfo, 'Calibration finished.\nThe squares on the bed should slightly touch each other.')
863                                 wx.CallAfter(self.infoBox.SetReadyIndicator)
864                                 wx.CallAfter(self.GetParent().FindWindowById(wx.ID_FORWARD).Enable)
865                                 wx.CallAfter(self.connectButton.Enable, True)
866                                 self._wizardState = 11
867                 elif self.comm.isError():
868                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
869
870         def mcMessage(self, message):
871                 pass
872
873         def mcProgress(self, lineNr):
874                 pass
875
876         def mcZChange(self, newZ):
877                 pass
878
879 class bedLevelWizard(wx.wizard.Wizard):
880         def __init__(self):
881                 super(bedLevelWizard, self).__init__(None, -1, "Bed leveling wizard")
882
883                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
884                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
885
886                 self.mainPage = bedLevelWizardMain(self)
887
888                 self.FitToPage(self.mainPage)
889                 self.GetPageAreaSizer().Add(self.mainPage)
890
891                 self.RunWizard(self.mainPage)
892                 self.Destroy()
893
894         def OnPageChanging(self, e):
895                 e.GetPage().StoreData()
896
897         def OnPageChanged(self, e):
898                 if e.GetPage().AllowNext():
899                         self.FindWindowById(wx.ID_FORWARD).Enable()
900                 else:
901                         self.FindWindowById(wx.ID_FORWARD).Disable()
902                 self.FindWindowById(wx.ID_BACKWARD).Disable()