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