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