chiark / gitweb /
Added online stat collection, added dropdown 3D button.
[cura.git] / Cura / gui / configWizard.py
1 # coding=utf-8
2 from __future__ import absolute_import
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.resources import getPathForImage
17
18 class InfoBox(wx.Panel):
19         def __init__(self, parent):
20                 super(InfoBox, self).__init__(parent)
21                 self.SetBackgroundColour('#FFFF80')
22
23                 self.sizer = wx.GridBagSizer(5, 5)
24                 self.SetSizer(self.sizer)
25
26                 self.attentionBitmap = wx.Bitmap(getPathForImage('attention.png'))
27                 self.errorBitmap = wx.Bitmap(getPathForImage('error.png'))
28                 self.readyBitmap = wx.Bitmap(getPathForImage('ready.png'))
29                 self.busyBitmap = [
30                         wx.Bitmap(getPathForImage('busy-0.png')),
31                         wx.Bitmap(getPathForImage('busy-1.png')),
32                         wx.Bitmap(getPathForImage('busy-2.png')),
33                         wx.Bitmap(getPathForImage('busy-3.png'))
34                 ]
35
36                 self.bitmap = wx.StaticBitmap(self, -1, wx.EmptyBitmapRGBA(24, 24, red=255, green=255, blue=255, alpha=1))
37                 self.text = wx.StaticText(self, -1, '')
38                 self.extraInfoButton = wx.Button(self, -1, 'i', style=wx.BU_EXACTFIT)
39                 self.sizer.Add(self.bitmap, pos=(0, 0), flag=wx.ALL, border=5)
40                 self.sizer.Add(self.text, pos=(0, 1), flag=wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, border=5)
41                 self.sizer.Add(self.extraInfoButton, pos=(0,2), flag=wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, border=5)
42                 self.sizer.AddGrowableCol(1)
43
44                 self.extraInfoButton.Show(False)
45
46                 self.extraInfoUrl = ''
47                 self.busyState = None
48                 self.timer = wx.Timer(self)
49                 self.Bind(wx.EVT_TIMER, self.doBusyUpdate, self.timer)
50                 self.Bind(wx.EVT_BUTTON, self.doExtraInfo, self.extraInfoButton)
51                 self.timer.Start(100)
52
53         def SetInfo(self, info):
54                 self.SetBackgroundColour('#FFFF80')
55                 self.text.SetLabel(info)
56                 self.extraInfoButton.Show(False)
57                 self.Refresh()
58
59         def SetError(self, info, extraInfoUrl):
60                 self.extraInfoUrl = extraInfoUrl
61                 self.SetBackgroundColour('#FF8080')
62                 self.text.SetLabel(info)
63                 self.extraInfoButton.Show(True)
64                 self.Layout()
65                 self.SetErrorIndicator()
66                 self.Refresh()
67
68         def SetAttention(self, info):
69                 self.SetBackgroundColour('#FFFF80')
70                 self.text.SetLabel(info)
71                 self.extraInfoButton.Show(False)
72                 self.SetAttentionIndicator()
73                 self.Layout()
74                 self.Refresh()
75
76         def SetBusy(self, info):
77                 self.SetInfo(info)
78                 self.SetBusyIndicator()
79
80         def SetBusyIndicator(self):
81                 self.busyState = 0
82                 self.bitmap.SetBitmap(self.busyBitmap[self.busyState])
83
84         def doExtraInfo(self, e):
85                 webbrowser.open(self.extraInfoUrl)
86
87         def doBusyUpdate(self, e):
88                 if self.busyState is None:
89                         return
90                 self.busyState += 1
91                 if self.busyState >= len(self.busyBitmap):
92                         self.busyState = 0
93                 self.bitmap.SetBitmap(self.busyBitmap[self.busyState])
94
95         def SetReadyIndicator(self):
96                 self.busyState = None
97                 self.bitmap.SetBitmap(self.readyBitmap)
98
99         def SetErrorIndicator(self):
100                 self.busyState = None
101                 self.bitmap.SetBitmap(self.errorBitmap)
102
103         def SetAttentionIndicator(self):
104                 self.busyState = None
105                 self.bitmap.SetBitmap(self.attentionBitmap)
106
107
108 class InfoPage(wx.wizard.WizardPageSimple):
109         def __init__(self, parent, title):
110                 wx.wizard.WizardPageSimple.__init__(self, parent)
111
112                 sizer = wx.GridBagSizer(5, 5)
113                 self.sizer = sizer
114                 self.SetSizer(sizer)
115
116                 title = wx.StaticText(self, -1, title)
117                 title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
118                 sizer.Add(title, pos=(0, 0), span=(1, 2), flag=wx.ALIGN_CENTRE | wx.ALL)
119                 sizer.Add(wx.StaticLine(self, -1), pos=(1, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
120                 sizer.AddGrowableCol(1)
121
122                 self.rowNr = 2
123
124         def AddText(self, info):
125                 text = wx.StaticText(self, -1, info)
126                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT)
127                 self.rowNr += 1
128                 return text
129
130         def AddSeperator(self):
131                 self.GetSizer().Add(wx.StaticLine(self, -1), pos=(self.rowNr, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
132                 self.rowNr += 1
133
134         def AddHiddenSeperator(self):
135                 self.AddText('')
136
137         def AddInfoBox(self):
138                 infoBox = InfoBox(self)
139                 self.GetSizer().Add(infoBox, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT | wx.EXPAND)
140                 self.rowNr += 1
141                 return infoBox
142
143         def AddRadioButton(self, label, style=0):
144                 radio = wx.RadioButton(self, -1, label, style=style)
145                 self.GetSizer().Add(radio, pos=(self.rowNr, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
146                 self.rowNr += 1
147                 return radio
148
149         def AddCheckbox(self, label, checked=False):
150                 check = wx.CheckBox(self, -1)
151                 text = wx.StaticText(self, -1, label)
152                 check.SetValue(checked)
153                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT | wx.RIGHT)
154                 self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1, 2), flag=wx.ALL)
155                 self.rowNr += 1
156                 return check
157
158         def AddButton(self, label):
159                 button = wx.Button(self, -1, label)
160                 self.GetSizer().Add(button, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT)
161                 self.rowNr += 1
162                 return button
163
164         def AddDualButton(self, label1, label2):
165                 button1 = wx.Button(self, -1, label1)
166                 self.GetSizer().Add(button1, pos=(self.rowNr, 0), flag=wx.RIGHT)
167                 button2 = wx.Button(self, -1, label2)
168                 self.GetSizer().Add(button2, pos=(self.rowNr, 1))
169                 self.rowNr += 1
170                 return button1, button2
171
172         def AddTextCtrl(self, value):
173                 ret = wx.TextCtrl(self, -1, value)
174                 self.GetSizer().Add(ret, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT)
175                 self.rowNr += 1
176                 return ret
177
178         def AddLabelTextCtrl(self, info, value):
179                 text = wx.StaticText(self, -1, info)
180                 ret = wx.TextCtrl(self, -1, value)
181                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT)
182                 self.GetSizer().Add(ret, pos=(self.rowNr, 1), span=(1, 1), flag=wx.LEFT)
183                 self.rowNr += 1
184                 return ret
185
186         def AddTextCtrlButton(self, value, buttonText):
187                 text = wx.TextCtrl(self, -1, value)
188                 button = wx.Button(self, -1, buttonText)
189                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT)
190                 self.GetSizer().Add(button, pos=(self.rowNr, 1), span=(1, 1), flag=wx.LEFT)
191                 self.rowNr += 1
192                 return text, button
193
194         def AddBitmap(self, bitmap):
195                 bitmap = wx.StaticBitmap(self, -1, bitmap)
196                 self.GetSizer().Add(bitmap, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT)
197                 self.rowNr += 1
198                 return bitmap
199
200         def AddCheckmark(self, label, bitmap):
201                 check = wx.StaticBitmap(self, -1, bitmap)
202                 text = wx.StaticText(self, -1, label)
203                 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT | wx.RIGHT)
204                 self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1, 1), flag=wx.ALL)
205                 self.rowNr += 1
206                 return check
207
208         def AllowNext(self):
209                 return True
210
211         def StoreData(self):
212                 pass
213
214
215 class FirstInfoPage(InfoPage):
216         def __init__(self, parent):
217                 super(FirstInfoPage, self).__init__(parent, "First time run wizard")
218                 self.AddText('Welcome, and thanks for trying Cura!')
219                 self.AddSeperator()
220                 self.AddText('This wizard will help you with the following steps:')
221                 self.AddText('* Configure Cura for your machine')
222                 self.AddText('* Upgrade your firmware')
223                 self.AddText('* Check if your machine is working safely')
224                 self.AddText('* Level your printer bed')
225
226                 #self.AddText('* Calibrate your machine')
227                 #self.AddText('* Do your first print')
228
229
230 class RepRapInfoPage(InfoPage):
231         def __init__(self, parent):
232                 super(RepRapInfoPage, self).__init__(parent, "RepRap information")
233                 self.AddText(
234                         'RepRap machines are vastly different, and there is no\ndefault configuration in Cura for any of them.')
235                 self.AddText('If you like a default profile for your machine added,\nthen make an issue on github.')
236                 self.AddSeperator()
237                 self.AddText('You will have to manually install Marlin or Sprinter firmware.')
238                 self.AddSeperator()
239                 self.machineWidth = self.AddLabelTextCtrl('Machine width (mm)', '80')
240                 self.machineDepth = self.AddLabelTextCtrl('Machine depth (mm)', '80')
241                 self.machineHeight = self.AddLabelTextCtrl('Machine height (mm)', '60')
242                 self.nozzleSize = self.AddLabelTextCtrl('Nozzle size (mm)', '0.5')
243                 self.heatedBed = self.AddCheckbox('Heated bed')
244                 self.HomeAtCenter = self.AddCheckbox('Bed center is 0,0,0 (RoStock)')
245
246         def StoreData(self):
247                 profile.putPreference('machine_width', self.machineWidth.GetValue())
248                 profile.putPreference('machine_depth', self.machineDepth.GetValue())
249                 profile.putPreference('machine_height', self.machineHeight.GetValue())
250                 profile.putProfileSetting('nozzle_size', self.nozzleSize.GetValue())
251                 profile.putProfileSetting('wall_thickness', float(profile.getProfileSettingFloat('nozzle_size')) * 2)
252                 profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))
253                 profile.putPreference('machine_center_is_zero', str(self.HomeAtCenter.GetValue()))
254
255
256 class MachineSelectPage(InfoPage):
257         def __init__(self, parent):
258                 super(MachineSelectPage, self).__init__(parent, "Select your machine")
259                 self.AddText('What kind of machine do you have:')
260
261                 self.UltimakerRadio = self.AddRadioButton("Ultimaker", style=wx.RB_GROUP)
262                 self.UltimakerRadio.SetValue(True)
263                 self.UltimakerRadio.Bind(wx.EVT_RADIOBUTTON, self.OnUltimakerSelect)
264                 self.OtherRadio = self.AddRadioButton("Other (Ex: RepRap)")
265                 self.OtherRadio.Bind(wx.EVT_RADIOBUTTON, self.OnOtherSelect)
266                 self.AddSeperator()
267                 self.AddText('The collection of anonymous usage information helps with the continued improvement of Cura.')
268                 self.AddText('This does NOT submit your models online nor gathers any privacy related information.')
269                 self.SubmitUserStats = self.AddCheckbox('Submit anonymous usage information:')
270                 self.AddText('For full details see: http://wiki.ultimaker.com/Cura:stats')
271                 self.SubmitUserStats.SetValue(True)
272
273         def OnUltimakerSelect(self, e):
274                 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().ultimakerFirmwareUpgradePage)
275
276         def OnOtherSelect(self, e):
277                 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().repRapInfoPage)
278
279         def StoreData(self):
280                 if self.UltimakerRadio.GetValue():
281                         profile.putPreference('machine_width', '205')
282                         profile.putPreference('machine_depth', '205')
283                         profile.putPreference('machine_height', '200')
284                         profile.putPreference('machine_type', 'ultimaker')
285                         profile.putPreference('machine_center_is_zero', 'False')
286                         profile.putProfileSetting('nozzle_size', '0.4')
287                 else:
288                         profile.putPreference('machine_width', '80')
289                         profile.putPreference('machine_depth', '80')
290                         profile.putPreference('machine_height', '60')
291                         profile.putPreference('machine_type', 'reprap')
292                         profile.putPreference('startMode', 'Normal')
293                         profile.putProfileSetting('nozzle_size', '0.5')
294                 profile.putProfileSetting('wall_thickness', float(profile.getProfileSetting('nozzle_size')) * 2)
295                 if self.SubmitUserStats.GetValue():
296                         profile.putPreference('submit_slice_information', 'True')
297                 else:
298                         profile.putPreference('submit_slice_information', 'False')
299
300 class SelectParts(InfoPage):
301         def __init__(self, parent):
302                 super(SelectParts, self).__init__(parent, "Select upgraded parts you have")
303                 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.')
304                 self.AddSeperator()
305                 self.springExtruder = self.AddCheckbox('Extruder drive upgrade')
306                 self.heatedBed = self.AddCheckbox('Heated printer bed (self built)')
307                 self.dualExtrusion = self.AddCheckbox('Dual extrusion (experimental)')
308                 self.AddSeperator()
309                 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.')
310                 self.AddText('This upgrade can be bought from the Ultimaker webshop\nor found on thingiverse as thing:26094')
311                 self.springExtruder.SetValue(True)
312
313         def StoreData(self):
314                 profile.putPreference('ultimaker_extruder_upgrade', str(self.springExtruder.GetValue()))
315                 profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))
316                 if self.dualExtrusion.GetValue():
317                         profile.putPreference('extruder_amount', '2')
318                 else:
319                         profile.putPreference('extruder_amount', '1')
320                 if profile.getPreference('ultimaker_extruder_upgrade') == 'True':
321                         profile.putProfileSetting('retraction_enable', 'True')
322                 else:
323                         profile.putProfileSetting('retraction_enable', 'False')
324
325
326 class FirmwareUpgradePage(InfoPage):
327         def __init__(self, parent):
328                 super(FirmwareUpgradePage, self).__init__(parent, "Upgrade Ultimaker Firmware")
329                 self.AddText(
330                         '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.')
331                 self.AddHiddenSeperator()
332                 self.AddText(
333                         'The firmware shipping with new Ultimakers works, but upgrades\nhave been made to make better prints, and make calibration easier.')
334                 self.AddHiddenSeperator()
335                 self.AddText(
336                         '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.')
337                 upgradeButton, skipUpgradeButton = self.AddDualButton('Upgrade to Marlin firmware', 'Skip upgrade')
338                 upgradeButton.Bind(wx.EVT_BUTTON, self.OnUpgradeClick)
339                 skipUpgradeButton.Bind(wx.EVT_BUTTON, self.OnSkipClick)
340                 self.AddHiddenSeperator()
341                 self.AddText('Do not upgrade to this firmware if:')
342                 self.AddText('* You have an older machine based on ATMega1280')
343                 self.AddText('* Have other changes in the firmware')
344                 button = self.AddButton('Goto this page for a custom firmware')
345                 button.Bind(wx.EVT_BUTTON, self.OnUrlClick)
346
347         def AllowNext(self):
348                 return False
349
350         def OnUpgradeClick(self, e):
351                 if firmwareInstall.InstallFirmware():
352                         self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
353
354         def OnSkipClick(self, e):
355                 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
356
357         def OnUrlClick(self, e):
358                 webbrowser.open('http://daid.mine.nu/~daid/marlin_build/')
359
360
361 class UltimakerCheckupPage(InfoPage):
362         def __init__(self, parent):
363                 super(UltimakerCheckupPage, self).__init__(parent, "Ultimaker Checkup")
364
365                 self.checkBitmap = wx.Bitmap(getPathForImage('checkmark.png'))
366                 self.crossBitmap = wx.Bitmap(getPathForImage('cross.png'))
367                 self.unknownBitmap = wx.Bitmap(getPathForImage('question.png'))
368                 self.endStopNoneBitmap = wx.Bitmap(getPathForImage('endstop_none.png'))
369                 self.endStopXMinBitmap = wx.Bitmap(getPathForImage('endstop_xmin.png'))
370                 self.endStopXMaxBitmap = wx.Bitmap(getPathForImage('endstop_xmax.png'))
371                 self.endStopYMinBitmap = wx.Bitmap(getPathForImage('endstop_ymin.png'))
372                 self.endStopYMaxBitmap = wx.Bitmap(getPathForImage('endstop_ymax.png'))
373                 self.endStopZMinBitmap = wx.Bitmap(getPathForImage('endstop_zmin.png'))
374                 self.endStopZMaxBitmap = wx.Bitmap(getPathForImage('endstop_zmax.png'))
375
376                 self.AddText(
377                         '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.')
378                 b1, b2 = self.AddDualButton('Run checks', 'Skip checks')
379                 b1.Bind(wx.EVT_BUTTON, self.OnCheckClick)
380                 b2.Bind(wx.EVT_BUTTON, self.OnSkipClick)
381                 self.AddSeperator()
382                 self.commState = self.AddCheckmark('Communication:', self.unknownBitmap)
383                 self.tempState = self.AddCheckmark('Temperature:', self.unknownBitmap)
384                 self.stopState = self.AddCheckmark('Endstops:', self.unknownBitmap)
385                 self.AddSeperator()
386                 self.infoBox = self.AddInfoBox()
387                 self.machineState = self.AddText('')
388                 self.temperatureLabel = self.AddText('')
389                 self.errorLogButton = self.AddButton('Show error log')
390                 self.errorLogButton.Show(False)
391                 self.AddSeperator()
392                 self.endstopBitmap = self.AddBitmap(self.endStopNoneBitmap)
393                 self.comm = None
394                 self.xMinStop = False
395                 self.xMaxStop = False
396                 self.yMinStop = False
397                 self.yMaxStop = False
398                 self.zMinStop = False
399                 self.zMaxStop = False
400
401                 self.Bind(wx.EVT_BUTTON, self.OnErrorLog, self.errorLogButton)
402
403         def __del__(self):
404                 if self.comm is not None:
405                         self.comm.close()
406
407         def AllowNext(self):
408                 self.endstopBitmap.Show(False)
409                 return False
410
411         def OnSkipClick(self, e):
412                 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
413
414         def OnCheckClick(self, e=None):
415                 self.errorLogButton.Show(False)
416                 if self.comm is not None:
417                         self.comm.close()
418                         del self.comm
419                         self.comm = None
420                         wx.CallAfter(self.OnCheckClick)
421                         return
422                 self.infoBox.SetBusy('Connecting to machine.')
423                 self.commState.SetBitmap(self.unknownBitmap)
424                 self.tempState.SetBitmap(self.unknownBitmap)
425                 self.stopState.SetBitmap(self.unknownBitmap)
426                 self.checkupState = 0
427                 self.comm = machineCom.MachineCom(callbackObject=self)
428
429         def OnErrorLog(self, e):
430                 printWindow.LogWindow('\n'.join(self.comm.getLog()))
431
432         def mcLog(self, message):
433                 pass
434
435         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
436                 if not self.comm.isOperational():
437                         return
438                 if self.checkupState == 0:
439                         self.tempCheckTimeout = 20
440                         if temp > 70:
441                                 self.checkupState = 1
442                                 wx.CallAfter(self.infoBox.SetInfo, 'Cooldown before temperature check.')
443                                 self.comm.sendCommand('M104 S0')
444                                 self.comm.sendCommand('M104 S0')
445                         else:
446                                 self.startTemp = temp
447                                 self.checkupState = 2
448                                 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
449                                 self.comm.sendCommand('M104 S200')
450                                 self.comm.sendCommand('M104 S200')
451                 elif self.checkupState == 1:
452                         if temp < 60:
453                                 self.startTemp = temp
454                                 self.checkupState = 2
455                                 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
456                                 self.comm.sendCommand('M104 S200')
457                                 self.comm.sendCommand('M104 S200')
458                 elif self.checkupState == 2:
459                         #print "WARNING, TEMPERATURE TEST DISABLED FOR TESTING!"
460                         if temp > self.startTemp + 40:
461                                 self.checkupState = 3
462                                 wx.CallAfter(self.infoBox.SetAttention, 'Please make sure none of the endstops are pressed.')
463                                 wx.CallAfter(self.endstopBitmap.Show, True)
464                                 wx.CallAfter(self.Layout)
465                                 self.comm.sendCommand('M104 S0')
466                                 self.comm.sendCommand('M104 S0')
467                                 self.comm.sendCommand('M119')
468                                 wx.CallAfter(self.tempState.SetBitmap, self.checkBitmap)
469                         else:
470                                 self.tempCheckTimeout -= 1
471                                 if self.tempCheckTimeout < 1:
472                                         self.checkupState = -1
473                                         wx.CallAfter(self.tempState.SetBitmap, self.crossBitmap)
474                                         wx.CallAfter(self.infoBox.SetError, 'Temperature measurement FAILED!', 'http://wiki.ultimaker.com/Cura:_Temperature_measurement_problems')
475                                         self.comm.sendCommand('M104 S0')
476                                         self.comm.sendCommand('M104 S0')
477                 elif self.checkupState >= 3 and self.checkupState < 10:
478                         self.comm.sendCommand('M119')
479                 wx.CallAfter(self.temperatureLabel.SetLabel, 'Head temperature: %d' % (temp))
480
481         def mcStateChange(self, state):
482                 if self.comm is None:
483                         return
484                 if self.comm.isOperational():
485                         wx.CallAfter(self.commState.SetBitmap, self.checkBitmap)
486                         wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
487                 elif self.comm.isError():
488                         wx.CallAfter(self.commState.SetBitmap, self.crossBitmap)
489                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
490                         wx.CallAfter(self.endstopBitmap.Show, False)
491                         wx.CallAfter(self.machineState.SetLabel, '%s' % (self.comm.getErrorString()))
492                         wx.CallAfter(self.errorLogButton.Show, True)
493                         wx.CallAfter(self.Layout)
494                 else:
495                         wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
496
497         def mcMessage(self, message):
498                 if self.checkupState >= 3 and self.checkupState < 10 and ('_min' in message or '_max' in message):
499                         for data in message.split(' '):
500                                 if ':' in data:
501                                         tag, value = data.split(':', 1)
502                                         if tag == 'x_min':
503                                                 self.xMinStop = (value == 'H' or value == 'TRIGGERED')
504                                         if tag == 'x_max':
505                                                 self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
506                                         if tag == 'y_min':
507                                                 self.yMinStop = (value == 'H' or value == 'TRIGGERED')
508                                         if tag == 'y_max':
509                                                 self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
510                                         if tag == 'z_min':
511                                                 self.zMinStop = (value == 'H' or value == 'TRIGGERED')
512                                         if tag == 'z_max':
513                                                 self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
514                         if ':' in message:
515                                 tag, value = map(str.strip, message.split(':', 1))
516                                 if tag == 'x_min':
517                                         self.xMinStop = (value == 'H' or value == 'TRIGGERED')
518                                 if tag == 'x_max':
519                                         self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
520                                 if tag == 'y_min':
521                                         self.yMinStop = (value == 'H' or value == 'TRIGGERED')
522                                 if tag == 'y_max':
523                                         self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
524                                 if tag == 'z_min':
525                                         self.zMinStop = (value == 'H' or value == 'TRIGGERED')
526                                 if tag == 'z_max':
527                                         self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
528                         if 'z_max' in message:
529                                 self.comm.sendCommand('M119')
530
531                         if self.checkupState == 3:
532                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
533                                         self.checkupState = 4
534                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the right X endstop.')
535                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMaxBitmap)
536                         elif self.checkupState == 4:
537                                 if not self.xMinStop and self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
538                                         self.checkupState = 5
539                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the left X endstop.')
540                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMinBitmap)
541                         elif self.checkupState == 5:
542                                 if self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
543                                         self.checkupState = 6
544                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the front Y endstop.')
545                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMinBitmap)
546                         elif self.checkupState == 6:
547                                 if not self.xMinStop and not self.xMaxStop and self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
548                                         self.checkupState = 7
549                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the back Y endstop.')
550                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMaxBitmap)
551                         elif self.checkupState == 7:
552                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and self.yMaxStop and not self.zMinStop and not self.zMaxStop:
553                                         self.checkupState = 8
554                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the top Z endstop.')
555                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMinBitmap)
556                         elif self.checkupState == 8:
557                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and self.zMinStop and not self.zMaxStop:
558                                         self.checkupState = 9
559                                         wx.CallAfter(self.infoBox.SetAttention, 'Please press the bottom Z endstop.')
560                                         wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMaxBitmap)
561                         elif self.checkupState == 9:
562                                 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and self.zMaxStop:
563                                         self.checkupState = 10
564                                         self.comm.close()
565                                         wx.CallAfter(self.infoBox.SetInfo, 'Checkup finished')
566                                         wx.CallAfter(self.infoBox.SetReadyIndicator)
567                                         wx.CallAfter(self.endstopBitmap.Show, False)
568                                         wx.CallAfter(self.stopState.SetBitmap, self.checkBitmap)
569                                         wx.CallAfter(self.OnSkipClick, None)
570
571         def mcProgress(self, lineNr):
572                 pass
573
574         def mcZChange(self, newZ):
575                 pass
576
577
578 class UltimakerCalibrationPage(InfoPage):
579         def __init__(self, parent):
580                 super(UltimakerCalibrationPage, self).__init__(parent, "Ultimaker Calibration")
581
582                 self.AddText("Your Ultimaker requires some calibration.")
583                 self.AddText("This calibration is needed for a proper extrusion amount.")
584                 self.AddSeperator()
585                 self.AddText("The following values are needed:")
586                 self.AddText("* Diameter of filament")
587                 self.AddText("* Number of steps per mm of filament extrusion")
588                 self.AddSeperator()
589                 self.AddText("The better you have calibrated these values, the better your prints\nwill become.")
590                 self.AddSeperator()
591                 self.AddText("First we need the diameter of your filament:")
592                 self.filamentDiameter = self.AddTextCtrl(profile.getProfileSetting('filament_diameter'))
593                 self.AddText(
594                         "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.")
595                 self.AddText("Note: This value can be changed later at any time.")
596
597         def StoreData(self):
598                 profile.putProfileSetting('filament_diameter', self.filamentDiameter.GetValue())
599
600
601 class UltimakerCalibrateStepsPerEPage(InfoPage):
602         def __init__(self, parent):
603                 super(UltimakerCalibrateStepsPerEPage, self).__init__(parent, "Ultimaker Calibration")
604
605                 if profile.getPreference('steps_per_e') == '0':
606                         profile.putPreference('steps_per_e', '865.888')
607
608                 self.AddText("Calibrating the Steps Per E requires some manual actions.")
609                 self.AddText("First remove any filament from your machine.")
610                 self.AddText("Next put in your filament so the tip is aligned with the\ntop of the extruder drive.")
611                 self.AddText("We'll push the filament 100mm")
612                 self.extrudeButton = self.AddButton("Extrude 100mm filament")
613                 self.AddText("Now measure the amount of extruded filament:\n(this can be more or less then 100mm)")
614                 self.lengthInput, self.saveLengthButton = self.AddTextCtrlButton('100', 'Save')
615                 self.AddText("This results in the following steps per E:")
616                 self.stepsPerEInput = self.AddTextCtrl(profile.getPreference('steps_per_e'))
617                 self.AddText("You can repeat these steps to get better calibration.")
618                 self.AddSeperator()
619                 self.AddText(
620                         "If you still have filament in your printer which needs\nheat to remove, press the heat up button below:")
621                 self.heatButton = self.AddButton("Heatup for filament removal")
622
623                 self.saveLengthButton.Bind(wx.EVT_BUTTON, self.OnSaveLengthClick)
624                 self.extrudeButton.Bind(wx.EVT_BUTTON, self.OnExtrudeClick)
625                 self.heatButton.Bind(wx.EVT_BUTTON, self.OnHeatClick)
626
627         def OnSaveLengthClick(self, e):
628                 currentEValue = float(self.stepsPerEInput.GetValue())
629                 realExtrudeLength = float(self.lengthInput.GetValue())
630                 newEValue = currentEValue * 100 / realExtrudeLength
631                 self.stepsPerEInput.SetValue(str(newEValue))
632                 self.lengthInput.SetValue("100")
633
634         def OnExtrudeClick(self, e):
635                 threading.Thread(target=self.OnExtrudeRun).start()
636
637         def OnExtrudeRun(self):
638                 self.heatButton.Enable(False)
639                 self.extrudeButton.Enable(False)
640                 currentEValue = float(self.stepsPerEInput.GetValue())
641                 self.comm = machineCom.MachineCom()
642                 if not self.comm.isOpen():
643                         wx.MessageBox(
644                                 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
645                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
646                         self.heatButton.Enable(True)
647                         self.extrudeButton.Enable(True)
648                         return
649                 while True:
650                         line = self.comm.readline()
651                         if line == '':
652                                 return
653                         if 'start' in line:
654                                 break
655                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
656                 time.sleep(3)
657
658                 self.sendGCommand('M302') #Disable cold extrusion protection
659                 self.sendGCommand("M92 E%f" % (currentEValue))
660                 self.sendGCommand("G92 E0")
661                 self.sendGCommand("G1 E100 F600")
662                 time.sleep(15)
663                 self.comm.close()
664                 self.extrudeButton.Enable()
665                 self.heatButton.Enable()
666
667         def OnHeatClick(self, e):
668                 threading.Thread(target=self.OnHeatRun).start()
669
670         def OnHeatRun(self):
671                 self.heatButton.Enable(False)
672                 self.extrudeButton.Enable(False)
673                 self.comm = machineCom.MachineCom()
674                 if not self.comm.isOpen():
675                         wx.MessageBox(
676                                 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
677                                 'Printer error', wx.OK | wx.ICON_INFORMATION)
678                         self.heatButton.Enable(True)
679                         self.extrudeButton.Enable(True)
680                         return
681                 while True:
682                         line = self.comm.readline()
683                         if line == '':
684                                 self.heatButton.Enable(True)
685                                 self.extrudeButton.Enable(True)
686                                 return
687                         if 'start' in line:
688                                 break
689                         #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
690                 time.sleep(3)
691
692                 self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.
693                 wx.MessageBox(
694                         'Wait till you can remove the filament from the machine, and press OK.\n(Temperature is set to 200C)',
695                         'Machine heatup', wx.OK | wx.ICON_INFORMATION)
696                 self.sendGCommand('M104 S0')
697                 time.sleep(1)
698                 self.comm.close()
699                 self.heatButton.Enable(True)
700                 self.extrudeButton.Enable(True)
701
702         def sendGCommand(self, cmd):
703                 self.comm.sendCommand(cmd) #Disable cold extrusion protection
704                 while True:
705                         line = self.comm.readline()
706                         if line == '':
707                                 return
708                         if line.startswith('ok'):
709                                 break
710
711         def StoreData(self):
712                 profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())
713
714
715 class configWizard(wx.wizard.Wizard):
716         def __init__(self):
717                 super(configWizard, self).__init__(None, -1, "Configuration Wizard")
718
719                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
720                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
721
722                 self.firstInfoPage = FirstInfoPage(self)
723                 self.machineSelectPage = MachineSelectPage(self)
724                 self.ultimakerSelectParts = SelectParts(self)
725                 self.ultimakerFirmwareUpgradePage = FirmwareUpgradePage(self)
726                 self.ultimakerCheckupPage = UltimakerCheckupPage(self)
727                 self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)
728                 self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)
729                 self.bedLevelPage = bedLevelWizardMain(self)
730                 self.repRapInfoPage = RepRapInfoPage(self)
731
732                 wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)
733                 wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerSelectParts)
734                 wx.wizard.WizardPageSimple.Chain(self.ultimakerSelectParts, self.ultimakerFirmwareUpgradePage)
735                 wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)
736                 wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.bedLevelPage)
737                 #wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)
738
739                 self.FitToPage(self.firstInfoPage)
740                 self.GetPageAreaSizer().Add(self.firstInfoPage)
741
742                 self.RunWizard(self.firstInfoPage)
743                 self.Destroy()
744
745         def OnPageChanging(self, e):
746                 e.GetPage().StoreData()
747
748         def OnPageChanged(self, e):
749                 if e.GetPage().AllowNext():
750                         self.FindWindowById(wx.ID_FORWARD).Enable()
751                 else:
752                         self.FindWindowById(wx.ID_FORWARD).Disable()
753                 self.FindWindowById(wx.ID_BACKWARD).Disable()
754
755 class bedLevelWizardMain(InfoPage):
756         def __init__(self, parent):
757                 super(bedLevelWizardMain, self).__init__(parent, "Bed leveling wizard")
758
759                 self.AddText('This wizard will help you in leveling your printer bed')
760                 self.AddSeperator()
761                 self.AddText('It will do the following steps')
762                 self.AddText('* Move the printer head to each corner')
763                 self.AddText('  and let you adjust the height of the bed to the nozzle')
764                 self.AddText('* Print a line around the bed to check if it is level')
765                 self.AddSeperator()
766
767                 self.connectButton = self.AddButton('Connect to printer')
768                 self.comm = None
769
770                 self.infoBox = self.AddInfoBox()
771                 self.resumeButton = self.AddButton('Resume')
772                 self.resumeButton.Enable(False)
773
774                 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
775                 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
776
777         def OnConnect(self, e = None):
778                 if self.comm is not None:
779                         self.comm.close()
780                         del self.comm
781                         self.comm = None
782                         wx.CallAfter(self.OnConnect)
783                         return
784                 self.connectButton.Enable(False)
785                 self.comm = machineCom.MachineCom(callbackObject=self)
786                 self.infoBox.SetBusy('Connecting to machine.')
787                 self._wizardState = 0
788
789         def AllowNext(self):
790                 return True
791
792         def OnResume(self, e):
793                 feedZ = profile.getProfileSettingFloat('max_z_speed') * 60
794                 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
795                 if self._wizardState == 2:
796                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back left corner...')
797                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
798                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, profile.getPreferenceFloat('machine_depth'), feedTravel))
799                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
800                         self.comm.sendCommand('M400')
801                         self._wizardState = 3
802                 elif self._wizardState == 4:
803                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back right corner...')
804                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
805                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width'), profile.getPreferenceFloat('machine_depth') - 25, feedTravel))
806                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
807                         self.comm.sendCommand('M400')
808                         self._wizardState = 5
809                 elif self._wizardState == 6:
810                         wx.CallAfter(self.infoBox.SetBusy, 'Moving head to front right corner...')
811                         self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
812                         self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width'), 20, feedTravel))
813                         self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
814                         self.comm.sendCommand('M400')
815                         self._wizardState = 7
816                 elif self._wizardState == 8:
817                         wx.CallAfter(self.infoBox.SetBusy, 'Heating up printer...')
818                         self.comm.sendCommand('G1 Z15 F%d' % (feedZ))
819                         self.comm.sendCommand('M104 S%d' % (profile.getProfileSettingFloat('print_temperature')))
820                         self.comm.sendCommand('G1 X%d Y%d F%d' % (0, 0, feedTravel))
821                         self._wizardState = 9
822                 self.resumeButton.Enable(False)
823
824         def mcLog(self, message):
825                 print 'Log:', message
826
827         def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
828                 if self._wizardState == 1:
829                         self._wizardState = 2
830                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front left screw of your printer bed\nSo the nozzle just hits the bed.')
831                         wx.CallAfter(self.resumeButton.Enable, True)
832                 elif self._wizardState == 3:
833                         self._wizardState = 4
834                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back left screw of your printer bed\nSo the nozzle just hits the bed.')
835                         wx.CallAfter(self.resumeButton.Enable, True)
836                 elif self._wizardState == 5:
837                         self._wizardState = 6
838                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back right screw of your printer bed\nSo the nozzle just hits the bed.')
839                         wx.CallAfter(self.resumeButton.Enable, True)
840                 elif self._wizardState == 7:
841                         self._wizardState = 8
842                         wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front right screw of your printer bed\nSo the nozzle just hits the bed.')
843                         wx.CallAfter(self.resumeButton.Enable, True)
844                 elif self._wizardState == 9:
845                         if temp < profile.getProfileSettingFloat('print_temperature') - 5:
846                                 wx.CallAfter(self.infoBox.SetInfo, 'Heating up printer: %d/%d' % (temp, profile.getProfileSettingFloat('print_temperature')))
847                         else:
848                                 self._wizardState = 10
849                                 wx.CallAfter(self.infoBox.SetInfo, 'Printing a square on the printer bed at 0.3mm height.')
850                                 feedZ = profile.getProfileSettingFloat('max_z_speed') * 60
851                                 feedPrint = profile.getProfileSettingFloat('print_speed') * 60
852                                 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
853                                 w = profile.getPreferenceFloat('machine_width')
854                                 d = profile.getPreferenceFloat('machine_depth')
855                                 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
856                                 filamentArea = math.pi * filamentRadius * filamentRadius
857                                 ePerMM = (profile.calculateEdgeWidth() * 0.3) / filamentArea
858                                 eValue = 0.0
859
860                                 gcodeList = [
861                                         'G1 Z2 F%d' % (feedZ),
862                                         'G92 E0',
863                                         'G1 X%d Y%d F%d' % (5, 5, feedTravel),
864                                         'G1 Z0.3 F%d' % (feedZ)]
865                                 eValue += 5;
866                                 gcodeList.append('G1 E%f F%d' % (eValue, profile.getProfileSettingFloat('retraction_speed') * 60))
867
868                                 for i in xrange(0, 3):
869                                         dist = 5.0 + 0.4 * i
870                                         eValue += (d - 2*dist) * ePerMM
871                                         gcodeList.append('G1 X%d Y%d E%f F%d' % (dist, d - dist, eValue, feedPrint))
872                                         eValue += (w - 2*dist) * ePerMM
873                                         gcodeList.append('G1 X%d Y%d E%f F%d' % (w - dist, d - dist, eValue, feedPrint))
874                                         eValue += (d - 2*dist) * ePerMM
875                                         gcodeList.append('G1 X%d Y%d E%f F%d' % (w - dist, dist, eValue, feedPrint))
876                                         eValue += (w - 2*dist) * ePerMM
877                                         gcodeList.append('G1 X%d Y%d E%f F%d' % (dist, dist, eValue, feedPrint))
878
879                                 gcodeList.append('M400')
880                                 self.comm.printGCode(gcodeList)
881
882         def mcStateChange(self, state):
883                 if self.comm is None:
884                         return
885                 if self.comm.isOperational():
886                         if self._wizardState == 0:
887                                 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer...')
888                                 self.comm.sendCommand('M105')
889                                 self.comm.sendCommand('G28')
890                                 self._wizardState = 1
891                         elif self._wizardState == 10 and not self.comm.isPrinting():
892                                 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('max_z_speed') * 60))
893                                 self.comm.sendCommand('G92 E0')
894                                 self.comm.sendCommand('G1 E-10 F%d' % (profile.getProfileSettingFloat('retraction_speed') * 60))
895                                 self.comm.sendCommand('M104 S0')
896                                 wx.CallAfter(self.infoBox.SetInfo, 'Calibration finished.\nThe squares on the bed should slightly touch each other.')
897                                 wx.CallAfter(self.infoBox.SetReadyIndicator)
898                                 wx.CallAfter(self.GetParent().FindWindowById(wx.ID_FORWARD).Enable)
899                                 wx.CallAfter(self.connectButton.Enable, True)
900                                 self._wizardState = 11
901                 elif self.comm.isError():
902                         wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
903
904         def mcMessage(self, message):
905                 pass
906
907         def mcProgress(self, lineNr):
908                 pass
909
910         def mcZChange(self, newZ):
911                 pass
912
913 class bedLevelWizard(wx.wizard.Wizard):
914         def __init__(self):
915                 super(bedLevelWizard, self).__init__(None, -1, "Bed leveling wizard")
916
917                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
918                 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
919
920                 self.mainPage = bedLevelWizardMain(self)
921
922                 self.FitToPage(self.mainPage)
923                 self.GetPageAreaSizer().Add(self.mainPage)
924
925                 self.RunWizard(self.mainPage)
926                 self.Destroy()
927
928         def OnPageChanging(self, e):
929                 e.GetPage().StoreData()
930
931         def OnPageChanged(self, e):
932                 if e.GetPage().AllowNext():
933                         self.FindWindowById(wx.ID_FORWARD).Enable()
934                 else:
935                         self.FindWindowById(wx.ID_FORWARD).Disable()
936                 self.FindWindowById(wx.ID_BACKWARD).Disable()