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