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