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