1 from __future__ import absolute_import
\r
4 import wx, os, platform, types, webbrowser, threading, time, re
\r
7 from gui import machineCom
\r
8 from gui import profile
\r
10 class InfoPage(wx.wizard.WizardPageSimple):
\r
11 def __init__(self, parent, title):
\r
12 wx.wizard.WizardPageSimple.__init__(self, parent)
\r
14 sizer = wx.BoxSizer(wx.VERTICAL)
\r
16 self.SetSizer(sizer)
\r
18 title = wx.StaticText(self, -1, title)
\r
19 title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
\r
20 sizer.Add(title, 0, wx.ALIGN_CENTRE|wx.ALL, 5)
\r
21 sizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 5)
\r
23 def AddText(self,info):
\r
24 text = wx.StaticText(self, -1, info)
\r
25 self.GetSizer().Add(text, 0, wx.LEFT|wx.RIGHT, 5)
\r
28 def AddSeperator(self):
\r
29 self.GetSizer().Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.ALL, 5)
\r
31 def AddHiddenSeperator(self):
\r
34 def AddRadioButton(self, label, style = 0):
\r
35 radio = wx.RadioButton(self, -1, label, style=style)
\r
36 self.GetSizer().Add(radio, 0, wx.EXPAND|wx.ALL, 5)
\r
39 def AddButton(self, label):
\r
40 button = wx.Button(self, -1, label)
\r
41 self.GetSizer().Add(button, 0, wx.LEFT, 5)
\r
44 def AddDualButton(self, label1, label2):
\r
46 p.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
\r
47 button1 = wx.Button(p, -1, label1)
\r
48 p.GetSizer().Add(button1, 0, wx.RIGHT, 8)
\r
49 button2 = wx.Button(p, -1, label2)
\r
50 p.GetSizer().Add(button2, 0)
\r
51 self.GetSizer().Add(p, 0, wx.LEFT, 5)
\r
52 return button1, button2
\r
54 def AllowNext(self):
\r
57 def StoreData(self):
\r
60 class FirstInfoPage(InfoPage):
\r
61 def __init__(self, parent):
\r
62 super(FirstInfoPage, self).__init__(parent, "First time run wizard")
\r
63 self.AddText('Welcome, and thanks for trying Cura!')
\r
65 self.AddText('This wizard will help you with the following steps:')
\r
66 self.AddText('* Configure Cura for your machine')
\r
67 self.AddText('* Upgrade your firmware')
\r
68 self.AddText('* Calibrate your machine')
\r
69 #self.AddText('* Do your first print')
\r
71 class RepRapInfoPage(InfoPage):
\r
72 def __init__(self, parent):
\r
73 super(RepRapInfoPage, self).__init__(parent, "RepRap information")
\r
74 self.AddText('Sorry, but this wizard will not help you with\nconfiguring and calibrating your RepRap.')
\r
76 self.AddText('You will have to manually install Marlin or Sprinter firmware\nand configure Cura.')
\r
78 class MachineSelectPage(InfoPage):
\r
79 def __init__(self, parent):
\r
80 super(MachineSelectPage, self).__init__(parent, "Select your machine")
\r
81 self.AddText('What kind of machine do you have:')
\r
83 self.UltimakerRadio = self.AddRadioButton("Ultimaker", style=wx.RB_GROUP)
\r
84 self.UltimakerRadio.SetValue(True)
\r
85 self.UltimakerRadio.Bind(wx.EVT_RADIOBUTTON, self.OnUltimakerSelect)
\r
86 self.OtherRadio = self.AddRadioButton("Other (Ex: RepRap)")
\r
87 self.OtherRadio.Bind(wx.EVT_RADIOBUTTON, self.OnOtherSelect)
\r
89 def OnUltimakerSelect(self, e):
\r
90 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().ultimakerFirmwareUpgradePage)
\r
92 def OnOtherSelect(self, e):
\r
93 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().repRapInfoPage)
\r
95 def StoreData(self):
\r
96 if self.UltimakerRadio.GetValue():
\r
97 profile.putPreference('machine_width', '205')
\r
98 profile.putPreference('machine_depth', '205')
\r
99 profile.putPreference('machine_height', '200')
\r
100 profile.putProfileSetting('nozzle_size', '0.4')
\r
101 profile.putProfileSetting('machine_center_x', '100')
\r
102 profile.putProfileSetting('machine_center_y', '100')
\r
104 profile.putPreference('machine_width', '80')
\r
105 profile.putPreference('machine_depth', '80')
\r
106 profile.putPreference('machine_height', '60')
\r
107 profile.putProfileSetting('nozzle_size', '0.5')
\r
108 profile.putProfileSetting('machine_center_x', '40')
\r
109 profile.putProfileSetting('machine_center_y', '40')
\r
110 profile.putProfileSetting('wall_thickness', float(profile.getProfileSetting('nozzle_size')) * 2)
\r
112 class FirmwareUpgradePage(InfoPage):
\r
113 def __init__(self, parent):
\r
114 super(FirmwareUpgradePage, self).__init__(parent, "Upgrade Ultimaker Firmware")
\r
115 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.')
\r
116 self.AddHiddenSeperator()
\r
117 self.AddText('The firmware shipping with new Ultimakers works, but upgrades\nhave been made to make better prints, and make calibration easier.')
\r
118 self.AddHiddenSeperator()
\r
119 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.')
\r
120 upgradeButton, skipUpgradeButton = self.AddDualButton('Upgrade to Marlin firmware', 'Skip upgrade')
\r
121 upgradeButton.Bind(wx.EVT_BUTTON, self.OnUpgradeClick)
\r
122 skipUpgradeButton.Bind(wx.EVT_BUTTON, self.OnSkipClick)
\r
123 self.AddHiddenSeperator()
\r
124 self.AddText('Do not upgrade to this firmware if:')
\r
125 self.AddText('* You have an older machine based on ATMega1280')
\r
126 self.AddText('* Using an LCD panel')
\r
127 self.AddText('* Have other changes in the firmware')
\r
128 button = self.AddButton('Goto this page for a custom firmware')
\r
129 button.Bind(wx.EVT_BUTTON, self.OnUrlClick)
\r
131 def AllowNext(self):
\r
134 def OnUpgradeClick(self, e):
\r
135 if machineCom.InstallFirmware(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../firmware/default.hex")):
\r
136 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
\r
138 def OnSkipClick(self, e):
\r
139 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
\r
141 def OnUrlClick(self, e):
\r
142 webbrowser.open('http://daid.mine.nu/~daid/marlin_build/')
\r
144 class UltimakerCheckupPage(InfoPage):
\r
145 def __init__(self, parent):
\r
146 super(UltimakerCheckupPage, self).__init__(parent, "Ultimaker Checkup")
\r
147 self.AddText('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.')
\r
148 b1, b2 = self.AddDualButton('Run checks', 'Skip checks')
\r
149 b1.Bind(wx.EVT_BUTTON, self.OnCheckClick)
\r
150 b2.Bind(wx.EVT_BUTTON, self.OnSkipClick)
\r
151 self.AddSeperator();
\r
152 self.checkPanel = None
\r
154 def AllowNext(self):
\r
157 def OnSkipClick(self, e):
\r
158 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
\r
160 def OnCheckClick(self, e):
\r
161 if self.checkPanel != None:
\r
162 self.checkPanel.Destroy()
\r
163 self.checkPanel = wx.Panel(self)
\r
164 self.checkPanel.SetSizer(wx.BoxSizer(wx.VERTICAL))
\r
165 self.GetSizer().Add(self.checkPanel, 0, wx.LEFT|wx.RIGHT, 5)
\r
166 threading.Thread(target=self.OnRun).start()
\r
168 def AddProgressText(self, info):
\r
169 text = wx.StaticText(self.checkPanel, -1, info)
\r
170 self.checkPanel.GetSizer().Add(text, 0)
\r
171 self.checkPanel.Layout()
\r
175 wx.CallAfter(self.AddProgressText, "Connecting to machine...")
\r
176 self.comm = machineCom.MachineCom()
\r
178 wx.CallAfter(self.AddProgressText, "Checking start message...")
\r
179 if self.DoCommCommandWithTimeout(None, 'start') == False:
\r
180 wx.CallAfter(self.AddProgressText, "Error: Missing start message.")
\r
184 wx.CallAfter(self.AddProgressText, "Disabling step motors...")
\r
185 if self.DoCommCommandWithTimeout('M84') == False:
\r
186 wx.CallAfter(self.AddProgressText, "Error: Missing reply to Deactivate steppers (M84).")
\r
187 wx.CallAfter(self.AddProgressText, "Possible cause: Temperature MIN/MAX.\nCheck temperature sensor connections.")
\r
191 if self.DoCommCommandWithTimeout("M104 S0") == False:
\r
192 wx.CallAfter(self.AddProgressText, "Failed to set temperature")
\r
196 wx.MessageBox('Please move the printer head to the center of the machine\nalso move the platform so it is not at the highest or lowest position,\nand make sure the machine is powered on.', 'Machine check', wx.OK | wx.ICON_INFORMATION)
\r
198 idleTemp = self.readTemp()
\r
200 wx.CallAfter(self.AddProgressText, "Waiting for head to cool down before temperature test...")
\r
201 while idleTemp > 40:
\r
202 idleTemp = self.readTemp()
\r
205 wx.CallAfter(self.AddProgressText, "Checking heater and temperature sensor...")
\r
206 wx.CallAfter(self.AddProgressText, "(This takes about 30 seconds)")
\r
207 if self.DoCommCommandWithTimeout("M104 S100") == False:
\r
208 wx.CallAfter(self.AddProgressText, "Failed to set temperature")
\r
213 tempInc = self.readTemp() - idleTemp
\r
215 if self.DoCommCommandWithTimeout("M104 S0") == False:
\r
216 wx.CallAfter(self.AddProgressText, "Failed to set temperature")
\r
221 wx.CallAfter(self.AddProgressText, "Your temperature sensor or heater is not working!")
\r
224 wx.CallAfter(self.AddProgressText, "Heater and temperature sensor working\nWarning: head might still be hot!")
\r
226 wx.CallAfter(self.AddProgressText, "Checking endstops")
\r
227 if self.DoCommCommandWithTimeout('M119', 'x_min') != "x_min:L x_max:L y_min:L y_max:L z_min:L z_max:L":
\r
228 wx.CallAfter(self.AddProgressText, "Error: There is a problem in your endstops!\nOne of them seems to be pressed while it shouldn't\ncheck the cable connections and the switches themselfs.")
\r
231 wx.CallAfter(self.AddProgressText, "Please press the X end switch in the front left corner.")
\r
232 if not self.DoCommCommandAndWaitForReply('M119', 'x_min', "x_min:H x_max:L y_min:L y_max:L z_min:L z_max:L"):
\r
233 wx.CallAfter(self.AddProgressText, "Failed to check the x_min endstop!")
\r
236 wx.CallAfter(self.AddProgressText, "Please press the X end switch in the front right corner.")
\r
237 if not self.DoCommCommandAndWaitForReply('M119', 'x_min', "x_min:L x_max:H y_min:L y_max:L z_min:L z_max:L"):
\r
238 wx.CallAfter(self.AddProgressText, "Failed to check the x_max endstop!")
\r
241 wx.CallAfter(self.AddProgressText, "Please press the Y end switch in the front left corner.")
\r
242 if not self.DoCommCommandAndWaitForReply('M119', 'x_min', "x_min:L x_max:L y_min:H y_max:L z_min:L z_max:L"):
\r
243 wx.CallAfter(self.AddProgressText, "Failed to check the x_max endstop!")
\r
246 wx.CallAfter(self.AddProgressText, "Please press the Y end switch in the back left corner.")
\r
247 if not self.DoCommCommandAndWaitForReply('M119', 'x_min', "x_min:L x_max:L y_min:L y_max:H z_min:L z_max:L"):
\r
248 wx.CallAfter(self.AddProgressText, "Failed to check the x_max endstop!")
\r
251 wx.CallAfter(self.AddProgressText, "Please press the Z end switch in the top.")
\r
252 if not self.DoCommCommandAndWaitForReply('M119', 'x_min', "x_min:L x_max:L y_min:L y_max:L z_min:H z_max:L"):
\r
253 wx.CallAfter(self.AddProgressText, "Failed to check the x_max endstop!")
\r
256 wx.CallAfter(self.AddProgressText, "Please press the Z end switch in the bottom.")
\r
257 if not self.DoCommCommandAndWaitForReply('M119', 'x_min', "x_min:L x_max:L y_min:L y_max:L z_min:L z_max:H"):
\r
258 wx.CallAfter(self.AddProgressText, "Failed to check the x_max endstop!")
\r
261 wx.CallAfter(self.AddProgressText, "End stops are working.")
\r
263 wx.CallAfter(self.AddProgressText, "Done!")
\r
264 wx.CallAfter(self.GetParent().FindWindowById(wx.ID_FORWARD).Enable)
\r
267 def readTemp(self):
\r
268 line = self.DoCommCommandWithTimeout("M105", "ok T:")
\r
271 return int(re.search('T:([0-9]*)', line).group(1))
\r
273 def DoCommCommandAndWaitForReply(self, cmd, replyStart, reply):
\r
275 ret = self.DoCommCommandWithTimeout(cmd, replyStart)
\r
282 def DoCommCommandWithTimeout(self, cmd = None, replyStart = 'ok'):
\r
284 self.comm.sendCommand(cmd)
\r
285 t = threading.Timer(5, self.OnSerialTimeout)
\r
288 line = self.comm.readline()
\r
292 if line.startswith(replyStart):
\r
295 return line.rstrip()
\r
297 def OnSerialTimeout(self):
\r
300 class UltimakerCalibrationPage(InfoPage):
\r
301 def __init__(self, parent):
\r
302 super(UltimakerCalibrationPage, self).__init__(parent, "Ultimaker Calibration")
\r
304 self.AddText("Your Ultimaker requires some calibration.");
\r
305 self.AddText("This calibration is needed for a proper extrusion amount.");
\r
306 self.AddSeperator()
\r
307 self.AddText("The following values are needed:");
\r
308 self.AddText("* Diameter of filament");
\r
309 self.AddText("* Number of steps per mm of filament extrusion");
\r
310 self.AddSeperator()
\r
311 self.AddText("The better you have calibrated these values, the better your prints\nwill become.");
\r
312 self.AddSeperator()
\r
313 self.AddText("First we need the diameter of your filament:");
\r
314 self.filamentDiameter = wx.TextCtrl(self, -1, profile.getProfileSetting('filament_diameter'))
\r
315 self.GetSizer().Add(self.filamentDiameter, 0, wx.LEFT, 5)
\r
316 self.AddText("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.");
\r
317 self.AddText("Note: This value can be changed later at any time.");
\r
319 def StoreData(self):
\r
320 profile.putProfileSetting('filament_diameter', self.filamentDiameter.GetValue())
\r
322 class UltimakerCalibrateStepsPerEPage(InfoPage):
\r
323 def __init__(self, parent):
\r
324 super(UltimakerCalibrateStepsPerEPage, self).__init__(parent, "Ultimaker Calibration")
\r
326 self.AddText("Calibrating the Steps Per E requires some manual actions.")
\r
327 self.AddText("First remove any filament from your machine.")
\r
328 self.AddText("Next put in your filament so the tip is aligned with the\ntop of the extruder drive.")
\r
329 self.AddText("We'll push the filament 100mm")
\r
330 self.extrudeButton = self.AddButton("Extrude 100mm filament")
\r
331 self.AddText("Now measure the amount of extruded filament:\n(this can be more or less then 100mm)")
\r
333 p.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
\r
334 self.lengthInput = wx.TextCtrl(p, -1, '100')
\r
335 p.GetSizer().Add(self.lengthInput, 0, wx.RIGHT, 8)
\r
336 self.saveLengthButton = wx.Button(p, -1, 'Save')
\r
337 p.GetSizer().Add(self.saveLengthButton, 0)
\r
338 self.GetSizer().Add(p, 0, wx.LEFT, 5)
\r
339 self.AddText("This results in the following steps per E:")
\r
340 self.stepsPerEInput = wx.TextCtrl(self, -1, profile.getPreference('steps_per_e'))
\r
341 self.GetSizer().Add(self.stepsPerEInput, 0, wx.LEFT, 5)
\r
342 self.AddText("You can repeat these steps to get better calibration.")
\r
343 self.AddSeperator()
\r
344 self.AddText("If you still have filament in your printer which needs\nheat to remove, press the heat up button below:")
\r
345 self.heatButton = self.AddButton("Heatup for filament removal")
\r
347 self.saveLengthButton.Bind(wx.EVT_BUTTON, self.OnSaveLengthClick)
\r
348 self.extrudeButton.Bind(wx.EVT_BUTTON, self.OnExtrudeClick)
\r
349 self.heatButton.Bind(wx.EVT_BUTTON, self.OnHeatClick)
\r
351 def OnSaveLengthClick(self, e):
\r
352 currentEValue = float(self.stepsPerEInput.GetValue())
\r
353 realExtrudeLength = float(self.lengthInput.GetValue())
\r
354 newEValue = currentEValue * 100 / realExtrudeLength
\r
355 self.stepsPerEInput.SetValue(str(newEValue))
\r
356 self.lengthInput.SetValue("100")
\r
358 def OnExtrudeClick(self, e):
\r
359 threading.Thread(target=self.OnExtrudeRun).start()
\r
361 def OnExtrudeRun(self):
\r
362 self.heatButton.Enable(False)
\r
363 self.extrudeButton.Enable(False)
\r
364 currentEValue = float(self.stepsPerEInput.GetValue())
\r
365 self.comm = machineCom.MachineCom()
\r
367 line = self.comm.readline()
\r
370 if line.startswith('start'):
\r
372 self.sendGCommand('M302') #Disable cold extrusion protection
\r
373 self.sendGCommand("M92 E%f" % (currentEValue));
\r
374 self.sendGCommand("G92 E0");
\r
375 self.sendGCommand("G1 E100 F600");
\r
378 self.extrudeButton.Enable()
\r
379 self.heatButton.Enable()
\r
381 def OnHeatClick(self, e):
\r
382 threading.Thread(target=self.OnHeatRun).start()
\r
384 def OnHeatRun(self):
\r
385 self.comm = machineCom.MachineCom()
\r
387 line = self.comm.readline()
\r
390 if line.startswith('start'):
\r
392 self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.
\r
393 wx.MessageBox('Wait till you can remove the filament from the machine, and press OK.\n(Temperature is set to 200C)', 'Machine heatup', wx.OK | wx.ICON_INFORMATION)
\r
394 self.sendGCommand('M104 S0')
\r
398 def sendGCommand(self, cmd):
\r
399 self.comm.sendCommand(cmd) #Disable cold extrusion protection
\r
401 line = self.comm.readline()
\r
404 if line.startswith('ok'):
\r
407 def StoreData(self):
\r
408 profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())
\r
410 class configWizard(wx.wizard.Wizard):
\r
411 def __init__(self):
\r
412 super(configWizard, self).__init__(None, -1, "Configuration Wizard")
\r
414 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
\r
415 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
\r
417 self.firstInfoPage = FirstInfoPage(self)
\r
418 self.machineSelectPage = MachineSelectPage(self)
\r
419 self.ultimakerFirmwareUpgradePage = FirmwareUpgradePage(self)
\r
420 self.ultimakerCheckupPage = UltimakerCheckupPage(self)
\r
421 self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)
\r
422 self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)
\r
423 self.repRapInfoPage = RepRapInfoPage(self)
\r
425 wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)
\r
426 wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerFirmwareUpgradePage)
\r
427 wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)
\r
428 wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.ultimakerCalibrationPage)
\r
429 wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)
\r
431 self.FitToPage(self.firstInfoPage)
\r
432 self.GetPageAreaSizer().Add(self.firstInfoPage)
\r
434 self.RunWizard(self.firstInfoPage)
\r
437 def OnPageChanging(self, e):
\r
438 e.GetPage().StoreData()
\r
440 def OnPageChanged(self, e):
\r
441 if e.GetPage().AllowNext():
\r
442 self.FindWindowById(wx.ID_FORWARD).Enable()
\r
444 self.FindWindowById(wx.ID_FORWARD).Disable()
\r
445 self.FindWindowById(wx.ID_BACKWARD).Disable()
\r