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