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