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