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