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