chiark / gitweb /
Remove WIP
[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 import gcodeGenerator
17 from Cura.util.resources import getPathForImage
18
19 class InfoBox(wx.Panel):
20         def __init__(self, parent):
21                 super(InfoBox, self).__init__(parent)
22                 self.SetBackgroundColour('#FFFF80')
23
24                 self.sizer = wx.GridBagSizer(5, 5)
25                 self.SetSizer(self.sizer)
26
27                 self.attentionBitmap = wx.Bitmap(getPathForImage('attention.png'))
28                 self.errorBitmap = wx.Bitmap(getPathForImage('error.png'))
29                 self.readyBitmap = wx.Bitmap(getPathForImage('ready.png'))
30                 self.busyBitmap = [
31                         wx.Bitmap(getPathForImage('busy-0.png')),
32                         wx.Bitmap(getPathForImage('busy-1.png')),
33                         wx.Bitmap(getPathForImage('busy-2.png')),
34                         wx.Bitmap(getPathForImage('busy-3.png'))
35                 ]
36
37                 self.bitmap = wx.StaticBitmap(self, -1, wx.EmptyBitmapRGBA(24, 24, red=255, green=255, blue=255, alpha=1))
38                 self.text = wx.StaticText(self, -1, '')
39                 self.extraInfoButton = wx.Button(self, -1, 'i', style=wx.BU_EXACTFIT)
40                 self.sizer.Add(self.bitmap, pos=(0, 0), flag=wx.ALL, border=5)
41                 self.sizer.Add(self.text, pos=(0, 1), flag=wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, border=5)
42                 self.sizer.Add(self.extraInfoButton, pos=(0,2), flag=wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, border=5)
43                 self.sizer.AddGrowableCol(1)
44
45                 self.extraInfoButton.Show(False)
46
47                 self.extraInfoUrl = ''
48                 self.busyState = None
49                 self.timer = wx.Timer(self)
50                 self.Bind(wx.EVT_TIMER, self.doBusyUpdate, self.timer)
51                 self.Bind(wx.EVT_BUTTON, self.doExtraInfo, self.extraInfoButton)
52                 self.timer.Start(100)
53
54         def SetInfo(self, info):
55                 self.SetBackgroundColour('#FFFF80')
56                 self.text.SetLabel(info)
57                 self.extraInfoButton.Show(False)
58                 self.Refresh()
59
60         def SetError(self, info, extraInfoUrl):
61                 self.extraInfoUrl = extraInfoUrl
62                 self.SetBackgroundColour('#FF8080')
63                 self.text.SetLabel(info)
64                 self.extraInfoButton.Show(True)
65                 self.Layout()
66                 self.SetErrorIndicator()
67                 self.Refresh()
68
69         def SetAttention(self, info):
70                 self.SetBackgroundColour('#FFFF80')
71                 self.text.SetLabel(info)
72                 self.extraInfoButton.Show(False)
73                 self.SetAttentionIndicator()
74                 self.Layout()
75                 self.Refresh()
76
77         def SetBusy(self, info):
78                 self.SetInfo(info)
79                 self.SetBusyIndicator()
80
81         def SetBusyIndicator(self):
82                 self.busyState = 0
83                 self.bitmap.SetBitmap(self.busyBitmap[self.busyState])
84
85         def doExtraInfo(self, e):
86                 webbrowser.open(self.extraInfoUrl)
87
88         def doBusyUpdate(self, e):
89                 if self.busyState is None:
90                         return
91                 self.busyState += 1
92                 if self.busyState >= len(self.busyBitmap):
93                         self.busyState = 0
94                 self.bitmap.SetBitmap(self.busyBitmap[self.busyState])
95
96         def SetReadyIndicator(self):
97                 self.busyState = None
98                 self.bitmap.SetBitmap(self.readyBitmap)
99
100         def SetErrorIndicator(self):
101                 self.busyState = None
102                 self.bitmap.SetBitmap(self.errorBitmap)
103
104         def SetAttentionIndicator(self):
105                 self.busyState = None
106                 self.bitmap.SetBitmap(self.attentionBitmap)
107
108
109 class InfoPage(wx.wizard.WizardPageSimple):
110         def __init__(self, parent, title):
111                 wx.wizard.WizardPageSimple.__init__(self, parent)
112
113                 sizer = wx.GridBagSizer(5, 5)
114                 self.sizer = sizer
115                 self.SetSizer(sizer)
116
117                 title = wx.StaticText(self, -1, title)
118                 title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
119                 sizer.Add(title, pos=(0, 0), span=(1, 2), flag=wx.ALIGN_CENTRE | wx.ALL)
120                 sizer.Add(wx.StaticLine(self, -1), pos=(1, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
121                 sizer.AddGrowableCol(1)
122
123                 self.rowNr = 2
124
125         def AddText(self, info):
126                 text = wx.StaticText(self, -1, info)
127                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT)
128                 self.rowNr += 1
129                 return text
130
131         def AddSeperator(self):
132                 self.GetSizer().Add(wx.StaticLine(self, -1), pos=(self.rowNr, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
133                 self.rowNr += 1
134
135         def AddHiddenSeperator(self):
136                 self.AddText('')
137
138         def AddInfoBox(self):
139                 infoBox = InfoBox(self)
140                 self.GetSizer().Add(infoBox, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT | wx.EXPAND)
141                 self.rowNr += 1
142                 return infoBox
143
144         def AddRadioButton(self, label, style=0):
145                 radio = wx.RadioButton(self, -1, label, style=style)
146                 self.GetSizer().Add(radio, pos=(self.rowNr, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
147                 self.rowNr += 1
148                 return radio
149
150         def AddCheckbox(self, label, checked=False):
151                 check = wx.CheckBox(self, -1)
152                 text = wx.StaticText(self, -1, label)
153                 check.SetValue(checked)
154                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT | wx.RIGHT)
155                 self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1, 2), flag=wx.ALL)
156                 self.rowNr += 1
157                 return check
158
159         def AddButton(self, label):
160                 button = wx.Button(self, -1, label)
161                 self.GetSizer().Add(button, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT)
162                 self.rowNr += 1
163                 return button
164
165         def AddDualButton(self, label1, label2):
166                 button1 = wx.Button(self, -1, label1)
167                 self.GetSizer().Add(button1, pos=(self.rowNr, 0), flag=wx.RIGHT)
168                 button2 = wx.Button(self, -1, label2)
169                 self.GetSizer().Add(button2, pos=(self.rowNr, 1))
170                 self.rowNr += 1
171                 return button1, button2
172
173         def AddTextCtrl(self, value):
174                 ret = wx.TextCtrl(self, -1, value)
175                 self.GetSizer().Add(ret, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT)
176                 self.rowNr += 1
177                 return ret
178
179         def AddLabelTextCtrl(self, info, value):
180                 text = wx.StaticText(self, -1, info)
181                 ret = wx.TextCtrl(self, -1, value)
182                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT)
183                 self.GetSizer().Add(ret, pos=(self.rowNr, 1), span=(1, 1), flag=wx.LEFT)
184                 self.rowNr += 1
185                 return ret
186
187         def AddTextCtrlButton(self, value, buttonText):
188                 text = wx.TextCtrl(self, -1, value)
189                 button = wx.Button(self, -1, buttonText)
190                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT)
191                 self.GetSizer().Add(button, pos=(self.rowNr, 1), span=(1, 1), flag=wx.LEFT)
192                 self.rowNr += 1
193                 return text, button
194
195         def AddBitmap(self, bitmap):
196                 bitmap = wx.StaticBitmap(self, -1, bitmap)
197                 self.GetSizer().Add(bitmap, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT)
198                 self.rowNr += 1
199                 return bitmap
200
201         def AddCheckmark(self, label, bitmap):
202                 check = wx.StaticBitmap(self, -1, bitmap)
203                 text = wx.StaticText(self, -1, label)
204                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT | wx.RIGHT)
205                 self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1, 1), flag=wx.ALL)
206                 self.rowNr += 1
207                 return check
208
209         def AllowNext(self):
210                 return True
211
212         def StoreData(self):
213                 pass
214
215
216 class FirstInfoPage(InfoPage):
217         def __init__(self, parent):
218                 super(FirstInfoPage, self).__init__(parent, "First time run wizard")
219                 self.AddText('Welcome, and thanks for trying Cura!')
220                 self.AddSeperator()
221                 self.AddText('This wizard will help you with the following steps:')
222                 self.AddText('* Configure Cura for your machine')
223                 self.AddText('* Upgrade your firmware')
224                 self.AddText('* Check if your machine is working safely')
225                 self.AddText('* Level your printer bed')
226
227                 #self.AddText('* Calibrate your machine')
228                 #self.AddText('* Do your first print')
229
230
231 class RepRapInfoPage(InfoPage):
232         def __init__(self, parent):
233                 super(RepRapInfoPage, self).__init__(parent, "RepRap information")
234                 self.AddText(
235                         'RepRap machines are vastly different, and there is no\ndefault configuration in Cura for any of them.')
236                 self.AddText('If you like a default profile for your machine added,\nthen make an issue on github.')
237                 self.AddSeperator()
238                 self.AddText('You will have to manually install Marlin or Sprinter firmware.')
239                 self.AddSeperator()
240                 self.machineWidth = self.AddLabelTextCtrl('Machine width (mm)', '80')
241                 self.machineDepth = self.AddLabelTextCtrl('Machine depth (mm)', '80')
242                 self.machineHeight = self.AddLabelTextCtrl('Machine height (mm)', '60')
243                 self.nozzleSize = self.AddLabelTextCtrl('Nozzle size (mm)', '0.5')
244                 self.heatedBed = self.AddCheckbox('Heated bed')
245                 self.HomeAtCenter = self.AddCheckbox('Bed center is 0,0,0 (RoStock)')
246
247         def StoreData(self):
248                 profile.putPreference('machine_width', self.machineWidth.GetValue())
249                 profile.putPreference('machine_depth', self.machineDepth.GetValue())
250                 profile.putPreference('machine_height', self.machineHeight.GetValue())
251                 profile.putProfileSetting('nozzle_size', self.nozzleSize.GetValue())
252                 profile.putProfileSetting('wall_thickness', float(profile.getProfileSettingFloat('nozzle_size')) * 2)
253                 profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))
254                 profile.putPreference('machine_center_is_zero', str(self.HomeAtCenter.GetValue()))
255                 profile.putPreference('extruder_head_size_min_x', '0')
256                 profile.putPreference('extruder_head_size_min_y', '0')
257                 profile.putPreference('extruder_head_size_max_x', '0')
258                 profile.putPreference('extruder_head_size_max_y', '0')
259                 profile.putPreference('extruder_head_size_height', '0')
260
261
262 class MachineSelectPage(InfoPage):
263         def __init__(self, parent):
264                 super(MachineSelectPage, self).__init__(parent, "Select your machine")
265                 self.AddText('What kind of machine do you have:')
266
267                 self.UltimakerRadio = self.AddRadioButton("Ultimaker", style=wx.RB_GROUP)
268                 self.UltimakerRadio.SetValue(True)
269                 self.UltimakerRadio.Bind(wx.EVT_RADIOBUTTON, self.OnUltimakerSelect)
270                 self.OtherRadio = self.AddRadioButton("Other (Ex: RepRap)")
271                 self.OtherRadio.Bind(wx.EVT_RADIOBUTTON, self.OnOtherSelect)
272                 self.AddSeperator()
273                 self.AddText('The collection of anonymous usage information helps with the continued improvement of Cura.')
274                 self.AddText('This does NOT submit your models online nor gathers any privacy related information.')
275                 self.SubmitUserStats = self.AddCheckbox('Submit anonymous usage information:')
276                 self.AddText('For full details see: http://wiki.ultimaker.com/Cura:stats')
277                 self.SubmitUserStats.SetValue(True)
278
279         def OnUltimakerSelect(self, e):
280                 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().ultimakerFirmwareUpgradePage)
281
282         def OnOtherSelect(self, e):
283                 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().repRapInfoPage)
284
285         def StoreData(self):
286                 if self.UltimakerRadio.GetValue():
287                         profile.putPreference('machine_width', '205')
288                         profile.putPreference('machine_depth', '205')
289                         profile.putPreference('machine_height', '200')
290                         profile.putPreference('machine_type', 'ultimaker')
291                         profile.putPreference('machine_center_is_zero', 'False')
292                         profile.putProfileSetting('nozzle_size', '0.4')
293                         profile.putPreference('extruder_head_size_min_x', '75.0')
294                         profile.putPreference('extruder_head_size_min_y', '18.0')
295                         profile.putPreference('extruder_head_size_max_x', '18.0')
296                         profile.putPreference('extruder_head_size_max_y', '35.0')
297                         profile.putPreference('extruder_head_size_height', '60.0')
298                 else:
299                         profile.putPreference('machine_width', '80')
300                         profile.putPreference('machine_depth', '80')
301                         profile.putPreference('machine_height', '60')
302                         profile.putPreference('machine_type', 'reprap')
303                         profile.putPreference('startMode', 'Normal')
304                         profile.putProfileSetting('nozzle_size', '0.5')
305                 profile.putProfileSetting('wall_thickness', float(profile.getProfileSetting('nozzle_size')) * 2)
306                 if self.SubmitUserStats.GetValue():
307                         profile.putPreference('submit_slice_information', 'True')
308                 else:
309                         profile.putPreference('submit_slice_information', 'False')
310
311 class SelectParts(InfoPage):
312         def __init__(self, parent):
313                 super(SelectParts, self).__init__(parent, "Select upgraded parts you have")
314                 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.')
315                 self.AddSeperator()
316                 self.springExtruder = self.AddCheckbox('Extruder drive upgrade')
317                 self.heatedBed = self.AddCheckbox('Heated printer bed (self built)')
318                 self.dualExtrusion = self.AddCheckbox('Dual extrusion (experimental)')
319                 self.AddSeperator()
320                 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.')
321                 self.AddText('This upgrade can be bought from the Ultimaker webshop\nor found on thingiverse as thing:26094')
322                 self.springExtruder.SetValue(True)
323
324         def StoreData(self):
325                 profile.putPreference('ultimaker_extruder_upgrade', str(self.springExtruder.GetValue()))
326                 profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))
327                 if self.dualExtrusion.GetValue():
328                         profile.putPreference('extruder_amount', '2')
329                 else:
330                         profile.putPreference('extruder_amount', '1')
331                 if profile.getPreference('ultimaker_extruder_upgrade') == 'True':
332                         profile.putProfileSetting('retraction_enable', 'True')
333                 else:
334                         profile.putProfileSetting('retraction_enable', 'False')
335
336
337 class FirmwareUpgradePage(InfoPage):
338         def __init__(self, parent):
339                 super(FirmwareUpgradePage, self).__init__(parent, "Upgrade Ultimaker Firmware")
340                 self.AddText(
341                         '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.')
342                 self.AddHiddenSeperator()
343                 self.AddText(
344                         'The firmware shipping with new Ultimakers works, but upgrades\nhave been made to make better prints, and make calibration easier.')
345                 self.AddHiddenSeperator()
346                 self.AddText(
347                         '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.')
348                 upgradeButton, skipUpgradeButton = self.AddDualButton('Upgrade to Marlin firmware', 'Skip upgrade')
349                 upgradeButton.Bind(wx.EVT_BUTTON, self.OnUpgradeClick)
350                 skipUpgradeButton.Bind(wx.EVT_BUTTON, self.OnSkipClick)
351                 self.AddHiddenSeperator()
352                 self.AddText('Do not upgrade to this firmware if:')
353                 self.AddText('* You have an older machine based on ATMega1280')
354                 self.AddText('* Have other changes in the firmware')
355 #               button = self.AddButton('Goto this page for a custom firmware')
356 #               button.Bind(wx.EVT_BUTTON, self.OnUrlClick)
357
358         def AllowNext(self):
359                 return False
360
361         def OnUpgradeClick(self, e):
362                 if firmwareInstall.InstallFirmware():
363                         self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
364
365         def OnSkipClick(self, e):
366                 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
367                 self.GetParent().ShowPage(self.GetNext())
368
369         def OnUrlClick(self, e):
370                 webbrowser.open('http://daid.mine.nu/~daid/marlin_build/')
371
372
373 class UltimakerCheckupPage(InfoPage):
374         def __init__(self, parent):
375                 super(UltimakerCheckupPage, self).__init__(parent, "Ultimaker Checkup")
376
377                 self.checkBitmap = wx.Bitmap(getPathForImage('checkmark.png'))
378                 self.crossBitmap = wx.Bitmap(getPathForImage('cross.png'))
379                 self.unknownBitmap = wx.Bitmap(getPathForImage('question.png'))
380                 self.endStopNoneBitmap = wx.Bitmap(getPathForImage('endstop_none.png'))
381                 self.endStopXMinBitmap = wx.Bitmap(getPathForImage('endstop_xmin.png'))
382                 self.endStopXMaxBitmap = wx.Bitmap(getPathForImage('endstop_xmax.png'))
383                 self.endStopYMinBitmap = wx.Bitmap(getPathForImage('endstop_ymin.png'))
384                 self.endStopYMaxBitmap = wx.Bitmap(getPathForImage('endstop_ymax.png'))
385                 self.endStopZMinBitmap = wx.Bitmap(getPathForImage('endstop_zmin.png'))
386                 self.endStopZMaxBitmap = wx.Bitmap(getPathForImage('endstop_zmax.png'))
387
388                 self.AddText(
389                         '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.')
390                 b1, b2 = self.AddDualButton('Run checks', 'Skip checks')
391                 b1.Bind(wx.EVT_BUTTON, self.OnCheckClick)
392                 b2.Bind(wx.EVT_BUTTON, self.OnSkipClick)
393                 self.AddSeperator()
394                 self.commState = self.AddCheckmark('Communication:', self.unknownBitmap)
395                 self.tempState = self.AddCheckmark('Temperature:', self.unknownBitmap)
396                 self.stopState = self.AddCheckmark('Endstops:', self.unknownBitmap)
397                 self.AddSeperator()
398                 self.infoBox = self.AddInfoBox()
399                 self.machineState = self.AddText('')
400                 self.temperatureLabel = self.AddText('')
401                 self.errorLogButton = self.AddButton('Show error log')
402                 self.errorLogButton.Show(False)
403                 self.AddSeperator()
404                 self.endstopBitmap = self.AddBitmap(self.endStopNoneBitmap)
405                 self.comm = None
406                 self.xMinStop = False
407                 self.xMaxStop = False
408                 self.yMinStop = False
409                 self.yMaxStop = False
410                 self.zMinStop = False
411                 self.zMaxStop = False
412
413                 self.Bind(wx.EVT_BUTTON, self.OnErrorLog, self.errorLogButton)
414
415         def __del__(self):
416                 if self.comm is not None:
417                         self.comm.close()
418
419         def AllowNext(self):
420                 self.endstopBitmap.Show(False)
421                 return False
422
423         def OnSkipClick(self, e):
424                 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
425                 self.GetParent().ShowPage(self.GetNext())
426
427         def OnCheckClick(self, e=None):
428                 self.errorLogButton.Show(False)
429                 if self.comm is not None:
430                         self.comm.close()
431                         del self.comm
432                         self.comm = None
433                         wx.CallAfter(self.OnCheckClick)
434                         return
435                 self.infoBox.SetBusy('Connecting to machine.')
436                 self.commState.SetBitmap(self.unknownBitmap)
437                 self.tempState.SetBitmap(self.unknownBitmap)
438                 self.stopState.SetBitmap(self.unknownBitmap)
439                 self.checkupState = 0
440                 self.comm = machineCom.MachineCom(callbackObject=self)
441
442         def OnErrorLog(self, e):
443                 printWindow.LogWindow('\n'.join(self.comm.getLog()))
444
445         def mcLog(self, message):
446                 pass
447
448         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
449                 if not self.comm.isOperational():
450                         return
451                 if self.checkupState == 0:
452                         self.tempCheckTimeout = 20
453                         if temp[self.checkExtruderNr] > 70:
454                                 self.checkupState = 1
455                                 wx.CallAfter(self.infoBox.SetInfo, 'Cooldown before temperature check.')
456                                 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
457                                 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
458                         else:
459                                 self.startTemp = temp[self.checkExtruderNr]
460                                 self.checkupState = 2
461                                 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
462                                 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
463                                 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
464                 elif self.checkupState == 1:
465                         if temp < 60:
466                                 self.startTemp = temp[self.checkExtruderNr]
467                                 self.checkupState = 2
468                                 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
469                                 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
470                                 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
471                 elif self.checkupState == 2:
472                         #print "WARNING, TEMPERATURE TEST DISABLED FOR TESTING!"
473                         if temp[self.checkExtruderNr] > self.startTemp + 40:
474                                 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
475                                 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
476                                 if self.checkExtruderNr < int(profile.getPreference('extruder_amount')):
477                                         self.checkExtruderNr = 0
478                                         self.checkupState = 3
479                                         wx.CallAfter(self.infoBox.SetAttention, 'Please make sure none of the endstops are pressed.')
480                                         wx.CallAfter(self.endstopBitmap.Show, True)
481                                         wx.CallAfter(self.Layout)
482                                         self.comm.sendCommand('M119')
483                                         wx.CallAfter(self.tempState.SetBitmap, self.checkBitmap)
484                                 else:
485                                         self.checkupState = 0
486                                         self.checkExtruderNr += 1
487                         else:
488                                 self.tempCheckTimeout -= 1
489                                 if self.tempCheckTimeout < 1:
490                                         self.checkupState = -1
491                                         wx.CallAfter(self.tempState.SetBitmap, self.crossBitmap)
492                                         wx.CallAfter(self.infoBox.SetError, 'Temperature measurement FAILED!', 'http://wiki.ultimaker.com/Cura:_Temperature_measurement_problems')
493                                         self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
494                                         self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
495                 elif self.checkupState >= 3 and self.checkupState < 10:
496                         self.comm.sendCommand('M119')
497                 wx.CallAfter(self.temperatureLabel.SetLabel, 'Head temperature: %d' % (temp[self.checkExtruderNr]))
498
499         def mcStateChange(self, state):
500                 if self.comm is None:
501                         return
502                 if self.comm.isOperational():
503                         wx.CallAfter(self.commState.SetBitmap, self.checkBitmap)
504                         wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
505                 elif self.comm.isError():
506                         wx.CallAfter(self.commState.SetBitmap, self.crossBitmap)
507                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
508                         wx.CallAfter(self.endstopBitmap.Show, False)
509                         wx.CallAfter(self.machineState.SetLabel, '%s' % (self.comm.getErrorString()))
510                         wx.CallAfter(self.errorLogButton.Show, True)
511                         wx.CallAfter(self.Layout)
512                 else:
513                         wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
514
515         def mcMessage(self, message):
516                 if self.checkupState >= 3 and self.checkupState < 10 and ('_min' in message or '_max' in message):
517                         for data in message.split(' '):
518                                 if ':' in data:
519                                         tag, value = data.split(':', 1)
520                                         if tag == 'x_min':
521                                                 self.xMinStop = (value == 'H' or value == 'TRIGGERED')
522                                         if tag == 'x_max':
523                                                 self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
524                                         if tag == 'y_min':
525                                                 self.yMinStop = (value == 'H' or value == 'TRIGGERED')
526                                         if tag == 'y_max':
527                                                 self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
528                                         if tag == 'z_min':
529                                                 self.zMinStop = (value == 'H' or value == 'TRIGGERED')
530                                         if tag == 'z_max':
531                                                 self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
532                         if ':' in message:
533                                 tag, value = map(str.strip, message.split(':', 1))
534                                 if tag == 'x_min':
535                                         self.xMinStop = (value == 'H' or value == 'TRIGGERED')
536                                 if tag == 'x_max':
537                                         self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
538                                 if tag == 'y_min':
539                                         self.yMinStop = (value == 'H' or value == 'TRIGGERED')
540                                 if tag == 'y_max':
541                                         self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
542                                 if tag == 'z_min':
543                                         self.zMinStop = (value == 'H' or value == 'TRIGGERED')
544                                 if tag == 'z_max':
545                                         self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
546                         if 'z_max' in message:
547                                 self.comm.sendCommand('M119')
548
549                         if self.checkupState == 3:
550                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
551                                         self.checkupState = 4
552                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the right X endstop.')
553                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMaxBitmap)
554                         elif self.checkupState == 4:
555                                 if not self.xMinStop and self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
556                                         self.checkupState = 5
557                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the left X endstop.')
558                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMinBitmap)
559                         elif self.checkupState == 5:
560                                 if self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
561                                         self.checkupState = 6
562                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the front Y endstop.')
563                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMinBitmap)
564                         elif self.checkupState == 6:
565                                 if not self.xMinStop and not self.xMaxStop and self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
566                                         self.checkupState = 7
567                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the back Y endstop.')
568                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMaxBitmap)
569                         elif self.checkupState == 7:
570                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and self.yMaxStop and not self.zMinStop and not self.zMaxStop:
571                                         self.checkupState = 8
572                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the top Z endstop.')
573                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMinBitmap)
574                         elif self.checkupState == 8:
575                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and self.zMinStop and not self.zMaxStop:
576                                         self.checkupState = 9
577                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the bottom Z endstop.')
578                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMaxBitmap)
579                         elif self.checkupState == 9:
580                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and self.zMaxStop:
581                                         self.checkupState = 10
582                                         self.comm.close()
583                                         wx.CallAfter(self.infoBox.SetInfo, 'Checkup finished')
584                                         wx.CallAfter(self.infoBox.SetReadyIndicator)
585                                         wx.CallAfter(self.endstopBitmap.Show, False)
586                                         wx.CallAfter(self.stopState.SetBitmap, self.checkBitmap)
587                                         wx.CallAfter(self.OnSkipClick, None)
588
589         def mcProgress(self, lineNr):
590                 pass
591
592         def mcZChange(self, newZ):
593                 pass
594
595
596 class UltimakerCalibrationPage(InfoPage):
597         def __init__(self, parent):
598                 super(UltimakerCalibrationPage, self).__init__(parent, "Ultimaker Calibration")
599
600                 self.AddText("Your Ultimaker requires some calibration.")
601                 self.AddText("This calibration is needed for a proper extrusion amount.")
602                 self.AddSeperator()
603                 self.AddText("The following values are needed:")
604                 self.AddText("* Diameter of filament")
605                 self.AddText("* Number of steps per mm of filament extrusion")
606                 self.AddSeperator()
607                 self.AddText("The better you have calibrated these values, the better your prints\nwill become.")
608                 self.AddSeperator()
609                 self.AddText("First we need the diameter of your filament:")
610                 self.filamentDiameter = self.AddTextCtrl(profile.getProfileSetting('filament_diameter'))
611                 self.AddText(
612                         "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.")
613                 self.AddText("Note: This value can be changed later at any time.")
614
615         def StoreData(self):
616                 profile.putProfileSetting('filament_diameter', self.filamentDiameter.GetValue())
617
618
619 class UltimakerCalibrateStepsPerEPage(InfoPage):
620         def __init__(self, parent):
621                 super(UltimakerCalibrateStepsPerEPage, self).__init__(parent, "Ultimaker Calibration")
622
623                 #if profile.getPreference('steps_per_e') == '0':
624                 #       profile.putPreference('steps_per_e', '865.888')
625
626                 self.AddText("Calibrating the Steps Per E requires some manual actions.")
627                 self.AddText("First remove any filament from your machine.")
628                 self.AddText("Next put in your filament so the tip is aligned with the\ntop of the extruder drive.")
629                 self.AddText("We'll push the filament 100mm")
630                 self.extrudeButton = self.AddButton("Extrude 100mm filament")
631                 self.AddText("Now measure the amount of extruded filament:\n(this can be more or less then 100mm)")
632                 self.lengthInput, self.saveLengthButton = self.AddTextCtrlButton('100', 'Save')
633                 self.AddText("This results in the following steps per E:")
634                 self.stepsPerEInput = self.AddTextCtrl(profile.getPreference('steps_per_e'))
635                 self.AddText("You can repeat these steps to get better calibration.")
636                 self.AddSeperator()
637                 self.AddText(
638                         "If you still have filament in your printer which needs\nheat to remove, press the heat up button below:")
639                 self.heatButton = self.AddButton("Heatup for filament removal")
640
641                 self.saveLengthButton.Bind(wx.EVT_BUTTON, self.OnSaveLengthClick)
642                 self.extrudeButton.Bind(wx.EVT_BUTTON, self.OnExtrudeClick)
643                 self.heatButton.Bind(wx.EVT_BUTTON, self.OnHeatClick)
644
645         def OnSaveLengthClick(self, e):
646                 currentEValue = float(self.stepsPerEInput.GetValue())
647                 realExtrudeLength = float(self.lengthInput.GetValue())
648                 newEValue = currentEValue * 100 / realExtrudeLength
649                 self.stepsPerEInput.SetValue(str(newEValue))
650                 self.lengthInput.SetValue("100")
651
652         def OnExtrudeClick(self, e):
653                 threading.Thread(target=self.OnExtrudeRun).start()
654
655         def OnExtrudeRun(self):
656                 self.heatButton.Enable(False)
657                 self.extrudeButton.Enable(False)
658                 currentEValue = float(self.stepsPerEInput.GetValue())
659                 self.comm = machineCom.MachineCom()
660                 if not self.comm.isOpen():
661                         wx.MessageBox(
662                                 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
663                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
664                         self.heatButton.Enable(True)
665                         self.extrudeButton.Enable(True)
666                         return
667                 while True:
668                         line = self.comm.readline()
669                         if line == '':
670                                 return
671                         if 'start' in line:
672                                 break
673                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
674                 time.sleep(3)
675
676                 self.sendGCommand('M302') #Disable cold extrusion protection
677                 self.sendGCommand("M92 E%f" % (currentEValue))
678                 self.sendGCommand("G92 E0")
679                 self.sendGCommand("G1 E100 F600")
680                 time.sleep(15)
681                 self.comm.close()
682                 self.extrudeButton.Enable()
683                 self.heatButton.Enable()
684
685         def OnHeatClick(self, e):
686                 threading.Thread(target=self.OnHeatRun).start()
687
688         def OnHeatRun(self):
689                 self.heatButton.Enable(False)
690                 self.extrudeButton.Enable(False)
691                 self.comm = machineCom.MachineCom()
692                 if not self.comm.isOpen():
693                         wx.MessageBox(
694                                 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
695                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
696                         self.heatButton.Enable(True)
697                         self.extrudeButton.Enable(True)
698                         return
699                 while True:
700                         line = self.comm.readline()
701                         if line == '':
702                                 self.heatButton.Enable(True)
703                                 self.extrudeButton.Enable(True)
704                                 return
705                         if 'start' in line:
706                                 break
707                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
708                 time.sleep(3)
709
710                 self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.
711                 wx.MessageBox(
712                         'Wait till you can remove the filament from the machine, and press OK.\n(Temperature is set to 200C)',
713                         'Machine heatup', wx.OK | wx.ICON_INFORMATION)
714                 self.sendGCommand('M104 S0')
715                 time.sleep(1)
716                 self.comm.close()
717                 self.heatButton.Enable(True)
718                 self.extrudeButton.Enable(True)
719
720         def sendGCommand(self, cmd):
721                 self.comm.sendCommand(cmd) #Disable cold extrusion protection
722                 while True:
723                         line = self.comm.readline()
724                         if line == '':
725                                 return
726                         if line.startswith('ok'):
727                                 break
728
729         def StoreData(self):
730                 profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())
731
732
733 class configWizard(wx.wizard.Wizard):
734         def __init__(self):
735                 super(configWizard, self).__init__(None, -1, "Configuration Wizard")
736
737                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
738                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
739
740                 self.firstInfoPage = FirstInfoPage(self)
741                 self.machineSelectPage = MachineSelectPage(self)
742                 self.ultimakerSelectParts = SelectParts(self)
743                 self.ultimakerFirmwareUpgradePage = FirmwareUpgradePage(self)
744                 self.ultimakerCheckupPage = UltimakerCheckupPage(self)
745                 self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)
746                 self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)
747                 self.bedLevelPage = bedLevelWizardMain(self)
748                 self.headOffsetCalibration = headOffsetCalibrationPage(self)
749                 self.repRapInfoPage = RepRapInfoPage(self)
750
751                 wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)
752                 wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerSelectParts)
753                 wx.wizard.WizardPageSimple.Chain(self.ultimakerSelectParts, self.ultimakerFirmwareUpgradePage)
754                 wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)
755                 wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.bedLevelPage)
756                 #wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)
757
758                 self.FitToPage(self.firstInfoPage)
759                 self.GetPageAreaSizer().Add(self.firstInfoPage)
760
761                 self.RunWizard(self.firstInfoPage)
762                 self.Destroy()
763
764         def OnPageChanging(self, e):
765                 e.GetPage().StoreData()
766
767         def OnPageChanged(self, e):
768                 if e.GetPage().AllowNext():
769                         self.FindWindowById(wx.ID_FORWARD).Enable()
770                 else:
771                         self.FindWindowById(wx.ID_FORWARD).Disable()
772                 self.FindWindowById(wx.ID_BACKWARD).Disable()
773
774 class bedLevelWizardMain(InfoPage):
775         def __init__(self, parent):
776                 super(bedLevelWizardMain, self).__init__(parent, "Bed leveling wizard")
777
778                 self.AddText('This wizard will help you in leveling your printer bed')
779                 self.AddSeperator()
780                 self.AddText('It will do the following steps')
781                 self.AddText('* Move the printer head to each corner')
782                 self.AddText('  and let you adjust the height of the bed to the nozzle')
783                 self.AddText('* Print a line around the bed to check if it is level')
784                 self.AddSeperator()
785
786                 self.connectButton = self.AddButton('Connect to printer')
787                 self.comm = None
788
789                 self.infoBox = self.AddInfoBox()
790                 self.resumeButton = self.AddButton('Resume')
791                 self.resumeButton.Enable(False)
792
793                 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
794                 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
795
796         def OnConnect(self, e = None):
797                 if self.comm is not None:
798                         self.comm.close()
799                         del self.comm
800                         self.comm = None
801                         wx.CallAfter(self.OnConnect)
802                         return
803                 self.connectButton.Enable(False)
804                 self.comm = machineCom.MachineCom(callbackObject=self)
805                 self.infoBox.SetBusy('Connecting to machine.')
806                 self._wizardState = 0
807
808         def AllowNext(self):
809                 if self.GetParent().headOffsetCalibration is not None and int(profile.getPreference('extruder_amount')) > 1:
810                         wx.wizard.WizardPageSimple.Chain(self, self.GetParent().headOffsetCalibration)
811                 return True
812
813         def OnResume(self, e):
814                 feedZ = profile.getProfileSettingFloat('print_speed') * 60
815                 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
816                 if self._wizardState == 2:
817                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back left corner...')
818                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
819                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, profile.getPreferenceFloat('machine_depth'), feedTravel))
820                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
821                         self.comm.sendCommand('M400')
822                         self._wizardState = 3
823                 elif self._wizardState == 4:
824                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back right corner...')
825                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
826                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width') - 5.0, profile.getPreferenceFloat('machine_depth') - 25, feedTravel))
827                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
828                         self.comm.sendCommand('M400')
829                         self._wizardState = 5
830                 elif self._wizardState == 6:
831                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to front right corner...')
832                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
833                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width') - 5.0, 20, feedTravel))
834                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
835                         self.comm.sendCommand('M400')
836                         self._wizardState = 7
837                 elif self._wizardState == 8:
838                         wx.CallAfter(self.infoBox.SetBusy, 'Heating up printer...')
839                         self.comm.sendCommand('G1 Z15 F%d' % (feedZ))
840                         self.comm.sendCommand('M104 S%d' % (profile.getProfileSettingFloat('print_temperature')))
841                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, 0, feedTravel))
842                         self._wizardState = 9
843                 elif self._wizardState == 10:
844                         self._wizardState = 11
845                         wx.CallAfter(self.infoBox.SetInfo, 'Printing a square on the printer bed at 0.3mm height.')
846                         feedZ = profile.getProfileSettingFloat('print_speed') * 60
847                         feedPrint = profile.getProfileSettingFloat('print_speed') * 60
848                         feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
849                         w = profile.getPreferenceFloat('machine_width')
850                         d = profile.getPreferenceFloat('machine_depth')
851                         filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
852                         filamentArea = math.pi * filamentRadius * filamentRadius
853                         ePerMM = (profile.calculateEdgeWidth() * 0.3) / filamentArea
854                         eValue = 0.0
855
856                         gcodeList = [
857                                 'G1 Z2 F%d' % (feedZ),
858                                 'G92 E0',
859                                 'G1 X%d Y%d F%d' % (5, 5, feedTravel),
860                                 'G1 Z0.3 F%d' % (feedZ)]
861                         eValue += 5.0
862                         gcodeList.append('G1 E%f F%d' % (eValue, profile.getProfileSettingFloat('retraction_speed') * 60))
863
864                         for i in xrange(0, 3):
865                                 dist = 5.0 + 0.4 * float(i)
866                                 eValue += (d - 2.0*dist) * ePerMM
867                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (dist, d - dist, eValue, feedPrint))
868                                 eValue += (w - 2.0*dist) * ePerMM
869                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (w - dist, d - dist, eValue, feedPrint))
870                                 eValue += (d - 2.0*dist) * ePerMM
871                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (w - dist, dist, eValue, feedPrint))
872                                 eValue += (w - 2.0*dist) * ePerMM
873                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (dist, dist, eValue, feedPrint))
874
875                         gcodeList.append('M400')
876                         self.comm.printGCode(gcodeList)
877                 self.resumeButton.Enable(False)
878
879         def mcLog(self, message):
880                 print 'Log:', message
881
882         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
883                 if self._wizardState == 1:
884                         self._wizardState = 2
885                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front left screw of your printer bed\nSo the nozzle just hits the bed.')
886                         wx.CallAfter(self.resumeButton.Enable, True)
887                 elif self._wizardState == 3:
888                         self._wizardState = 4
889                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back left screw of your printer bed\nSo the nozzle just hits the bed.')
890                         wx.CallAfter(self.resumeButton.Enable, True)
891                 elif self._wizardState == 5:
892                         self._wizardState = 6
893                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back right screw of your printer bed\nSo the nozzle just hits the bed.')
894                         wx.CallAfter(self.resumeButton.Enable, True)
895                 elif self._wizardState == 7:
896                         self._wizardState = 8
897                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front right screw of your printer bed\nSo the nozzle just hits the bed.')
898                         wx.CallAfter(self.resumeButton.Enable, True)
899                 elif self._wizardState == 9:
900                         if temp[0] < profile.getProfileSettingFloat('print_temperature') - 5:
901                                 wx.CallAfter(self.infoBox.SetInfo, 'Heating up printer: %d/%d' % (temp[0], profile.getProfileSettingFloat('print_temperature')))
902                         else:
903                                 wx.CallAfter(self.infoBox.SetAttention, 'The printer is hot now. Please insert some PLA filament into the printer.')
904                                 wx.CallAfter(self.resumeButton.Enable, True)
905                                 self._wizardState = 10
906
907         def mcStateChange(self, state):
908                 if self.comm is None:
909                         return
910                 if self.comm.isOperational():
911                         if self._wizardState == 0:
912                                 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer...')
913                                 self.comm.sendCommand('M105')
914                                 self.comm.sendCommand('G28')
915                                 self._wizardState = 1
916                         elif self._wizardState == 11 and not self.comm.isPrinting():
917                                 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('print_speed') * 60))
918                                 self.comm.sendCommand('G92 E0')
919                                 self.comm.sendCommand('G1 E-10 F%d' % (profile.getProfileSettingFloat('retraction_speed') * 60))
920                                 self.comm.sendCommand('M104 S0')
921                                 wx.CallAfter(self.infoBox.SetInfo, 'Calibration finished.\nThe squares on the bed should slightly touch each other.')
922                                 wx.CallAfter(self.infoBox.SetReadyIndicator)
923                                 wx.CallAfter(self.GetParent().FindWindowById(wx.ID_FORWARD).Enable)
924                                 wx.CallAfter(self.connectButton.Enable, True)
925                                 self._wizardState = 12
926                 elif self.comm.isError():
927                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
928
929         def mcMessage(self, message):
930                 pass
931
932         def mcProgress(self, lineNr):
933                 pass
934
935         def mcZChange(self, newZ):
936                 pass
937
938 class headOffsetCalibrationPage(InfoPage):
939         def __init__(self, parent):
940                 super(headOffsetCalibrationPage, self).__init__(parent, "Printer head offset calibration")
941
942                 self.AddText('This wizard will help you in calibrating the printer head offsets of your dual extrusion machine')
943                 self.AddSeperator()
944
945                 self.connectButton = self.AddButton('Connect to printer')
946                 self.comm = None
947
948                 self.infoBox = self.AddInfoBox()
949                 self.textEntry = self.AddTextCtrl('')
950                 self.textEntry.Enable(False)
951                 self.resumeButton = self.AddButton('Resume')
952                 self.resumeButton.Enable(False)
953
954                 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
955                 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
956
957         def OnConnect(self, e = None):
958                 if self.comm is not None:
959                         self.comm.close()
960                         del self.comm
961                         self.comm = None
962                         wx.CallAfter(self.OnConnect)
963                         return
964                 self.connectButton.Enable(False)
965                 self.comm = machineCom.MachineCom(callbackObject=self)
966                 self.infoBox.SetBusy('Connecting to machine.')
967                 self._wizardState = 0
968
969         def OnResume(self, e):
970                 if self._wizardState == 2:
971                         self._wizardState = 3
972                         wx.CallAfter(self.infoBox.SetBusy, 'Printing initial calibration cross')
973
974                         w = profile.getPreferenceFloat('machine_width')
975                         d = profile.getPreferenceFloat('machine_depth')
976
977                         gcode = gcodeGenerator.gcodeGenerator()
978                         gcode.setExtrusionRate(profile.getProfileSettingFloat('nozzle_size') * 1.5, 0.2)
979                         gcode.setPrintSpeed(profile.getProfileSettingFloat('bottom_layer_speed'))
980                         gcode.addCmd('T0')
981                         gcode.addPrime(15)
982                         gcode.addCmd('T1')
983                         gcode.addPrime(15)
984
985                         gcode.addCmd('T0')
986                         gcode.addMove(w/2, 5)
987                         gcode.addMove(z=0.2)
988                         gcode.addPrime()
989                         gcode.addExtrude(w/2, d-5.0)
990                         gcode.addRetract()
991                         gcode.addMove(5, d/2)
992                         gcode.addPrime()
993                         gcode.addExtrude(w-5.0, d/2)
994                         gcode.addRetract(15)
995
996                         gcode.addCmd('T1')
997                         gcode.addMove(w/2, 5)
998                         gcode.addPrime()
999                         gcode.addExtrude(w/2, d-5.0)
1000                         gcode.addRetract()
1001                         gcode.addMove(5, d/2)
1002                         gcode.addPrime()
1003                         gcode.addExtrude(w-5.0, d/2)
1004                         gcode.addRetract(15)
1005                         gcode.addCmd('T0')
1006
1007                         gcode.addMove(z=25)
1008                         gcode.addMove(0, 0)
1009                         gcode.addCmd('M400')
1010
1011                         self.comm.printGCode(gcode.list())
1012                         self.resumeButton.Enable(False)
1013                 elif self._wizardState == 4:
1014                         try:
1015                                 float(self.textEntry.GetValue())
1016                         except ValueError:
1017                                 return
1018                         profile.putPreference('extruder_offset_x1', self.textEntry.GetValue())
1019                         self._wizardState = 5
1020                         self.infoBox.SetAttention('Please measure the distance between the horizontal lines in millimeters.')
1021                         self.textEntry.SetValue('0.0')
1022                         self.textEntry.Enable(True)
1023                 elif self._wizardState == 5:
1024                         try:
1025                                 float(self.textEntry.GetValue())
1026                         except ValueError:
1027                                 return
1028                         profile.putPreference('extruder_offset_y1', self.textEntry.GetValue())
1029                         self._wizardState = 6
1030                         self.infoBox.SetBusy('Printing the fine calibration lines.')
1031                         self.textEntry.SetValue('')
1032                         self.textEntry.Enable(False)
1033                         self.resumeButton.Enable(False)
1034
1035                         x = profile.getPreferenceFloat('extruder_offset_x1')
1036                         y = profile.getPreferenceFloat('extruder_offset_y1')
1037                         gcode = gcodeGenerator.gcodeGenerator()
1038                         gcode.setExtrusionRate(profile.getProfileSettingFloat('nozzle_size') * 1.5, 0.2)
1039                         gcode.setPrintSpeed(25)
1040                         gcode.addHome()
1041                         gcode.addCmd('T0')
1042                         gcode.addMove(50, 40, 0.2)
1043                         gcode.addPrime(15)
1044                         for n in xrange(0, 10):
1045                                 gcode.addExtrude(50 + n * 10, 150)
1046                                 gcode.addExtrude(50 + n * 10 + 5, 150)
1047                                 gcode.addExtrude(50 + n * 10 + 5, 40)
1048                                 gcode.addExtrude(50 + n * 10 + 10, 40)
1049                         gcode.addMove(40, 50)
1050                         for n in xrange(0, 10):
1051                                 gcode.addExtrude(150, 50 + n * 10)
1052                                 gcode.addExtrude(150, 50 + n * 10 + 5)
1053                                 gcode.addExtrude(40, 50 + n * 10 + 5)
1054                                 gcode.addExtrude(40, 50 + n * 10 + 10)
1055                         gcode.addRetract(15)
1056
1057                         gcode.addCmd('T1')
1058                         gcode.addMove(50 - x, 30 - y, 0.2)
1059                         gcode.addPrime(15)
1060                         for n in xrange(0, 10):
1061                                 gcode.addExtrude(50 + n * 10.2 - 1.0 - x, 140 - y)
1062                                 gcode.addExtrude(50 + n * 10.2 - 1.0 + 5.1 - x, 140 - y)
1063                                 gcode.addExtrude(50 + n * 10.2 - 1.0 + 5.1 - x, 30 - y)
1064                                 gcode.addExtrude(50 + n * 10.2 - 1.0 + 10 - x, 30 - y)
1065                         gcode.addMove(30 - x, 50 - y, 0.2)
1066                         for n in xrange(0, 10):
1067                                 gcode.addExtrude(160 - x, 50 + n * 10.2 - 1.0 - y)
1068                                 gcode.addExtrude(160 - x, 50 + n * 10.2 - 1.0 + 5.1 - y)
1069                                 gcode.addExtrude(30 - x, 50 + n * 10.2 - 1.0 + 5.1 - y)
1070                                 gcode.addExtrude(30 - x, 50 + n * 10.2 - 1.0 + 10 - y)
1071                         gcode.addRetract(15)
1072                         gcode.addMove(z=15)
1073                         gcode.addCmd('M400')
1074                         self.comm.printGCode(gcode.list())
1075                 elif self._wizardState == 7:
1076                         try:
1077                                 n = int(self.textEntry.GetValue())
1078                         except:
1079                                 return
1080                         x = profile.getPreferenceFloat('extruder_offset_x1')
1081                         x += -1.0 + n * 0.1
1082                         profile.putPreference('extruder_offset_x1', '%0.2f' % (x))
1083                         self.infoBox.SetAttention('Which horizontal line number lays perfect on top of each other? Front most line is zero.')
1084                         self._wizardState = 8
1085                 elif self._wizardState == 8:
1086                         try:
1087                                 n = int(self.textEntry.GetValue())
1088                         except:
1089                                 return
1090                         y = profile.getPreferenceFloat('extruder_offset_y1')
1091                         y += -1.0 + n * 0.1
1092                         profile.putPreference('extruder_offset_y1', '%0.2f' % (y))
1093                         self.infoBox.SetInfo('Calibration finished. Offsets are: %s %s' % (profile.getPreferenceFloat('extruder_offset_x1'), profile.getPreferenceFloat('extruder_offset_y1')))
1094                         self.infoBox.SetReadyIndicator()
1095                         self._wizardState = 8
1096                         self.comm.close()
1097
1098         def mcLog(self, message):
1099                 print 'Log:', message
1100
1101         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
1102                 if self._wizardState == 1:
1103                         if temp[0] >= 210 and temp[1] >= 210:
1104                                 self._wizardState = 2
1105                                 wx.CallAfter(self.infoBox.SetAttention, 'Please load both extruders with PLA.')
1106                                 wx.CallAfter(self.resumeButton.Enable, True)
1107                                 wx.CallAfter(self.resumeButton.SetFocus)
1108
1109         def mcStateChange(self, state):
1110                 if self.comm is None:
1111                         return
1112                 if self.comm.isOperational():
1113                         if self._wizardState == 0:
1114                                 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer and heating up both extruders.')
1115                                 self.comm.sendCommand('M105')
1116                                 self.comm.sendCommand('M104 S220 T0')
1117                                 self.comm.sendCommand('M104 S220 T1')
1118                                 self.comm.sendCommand('G28')
1119                                 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('print_speed') * 60))
1120                                 self._wizardState = 1
1121                         if not self.comm.isPrinting():
1122                                 if self._wizardState == 3:
1123                                         self._wizardState = 4
1124                                         wx.CallAfter(self.infoBox.SetAttention, 'Please measure the distance between the vertical lines in millimeters.')
1125                                         wx.CallAfter(self.textEntry.SetValue, '0.0')
1126                                         wx.CallAfter(self.textEntry.Enable, True)
1127                                         wx.CallAfter(self.resumeButton.Enable, True)
1128                                         wx.CallAfter(self.resumeButton.SetFocus)
1129                                 elif self._wizardState == 6:
1130                                         self._wizardState = 7
1131                                         wx.CallAfter(self.infoBox.SetAttention, 'Which vertical line number lays perfect on top of each other? Leftmost line is zero.')
1132                                         wx.CallAfter(self.textEntry.SetValue, '5')
1133                                         wx.CallAfter(self.textEntry.Enable, True)
1134                                         wx.CallAfter(self.resumeButton.Enable, True)
1135                                         wx.CallAfter(self.resumeButton.SetFocus)
1136
1137                 elif self.comm.isError():
1138                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
1139
1140         def mcMessage(self, message):
1141                 pass
1142
1143         def mcProgress(self, lineNr):
1144                 pass
1145
1146         def mcZChange(self, newZ):
1147                 pass
1148
1149 class bedLevelWizard(wx.wizard.Wizard):
1150         def __init__(self):
1151                 super(bedLevelWizard, self).__init__(None, -1, "Bed leveling wizard")
1152
1153                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
1154                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
1155
1156                 self.mainPage = bedLevelWizardMain(self)
1157                 self.headOffsetCalibration = None
1158
1159                 self.FitToPage(self.mainPage)
1160                 self.GetPageAreaSizer().Add(self.mainPage)
1161
1162                 self.RunWizard(self.mainPage)
1163                 self.Destroy()
1164
1165         def OnPageChanging(self, e):
1166                 e.GetPage().StoreData()
1167
1168         def OnPageChanged(self, e):
1169                 if e.GetPage().AllowNext():
1170                         self.FindWindowById(wx.ID_FORWARD).Enable()
1171                 else:
1172                         self.FindWindowById(wx.ID_FORWARD).Disable()
1173                 self.FindWindowById(wx.ID_BACKWARD).Disable()
1174
1175 class headOffsetWizard(wx.wizard.Wizard):
1176         def __init__(self):
1177                 super(headOffsetWizard, self).__init__(None, -1, "Head offset wizard")
1178
1179                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
1180                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
1181
1182                 self.mainPage = headOffsetCalibrationPage(self)
1183
1184                 self.FitToPage(self.mainPage)
1185                 self.GetPageAreaSizer().Add(self.mainPage)
1186
1187                 self.RunWizard(self.mainPage)
1188                 self.Destroy()
1189
1190         def OnPageChanging(self, e):
1191                 e.GetPage().StoreData()
1192
1193         def OnPageChanged(self, e):
1194                 if e.GetPage().AllowNext():
1195                         self.FindWindowById(wx.ID_FORWARD).Enable()
1196                 else:
1197                         self.FindWindowById(wx.ID_FORWARD).Disable()
1198                 self.FindWindowById(wx.ID_BACKWARD).Disable()