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