2 from __future__ import absolute_import
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
19 class InfoBox(wx.Panel):
20 def __init__(self, parent):
21 super(InfoBox, self).__init__(parent)
22 self.SetBackgroundColour('#FFFF80')
24 self.sizer = wx.GridBagSizer(5, 5)
25 self.SetSizer(self.sizer)
27 self.attentionBitmap = wx.Bitmap(getPathForImage('attention.png'))
28 self.errorBitmap = wx.Bitmap(getPathForImage('error.png'))
29 self.readyBitmap = wx.Bitmap(getPathForImage('ready.png'))
31 wx.Bitmap(getPathForImage('busy-0.png')),
32 wx.Bitmap(getPathForImage('busy-1.png')),
33 wx.Bitmap(getPathForImage('busy-2.png')),
34 wx.Bitmap(getPathForImage('busy-3.png'))
37 self.bitmap = wx.StaticBitmap(self, -1, wx.EmptyBitmapRGBA(24, 24, red=255, green=255, blue=255, alpha=1))
38 self.text = wx.StaticText(self, -1, '')
39 self.extraInfoButton = wx.Button(self, -1, 'i', style=wx.BU_EXACTFIT)
40 self.sizer.Add(self.bitmap, pos=(0, 0), flag=wx.ALL, border=5)
41 self.sizer.Add(self.text, pos=(0, 1), flag=wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, border=5)
42 self.sizer.Add(self.extraInfoButton, pos=(0,2), flag=wx.ALL|wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL, border=5)
43 self.sizer.AddGrowableCol(1)
45 self.extraInfoButton.Show(False)
47 self.extraInfoUrl = ''
49 self.timer = wx.Timer(self)
50 self.Bind(wx.EVT_TIMER, self.doBusyUpdate, self.timer)
51 self.Bind(wx.EVT_BUTTON, self.doExtraInfo, self.extraInfoButton)
54 def SetInfo(self, info):
55 self.SetBackgroundColour('#FFFF80')
56 self.text.SetLabel(info)
57 self.extraInfoButton.Show(False)
60 def SetError(self, info, extraInfoUrl):
61 self.extraInfoUrl = extraInfoUrl
62 self.SetBackgroundColour('#FF8080')
63 self.text.SetLabel(info)
64 self.extraInfoButton.Show(True)
66 self.SetErrorIndicator()
69 def SetAttention(self, info):
70 self.SetBackgroundColour('#FFFF80')
71 self.text.SetLabel(info)
72 self.extraInfoButton.Show(False)
73 self.SetAttentionIndicator()
77 def SetBusy(self, info):
79 self.SetBusyIndicator()
81 def SetBusyIndicator(self):
83 self.bitmap.SetBitmap(self.busyBitmap[self.busyState])
85 def doExtraInfo(self, e):
86 webbrowser.open(self.extraInfoUrl)
88 def doBusyUpdate(self, e):
89 if self.busyState is None:
92 if self.busyState >= len(self.busyBitmap):
94 self.bitmap.SetBitmap(self.busyBitmap[self.busyState])
96 def SetReadyIndicator(self):
98 self.bitmap.SetBitmap(self.readyBitmap)
100 def SetErrorIndicator(self):
101 self.busyState = None
102 self.bitmap.SetBitmap(self.errorBitmap)
104 def SetAttentionIndicator(self):
105 self.busyState = None
106 self.bitmap.SetBitmap(self.attentionBitmap)
109 class InfoPage(wx.wizard.WizardPageSimple):
110 def __init__(self, parent, title):
111 wx.wizard.WizardPageSimple.__init__(self, parent)
113 sizer = wx.GridBagSizer(5, 5)
117 title = wx.StaticText(self, -1, title)
118 title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
119 sizer.Add(title, pos=(0, 0), span=(1, 2), flag=wx.ALIGN_CENTRE | wx.ALL)
120 sizer.Add(wx.StaticLine(self, -1), pos=(1, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
121 sizer.AddGrowableCol(1)
125 def AddText(self, info):
126 text = wx.StaticText(self, -1, info)
127 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT)
131 def AddSeperator(self):
132 self.GetSizer().Add(wx.StaticLine(self, -1), pos=(self.rowNr, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
135 def AddHiddenSeperator(self):
138 def AddInfoBox(self):
139 infoBox = InfoBox(self)
140 self.GetSizer().Add(infoBox, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT | wx.EXPAND)
144 def AddRadioButton(self, label, style=0):
145 radio = wx.RadioButton(self, -1, label, style=style)
146 self.GetSizer().Add(radio, pos=(self.rowNr, 0), span=(1, 2), flag=wx.EXPAND | wx.ALL)
150 def AddCheckbox(self, label, checked=False):
151 check = wx.CheckBox(self, -1)
152 text = wx.StaticText(self, -1, label)
153 check.SetValue(checked)
154 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT | wx.RIGHT)
155 self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1, 2), flag=wx.ALL)
159 def AddButton(self, label):
160 button = wx.Button(self, -1, label)
161 self.GetSizer().Add(button, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT)
165 def AddDualButton(self, label1, label2):
166 button1 = wx.Button(self, -1, label1)
167 self.GetSizer().Add(button1, pos=(self.rowNr, 0), flag=wx.RIGHT)
168 button2 = wx.Button(self, -1, label2)
169 self.GetSizer().Add(button2, pos=(self.rowNr, 1))
171 return button1, button2
173 def AddTextCtrl(self, value):
174 ret = wx.TextCtrl(self, -1, value)
175 self.GetSizer().Add(ret, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT)
179 def AddLabelTextCtrl(self, info, value):
180 text = wx.StaticText(self, -1, info)
181 ret = wx.TextCtrl(self, -1, value)
182 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT)
183 self.GetSizer().Add(ret, pos=(self.rowNr, 1), span=(1, 1), flag=wx.LEFT)
187 def AddTextCtrlButton(self, value, buttonText):
188 text = wx.TextCtrl(self, -1, value)
189 button = wx.Button(self, -1, buttonText)
190 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT)
191 self.GetSizer().Add(button, pos=(self.rowNr, 1), span=(1, 1), flag=wx.LEFT)
195 def AddBitmap(self, bitmap):
196 bitmap = wx.StaticBitmap(self, -1, bitmap)
197 self.GetSizer().Add(bitmap, pos=(self.rowNr, 0), span=(1, 2), flag=wx.LEFT | wx.RIGHT)
201 def AddCheckmark(self, label, bitmap):
202 check = wx.StaticBitmap(self, -1, bitmap)
203 text = wx.StaticText(self, -1, label)
204 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1, 1), flag=wx.LEFT | wx.RIGHT)
205 self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1, 1), flag=wx.ALL)
216 class FirstInfoPage(InfoPage):
217 def __init__(self, parent):
218 super(FirstInfoPage, self).__init__(parent, "First time run wizard")
219 self.AddText('Welcome, and thanks for trying Cura!')
221 self.AddText('This wizard will help you with the following steps:')
222 self.AddText('* Configure Cura for your machine')
223 self.AddText('* Upgrade your firmware')
224 self.AddText('* Check if your machine is working safely')
225 self.AddText('* Level your printer bed')
227 #self.AddText('* Calibrate your machine')
228 #self.AddText('* Do your first print')
231 class RepRapInfoPage(InfoPage):
232 def __init__(self, parent):
233 super(RepRapInfoPage, self).__init__(parent, "RepRap information")
235 'RepRap machines are vastly different, and there is no\ndefault configuration in Cura for any of them.')
236 self.AddText('If you like a default profile for your machine added,\nthen make an issue on github.')
238 self.AddText('You will have to manually install Marlin or Sprinter firmware.')
240 self.machineWidth = self.AddLabelTextCtrl('Machine width (mm)', '80')
241 self.machineDepth = self.AddLabelTextCtrl('Machine depth (mm)', '80')
242 self.machineHeight = self.AddLabelTextCtrl('Machine height (mm)', '60')
243 self.nozzleSize = self.AddLabelTextCtrl('Nozzle size (mm)', '0.5')
244 self.heatedBed = self.AddCheckbox('Heated bed')
245 self.HomeAtCenter = self.AddCheckbox('Bed center is 0,0,0 (RoStock)')
248 profile.putPreference('machine_width', self.machineWidth.GetValue())
249 profile.putPreference('machine_depth', self.machineDepth.GetValue())
250 profile.putPreference('machine_height', self.machineHeight.GetValue())
251 profile.putProfileSetting('nozzle_size', self.nozzleSize.GetValue())
252 profile.putProfileSetting('wall_thickness', float(profile.getProfileSettingFloat('nozzle_size')) * 2)
253 profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))
254 profile.putPreference('machine_center_is_zero', str(self.HomeAtCenter.GetValue()))
255 profile.putPreference('extruder_head_size_min_x', '0')
256 profile.putPreference('extruder_head_size_min_y', '0')
257 profile.putPreference('extruder_head_size_max_x', '0')
258 profile.putPreference('extruder_head_size_max_y', '0')
259 profile.putPreference('extruder_head_size_height', '0')
262 class MachineSelectPage(InfoPage):
263 def __init__(self, parent):
264 super(MachineSelectPage, self).__init__(parent, "Select your machine")
265 self.AddText('What kind of machine do you have:')
267 self.UltimakerRadio = self.AddRadioButton("Ultimaker", style=wx.RB_GROUP)
268 self.UltimakerRadio.SetValue(True)
269 self.UltimakerRadio.Bind(wx.EVT_RADIOBUTTON, self.OnUltimakerSelect)
270 self.OtherRadio = self.AddRadioButton("Other (Ex: RepRap)")
271 self.OtherRadio.Bind(wx.EVT_RADIOBUTTON, self.OnOtherSelect)
273 self.AddText('The collection of anonymous usage information helps with the continued improvement of Cura.')
274 self.AddText('This does NOT submit your models online nor gathers any privacy related information.')
275 self.SubmitUserStats = self.AddCheckbox('Submit anonymous usage information:')
276 self.AddText('For full details see: http://wiki.ultimaker.com/Cura:stats')
277 self.SubmitUserStats.SetValue(True)
279 def OnUltimakerSelect(self, e):
280 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().ultimakerFirmwareUpgradePage)
282 def OnOtherSelect(self, e):
283 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().repRapInfoPage)
286 if self.UltimakerRadio.GetValue():
287 profile.putPreference('machine_width', '205')
288 profile.putPreference('machine_depth', '205')
289 profile.putPreference('machine_height', '200')
290 profile.putPreference('machine_type', 'ultimaker')
291 profile.putPreference('machine_center_is_zero', 'False')
292 profile.putProfileSetting('nozzle_size', '0.4')
293 profile.putPreference('extruder_head_size_min_x', '75.0')
294 profile.putPreference('extruder_head_size_min_y', '18.0')
295 profile.putPreference('extruder_head_size_max_x', '18.0')
296 profile.putPreference('extruder_head_size_max_y', '35.0')
297 profile.putPreference('extruder_head_size_height', '60.0')
299 profile.putPreference('machine_width', '80')
300 profile.putPreference('machine_depth', '80')
301 profile.putPreference('machine_height', '60')
302 profile.putPreference('machine_type', 'reprap')
303 profile.putPreference('startMode', 'Normal')
304 profile.putProfileSetting('nozzle_size', '0.5')
305 profile.putProfileSetting('wall_thickness', float(profile.getProfileSetting('nozzle_size')) * 2)
306 if self.SubmitUserStats.GetValue():
307 profile.putPreference('submit_slice_information', 'True')
309 profile.putPreference('submit_slice_information', 'False')
311 class SelectParts(InfoPage):
312 def __init__(self, parent):
313 super(SelectParts, self).__init__(parent, "Select upgraded parts you have")
314 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.')
316 self.springExtruder = self.AddCheckbox('Extruder drive upgrade')
317 self.heatedBed = self.AddCheckbox('Heated printer bed (self built)')
318 self.dualExtrusion = self.AddCheckbox('Dual extrusion (experimental)')
320 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.')
321 self.AddText('This upgrade can be bought from the Ultimaker webshop\nor found on thingiverse as thing:26094')
322 self.springExtruder.SetValue(True)
325 profile.putPreference('ultimaker_extruder_upgrade', str(self.springExtruder.GetValue()))
326 profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))
327 if self.dualExtrusion.GetValue():
328 profile.putPreference('extruder_amount', '2')
330 profile.putPreference('extruder_amount', '1')
331 if profile.getPreference('ultimaker_extruder_upgrade') == 'True':
332 profile.putProfileSetting('retraction_enable', 'True')
334 profile.putProfileSetting('retraction_enable', 'False')
337 class FirmwareUpgradePage(InfoPage):
338 def __init__(self, parent):
339 super(FirmwareUpgradePage, self).__init__(parent, "Upgrade Ultimaker Firmware")
341 '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.')
342 self.AddHiddenSeperator()
344 'The firmware shipping with new Ultimakers works, but upgrades\nhave been made to make better prints, and make calibration easier.')
345 self.AddHiddenSeperator()
347 '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.')
348 upgradeButton, skipUpgradeButton = self.AddDualButton('Upgrade to Marlin firmware', 'Skip upgrade')
349 upgradeButton.Bind(wx.EVT_BUTTON, self.OnUpgradeClick)
350 skipUpgradeButton.Bind(wx.EVT_BUTTON, self.OnSkipClick)
351 self.AddHiddenSeperator()
352 self.AddText('Do not upgrade to this firmware if:')
353 self.AddText('* You have an older machine based on ATMega1280')
354 self.AddText('* Have other changes in the firmware')
355 # button = self.AddButton('Goto this page for a custom firmware')
356 # button.Bind(wx.EVT_BUTTON, self.OnUrlClick)
361 def OnUpgradeClick(self, e):
362 if firmwareInstall.InstallFirmware():
363 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
365 def OnSkipClick(self, e):
366 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
367 self.GetParent().ShowPage(self.GetNext())
369 def OnUrlClick(self, e):
370 webbrowser.open('http://daid.mine.nu/~daid/marlin_build/')
373 class UltimakerCheckupPage(InfoPage):
374 def __init__(self, parent):
375 super(UltimakerCheckupPage, self).__init__(parent, "Ultimaker Checkup")
377 self.checkBitmap = wx.Bitmap(getPathForImage('checkmark.png'))
378 self.crossBitmap = wx.Bitmap(getPathForImage('cross.png'))
379 self.unknownBitmap = wx.Bitmap(getPathForImage('question.png'))
380 self.endStopNoneBitmap = wx.Bitmap(getPathForImage('endstop_none.png'))
381 self.endStopXMinBitmap = wx.Bitmap(getPathForImage('endstop_xmin.png'))
382 self.endStopXMaxBitmap = wx.Bitmap(getPathForImage('endstop_xmax.png'))
383 self.endStopYMinBitmap = wx.Bitmap(getPathForImage('endstop_ymin.png'))
384 self.endStopYMaxBitmap = wx.Bitmap(getPathForImage('endstop_ymax.png'))
385 self.endStopZMinBitmap = wx.Bitmap(getPathForImage('endstop_zmin.png'))
386 self.endStopZMaxBitmap = wx.Bitmap(getPathForImage('endstop_zmax.png'))
389 '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.')
390 b1, b2 = self.AddDualButton('Run checks', 'Skip checks')
391 b1.Bind(wx.EVT_BUTTON, self.OnCheckClick)
392 b2.Bind(wx.EVT_BUTTON, self.OnSkipClick)
394 self.commState = self.AddCheckmark('Communication:', self.unknownBitmap)
395 self.tempState = self.AddCheckmark('Temperature:', self.unknownBitmap)
396 self.stopState = self.AddCheckmark('Endstops:', self.unknownBitmap)
398 self.infoBox = self.AddInfoBox()
399 self.machineState = self.AddText('')
400 self.temperatureLabel = self.AddText('')
401 self.errorLogButton = self.AddButton('Show error log')
402 self.errorLogButton.Show(False)
404 self.endstopBitmap = self.AddBitmap(self.endStopNoneBitmap)
406 self.xMinStop = False
407 self.xMaxStop = False
408 self.yMinStop = False
409 self.yMaxStop = False
410 self.zMinStop = False
411 self.zMaxStop = False
413 self.Bind(wx.EVT_BUTTON, self.OnErrorLog, self.errorLogButton)
416 if self.comm is not None:
420 self.endstopBitmap.Show(False)
423 def OnSkipClick(self, e):
424 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
425 self.GetParent().ShowPage(self.GetNext())
427 def OnCheckClick(self, e=None):
428 self.errorLogButton.Show(False)
429 if self.comm is not None:
433 wx.CallAfter(self.OnCheckClick)
435 self.infoBox.SetBusy('Connecting to machine.')
436 self.commState.SetBitmap(self.unknownBitmap)
437 self.tempState.SetBitmap(self.unknownBitmap)
438 self.stopState.SetBitmap(self.unknownBitmap)
439 self.checkupState = 0
440 self.comm = machineCom.MachineCom(callbackObject=self)
442 def OnErrorLog(self, e):
443 printWindow.LogWindow('\n'.join(self.comm.getLog()))
445 def mcLog(self, message):
448 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
449 if not self.comm.isOperational():
451 if self.checkupState == 0:
452 self.tempCheckTimeout = 20
453 if temp[self.checkExtruderNr] > 70:
454 self.checkupState = 1
455 wx.CallAfter(self.infoBox.SetInfo, 'Cooldown before temperature check.')
456 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
457 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
459 self.startTemp = temp[self.checkExtruderNr]
460 self.checkupState = 2
461 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
462 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
463 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
464 elif self.checkupState == 1:
466 self.startTemp = temp[self.checkExtruderNr]
467 self.checkupState = 2
468 wx.CallAfter(self.infoBox.SetInfo, 'Checking the heater and temperature sensor.')
469 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
470 self.comm.sendCommand('M104 S200 T%d' % (self.checkExtruderNr))
471 elif self.checkupState == 2:
472 #print "WARNING, TEMPERATURE TEST DISABLED FOR TESTING!"
473 if temp[self.checkExtruderNr] > self.startTemp + 40:
474 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
475 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
476 if self.checkExtruderNr < int(profile.getPreference('extruder_amount')):
477 self.checkExtruderNr = 0
478 self.checkupState = 3
479 wx.CallAfter(self.infoBox.SetAttention, 'Please make sure none of the endstops are pressed.')
480 wx.CallAfter(self.endstopBitmap.Show, True)
481 wx.CallAfter(self.Layout)
482 self.comm.sendCommand('M119')
483 wx.CallAfter(self.tempState.SetBitmap, self.checkBitmap)
485 self.checkupState = 0
486 self.checkExtruderNr += 1
488 self.tempCheckTimeout -= 1
489 if self.tempCheckTimeout < 1:
490 self.checkupState = -1
491 wx.CallAfter(self.tempState.SetBitmap, self.crossBitmap)
492 wx.CallAfter(self.infoBox.SetError, 'Temperature measurement FAILED!', 'http://wiki.ultimaker.com/Cura:_Temperature_measurement_problems')
493 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
494 self.comm.sendCommand('M104 S0 T%d' % (self.checkExtruderNr))
495 elif self.checkupState >= 3 and self.checkupState < 10:
496 self.comm.sendCommand('M119')
497 wx.CallAfter(self.temperatureLabel.SetLabel, 'Head temperature: %d' % (temp[self.checkExtruderNr]))
499 def mcStateChange(self, state):
500 if self.comm is None:
502 if self.comm.isOperational():
503 wx.CallAfter(self.commState.SetBitmap, self.checkBitmap)
504 wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
505 elif self.comm.isError():
506 wx.CallAfter(self.commState.SetBitmap, self.crossBitmap)
507 wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
508 wx.CallAfter(self.endstopBitmap.Show, False)
509 wx.CallAfter(self.machineState.SetLabel, '%s' % (self.comm.getErrorString()))
510 wx.CallAfter(self.errorLogButton.Show, True)
511 wx.CallAfter(self.Layout)
513 wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
515 def mcMessage(self, message):
516 if self.checkupState >= 3 and self.checkupState < 10 and ('_min' in message or '_max' in message):
517 for data in message.split(' '):
519 tag, value = data.split(':', 1)
521 self.xMinStop = (value == 'H' or value == 'TRIGGERED')
523 self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
525 self.yMinStop = (value == 'H' or value == 'TRIGGERED')
527 self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
529 self.zMinStop = (value == 'H' or value == 'TRIGGERED')
531 self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
533 tag, value = map(str.strip, message.split(':', 1))
535 self.xMinStop = (value == 'H' or value == 'TRIGGERED')
537 self.xMaxStop = (value == 'H' or value == 'TRIGGERED')
539 self.yMinStop = (value == 'H' or value == 'TRIGGERED')
541 self.yMaxStop = (value == 'H' or value == 'TRIGGERED')
543 self.zMinStop = (value == 'H' or value == 'TRIGGERED')
545 self.zMaxStop = (value == 'H' or value == 'TRIGGERED')
546 if 'z_max' in message:
547 self.comm.sendCommand('M119')
549 if self.checkupState == 3:
550 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
551 self.checkupState = 4
552 wx.CallAfter(self.infoBox.SetAttention, 'Please press the right X endstop.')
553 wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMaxBitmap)
554 elif self.checkupState == 4:
555 if not self.xMinStop and self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
556 self.checkupState = 5
557 wx.CallAfter(self.infoBox.SetAttention, 'Please press the left X endstop.')
558 wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopXMinBitmap)
559 elif self.checkupState == 5:
560 if self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
561 self.checkupState = 6
562 wx.CallAfter(self.infoBox.SetAttention, 'Please press the front Y endstop.')
563 wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMinBitmap)
564 elif self.checkupState == 6:
565 if not self.xMinStop and not self.xMaxStop and self.yMinStop and not self.yMaxStop and not self.zMinStop and not self.zMaxStop:
566 self.checkupState = 7
567 wx.CallAfter(self.infoBox.SetAttention, 'Please press the back Y endstop.')
568 wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopYMaxBitmap)
569 elif self.checkupState == 7:
570 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and self.yMaxStop and not self.zMinStop and not self.zMaxStop:
571 self.checkupState = 8
572 wx.CallAfter(self.infoBox.SetAttention, 'Please press the top Z endstop.')
573 wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMinBitmap)
574 elif self.checkupState == 8:
575 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and self.zMinStop and not self.zMaxStop:
576 self.checkupState = 9
577 wx.CallAfter(self.infoBox.SetAttention, 'Please press the bottom Z endstop.')
578 wx.CallAfter(self.endstopBitmap.SetBitmap, self.endStopZMaxBitmap)
579 elif self.checkupState == 9:
580 if not self.xMinStop and not self.xMaxStop and not self.yMinStop and not self.yMaxStop and not self.zMinStop and self.zMaxStop:
581 self.checkupState = 10
583 wx.CallAfter(self.infoBox.SetInfo, 'Checkup finished')
584 wx.CallAfter(self.infoBox.SetReadyIndicator)
585 wx.CallAfter(self.endstopBitmap.Show, False)
586 wx.CallAfter(self.stopState.SetBitmap, self.checkBitmap)
587 wx.CallAfter(self.OnSkipClick, None)
589 def mcProgress(self, lineNr):
592 def mcZChange(self, newZ):
596 class UltimakerCalibrationPage(InfoPage):
597 def __init__(self, parent):
598 super(UltimakerCalibrationPage, self).__init__(parent, "Ultimaker Calibration")
600 self.AddText("Your Ultimaker requires some calibration.")
601 self.AddText("This calibration is needed for a proper extrusion amount.")
603 self.AddText("The following values are needed:")
604 self.AddText("* Diameter of filament")
605 self.AddText("* Number of steps per mm of filament extrusion")
607 self.AddText("The better you have calibrated these values, the better your prints\nwill become.")
609 self.AddText("First we need the diameter of your filament:")
610 self.filamentDiameter = self.AddTextCtrl(profile.getProfileSetting('filament_diameter'))
612 "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.")
613 self.AddText("Note: This value can be changed later at any time.")
616 profile.putProfileSetting('filament_diameter', self.filamentDiameter.GetValue())
619 class UltimakerCalibrateStepsPerEPage(InfoPage):
620 def __init__(self, parent):
621 super(UltimakerCalibrateStepsPerEPage, self).__init__(parent, "Ultimaker Calibration")
623 #if profile.getPreference('steps_per_e') == '0':
624 # profile.putPreference('steps_per_e', '865.888')
626 self.AddText("Calibrating the Steps Per E requires some manual actions.")
627 self.AddText("First remove any filament from your machine.")
628 self.AddText("Next put in your filament so the tip is aligned with the\ntop of the extruder drive.")
629 self.AddText("We'll push the filament 100mm")
630 self.extrudeButton = self.AddButton("Extrude 100mm filament")
631 self.AddText("Now measure the amount of extruded filament:\n(this can be more or less then 100mm)")
632 self.lengthInput, self.saveLengthButton = self.AddTextCtrlButton('100', 'Save')
633 self.AddText("This results in the following steps per E:")
634 self.stepsPerEInput = self.AddTextCtrl(profile.getPreference('steps_per_e'))
635 self.AddText("You can repeat these steps to get better calibration.")
638 "If you still have filament in your printer which needs\nheat to remove, press the heat up button below:")
639 self.heatButton = self.AddButton("Heatup for filament removal")
641 self.saveLengthButton.Bind(wx.EVT_BUTTON, self.OnSaveLengthClick)
642 self.extrudeButton.Bind(wx.EVT_BUTTON, self.OnExtrudeClick)
643 self.heatButton.Bind(wx.EVT_BUTTON, self.OnHeatClick)
645 def OnSaveLengthClick(self, e):
646 currentEValue = float(self.stepsPerEInput.GetValue())
647 realExtrudeLength = float(self.lengthInput.GetValue())
648 newEValue = currentEValue * 100 / realExtrudeLength
649 self.stepsPerEInput.SetValue(str(newEValue))
650 self.lengthInput.SetValue("100")
652 def OnExtrudeClick(self, e):
653 threading.Thread(target=self.OnExtrudeRun).start()
655 def OnExtrudeRun(self):
656 self.heatButton.Enable(False)
657 self.extrudeButton.Enable(False)
658 currentEValue = float(self.stepsPerEInput.GetValue())
659 self.comm = machineCom.MachineCom()
660 if not self.comm.isOpen():
662 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
663 'Printer error', wx.OK | wx.ICON_INFORMATION)
664 self.heatButton.Enable(True)
665 self.extrudeButton.Enable(True)
668 line = self.comm.readline()
673 #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
676 self.sendGCommand('M302') #Disable cold extrusion protection
677 self.sendGCommand("M92 E%f" % (currentEValue))
678 self.sendGCommand("G92 E0")
679 self.sendGCommand("G1 E100 F600")
682 self.extrudeButton.Enable()
683 self.heatButton.Enable()
685 def OnHeatClick(self, e):
686 threading.Thread(target=self.OnHeatRun).start()
689 self.heatButton.Enable(False)
690 self.extrudeButton.Enable(False)
691 self.comm = machineCom.MachineCom()
692 if not self.comm.isOpen():
694 "Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable",
695 'Printer error', wx.OK | wx.ICON_INFORMATION)
696 self.heatButton.Enable(True)
697 self.extrudeButton.Enable(True)
700 line = self.comm.readline()
702 self.heatButton.Enable(True)
703 self.extrudeButton.Enable(True)
707 #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
710 self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.
712 'Wait till you can remove the filament from the machine, and press OK.\n(Temperature is set to 200C)',
713 'Machine heatup', wx.OK | wx.ICON_INFORMATION)
714 self.sendGCommand('M104 S0')
717 self.heatButton.Enable(True)
718 self.extrudeButton.Enable(True)
720 def sendGCommand(self, cmd):
721 self.comm.sendCommand(cmd) #Disable cold extrusion protection
723 line = self.comm.readline()
726 if line.startswith('ok'):
730 profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())
733 class configWizard(wx.wizard.Wizard):
735 super(configWizard, self).__init__(None, -1, "Configuration Wizard")
737 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
738 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
740 self.firstInfoPage = FirstInfoPage(self)
741 self.machineSelectPage = MachineSelectPage(self)
742 self.ultimakerSelectParts = SelectParts(self)
743 self.ultimakerFirmwareUpgradePage = FirmwareUpgradePage(self)
744 self.ultimakerCheckupPage = UltimakerCheckupPage(self)
745 self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)
746 self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)
747 self.bedLevelPage = bedLevelWizardMain(self)
748 self.headOffsetCalibration = headOffsetCalibrationPage(self)
749 self.repRapInfoPage = RepRapInfoPage(self)
751 wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)
752 wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerSelectParts)
753 wx.wizard.WizardPageSimple.Chain(self.ultimakerSelectParts, self.ultimakerFirmwareUpgradePage)
754 wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)
755 wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.bedLevelPage)
756 #wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)
758 self.FitToPage(self.firstInfoPage)
759 self.GetPageAreaSizer().Add(self.firstInfoPage)
761 self.RunWizard(self.firstInfoPage)
764 def OnPageChanging(self, e):
765 e.GetPage().StoreData()
767 def OnPageChanged(self, e):
768 if e.GetPage().AllowNext():
769 self.FindWindowById(wx.ID_FORWARD).Enable()
771 self.FindWindowById(wx.ID_FORWARD).Disable()
772 self.FindWindowById(wx.ID_BACKWARD).Disable()
774 class bedLevelWizardMain(InfoPage):
775 def __init__(self, parent):
776 super(bedLevelWizardMain, self).__init__(parent, "Bed leveling wizard")
778 self.AddText('This wizard will help you in leveling your printer bed')
780 self.AddText('It will do the following steps')
781 self.AddText('* Move the printer head to each corner')
782 self.AddText(' and let you adjust the height of the bed to the nozzle')
783 self.AddText('* Print a line around the bed to check if it is level')
786 self.connectButton = self.AddButton('Connect to printer')
789 self.infoBox = self.AddInfoBox()
790 self.resumeButton = self.AddButton('Resume')
791 self.resumeButton.Enable(False)
793 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
794 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
796 def OnConnect(self, e = None):
797 if self.comm is not None:
801 wx.CallAfter(self.OnConnect)
803 self.connectButton.Enable(False)
804 self.comm = machineCom.MachineCom(callbackObject=self)
805 self.infoBox.SetBusy('Connecting to machine.')
806 self._wizardState = 0
809 if self.GetParent().headOffsetCalibration is not None and int(profile.getPreference('extruder_amount')) > 1:
810 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().headOffsetCalibration)
813 def OnResume(self, e):
814 feedZ = profile.getProfileSettingFloat('print_speed') * 60
815 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
816 if self._wizardState == 2:
817 wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back left corner...')
818 self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
819 self.comm.sendCommand('G1 X%d Y%d F%d' % (0, profile.getPreferenceFloat('machine_depth'), feedTravel))
820 self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
821 self.comm.sendCommand('M400')
822 self._wizardState = 3
823 elif self._wizardState == 4:
824 wx.CallAfter(self.infoBox.SetBusy, 'Moving head to back right corner...')
825 self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
826 self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width') - 5.0, profile.getPreferenceFloat('machine_depth') - 25, feedTravel))
827 self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
828 self.comm.sendCommand('M400')
829 self._wizardState = 5
830 elif self._wizardState == 6:
831 wx.CallAfter(self.infoBox.SetBusy, 'Moving head to front right corner...')
832 self.comm.sendCommand('G1 Z3 F%d' % (feedZ))
833 self.comm.sendCommand('G1 X%d Y%d F%d' % (profile.getPreferenceFloat('machine_width') - 5.0, 20, feedTravel))
834 self.comm.sendCommand('G1 Z0 F%d' % (feedZ))
835 self.comm.sendCommand('M400')
836 self._wizardState = 7
837 elif self._wizardState == 8:
838 wx.CallAfter(self.infoBox.SetBusy, 'Heating up printer...')
839 self.comm.sendCommand('G1 Z15 F%d' % (feedZ))
840 self.comm.sendCommand('M104 S%d' % (profile.getProfileSettingFloat('print_temperature')))
841 self.comm.sendCommand('G1 X%d Y%d F%d' % (0, 0, feedTravel))
842 self._wizardState = 9
843 elif self._wizardState == 10:
844 self._wizardState = 11
845 wx.CallAfter(self.infoBox.SetInfo, 'Printing a square on the printer bed at 0.3mm height.')
846 feedZ = profile.getProfileSettingFloat('print_speed') * 60
847 feedPrint = profile.getProfileSettingFloat('print_speed') * 60
848 feedTravel = profile.getProfileSettingFloat('travel_speed') * 60
849 w = profile.getPreferenceFloat('machine_width')
850 d = profile.getPreferenceFloat('machine_depth')
851 filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2
852 filamentArea = math.pi * filamentRadius * filamentRadius
853 ePerMM = (profile.calculateEdgeWidth() * 0.3) / filamentArea
857 'G1 Z2 F%d' % (feedZ),
859 'G1 X%d Y%d F%d' % (5, 5, feedTravel),
860 'G1 Z0.3 F%d' % (feedZ)]
862 gcodeList.append('G1 E%f F%d' % (eValue, profile.getProfileSettingFloat('retraction_speed') * 60))
864 for i in xrange(0, 3):
865 dist = 5.0 + 0.4 * float(i)
866 eValue += (d - 2.0*dist) * ePerMM
867 gcodeList.append('G1 X%f Y%f E%f F%d' % (dist, d - dist, eValue, feedPrint))
868 eValue += (w - 2.0*dist) * ePerMM
869 gcodeList.append('G1 X%f Y%f E%f F%d' % (w - dist, d - dist, eValue, feedPrint))
870 eValue += (d - 2.0*dist) * ePerMM
871 gcodeList.append('G1 X%f Y%f E%f F%d' % (w - dist, dist, eValue, feedPrint))
872 eValue += (w - 2.0*dist) * ePerMM
873 gcodeList.append('G1 X%f Y%f E%f F%d' % (dist, dist, eValue, feedPrint))
875 gcodeList.append('M400')
876 self.comm.printGCode(gcodeList)
877 self.resumeButton.Enable(False)
879 def mcLog(self, message):
880 print 'Log:', message
882 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
883 if self._wizardState == 1:
884 self._wizardState = 2
885 wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front left screw of your printer bed\nSo the nozzle just hits the bed.')
886 wx.CallAfter(self.resumeButton.Enable, True)
887 elif self._wizardState == 3:
888 self._wizardState = 4
889 wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back left screw of your printer bed\nSo the nozzle just hits the bed.')
890 wx.CallAfter(self.resumeButton.Enable, True)
891 elif self._wizardState == 5:
892 self._wizardState = 6
893 wx.CallAfter(self.infoBox.SetAttention, 'Adjust the back right screw of your printer bed\nSo the nozzle just hits the bed.')
894 wx.CallAfter(self.resumeButton.Enable, True)
895 elif self._wizardState == 7:
896 self._wizardState = 8
897 wx.CallAfter(self.infoBox.SetAttention, 'Adjust the front right screw of your printer bed\nSo the nozzle just hits the bed.')
898 wx.CallAfter(self.resumeButton.Enable, True)
899 elif self._wizardState == 9:
900 if temp[0] < profile.getProfileSettingFloat('print_temperature') - 5:
901 wx.CallAfter(self.infoBox.SetInfo, 'Heating up printer: %d/%d' % (temp[0], profile.getProfileSettingFloat('print_temperature')))
903 wx.CallAfter(self.infoBox.SetAttention, 'The printer is hot now. Please insert some PLA filament into the printer.')
904 wx.CallAfter(self.resumeButton.Enable, True)
905 self._wizardState = 10
907 def mcStateChange(self, state):
908 if self.comm is None:
910 if self.comm.isOperational():
911 if self._wizardState == 0:
912 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer...')
913 self.comm.sendCommand('M105')
914 self.comm.sendCommand('G28')
915 self._wizardState = 1
916 elif self._wizardState == 11 and not self.comm.isPrinting():
917 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('print_speed') * 60))
918 self.comm.sendCommand('G92 E0')
919 self.comm.sendCommand('G1 E-10 F%d' % (profile.getProfileSettingFloat('retraction_speed') * 60))
920 self.comm.sendCommand('M104 S0')
921 wx.CallAfter(self.infoBox.SetInfo, 'Calibration finished.\nThe squares on the bed should slightly touch each other.')
922 wx.CallAfter(self.infoBox.SetReadyIndicator)
923 wx.CallAfter(self.GetParent().FindWindowById(wx.ID_FORWARD).Enable)
924 wx.CallAfter(self.connectButton.Enable, True)
925 self._wizardState = 12
926 elif self.comm.isError():
927 wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
929 def mcMessage(self, message):
932 def mcProgress(self, lineNr):
935 def mcZChange(self, newZ):
938 class headOffsetCalibrationPage(InfoPage):
939 def __init__(self, parent):
940 super(headOffsetCalibrationPage, self).__init__(parent, "Printer head offset calibration (WIP)")
942 self.AddText('This wizard will help you in calibrating the printer head offsets of your dual extrusion machine')
945 self.connectButton = self.AddButton('Connect to printer')
948 self.infoBox = self.AddInfoBox()
949 self.textEntry = self.AddTextCtrl('')
950 self.textEntry.Enable(False)
951 self.resumeButton = self.AddButton('Resume')
952 self.resumeButton.Enable(False)
954 self.Bind(wx.EVT_BUTTON, self.OnConnect, self.connectButton)
955 self.Bind(wx.EVT_BUTTON, self.OnResume, self.resumeButton)
957 def OnConnect(self, e = None):
958 if self.comm is not None:
962 wx.CallAfter(self.OnConnect)
964 self.connectButton.Enable(False)
965 self.comm = machineCom.MachineCom(callbackObject=self)
966 self.infoBox.SetBusy('Connecting to machine.')
967 self._wizardState = 0
969 def OnResume(self, e):
970 if self._wizardState == 2:
971 self._wizardState = 3
972 wx.CallAfter(self.infoBox.SetBusy, 'Printing initial calibration cross')
974 w = profile.getPreferenceFloat('machine_width')
975 d = profile.getPreferenceFloat('machine_depth')
977 gcode = gcodeGenerator.gcodeGenerator()
978 gcode.setExtrusionRate(profile.getProfileSettingFloat('nozzle_size') * 1.5, 0.3)
985 gcode.addMove(w/2, 5, 0.3)
986 gcode.addExtrude(w/2, d-5.0)
988 gcode.addMove(5, d/2)
990 gcode.addExtrude(w-5.0, d/2)
994 gcode.addMove(w/2, 5)
995 gcode.addExtrude(w/2, d-5.0)
997 gcode.addMove(5, d/2)
999 gcode.addExtrude(w-5.0, d/2)
1001 gcode.addRetract(15)
1005 self.comm.printGCode(gcode.list())
1006 self.resumeButton.Enable(False)
1007 if self._wizardState == 4:
1009 float(self.textEntry.GetValue())
1012 profile.putPreference('extruder_offset_x1', self.textEntry.GetValue())
1013 self._wizardState = 5
1014 self.infoBox.SetAttention('Please measure the distance between the horizontal lines in millimeters.')
1015 self.textEntry.SetValue('0.0')
1016 self.textEntry.Enable(True)
1017 if self._wizardState == 5:
1019 float(self.textEntry.GetValue())
1022 profile.putPreference('extruder_offset_y1', self.textEntry.GetValue())
1023 self._wizardState = 6
1024 self.infoBox.SetBusy('Printing the fine calibration lines.')
1025 self.textEntry.SetValue('')
1026 self.textEntry.Enable(False)
1027 self.resumeButton.Enable(False)
1029 def mcLog(self, message):
1030 print 'Log:', message
1032 def mcTempUpdate(self, temp, bedTemp, targetTemp, bedTargetTemp):
1033 if self._wizardState == 1:
1034 if temp[0] >= 210 and temp[1] >= 210:
1035 self._wizardState = 2
1036 wx.CallAfter(self.infoBox.SetAttention, 'Please load both extruders with PLA.')
1037 wx.CallAfter(self.resumeButton.Enable, True)
1039 def mcStateChange(self, state):
1040 if self.comm is None:
1042 if self.comm.isOperational():
1043 if self._wizardState == 0:
1044 wx.CallAfter(self.infoBox.SetInfo, 'Homing printer and heating up both extruders.')
1045 self.comm.sendCommand('M105')
1046 self.comm.sendCommand('G28')
1047 self.comm.sendCommand('G1 Z15 F%d' % (profile.getProfileSettingFloat('print_speed') * 60))
1048 self.comm.sendCommand('M104 S220 T0')
1049 self.comm.sendCommand('M104 S220 T1')
1050 self._wizardState = 1
1051 if not self.comm.isPrinting():
1052 if self._wizardState == 3:
1053 self._wizardState = 4
1054 wx.CallAfter(self.infoBox.SetAttention, 'Please measure the distance between the vertical lines in millimeters.')
1055 wx.CallAfter(self.textEntry.SetValue, '0.0')
1056 wx.CallAfter(self.textEntry.Enable, True)
1057 wx.CallAfter(self.resumeButton.Enable, True)
1058 wx.CallAfter(self.resumeButton.SetFocus)
1059 elif self.comm.isError():
1060 wx.CallAfter(self.infoBox.SetError, 'Failed to establish connection with the printer.', 'http://wiki.ultimaker.com/Cura:_Connection_problems')
1062 def mcMessage(self, message):
1065 def mcProgress(self, lineNr):
1068 def mcZChange(self, newZ):
1071 class bedLevelWizard(wx.wizard.Wizard):
1073 super(bedLevelWizard, self).__init__(None, -1, "Bed leveling wizard")
1075 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
1076 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
1078 self.mainPage = bedLevelWizardMain(self)
1079 self.headOffsetCalibration = None
1081 self.FitToPage(self.mainPage)
1082 self.GetPageAreaSizer().Add(self.mainPage)
1084 self.RunWizard(self.mainPage)
1087 def OnPageChanging(self, e):
1088 e.GetPage().StoreData()
1090 def OnPageChanged(self, e):
1091 if e.GetPage().AllowNext():
1092 self.FindWindowById(wx.ID_FORWARD).Enable()
1094 self.FindWindowById(wx.ID_FORWARD).Disable()
1095 self.FindWindowById(wx.ID_BACKWARD).Disable()