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