chiark / gitweb /
42a11b4043e02d50325b42f4ccfedf87d537ac54
[cura.git] / Cura / gui / configWizard.py
1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
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('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.')
341                 self.AddHiddenSeperator()
342                 self.AddText('The firmware shipping with new Ultimakers works, but upgrades\nhave been made to make better prints, and make calibration easier.')
343                 self.AddHiddenSeperator()
344                 self.AddText('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.')
345                 upgradeButton, skipUpgradeButton = self.AddDualButton('Upgrade to Marlin firmware', 'Skip upgrade')
346                 upgradeButton.Bind(wx.EVT_BUTTON, self.OnUpgradeClick)
347                 skipUpgradeButton.Bind(wx.EVT_BUTTON, self.OnSkipClick)
348                 self.AddHiddenSeperator()
349                 self.AddText('Do not upgrade to this firmware if:')
350                 self.AddText('* You have an older machine based on ATMega1280')
351                 self.AddText('* Have other changes in the firmware')
352 #               button = self.AddButton('Goto this page for a custom firmware')
353 #               button.Bind(wx.EVT_BUTTON, self.OnUrlClick)
354
355         def AllowNext(self):
356                 return False
357
358         def OnUpgradeClick(self, e):
359                 if firmwareInstall.InstallFirmware():
360                         self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
361
362         def OnSkipClick(self, e):
363                 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
364                 self.GetParent().ShowPage(self.GetNext())
365
366         def OnUrlClick(self, e):
367                 webbrowser.open('http://marlinbuilder.robotfuzz.com/')
368
369
370 class UltimakerCheckupPage(InfoPage):
371         def __init__(self, parent):
372                 super(UltimakerCheckupPage, self).__init__(parent, "Ultimaker Checkup")
373
374                 self.checkBitmap = wx.Bitmap(getPathForImage('checkmark.png'))
375                 self.crossBitmap = wx.Bitmap(getPathForImage('cross.png'))
376                 self.unknownBitmap = wx.Bitmap(getPathForImage('question.png'))
377                 self.endStopNoneBitmap = wx.Bitmap(getPathForImage('endstop_none.png'))
378                 self.endStopXMinBitmap = wx.Bitmap(getPathForImage('endstop_xmin.png'))
379                 self.endStopXMaxBitmap = wx.Bitmap(getPathForImage('endstop_xmax.png'))
380                 self.endStopYMinBitmap = wx.Bitmap(getPathForImage('endstop_ymin.png'))
381                 self.endStopYMaxBitmap = wx.Bitmap(getPathForImage('endstop_ymax.png'))
382                 self.endStopZMinBitmap = wx.Bitmap(getPathForImage('endstop_zmin.png'))
383                 self.endStopZMaxBitmap = wx.Bitmap(getPathForImage('endstop_zmax.png'))
384
385                 self.AddText(
386                         '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.')
387                 b1, b2 = self.AddDualButton('Run checks', 'Skip checks')
388                 b1.Bind(wx.EVT_BUTTON, self.OnCheckClick)
389                 b2.Bind(wx.EVT_BUTTON, self.OnSkipClick)
390                 self.AddSeperator()
391                 self.commState = self.AddCheckmark('Communication:', self.unknownBitmap)
392                 self.tempState = self.AddCheckmark('Temperature:', self.unknownBitmap)
393                 self.stopState = self.AddCheckmark('Endstops:', self.unknownBitmap)
394                 self.AddSeperator()
395                 self.infoBox = self.AddInfoBox()
396                 self.machineState = self.AddText('')
397                 self.temperatureLabel = self.AddText('')
398                 self.errorLogButton = self.AddButton('Show error log')
399                 self.errorLogButton.Show(False)
400                 self.AddSeperator()
401                 self.endstopBitmap = self.AddBitmap(self.endStopNoneBitmap)
402                 self.comm = None
403                 self.xMinStop = False
404                 self.xMaxStop = False
405                 self.yMinStop = False
406                 self.yMaxStop = False
407                 self.zMinStop = False
408                 self.zMaxStop = False
409
410                 self.Bind(wx.EVT_BUTTON, self.OnErrorLog, self.errorLogButton)
411
412         def __del__(self):
413                 if self.comm is not None:
414                         self.comm.close()
415
416         def AllowNext(self):
417                 self.endstopBitmap.Show(False)
418                 return False
419
420         def OnSkipClick(self, e):
421                 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
422                 self.GetParent().ShowPage(self.GetNext())
423
424         def OnCheckClick(self, e=None):
425                 self.errorLogButton.Show(False)
426                 if self.comm is not None:
427                         self.comm.close()
428                         del self.comm
429                         self.comm = None
430                         wx.CallAfter(self.OnCheckClick)
431                         return
432                 self.infoBox.SetBusy('Connecting to machine.')
433                 self.commState.SetBitmap(self.unknownBitmap)
434                 self.tempState.SetBitmap(self.unknownBitmap)
435                 self.stopState.SetBitmap(self.unknownBitmap)
436                 self.checkupState = 0
437                 self.checkExtruderNr = 0
438                 self.comm = machineCom.MachineCom(callbackObject=self)
439
440         def OnErrorLog(self, e):
441                 printWindow.LogWindow('\n'.join(self.comm.getLog()))
442
443         def mcLog(self, message):
444                 pass
445
446         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
447                 if not self.comm.isOperational():
448                         return
449                 if self.checkupState == 0:
450                         self.tempCheckTimeout = 20
451                         if temp[self.checkExtruderNr] > 70:
452                                 self.checkupState = 1
453                                 wx.CallAfter(self.infoBox.SetInfo, 'Cooldown before temperature check.')
454                                 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
455                                 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
456                         else:
457                                 self.startTemp = temp[self.checkExtruderNr]
458                                 self.checkupState = 2
459                                 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
460                                 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
461                                 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
462                 elif self.checkupState == 1:
463                         if temp < 60:
464                                 self.startTemp = temp[self.checkExtruderNr]
465                                 self.checkupState = 2
466                                 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
467                                 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
468                                 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
469                 elif self.checkupState == 2:
470                         #print "WARNING, TEMPERATURE TEST DISABLED FOR TESTING!"
471                         if temp[self.checkExtruderNr] > self.startTemp + 40:
472                                 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
473                                 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
474                                 if self.checkExtruderNr < int(profile.getPreference('extruder_amount')):
475                                         self.checkExtruderNr = 0
476                                         self.checkupState = 3
477                                         wx.CallAfter(self.infoBox.SetAttention, 'Please make sure none of the endstops are pressed.')
478                                         wx.CallAfter(self.endstopBitmap.Show, True)
479                                         wx.CallAfter(self.Layout)
480                                         self.comm.sendCommand('M119')
481                                         wx.CallAfter(self.tempState.SetBitmap, self.checkBitmap)
482                                 else:
483                                         self.checkupState = 0
484                                         self.checkExtruderNr += 1
485                         else:
486                                 self.tempCheckTimeout -= 1
487                                 if self.tempCheckTimeout < 1:
488                                         self.checkupState = -1
489                                         wx.CallAfter(self.tempState.SetBitmap, self.crossBitmap)
490                                         wx.CallAfter(self.infoBox.SetError, 'Temperature measurement FAILED!', 'http://wiki.ultimaker.com/Cura:_Temperature_measurement_problems')
491                                         self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
492                                         self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
493                 elif self.checkupState >= 3 and self.checkupState < 10:
494                         self.comm.sendCommand('M119')
495                 wx.CallAfter(self.temperatureLabel.SetLabel, 'Head temperature: %d' % (temp[self.checkExtruderNr]))
496
497         def mcStateChange(self, state):
498                 if self.comm is None:
499                         return
500                 if self.comm.isOperational():
501                         wx.CallAfter(self.commState.SetBitmap, self.checkBitmap)
502                         wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
503                 elif self.comm.isError():
504                         wx.CallAfter(self.commState.SetBitmap, self.crossBitmap)
505                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
506                         wx.CallAfter(self.endstopBitmap.Show, False)
507                         wx.CallAfter(self.machineState.SetLabel, '%s' % (self.comm.getErrorString()))
508                         wx.CallAfter(self.errorLogButton.Show, True)
509                         wx.CallAfter(self.Layout)
510                 else:
511                         wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
512
513         def mcMessage(self, message):
514                 if self.checkupState >= 3 and self.checkupState < 10 and ('_min' in message or '_max' in message):
515                         for data in message.split(' '):
516                                 if ':' in data:
517                                         tag, value = data.split(':', 1)
518                                         if tag == 'x_min':
519                                                 self.xMinStop = (value == 'H' or value == 'TRIGGERED')
520                                         if tag == 'x_max':
521                                                 self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
522                                         if tag == 'y_min':
523                                                 self.yMinStop = (value == 'H' or value == 'TRIGGERED')
524                                         if tag == 'y_max':
525                                                 self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
526                                         if tag == 'z_min':
527                                                 self.zMinStop = (value == 'H' or value == 'TRIGGERED')
528                                         if tag == 'z_max':
529                                                 self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
530                         if ':' in message:
531                                 tag, value = map(str.strip, message.split(':', 1))
532                                 if tag == 'x_min':
533                                         self.xMinStop = (value == 'H' or value == 'TRIGGERED')
534                                 if tag == 'x_max':
535                                         self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
536                                 if tag == 'y_min':
537                                         self.yMinStop = (value == 'H' or value == 'TRIGGERED')
538                                 if tag == 'y_max':
539                                         self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
540                                 if tag == 'z_min':
541                                         self.zMinStop = (value == 'H' or value == 'TRIGGERED')
542                                 if tag == 'z_max':
543                                         self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
544                         if 'z_max' in message:
545                                 self.comm.sendCommand('M119')
546
547                         if self.checkupState == 3:
548                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
549                                         self.checkupState = 4
550                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the right X endstop.')
551                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMaxBitmap)
552                         elif self.checkupState == 4:
553                                 if not self.xMinStop and self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
554                                         self.checkupState = 5
555                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the left X endstop.')
556                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMinBitmap)
557                         elif self.checkupState == 5:
558                                 if self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
559                                         self.checkupState = 6
560                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the front Y endstop.')
561                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMinBitmap)
562                         elif self.checkupState == 6:
563                                 if not self.xMinStop and not self.xMaxStop and self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
564                                         self.checkupState = 7
565                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the back Y endstop.')
566                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMaxBitmap)
567                         elif self.checkupState == 7:
568                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and self.yMaxStop and not self.zMinStop and not self.zMaxStop:
569                                         self.checkupState = 8
570                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the top Z endstop.')
571                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMinBitmap)
572                         elif self.checkupState == 8:
573                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and self.zMinStop and not self.zMaxStop:
574                                         self.checkupState = 9
575                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the bottom Z endstop.')
576                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMaxBitmap)
577                         elif self.checkupState == 9:
578                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and self.zMaxStop:
579                                         self.checkupState = 10
580                                         self.comm.close()
581                                         wx.CallAfter(self.infoBox.SetInfo, 'Checkup finished')
582                                         wx.CallAfter(self.infoBox.SetReadyIndicator)
583                                         wx.CallAfter(self.endstopBitmap.Show, False)
584                                         wx.CallAfter(self.stopState.SetBitmap, self.checkBitmap)
585                                         wx.CallAfter(self.OnSkipClick, None)
586
587         def mcProgress(self, lineNr):
588                 pass
589
590         def mcZChange(self, newZ):
591                 pass
592
593
594 class UltimakerCalibrationPage(InfoPage):
595         def __init__(self, parent):
596                 super(UltimakerCalibrationPage, self).__init__(parent, "Ultimaker Calibration")
597
598                 self.AddText("Your Ultimaker requires some calibration.")
599                 self.AddText("This calibration is needed for a proper extrusion amount.")
600                 self.AddSeperator()
601                 self.AddText("The following values are needed:")
602                 self.AddText("* Diameter of filament")
603                 self.AddText("* Number of steps per mm of filament extrusion")
604                 self.AddSeperator()
605                 self.AddText("The better you have calibrated these values, the better your prints\nwill become.")
606                 self.AddSeperator()
607                 self.AddText("First we need the diameter of your filament:")
608                 self.filamentDiameter = self.AddTextCtrl(profile.getProfileSetting('filament_diameter'))
609                 self.AddText(
610                         "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.")
611                 self.AddText("Note: This value can be changed later at any time.")
612
613         def StoreData(self):
614                 profile.putProfileSetting('filament_diameter', self.filamentDiameter.GetValue())
615
616
617 class UltimakerCalibrateStepsPerEPage(InfoPage):
618         def __init__(self, parent):
619                 super(UltimakerCalibrateStepsPerEPage, self).__init__(parent, "Ultimaker Calibration")
620
621                 #if profile.getPreference('steps_per_e') == '0':
622                 #       profile.putPreference('steps_per_e', '865.888')
623
624                 self.AddText("Calibrating the Steps Per E requires some manual actions.")
625                 self.AddText("First remove any filament from your machine.")
626                 self.AddText("Next put in your filament so the tip is aligned with the\ntop of the extruder drive.")
627                 self.AddText("We'll push the filament 100mm")
628                 self.extrudeButton = self.AddButton("Extrude 100mm filament")
629                 self.AddText("Now measure the amount of extruded filament:\n(this can be more or less then 100mm)")
630                 self.lengthInput, self.saveLengthButton = self.AddTextCtrlButton('100', 'Save')
631                 self.AddText("This results in the following steps per E:")
632                 self.stepsPerEInput = self.AddTextCtrl(profile.getPreference('steps_per_e'))
633                 self.AddText("You can repeat these steps to get better calibration.")
634                 self.AddSeperator()
635                 self.AddText(
636                         "If you still have filament in your printer which needs\nheat to remove, press the heat up button below:")
637                 self.heatButton = self.AddButton("Heatup for filament removal")
638
639                 self.saveLengthButton.Bind(wx.EVT_BUTTON, self.OnSaveLengthClick)
640                 self.extrudeButton.Bind(wx.EVT_BUTTON, self.OnExtrudeClick)
641                 self.heatButton.Bind(wx.EVT_BUTTON, self.OnHeatClick)
642
643         def OnSaveLengthClick(self, e):
644                 currentEValue = float(self.stepsPerEInput.GetValue())
645                 realExtrudeLength = float(self.lengthInput.GetValue())
646                 newEValue = currentEValue * 100 / realExtrudeLength
647                 self.stepsPerEInput.SetValue(str(newEValue))
648                 self.lengthInput.SetValue("100")
649
650         def OnExtrudeClick(self, e):
651                 threading.Thread(target=self.OnExtrudeRun).start()
652
653         def OnExtrudeRun(self):
654                 self.heatButton.Enable(False)
655                 self.extrudeButton.Enable(False)
656                 currentEValue = float(self.stepsPerEInput.GetValue())
657                 self.comm = machineCom.MachineCom()
658                 if not self.comm.isOpen():
659                         wx.MessageBox(
660                                 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
661                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
662                         self.heatButton.Enable(True)
663                         self.extrudeButton.Enable(True)
664                         return
665                 while True:
666                         line = self.comm.readline()
667                         if line == '':
668                                 return
669                         if 'start' in line:
670                                 break
671                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
672                 time.sleep(3)
673
674                 self.sendGCommand('M302') #Disable cold extrusion protection
675                 self.sendGCommand("M92 E%f" % (currentEValue))
676                 self.sendGCommand("G92 E0")
677                 self.sendGCommand("G1 E100 F600")
678                 time.sleep(15)
679                 self.comm.close()
680                 self.extrudeButton.Enable()
681                 self.heatButton.Enable()
682
683         def OnHeatClick(self, e):
684                 threading.Thread(target=self.OnHeatRun).start()
685
686         def OnHeatRun(self):
687                 self.heatButton.Enable(False)
688                 self.extrudeButton.Enable(False)
689                 self.comm = machineCom.MachineCom()
690                 if not self.comm.isOpen():
691                         wx.MessageBox(
692                                 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
693                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
694                         self.heatButton.Enable(True)
695                         self.extrudeButton.Enable(True)
696                         return
697                 while True:
698                         line = self.comm.readline()
699                         if line == '':
700                                 self.heatButton.Enable(True)
701                                 self.extrudeButton.Enable(True)
702                                 return
703                         if 'start' in line:
704                                 break
705                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
706                 time.sleep(3)
707
708                 self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.
709                 wx.MessageBox(
710                         'Wait till you can remove the filament from the machine, and press OK.\n(Temperature is set to 200C)',
711                         'Machine heatup', wx.OK | wx.ICON_INFORMATION)
712                 self.sendGCommand('M104 S0')
713                 time.sleep(1)
714                 self.comm.close()
715                 self.heatButton.Enable(True)
716                 self.extrudeButton.Enable(True)
717
718         def sendGCommand(self, cmd):
719                 self.comm.sendCommand(cmd) #Disable cold extrusion protection
720                 while True:
721                         line = self.comm.readline()
722                         if line == '':
723                                 return
724                         if line.startswith('ok'):
725                                 break
726
727         def StoreData(self):
728                 profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())
729
730
731 class configWizard(wx.wizard.Wizard):
732         def __init__(self):
733                 super(configWizard, self).__init__(None, -1, "Configuration Wizard")
734
735                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
736                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
737
738                 self.firstInfoPage = FirstInfoPage(self)
739                 self.machineSelectPage = MachineSelectPage(self)
740                 self.ultimakerSelectParts = SelectParts(self)
741                 self.ultimakerFirmwareUpgradePage = FirmwareUpgradePage(self)
742                 self.ultimakerCheckupPage = UltimakerCheckupPage(self)
743                 self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)
744                 self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)
745                 self.bedLevelPage = bedLevelWizardMain(self)
746                 self.headOffsetCalibration = headOffsetCalibrationPage(self)
747                 self.repRapInfoPage = RepRapInfoPage(self)
748
749                 wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)
750                 wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerSelectParts)
751                 wx.wizard.WizardPageSimple.Chain(self.ultimakerSelectParts, self.ultimakerFirmwareUpgradePage)
752                 wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)
753                 wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.bedLevelPage)
754                 #wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)
755
756                 self.FitToPage(self.firstInfoPage)
757                 self.GetPageAreaSizer().Add(self.firstInfoPage)
758
759                 self.RunWizard(self.firstInfoPage)
760                 self.Destroy()
761
762         def OnPageChanging(self, e):
763                 e.GetPage().StoreData()
764
765         def OnPageChanged(self, e):
766                 if e.GetPage().AllowNext():
767                         self.FindWindowById(wx.ID_FORWARD).Enable()
768                 else:
769                         self.FindWindowById(wx.ID_FORWARD).Disable()
770                 self.FindWindowById(wx.ID_BACKWARD).Disable()
771
772 class bedLevelWizardMain(InfoPage):
773         def __init__(self, parent):
774                 super(bedLevelWizardMain, self).__init__(parent, "Bed leveling wizard")
775
776                 self.AddText('This wizard will help you in leveling your printer bed')
777                 self.AddSeperator()
778                 self.AddText('It will do the following steps')
779                 self.AddText('* Move the printer head to each corner')
780                 self.AddText('  and let you adjust the height of the bed to the nozzle')
781                 self.AddText('* Print a line around the bed to check if it is level')
782                 self.AddSeperator()
783
784                 self.connectButton = self.AddButton('Connect to printer')
785                 self.comm = None
786
787                 self.infoBox = self.AddInfoBox()
788                 self.resumeButton = self.AddButton('Resume')
789                 self.resumeButton.Enable(False)
790
791                 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
792                 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
793
794         def OnConnect(self, e = None):
795                 if self.comm is not None:
796                         self.comm.close()
797                         del self.comm
798                         self.comm = None
799                         wx.CallAfter(self.OnConnect)
800                         return
801                 self.connectButton.Enable(False)
802                 self.comm = machineCom.MachineCom(callbackObject=self)
803                 self.infoBox.SetBusy('Connecting to machine.')
804                 self._wizardState = 0
805
806         def AllowNext(self):
807                 if self.GetParent().headOffsetCalibration is not None and int(profile.getPreference('extruder_amount')) > 1:
808                         wx.wizard.WizardPageSimple.Chain(self, self.GetParent().headOffsetCalibration)
809                 return True
810
811         def OnResume(self, e):
812                 feedZ = profile.getProfileSettingFloat('print_speed') * 60
813                 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
814                 if self._wizardState == 2:
815                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back left corner...')
816                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
817                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, profile.getPreferenceFloat('machine_depth'), feedTravel))
818                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
819                         self.comm.sendCommand('M400')
820                         self._wizardState = 3
821                 elif self._wizardState == 4:
822                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back right corner...')
823                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
824                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width') - 5.0, profile.getPreferenceFloat('machine_depth') - 25, feedTravel))
825                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
826                         self.comm.sendCommand('M400')
827                         self._wizardState = 5
828                 elif self._wizardState == 6:
829                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to front right corner...')
830                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
831                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width') - 5.0, 20, feedTravel))
832                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
833                         self.comm.sendCommand('M400')
834                         self._wizardState = 7
835                 elif self._wizardState == 8:
836                         wx.CallAfter(self.infoBox.SetBusy, 'Heating up printer...')
837                         self.comm.sendCommand('G1 Z15 F%d' % (feedZ))
838                         self.comm.sendCommand('M104 S%d' % (profile.getProfileSettingFloat('print_temperature')))
839                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, 0, feedTravel))
840                         self._wizardState = 9
841                 elif self._wizardState == 10:
842                         self._wizardState = 11
843                         wx.CallAfter(self.infoBox.SetInfo, 'Printing a square on the printer bed at 0.3mm height.')
844                         feedZ = profile.getProfileSettingFloat('print_speed') * 60
845                         feedPrint = profile.getProfileSettingFloat('print_speed') * 60
846                         feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
847                         w = profile.getPreferenceFloat('machine_width')
848                         d = profile.getPreferenceFloat('machine_depth')
849                         filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
850                         filamentArea = math.pi * filamentRadius * filamentRadius
851                         ePerMM = (profile.calculateEdgeWidth() * 0.3) / filamentArea
852                         eValue = 0.0
853
854                         gcodeList = [
855                                 'G1 Z2 F%d' % (feedZ),
856                                 'G92 E0',
857                                 'G1 X%d Y%d F%d' % (5, 5, feedTravel),
858                                 'G1 Z0.3 F%d' % (feedZ)]
859                         eValue += 5.0
860                         gcodeList.append('G1 E%f F%d' % (eValue, profile.getProfileSettingFloat('retraction_speed') * 60))
861
862                         for i in xrange(0, 3):
863                                 dist = 5.0 + 0.4 * float(i)
864                                 eValue += (d - 2.0*dist) * ePerMM
865                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (dist, d - dist, eValue, feedPrint))
866                                 eValue += (w - 2.0*dist) * ePerMM
867                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (w - dist, d - dist, eValue, feedPrint))
868                                 eValue += (d - 2.0*dist) * ePerMM
869                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (w - dist, dist, eValue, feedPrint))
870                                 eValue += (w - 2.0*dist) * ePerMM
871                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (dist, dist, eValue, feedPrint))
872
873                         gcodeList.append('M400')
874                         self.comm.printGCode(gcodeList)
875                 self.resumeButton.Enable(False)
876
877         def mcLog(self, message):
878                 print 'Log:', message
879
880         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
881                 if self._wizardState == 1:
882                         self._wizardState = 2
883                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front left screw of your printer bed\nSo the nozzle just hits the bed.')
884                         wx.CallAfter(self.resumeButton.Enable, True)
885                 elif self._wizardState == 3:
886                         self._wizardState = 4
887                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back left screw of your printer bed\nSo the nozzle just hits the bed.')
888                         wx.CallAfter(self.resumeButton.Enable, True)
889                 elif self._wizardState == 5:
890                         self._wizardState = 6
891                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back right screw of your printer bed\nSo the nozzle just hits the bed.')
892                         wx.CallAfter(self.resumeButton.Enable, True)
893                 elif self._wizardState == 7:
894                         self._wizardState = 8
895                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front right screw of your printer bed\nSo the nozzle just hits the bed.')
896                         wx.CallAfter(self.resumeButton.Enable, True)
897                 elif self._wizardState == 9:
898                         if temp[0] < profile.getProfileSettingFloat('print_temperature') - 5:
899                                 wx.CallAfter(self.infoBox.SetInfo, 'Heating up printer: %d/%d' % (temp[0], profile.getProfileSettingFloat('print_temperature')))
900                         else:
901                                 wx.CallAfter(self.infoBox.SetAttention, 'The printer is hot now. Please insert some PLA filament into the printer.')
902                                 wx.CallAfter(self.resumeButton.Enable, True)
903                                 self._wizardState = 10
904
905         def mcStateChange(self, state):
906                 if self.comm is None:
907                         return
908                 if self.comm.isOperational():
909                         if self._wizardState == 0:
910                                 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer...')
911                                 self.comm.sendCommand('M105')
912                                 self.comm.sendCommand('G28')
913                                 self._wizardState = 1
914                         elif self._wizardState == 11 and not self.comm.isPrinting():
915                                 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('print_speed') * 60))
916                                 self.comm.sendCommand('G92 E0')
917                                 self.comm.sendCommand('G1 E-10 F%d' % (profile.getProfileSettingFloat('retraction_speed') * 60))
918                                 self.comm.sendCommand('M104 S0')
919                                 wx.CallAfter(self.infoBox.SetInfo, 'Calibration finished.\nThe squares on the bed should slightly touch each other.')
920                                 wx.CallAfter(self.infoBox.SetReadyIndicator)
921                                 wx.CallAfter(self.GetParent().FindWindowById(wx.ID_FORWARD).Enable)
922                                 wx.CallAfter(self.connectButton.Enable, True)
923                                 self._wizardState = 12
924                 elif self.comm.isError():
925                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
926
927         def mcMessage(self, message):
928                 pass
929
930         def mcProgress(self, lineNr):
931                 pass
932
933         def mcZChange(self, newZ):
934                 pass
935
936 class headOffsetCalibrationPage(InfoPage):
937         def __init__(self, parent):
938                 super(headOffsetCalibrationPage, self).__init__(parent, "Printer head offset calibration")
939
940                 self.AddText('This wizard will help you in calibrating the printer head offsets of your dual extrusion machine')
941                 self.AddSeperator()
942
943                 self.connectButton = self.AddButton('Connect to printer')
944                 self.comm = None
945
946                 self.infoBox = self.AddInfoBox()
947                 self.textEntry = self.AddTextCtrl('')
948                 self.textEntry.Enable(False)
949                 self.resumeButton = self.AddButton('Resume')
950                 self.resumeButton.Enable(False)
951
952                 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
953                 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
954
955         def OnConnect(self, e = None):
956                 if self.comm is not None:
957                         self.comm.close()
958                         del self.comm
959                         self.comm = None
960                         wx.CallAfter(self.OnConnect)
961                         return
962                 self.connectButton.Enable(False)
963                 self.comm = machineCom.MachineCom(callbackObject=self)
964                 self.infoBox.SetBusy('Connecting to machine.')
965                 self._wizardState = 0
966
967         def OnResume(self, e):
968                 if self._wizardState == 2:
969                         self._wizardState = 3
970                         wx.CallAfter(self.infoBox.SetBusy, 'Printing initial calibration cross')
971
972                         w = profile.getPreferenceFloat('machine_width')
973                         d = profile.getPreferenceFloat('machine_depth')
974
975                         gcode = gcodeGenerator.gcodeGenerator()
976                         gcode.setExtrusionRate(profile.getProfileSettingFloat('nozzle_size') * 1.5, 0.2)
977                         gcode.setPrintSpeed(profile.getProfileSettingFloat('bottom_layer_speed'))
978                         gcode.addCmd('T0')
979                         gcode.addPrime(15)
980                         gcode.addCmd('T1')
981                         gcode.addPrime(15)
982
983                         gcode.addCmd('T0')
984                         gcode.addMove(w/2, 5)
985                         gcode.addMove(z=0.2)
986                         gcode.addPrime()
987                         gcode.addExtrude(w/2, d-5.0)
988                         gcode.addRetract()
989                         gcode.addMove(5, d/2)
990                         gcode.addPrime()
991                         gcode.addExtrude(w-5.0, d/2)
992                         gcode.addRetract(15)
993
994                         gcode.addCmd('T1')
995                         gcode.addMove(w/2, 5)
996                         gcode.addPrime()
997                         gcode.addExtrude(w/2, d-5.0)
998                         gcode.addRetract()
999                         gcode.addMove(5, d/2)
1000                         gcode.addPrime()
1001                         gcode.addExtrude(w-5.0, d/2)
1002                         gcode.addRetract(15)
1003                         gcode.addCmd('T0')
1004
1005                         gcode.addMove(z=25)
1006                         gcode.addMove(0, 0)
1007                         gcode.addCmd('M400')
1008
1009                         self.comm.printGCode(gcode.list())
1010                         self.resumeButton.Enable(False)
1011                 elif self._wizardState == 4:
1012                         try:
1013                                 float(self.textEntry.GetValue())
1014                         except ValueError:
1015                                 return
1016                         profile.putPreference('extruder_offset_x1', self.textEntry.GetValue())
1017                         self._wizardState = 5
1018                         self.infoBox.SetAttention('Please measure the distance between the horizontal lines in millimeters.')
1019                         self.textEntry.SetValue('0.0')
1020                         self.textEntry.Enable(True)
1021                 elif self._wizardState == 5:
1022                         try:
1023                                 float(self.textEntry.GetValue())
1024                         except ValueError:
1025                                 return
1026                         profile.putPreference('extruder_offset_y1', self.textEntry.GetValue())
1027                         self._wizardState = 6
1028                         self.infoBox.SetBusy('Printing the fine calibration lines.')
1029                         self.textEntry.SetValue('')
1030                         self.textEntry.Enable(False)
1031                         self.resumeButton.Enable(False)
1032
1033                         x = profile.getPreferenceFloat('extruder_offset_x1')
1034                         y = profile.getPreferenceFloat('extruder_offset_y1')
1035                         gcode = gcodeGenerator.gcodeGenerator()
1036                         gcode.setExtrusionRate(profile.getProfileSettingFloat('nozzle_size') * 1.5, 0.2)
1037                         gcode.setPrintSpeed(25)
1038                         gcode.addHome()
1039                         gcode.addCmd('T0')
1040                         gcode.addMove(50, 40, 0.2)
1041                         gcode.addPrime(15)
1042                         for n in xrange(0, 10):
1043                                 gcode.addExtrude(50 + n * 10, 150)
1044                                 gcode.addExtrude(50 + n * 10 + 5, 150)
1045                                 gcode.addExtrude(50 + n * 10 + 5, 40)
1046                                 gcode.addExtrude(50 + n * 10 + 10, 40)
1047                         gcode.addMove(40, 50)
1048                         for n in xrange(0, 10):
1049                                 gcode.addExtrude(150, 50 + n * 10)
1050                                 gcode.addExtrude(150, 50 + n * 10 + 5)
1051                                 gcode.addExtrude(40, 50 + n * 10 + 5)
1052                                 gcode.addExtrude(40, 50 + n * 10 + 10)
1053                         gcode.addRetract(15)
1054
1055                         gcode.addCmd('T1')
1056                         gcode.addMove(50 - x, 30 - y, 0.2)
1057                         gcode.addPrime(15)
1058                         for n in xrange(0, 10):
1059                                 gcode.addExtrude(50 + n * 10.2 - 1.0 - x, 140 - y)
1060                                 gcode.addExtrude(50 + n * 10.2 - 1.0 + 5.1 - x, 140 - y)
1061                                 gcode.addExtrude(50 + n * 10.2 - 1.0 + 5.1 - x, 30 - y)
1062                                 gcode.addExtrude(50 + n * 10.2 - 1.0 + 10 - x, 30 - y)
1063                         gcode.addMove(30 - x, 50 - y, 0.2)
1064                         for n in xrange(0, 10):
1065                                 gcode.addExtrude(160 - x, 50 + n * 10.2 - 1.0 - y)
1066                                 gcode.addExtrude(160 - x, 50 + n * 10.2 - 1.0 + 5.1 - y)
1067                                 gcode.addExtrude(30 - x, 50 + n * 10.2 - 1.0 + 5.1 - y)
1068                                 gcode.addExtrude(30 - x, 50 + n * 10.2 - 1.0 + 10 - y)
1069                         gcode.addRetract(15)
1070                         gcode.addMove(z=15)
1071                         gcode.addCmd('M400')
1072                         gcode.addCmd('M104 T0 S0')
1073                         gcode.addCmd('M104 T1 S0')
1074                         self.comm.printGCode(gcode.list())
1075                 elif self._wizardState == 7:
1076                         try:
1077                                 n = int(self.textEntry.GetValue()) - 1
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.textEntry.SetValue('10')
1085                         self._wizardState = 8
1086                 elif self._wizardState == 8:
1087                         try:
1088                                 n = int(self.textEntry.GetValue()) - 1
1089                         except:
1090                                 return
1091                         y = profile.getPreferenceFloat('extruder_offset_y1')
1092                         y += -1.0 + n * 0.1
1093                         profile.putPreference('extruder_offset_y1', '%0.2f' % (y))
1094                         self.infoBox.SetInfo('Calibration finished. Offsets are: %s %s' % (profile.getPreferenceFloat('extruder_offset_x1'), profile.getPreferenceFloat('extruder_offset_y1')))
1095                         self.infoBox.SetReadyIndicator()
1096                         self._wizardState = 8
1097                         self.comm.close()
1098                         self.resumeButton.Enable(False)
1099
1100         def mcLog(self, message):
1101                 print 'Log:', message
1102
1103         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
1104                 if self._wizardState == 1:
1105                         if temp[0] >= 210 and temp[1] >= 210:
1106                                 self._wizardState = 2
1107                                 wx.CallAfter(self.infoBox.SetAttention, 'Please load both extruders with PLA.')
1108                                 wx.CallAfter(self.resumeButton.Enable, True)
1109                                 wx.CallAfter(self.resumeButton.SetFocus)
1110
1111         def mcStateChange(self, state):
1112                 if self.comm is None:
1113                         return
1114                 if self.comm.isOperational():
1115                         if self._wizardState == 0:
1116                                 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer and heating up both extruders.')
1117                                 self.comm.sendCommand('M105')
1118                                 self.comm.sendCommand('M104 S220 T0')
1119                                 self.comm.sendCommand('M104 S220 T1')
1120                                 self.comm.sendCommand('G28')
1121                                 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('print_speed') * 60))
1122                                 self._wizardState = 1
1123                         if not self.comm.isPrinting():
1124                                 if self._wizardState == 3:
1125                                         self._wizardState = 4
1126                                         wx.CallAfter(self.infoBox.SetAttention, 'Please measure the distance between the vertical lines in millimeters.')
1127                                         wx.CallAfter(self.textEntry.SetValue, '0.0')
1128                                         wx.CallAfter(self.textEntry.Enable, True)
1129                                         wx.CallAfter(self.resumeButton.Enable, True)
1130                                         wx.CallAfter(self.resumeButton.SetFocus)
1131                                 elif self._wizardState == 6:
1132                                         self._wizardState = 7
1133                                         wx.CallAfter(self.infoBox.SetAttention, 'Which vertical line number lays perfect on top of each other? Leftmost line is zero.')
1134                                         wx.CallAfter(self.textEntry.SetValue, '10')
1135                                         wx.CallAfter(self.textEntry.Enable, True)
1136                                         wx.CallAfter(self.resumeButton.Enable, True)
1137                                         wx.CallAfter(self.resumeButton.SetFocus)
1138
1139                 elif self.comm.isError():
1140                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
1141
1142         def mcMessage(self, message):
1143                 pass
1144
1145         def mcProgress(self, lineNr):
1146                 pass
1147
1148         def mcZChange(self, newZ):
1149                 pass
1150
1151 class bedLevelWizard(wx.wizard.Wizard):
1152         def __init__(self):
1153                 super(bedLevelWizard, self).__init__(None, -1, "Bed leveling wizard")
1154
1155                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
1156                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
1157
1158                 self.mainPage = bedLevelWizardMain(self)
1159                 self.headOffsetCalibration = None
1160
1161                 self.FitToPage(self.mainPage)
1162                 self.GetPageAreaSizer().Add(self.mainPage)
1163
1164                 self.RunWizard(self.mainPage)
1165                 self.Destroy()
1166
1167         def OnPageChanging(self, e):
1168                 e.GetPage().StoreData()
1169
1170         def OnPageChanged(self, e):
1171                 if e.GetPage().AllowNext():
1172                         self.FindWindowById(wx.ID_FORWARD).Enable()
1173                 else:
1174                         self.FindWindowById(wx.ID_FORWARD).Disable()
1175                 self.FindWindowById(wx.ID_BACKWARD).Disable()
1176
1177 class headOffsetWizard(wx.wizard.Wizard):
1178         def __init__(self):
1179                 super(headOffsetWizard, self).__init__(None, -1, "Head offset wizard")
1180
1181                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
1182                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
1183
1184                 self.mainPage = headOffsetCalibrationPage(self)
1185
1186                 self.FitToPage(self.mainPage)
1187                 self.GetPageAreaSizer().Add(self.mainPage)
1188
1189                 self.RunWizard(self.mainPage)
1190                 self.Destroy()
1191
1192         def OnPageChanging(self, e):
1193                 e.GetPage().StoreData()
1194
1195         def OnPageChanged(self, e):
1196                 if e.GetPage().AllowNext():
1197                         self.FindWindowById(wx.ID_FORWARD).Enable()
1198                 else:
1199                         self.FindWindowById(wx.ID_FORWARD).Disable()
1200                 self.FindWindowById(wx.ID_BACKWARD).Disable()