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