chiark / gitweb /
Make more threads into daemons so Cura closes easier
[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                 t = threading.Thread(target=self.OnExtrudeRun)
730                 t.daemon = True
731                 t.start()
732
733         def OnExtrudeRun(self):
734                 self.heatButton.Enable(False)
735                 self.extrudeButton.Enable(False)
736                 currentEValue = float(self.stepsPerEInput.GetValue())
737                 self.comm = machineCom.MachineCom()
738                 if not self.comm.isOpen():
739                         wx.MessageBox(
740                                 _("Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable"),
741                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
742                         self.heatButton.Enable(True)
743                         self.extrudeButton.Enable(True)
744                         return
745                 while True:
746                         line = self.comm.readline()
747                         if line == '':
748                                 return
749                         if 'start' in line:
750                                 break
751                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
752                 time.sleep(3)
753
754                 self.sendGCommand('M302') #Disable cold extrusion protection
755                 self.sendGCommand("M92 E%f" % (currentEValue))
756                 self.sendGCommand("G92 E0")
757                 self.sendGCommand("G1 E100 F600")
758                 time.sleep(15)
759                 self.comm.close()
760                 self.extrudeButton.Enable()
761                 self.heatButton.Enable()
762
763         def OnHeatClick(self, e):
764                 t = threading.Thread(target=self.OnHeatRun)
765                 t.daemon = True
766                 t.start()
767
768         def OnHeatRun(self):
769                 self.heatButton.Enable(False)
770                 self.extrudeButton.Enable(False)
771                 self.comm = machineCom.MachineCom()
772                 if not self.comm.isOpen():
773                         wx.MessageBox(
774                                 _("Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable"),
775                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
776                         self.heatButton.Enable(True)
777                         self.extrudeButton.Enable(True)
778                         return
779                 while True:
780                         line = self.comm.readline()
781                         if line == '':
782                                 self.heatButton.Enable(True)
783                                 self.extrudeButton.Enable(True)
784                                 return
785                         if 'start' in line:
786                                 break
787                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
788                 time.sleep(3)
789
790                 self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.
791                 wx.MessageBox(
792                         'Wait till you can remove the filament from the machine, and press OK.\n(Temperature is set to 200C)',
793                         'Machine heatup', wx.OK | wx.ICON_INFORMATION)
794                 self.sendGCommand('M104 S0')
795                 time.sleep(1)
796                 self.comm.close()
797                 self.heatButton.Enable(True)
798                 self.extrudeButton.Enable(True)
799
800         def sendGCommand(self, cmd):
801                 self.comm.sendCommand(cmd) #Disable cold extrusion protection
802                 while True:
803                         line = self.comm.readline()
804                         if line == '':
805                                 return
806                         if line.startswith('ok'):
807                                 break
808
809         def StoreData(self):
810                 profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())
811
812 class Ultimaker2ReadyPage(InfoPage):
813         def __init__(self, parent):
814                 super(Ultimaker2ReadyPage, self).__init__(parent, "Ultimaker2")
815                 self.AddText('Congratulations on your the purchase of your brand new Ultimaker2.')
816                 self.AddText('Cura is now ready to be used with your Ultimaker2.')
817                 self.AddSeperator()
818
819 class configWizard(wx.wizard.Wizard):
820         def __init__(self, addNew = False):
821                 super(configWizard, self).__init__(None, -1, "Configuration Wizard")
822
823                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
824                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
825
826                 self.firstInfoPage = FirstInfoPage(self, addNew)
827                 self.machineSelectPage = MachineSelectPage(self)
828                 self.ultimakerSelectParts = SelectParts(self)
829                 self.ultimakerFirmwareUpgradePage = UltimakerFirmwareUpgradePage(self)
830                 self.ultimakerCheckupPage = UltimakerCheckupPage(self)
831                 self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)
832                 self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)
833                 self.bedLevelPage = bedLevelWizardMain(self)
834                 self.headOffsetCalibration = headOffsetCalibrationPage(self)
835                 self.otherMachineSelectPage = OtherMachineSelectPage(self)
836                 self.customRepRapInfoPage = CustomRepRapInfoPage(self)
837                 self.otherMachineInfoPage = OtherMachineInfoPage(self)
838
839                 self.ultimaker2ReadyPage = Ultimaker2ReadyPage(self)
840
841                 wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)
842                 #wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimaker2ReadyPage)
843                 wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerSelectParts)
844                 wx.wizard.WizardPageSimple.Chain(self.ultimakerSelectParts, self.ultimakerFirmwareUpgradePage)
845                 wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)
846                 wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.bedLevelPage)
847                 #wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)
848                 wx.wizard.WizardPageSimple.Chain(self.otherMachineSelectPage, self.customRepRapInfoPage)
849
850                 self.FitToPage(self.firstInfoPage)
851                 self.GetPageAreaSizer().Add(self.firstInfoPage)
852
853                 self.RunWizard(self.firstInfoPage)
854                 self.Destroy()
855
856         def OnPageChanging(self, e):
857                 e.GetPage().StoreData()
858
859         def OnPageChanged(self, e):
860                 if e.GetPage().AllowNext():
861                         self.FindWindowById(wx.ID_FORWARD).Enable()
862                 else:
863                         self.FindWindowById(wx.ID_FORWARD).Disable()
864                 self.FindWindowById(wx.ID_BACKWARD).Disable()
865
866 class bedLevelWizardMain(InfoPage):
867         def __init__(self, parent):
868                 super(bedLevelWizardMain, self).__init__(parent, "Bed leveling wizard")
869
870                 self.AddText('This wizard will help you in leveling your printer bed')
871                 self.AddSeperator()
872                 self.AddText('It will do the following steps')
873                 self.AddText('* Move the printer head to each corner')
874                 self.AddText('  and let you adjust the height of the bed to the nozzle')
875                 self.AddText('* Print a line around the bed to check if it is level')
876                 self.AddSeperator()
877
878                 self.connectButton = self.AddButton('Connect to printer')
879                 self.comm = None
880
881                 self.infoBox = self.AddInfoBox()
882                 self.resumeButton = self.AddButton('Resume')
883                 self.resumeButton.Enable(False)
884
885                 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
886                 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
887
888         def OnConnect(self, e = None):
889                 if self.comm is not None:
890                         self.comm.close()
891                         del self.comm
892                         self.comm = None
893                         wx.CallAfter(self.OnConnect)
894                         return
895                 self.connectButton.Enable(False)
896                 self.comm = machineCom.MachineCom(callbackObject=self)
897                 self.infoBox.SetBusy('Connecting to machine.')
898                 self._wizardState = 0
899
900         def AllowNext(self):
901                 if self.GetParent().headOffsetCalibration is not None and int(profile.getMachineSetting('extruder_amount')) > 1:
902                         wx.wizard.WizardPageSimple.Chain(self, self.GetParent().headOffsetCalibration)
903                 return True
904
905         def OnResume(self, e):
906                 feedZ = profile.getProfileSettingFloat('print_speed') * 60
907                 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
908                 if self._wizardState == 2:
909                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back left corner...')
910                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
911                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, profile.getMachineSettingFloat('machine_depth'), feedTravel))
912                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
913                         self.comm.sendCommand('M400')
914                         self._wizardState = 3
915                 elif self._wizardState == 4:
916                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back right corner...')
917                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
918                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getMachineSettingFloat('machine_width') - 5.0, profile.getMachineSettingFloat('machine_depth') - 25, feedTravel))
919                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
920                         self.comm.sendCommand('M400')
921                         self._wizardState = 5
922                 elif self._wizardState == 6:
923                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to front right corner...')
924                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
925                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getMachineSettingFloat('machine_width') - 5.0, 20, feedTravel))
926                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
927                         self.comm.sendCommand('M400')
928                         self._wizardState = 7
929                 elif self._wizardState == 8:
930                         wx.CallAfter(self.infoBox.SetBusy, 'Heating up printer...')
931                         self.comm.sendCommand('G1 Z15 F%d' % (feedZ))
932                         self.comm.sendCommand('M104 S%d' % (profile.getProfileSettingFloat('print_temperature')))
933                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, 0, feedTravel))
934                         self._wizardState = 9
935                 elif self._wizardState == 10:
936                         self._wizardState = 11
937                         wx.CallAfter(self.infoBox.SetInfo, 'Printing a square on the printer bed at 0.3mm height.')
938                         feedZ = profile.getProfileSettingFloat('print_speed') * 60
939                         feedPrint = profile.getProfileSettingFloat('print_speed') * 60
940                         feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
941                         w = profile.getMachineSettingFloat('machine_width')
942                         d = profile.getMachineSettingFloat('machine_depth')
943                         filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
944                         filamentArea = math.pi * filamentRadius * filamentRadius
945                         ePerMM = (profile.calculateEdgeWidth() * 0.3) / filamentArea
946                         eValue = 0.0
947
948                         gcodeList = [
949                                 'G1 Z2 F%d' % (feedZ),
950                                 'G92 E0',
951                                 'G1 X%d Y%d F%d' % (5, 5, feedTravel),
952                                 'G1 Z0.3 F%d' % (feedZ)]
953                         eValue += 5.0
954                         gcodeList.append('G1 E%f F%d' % (eValue, profile.getProfileSettingFloat('retraction_speed') * 60))
955
956                         for i in xrange(0, 3):
957                                 dist = 5.0 + 0.4 * float(i)
958                                 eValue += (d - 2.0*dist) * ePerMM
959                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (dist, d - dist, eValue, feedPrint))
960                                 eValue += (w - 2.0*dist) * ePerMM
961                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (w - dist, d - dist, eValue, feedPrint))
962                                 eValue += (d - 2.0*dist) * ePerMM
963                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (w - dist, dist, eValue, feedPrint))
964                                 eValue += (w - 2.0*dist) * ePerMM
965                                 gcodeList.append('G1 X%f Y%f E%f F%d' % (dist, dist, eValue, feedPrint))
966
967                         gcodeList.append('M400')
968                         self.comm.printGCode(gcodeList)
969                 self.resumeButton.Enable(False)
970
971         def mcLog(self, message):
972                 print 'Log:', message
973
974         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
975                 if self._wizardState == 1:
976                         self._wizardState = 2
977                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front left screw of your printer bed\nSo the nozzle just hits the bed.')
978                         wx.CallAfter(self.resumeButton.Enable, True)
979                 elif self._wizardState == 3:
980                         self._wizardState = 4
981                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back left screw of your printer bed\nSo the nozzle just hits the bed.')
982                         wx.CallAfter(self.resumeButton.Enable, True)
983                 elif self._wizardState == 5:
984                         self._wizardState = 6
985                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back right screw of your printer bed\nSo the nozzle just hits the bed.')
986                         wx.CallAfter(self.resumeButton.Enable, True)
987                 elif self._wizardState == 7:
988                         self._wizardState = 8
989                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front right screw of your printer bed\nSo the nozzle just hits the bed.')
990                         wx.CallAfter(self.resumeButton.Enable, True)
991                 elif self._wizardState == 9:
992                         if temp[0] < profile.getProfileSettingFloat('print_temperature') - 5:
993                                 wx.CallAfter(self.infoBox.SetInfo, 'Heating up printer: %d/%d' % (temp[0], profile.getProfileSettingFloat('print_temperature')))
994                         else:
995                                 wx.CallAfter(self.infoBox.SetAttention, 'The printer is hot now. Please insert some PLA filament into the printer.')
996                                 wx.CallAfter(self.resumeButton.Enable, True)
997                                 self._wizardState = 10
998
999         def mcStateChange(self, state):
1000                 if self.comm is None:
1001                         return
1002                 if self.comm.isOperational():
1003                         if self._wizardState == 0:
1004                                 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer...')
1005                                 self.comm.sendCommand('M105')
1006                                 self.comm.sendCommand('G28')
1007                                 self._wizardState = 1
1008                         elif self._wizardState == 11 and not self.comm.isPrinting():
1009                                 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('print_speed') * 60))
1010                                 self.comm.sendCommand('G92 E0')
1011                                 self.comm.sendCommand('G1 E-10 F%d' % (profile.getProfileSettingFloat('retraction_speed') * 60))
1012                                 self.comm.sendCommand('M104 S0')
1013                                 wx.CallAfter(self.infoBox.SetInfo, 'Calibration finished.\nThe squares on the bed should slightly touch each other.')
1014                                 wx.CallAfter(self.infoBox.SetReadyIndicator)
1015                                 wx.CallAfter(self.GetParent().FindWindowById(wx.ID_FORWARD).Enable)
1016                                 wx.CallAfter(self.connectButton.Enable, True)
1017                                 self._wizardState = 12
1018                 elif self.comm.isError():
1019                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
1020
1021         def mcMessage(self, message):
1022                 pass
1023
1024         def mcProgress(self, lineNr):
1025                 pass
1026
1027         def mcZChange(self, newZ):
1028                 pass
1029
1030 class headOffsetCalibrationPage(InfoPage):
1031         def __init__(self, parent):
1032                 super(headOffsetCalibrationPage, self).__init__(parent, "Printer head offset calibration")
1033
1034                 self.AddText('This wizard will help you in calibrating the printer head offsets of your dual extrusion machine')
1035                 self.AddSeperator()
1036
1037                 self.connectButton = self.AddButton('Connect to printer')
1038                 self.comm = None
1039
1040                 self.infoBox = self.AddInfoBox()
1041                 self.textEntry = self.AddTextCtrl('')
1042                 self.textEntry.Enable(False)
1043                 self.resumeButton = self.AddButton('Resume')
1044                 self.resumeButton.Enable(False)
1045
1046                 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
1047                 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
1048
1049         def OnConnect(self, e = None):
1050                 if self.comm is not None:
1051                         self.comm.close()
1052                         del self.comm
1053                         self.comm = None
1054                         wx.CallAfter(self.OnConnect)
1055                         return
1056                 self.connectButton.Enable(False)
1057                 self.comm = machineCom.MachineCom(callbackObject=self)
1058                 self.infoBox.SetBusy('Connecting to machine.')
1059                 self._wizardState = 0
1060
1061         def OnResume(self, e):
1062                 if self._wizardState == 2:
1063                         self._wizardState = 3
1064                         wx.CallAfter(self.infoBox.SetBusy, 'Printing initial calibration cross')
1065
1066                         w = profile.getMachineSettingFloat('machine_width')
1067                         d = profile.getMachineSettingFloat('machine_depth')
1068
1069                         gcode = gcodeGenerator.gcodeGenerator()
1070                         gcode.setExtrusionRate(profile.getProfileSettingFloat('nozzle_size') * 1.5, 0.2)
1071                         gcode.setPrintSpeed(profile.getProfileSettingFloat('bottom_layer_speed'))
1072                         gcode.addCmd('T0')
1073                         gcode.addPrime(15)
1074                         gcode.addCmd('T1')
1075                         gcode.addPrime(15)
1076
1077                         gcode.addCmd('T0')
1078                         gcode.addMove(w/2, 5)
1079                         gcode.addMove(z=0.2)
1080                         gcode.addPrime()
1081                         gcode.addExtrude(w/2, d-5.0)
1082                         gcode.addRetract()
1083                         gcode.addMove(5, d/2)
1084                         gcode.addPrime()
1085                         gcode.addExtrude(w-5.0, d/2)
1086                         gcode.addRetract(15)
1087
1088                         gcode.addCmd('T1')
1089                         gcode.addMove(w/2, 5)
1090                         gcode.addPrime()
1091                         gcode.addExtrude(w/2, d-5.0)
1092                         gcode.addRetract()
1093                         gcode.addMove(5, d/2)
1094                         gcode.addPrime()
1095                         gcode.addExtrude(w-5.0, d/2)
1096                         gcode.addRetract(15)
1097                         gcode.addCmd('T0')
1098
1099                         gcode.addMove(z=25)
1100                         gcode.addMove(0, 0)
1101                         gcode.addCmd('M400')
1102
1103                         self.comm.printGCode(gcode.list())
1104                         self.resumeButton.Enable(False)
1105                 elif self._wizardState == 4:
1106                         try:
1107                                 float(self.textEntry.GetValue())
1108                         except ValueError:
1109                                 return
1110                         profile.putPreference('extruder_offset_x1', self.textEntry.GetValue())
1111                         self._wizardState = 5
1112                         self.infoBox.SetAttention('Please measure the distance between the horizontal lines in millimeters.')
1113                         self.textEntry.SetValue('0.0')
1114                         self.textEntry.Enable(True)
1115                 elif self._wizardState == 5:
1116                         try:
1117                                 float(self.textEntry.GetValue())
1118                         except ValueError:
1119                                 return
1120                         profile.putPreference('extruder_offset_y1', self.textEntry.GetValue())
1121                         self._wizardState = 6
1122                         self.infoBox.SetBusy('Printing the fine calibration lines.')
1123                         self.textEntry.SetValue('')
1124                         self.textEntry.Enable(False)
1125                         self.resumeButton.Enable(False)
1126
1127                         x = profile.getMachineSettingFloat('extruder_offset_x1')
1128                         y = profile.getMachineSettingFloat('extruder_offset_y1')
1129                         gcode = gcodeGenerator.gcodeGenerator()
1130                         gcode.setExtrusionRate(profile.getProfileSettingFloat('nozzle_size') * 1.5, 0.2)
1131                         gcode.setPrintSpeed(25)
1132                         gcode.addHome()
1133                         gcode.addCmd('T0')
1134                         gcode.addMove(50, 40, 0.2)
1135                         gcode.addPrime(15)
1136                         for n in xrange(0, 10):
1137                                 gcode.addExtrude(50 + n * 10, 150)
1138                                 gcode.addExtrude(50 + n * 10 + 5, 150)
1139                                 gcode.addExtrude(50 + n * 10 + 5, 40)
1140                                 gcode.addExtrude(50 + n * 10 + 10, 40)
1141                         gcode.addMove(40, 50)
1142                         for n in xrange(0, 10):
1143                                 gcode.addExtrude(150, 50 + n * 10)
1144                                 gcode.addExtrude(150, 50 + n * 10 + 5)
1145                                 gcode.addExtrude(40, 50 + n * 10 + 5)
1146                                 gcode.addExtrude(40, 50 + n * 10 + 10)
1147                         gcode.addRetract(15)
1148
1149                         gcode.addCmd('T1')
1150                         gcode.addMove(50 - x, 30 - y, 0.2)
1151                         gcode.addPrime(15)
1152                         for n in xrange(0, 10):
1153                                 gcode.addExtrude(50 + n * 10.2 - 1.0 - x, 140 - y)
1154                                 gcode.addExtrude(50 + n * 10.2 - 1.0 + 5.1 - x, 140 - y)
1155                                 gcode.addExtrude(50 + n * 10.2 - 1.0 + 5.1 - x, 30 - y)
1156                                 gcode.addExtrude(50 + n * 10.2 - 1.0 + 10 - x, 30 - y)
1157                         gcode.addMove(30 - x, 50 - y, 0.2)
1158                         for n in xrange(0, 10):
1159                                 gcode.addExtrude(160 - x, 50 + n * 10.2 - 1.0 - y)
1160                                 gcode.addExtrude(160 - x, 50 + n * 10.2 - 1.0 + 5.1 - y)
1161                                 gcode.addExtrude(30 - x, 50 + n * 10.2 - 1.0 + 5.1 - y)
1162                                 gcode.addExtrude(30 - x, 50 + n * 10.2 - 1.0 + 10 - y)
1163                         gcode.addRetract(15)
1164                         gcode.addMove(z=15)
1165                         gcode.addCmd('M400')
1166                         gcode.addCmd('M104 T0 S0')
1167                         gcode.addCmd('M104 T1 S0')
1168                         self.comm.printGCode(gcode.list())
1169                 elif self._wizardState == 7:
1170                         try:
1171                                 n = int(self.textEntry.GetValue()) - 1
1172                         except:
1173                                 return
1174                         x = profile.getMachineSettingFloat('extruder_offset_x1')
1175                         x += -1.0 + n * 0.1
1176                         profile.putPreference('extruder_offset_x1', '%0.2f' % (x))
1177                         self.infoBox.SetAttention('Which horizontal line number lays perfect on top of each other? Front most line is zero.')
1178                         self.textEntry.SetValue('10')
1179                         self._wizardState = 8
1180                 elif self._wizardState == 8:
1181                         try:
1182                                 n = int(self.textEntry.GetValue()) - 1
1183                         except:
1184                                 return
1185                         y = profile.getMachineSettingFloat('extruder_offset_y1')
1186                         y += -1.0 + n * 0.1
1187                         profile.putPreference('extruder_offset_y1', '%0.2f' % (y))
1188                         self.infoBox.SetInfo('Calibration finished. Offsets are: %s %s' % (profile.getMachineSettingFloat('extruder_offset_x1'), profile.getMachineSettingFloat('extruder_offset_y1')))
1189                         self.infoBox.SetReadyIndicator()
1190                         self._wizardState = 8
1191                         self.comm.close()
1192                         self.resumeButton.Enable(False)
1193
1194         def mcLog(self, message):
1195                 print 'Log:', message
1196
1197         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
1198                 if self._wizardState == 1:
1199                         if temp[0] >= 210 and temp[1] >= 210:
1200                                 self._wizardState = 2
1201                                 wx.CallAfter(self.infoBox.SetAttention, 'Please load both extruders with PLA.')
1202                                 wx.CallAfter(self.resumeButton.Enable, True)
1203                                 wx.CallAfter(self.resumeButton.SetFocus)
1204
1205         def mcStateChange(self, state):
1206                 if self.comm is None:
1207                         return
1208                 if self.comm.isOperational():
1209                         if self._wizardState == 0:
1210                                 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer and heating up both extruders.')
1211                                 self.comm.sendCommand('M105')
1212                                 self.comm.sendCommand('M104 S220 T0')
1213                                 self.comm.sendCommand('M104 S220 T1')
1214                                 self.comm.sendCommand('G28')
1215                                 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('print_speed') * 60))
1216                                 self._wizardState = 1
1217                         if not self.comm.isPrinting():
1218                                 if self._wizardState == 3:
1219                                         self._wizardState = 4
1220                                         wx.CallAfter(self.infoBox.SetAttention, 'Please measure the distance between the vertical lines in millimeters.')
1221                                         wx.CallAfter(self.textEntry.SetValue, '0.0')
1222                                         wx.CallAfter(self.textEntry.Enable, True)
1223                                         wx.CallAfter(self.resumeButton.Enable, True)
1224                                         wx.CallAfter(self.resumeButton.SetFocus)
1225                                 elif self._wizardState == 6:
1226                                         self._wizardState = 7
1227                                         wx.CallAfter(self.infoBox.SetAttention, 'Which vertical line number lays perfect on top of each other? Leftmost line is zero.')
1228                                         wx.CallAfter(self.textEntry.SetValue, '10')
1229                                         wx.CallAfter(self.textEntry.Enable, True)
1230                                         wx.CallAfter(self.resumeButton.Enable, True)
1231                                         wx.CallAfter(self.resumeButton.SetFocus)
1232
1233                 elif self.comm.isError():
1234                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
1235
1236         def mcMessage(self, message):
1237                 pass
1238
1239         def mcProgress(self, lineNr):
1240                 pass
1241
1242         def mcZChange(self, newZ):
1243                 pass
1244
1245 class bedLevelWizard(wx.wizard.Wizard):
1246         def __init__(self):
1247                 super(bedLevelWizard, self).__init__(None, -1, "Bed leveling wizard")
1248
1249                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
1250                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
1251
1252                 self.mainPage = bedLevelWizardMain(self)
1253                 self.headOffsetCalibration = None
1254
1255                 self.FitToPage(self.mainPage)
1256                 self.GetPageAreaSizer().Add(self.mainPage)
1257
1258                 self.RunWizard(self.mainPage)
1259                 self.Destroy()
1260
1261         def OnPageChanging(self, e):
1262                 e.GetPage().StoreData()
1263
1264         def OnPageChanged(self, e):
1265                 if e.GetPage().AllowNext():
1266                         self.FindWindowById(wx.ID_FORWARD).Enable()
1267                 else:
1268                         self.FindWindowById(wx.ID_FORWARD).Disable()
1269                 self.FindWindowById(wx.ID_BACKWARD).Disable()
1270
1271 class headOffsetWizard(wx.wizard.Wizard):
1272         def __init__(self):
1273                 super(headOffsetWizard, self).__init__(None, -1, "Head offset wizard")
1274
1275                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
1276                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
1277
1278                 self.mainPage = headOffsetCalibrationPage(self)
1279
1280                 self.FitToPage(self.mainPage)
1281                 self.GetPageAreaSizer().Add(self.mainPage)
1282
1283                 self.RunWizard(self.mainPage)
1284                 self.Destroy()
1285
1286         def OnPageChanging(self, e):
1287                 e.GetPage().StoreData()
1288
1289         def OnPageChanged(self, e):
1290                 if e.GetPage().AllowNext():
1291                         self.FindWindowById(wx.ID_FORWARD).Enable()
1292                 else:
1293                         self.FindWindowById(wx.ID_FORWARD).Disable()
1294                 self.FindWindowById(wx.ID_BACKWARD).Disable()