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