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