1 from __future__ import absolute_import
\r
4 import wx, os, platform, types, webbrowser, threading, time, re
\r
7 from gui import firmwareInstall
\r
8 from gui import toolbarUtil
\r
9 from util import machineCom
\r
10 from util import profile
\r
12 class InfoPage(wx.wizard.WizardPageSimple):
\r
13 def __init__(self, parent, title):
\r
14 wx.wizard.WizardPageSimple.__init__(self, parent)
\r
16 sizer = wx.GridBagSizer(5, 5)
\r
18 self.SetSizer(sizer)
\r
20 title = wx.StaticText(self, -1, title)
\r
21 title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD))
\r
22 sizer.Add(title, pos=(0, 0), span=(1,2), flag=wx.ALIGN_CENTRE|wx.ALL)
\r
23 sizer.Add(wx.StaticLine(self, -1), pos=(1,0), span=(1,2), flag=wx.EXPAND|wx.ALL)
\r
24 sizer.AddGrowableCol(1)
\r
28 def AddText(self,info):
\r
29 text = wx.StaticText(self, -1, info)
\r
30 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1,2), flag=wx.LEFT|wx.RIGHT)
\r
34 def AddSeperator(self):
\r
35 self.GetSizer().Add(wx.StaticLine(self, -1), pos=(self.rowNr, 0), span=(1,2), flag=wx.EXPAND|wx.ALL)
\r
38 def AddHiddenSeperator(self):
\r
41 def AddRadioButton(self, label, style = 0):
\r
42 radio = wx.RadioButton(self, -1, label, style=style)
\r
43 self.GetSizer().Add(radio, pos=(self.rowNr, 0), span=(1,2), flag=wx.EXPAND|wx.ALL)
\r
47 def AddCheckbox(self, label, checked = False):
\r
48 check = wx.CheckBox(self, -1)
\r
49 text = wx.StaticText(self, -1, label)
\r
50 check.SetValue(checked)
\r
51 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1,1), flag=wx.LEFT|wx.RIGHT)
\r
52 self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1,2), flag=wx.ALL)
\r
56 def AddButton(self, label):
\r
57 button = wx.Button(self, -1, label)
\r
58 self.GetSizer().Add(button, pos=(self.rowNr, 0), span=(1,2), flag=wx.LEFT)
\r
62 def AddDualButton(self, label1, label2):
\r
63 button1 = wx.Button(self, -1, label1)
\r
64 self.GetSizer().Add(button1, pos=(self.rowNr, 0), flag=wx.RIGHT)
\r
65 button2 = wx.Button(self, -1, label2)
\r
66 self.GetSizer().Add(button2, pos=(self.rowNr, 1))
\r
68 return button1, button2
\r
70 def AddTextCtrl(self, value):
\r
71 ret = wx.TextCtrl(self, -1, value)
\r
72 self.GetSizer().Add(ret, pos=(self.rowNr, 0), span=(1,2), flag=wx.LEFT)
\r
76 def AddLabelTextCtrl(self, info, value):
\r
77 text = wx.StaticText(self, -1, info)
\r
78 ret = wx.TextCtrl(self, -1, value)
\r
79 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1,1), flag=wx.LEFT)
\r
80 self.GetSizer().Add(ret, pos=(self.rowNr, 1), span=(1,1), flag=wx.LEFT)
\r
84 def AddTextCtrlButton(self, value, buttonText):
\r
85 text = wx.TextCtrl(self, -1, value)
\r
86 button = wx.Button(self, -1, buttonText)
\r
87 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1,1), flag=wx.LEFT)
\r
88 self.GetSizer().Add(button, pos=(self.rowNr, 1), span=(1,1), flag=wx.LEFT)
\r
92 def AddCheckmark(self, label, bitmap):
\r
93 check = wx.StaticBitmap(self, -1, bitmap)
\r
94 text = wx.StaticText(self, -1, label)
\r
95 self.GetSizer().Add(text, pos=(self.rowNr, 0), span=(1,1), flag=wx.LEFT|wx.RIGHT)
\r
96 self.GetSizer().Add(check, pos=(self.rowNr, 1), span=(1,2), flag=wx.ALL)
\r
100 def AllowNext(self):
\r
103 def StoreData(self):
\r
106 class FirstInfoPage(InfoPage):
\r
107 def __init__(self, parent):
\r
108 super(FirstInfoPage, self).__init__(parent, "First time run wizard")
\r
109 self.AddText('Welcome, and thanks for trying Cura!')
\r
110 self.AddSeperator()
\r
111 self.AddText('This wizard will help you with the following steps:')
\r
112 self.AddText('* Configure Cura for your machine')
\r
113 self.AddText('* Upgrade your firmware')
\r
114 self.AddText('* Check if your machine is working safely')
\r
115 #self.AddText('* Calibrate your machine')
\r
116 #self.AddText('* Do your first print')
\r
118 class RepRapInfoPage(InfoPage):
\r
119 def __init__(self, parent):
\r
120 super(RepRapInfoPage, self).__init__(parent, "RepRap information")
\r
121 self.AddText('RepRap machines are vastly different, and there is no\ndefault configuration in Cura for any of them.')
\r
122 self.AddText('If you like a default profile for your machine added,\nthen make an issue on github.')
\r
123 self.AddSeperator()
\r
124 self.AddText('You will have to manually install Marlin or Sprinter firmware.')
\r
125 self.AddSeperator()
\r
126 self.machineWidth = self.AddLabelTextCtrl('Machine width (mm)', '80')
\r
127 self.machineDepth = self.AddLabelTextCtrl('Machine depth (mm)', '80')
\r
128 self.machineHeight = self.AddLabelTextCtrl('Machine height (mm)', '60')
\r
129 self.nozzleSize = self.AddLabelTextCtrl('Nozzle size (mm)', '0.5')
\r
130 self.heatedBed = self.AddCheckbox('Heated bed')
\r
132 def StoreData(self):
\r
133 profile.putPreference('machine_width', self.machineWidth.GetValue())
\r
134 profile.putPreference('machine_depth', self.machineDepth.GetValue())
\r
135 profile.putPreference('machine_height', self.machineHeight.GetValue())
\r
136 profile.putProfileSetting('nozzle_size', self.nozzleSize.GetValue())
\r
137 profile.putProfileSetting('machine_center_x', profile.getPreferenceFloat('machine_width') / 2)
\r
138 profile.putProfileSetting('machine_center_y', profile.getPreferenceFloat('machine_depth') / 2)
\r
139 profile.putProfileSetting('wall_thickness', float(profile.getProfileSettingFloat('nozzle_size')) * 2)
\r
140 profile.putPreference('has_heated_bed', str(self.heatedBed.GetValue()))
\r
142 class MachineSelectPage(InfoPage):
\r
143 def __init__(self, parent):
\r
144 super(MachineSelectPage, self).__init__(parent, "Select your machine")
\r
145 self.AddText('What kind of machine do you have:')
\r
147 self.UltimakerRadio = self.AddRadioButton("Ultimaker", style=wx.RB_GROUP)
\r
148 self.UltimakerRadio.SetValue(True)
\r
149 self.UltimakerRadio.Bind(wx.EVT_RADIOBUTTON, self.OnUltimakerSelect)
\r
150 self.OtherRadio = self.AddRadioButton("Other (Ex: RepRap)")
\r
151 self.OtherRadio.Bind(wx.EVT_RADIOBUTTON, self.OnOtherSelect)
\r
153 def OnUltimakerSelect(self, e):
\r
154 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().ultimakerFirmwareUpgradePage)
\r
156 def OnOtherSelect(self, e):
\r
157 wx.wizard.WizardPageSimple.Chain(self, self.GetParent().repRapInfoPage)
\r
159 def StoreData(self):
\r
160 if self.UltimakerRadio.GetValue():
\r
161 profile.putPreference('machine_width', '205')
\r
162 profile.putPreference('machine_depth', '205')
\r
163 profile.putPreference('machine_height', '200')
\r
164 profile.putPreference('machine_type', 'ultimaker')
\r
165 profile.putProfileSetting('nozzle_size', '0.4')
\r
166 profile.putProfileSetting('machine_center_x', '100')
\r
167 profile.putProfileSetting('machine_center_y', '100')
\r
169 profile.putPreference('machine_width', '80')
\r
170 profile.putPreference('machine_depth', '80')
\r
171 profile.putPreference('machine_height', '60')
\r
172 profile.putPreference('machine_type', 'reprap')
\r
173 profile.putProfileSetting('nozzle_size', '0.5')
\r
174 profile.putProfileSetting('machine_center_x', '40')
\r
175 profile.putProfileSetting('machine_center_y', '40')
\r
176 profile.putProfileSetting('wall_thickness', float(profile.getProfileSetting('nozzle_size')) * 2)
\r
178 class FirmwareUpgradePage(InfoPage):
\r
179 def __init__(self, parent):
\r
180 super(FirmwareUpgradePage, self).__init__(parent, "Upgrade Ultimaker Firmware")
\r
181 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
182 self.AddHiddenSeperator()
\r
183 self.AddText('The firmware shipping with new Ultimakers works, but upgrades\nhave been made to make better prints, and make calibration easier.')
\r
184 self.AddHiddenSeperator()
\r
185 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
186 upgradeButton, skipUpgradeButton = self.AddDualButton('Upgrade to Marlin firmware', 'Skip upgrade')
\r
187 upgradeButton.Bind(wx.EVT_BUTTON, self.OnUpgradeClick)
\r
188 skipUpgradeButton.Bind(wx.EVT_BUTTON, self.OnSkipClick)
\r
189 self.AddHiddenSeperator()
\r
190 self.AddText('Do not upgrade to this firmware if:')
\r
191 self.AddText('* You have an older machine based on ATMega1280')
\r
192 self.AddText('* Have other changes in the firmware')
\r
193 button = self.AddButton('Goto this page for a custom firmware')
\r
194 button.Bind(wx.EVT_BUTTON, self.OnUrlClick)
\r
196 def AllowNext(self):
\r
199 def OnUpgradeClick(self, e):
\r
200 if firmwareInstall.InstallFirmware():
\r
201 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
\r
203 def OnSkipClick(self, e):
\r
204 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
\r
206 def OnUrlClick(self, e):
\r
207 webbrowser.open('http://daid.mine.nu/~daid/marlin_build/')
\r
209 class UltimakerCheckupPage(InfoPage):
\r
210 def __init__(self, parent):
\r
211 super(UltimakerCheckupPage, self).__init__(parent, "Ultimaker Checkup")
\r
212 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
213 b1, b2 = self.AddDualButton('Run checks', 'Skip checks')
\r
214 b1.Bind(wx.EVT_BUTTON, self.OnCheckClick)
\r
215 b2.Bind(wx.EVT_BUTTON, self.OnSkipClick)
\r
216 self.AddSeperator()
\r
217 self.checkBitmap = toolbarUtil.getBitmapImage('checkmark.png')
\r
218 self.crossBitmap = toolbarUtil.getBitmapImage('cross.png')
\r
219 self.unknownBitmap = toolbarUtil.getBitmapImage('question.png')
\r
220 self.commState = self.AddCheckmark('Communication:', self.unknownBitmap)
\r
221 self.tempState = self.AddCheckmark('Temperature:', self.unknownBitmap)
\r
222 self.stopState = self.AddCheckmark('Endstops:', self.unknownBitmap)
\r
223 self.AddSeperator()
\r
224 self.checkState = self.AddText('')
\r
225 self.machineState = self.AddText('')
\r
226 self.temperatureLabel = self.AddText('')
\r
228 self.xMinStop = False
\r
229 self.xMaxStop = False
\r
230 self.yMinStop = False
\r
231 self.yMaxStop = False
\r
232 self.zMinStop = False
\r
233 self.zMaxStop = False
\r
235 def AllowNext(self):
\r
238 def OnSkipClick(self, e):
\r
239 self.GetParent().FindWindowById(wx.ID_FORWARD).Enable()
\r
241 def OnCheckClick(self, e):
\r
242 if self.comm != None:
\r
245 wx.CallAfter(self.checkState.SetLabel, 'Connecting to machine.')
\r
246 self.commState.SetBitmap(self.unknownBitmap)
\r
247 self.tempState.SetBitmap(self.unknownBitmap)
\r
248 self.stopState.SetBitmap(self.unknownBitmap)
\r
249 self.checkupState = 0
\r
250 self.comm = machineCom.MachineCom(callbackObject=self)
\r
252 def mcLog(self, message):
\r
255 def mcTempUpdate(self, temp, bedTemp):
\r
256 if self.checkupState == 0:
\r
257 self.tempCheckTimeout = 20
\r
259 self.checkupState = 1
\r
260 wx.CallAfter(self.checkState.SetLabel, 'Cooldown before temperature check.')
\r
261 self.comm.sendCommand('M104 S0')
\r
262 self.comm.sendCommand('M104 S0')
\r
264 self.startTemp = temp
\r
265 self.checkupState = 2
\r
266 wx.CallAfter(self.checkState.SetLabel, 'Checking the heater and temperature sensor.')
\r
267 self.comm.sendCommand('M104 S200')
\r
268 self.comm.sendCommand('M104 S200')
\r
269 elif self.checkupState == 1:
\r
271 self.startTemp = temp
\r
272 self.checkupState = 2
\r
273 wx.CallAfter(self.checkState.SetLabel, 'Checking the heater and temperature sensor.')
\r
274 self.comm.sendCommand('M104 S200')
\r
275 self.comm.sendCommand('M104 S200')
\r
276 elif self.checkupState == 2:
\r
277 if temp > self.startTemp:# + 40:
\r
278 self.checkupState = 3
\r
279 wx.CallAfter(self.checkState.SetLabel, 'Testing the endstops...')
\r
280 self.comm.sendCommand('M104 S0')
\r
281 self.comm.sendCommand('M104 S0')
\r
282 self.comm.sendCommand('M119')
\r
283 self.tempState.SetBitmap(self.checkBitmap)
\r
285 self.tempCheckTimeout -= 1
\r
286 if self.tempCheckTimeout < 1:
\r
287 self.checkupState = -1
\r
288 self.tempState.SetBitmap(self.crossBitmap)
\r
289 wx.CallAfter(self.checkState.SetLabel, 'Temperature measurement FAILED!')
\r
290 self.comm.sendCommand('M104 S0')
\r
291 self.comm.sendCommand('M104 S0')
\r
292 wx.CallAfter(self.temperatureLabel.SetLabel, 'Head temperature: %d' % (temp))
\r
294 def mcStateChange(self, state):
\r
295 if self.comm.isOperational():
\r
296 self.commState.SetBitmap(self.checkBitmap)
\r
297 elif self.comm.isError():
\r
298 self.commState.SetBitmap(self.crossBitmap)
\r
299 wx.CallAfter(self.machineState.SetLabel, 'Communication State: %s' % (self.comm.getStateString()))
\r
301 def mcMessage(self, message):
\r
302 if self.checkupState >= 3 and 'x_min' in message:
\r
303 for data in message.split(' '):
\r
305 tag, value = data.split(':', 2)
\r
307 self.xMinStop = (value == 'H')
\r
309 self.xMaxStop = (value == 'H')
\r
311 self.yMinStop = (value == 'H')
\r
313 self.yMaxStop = (value == 'H')
\r
315 self.zMinStop = (value == 'H')
\r
317 self.zMaxStop = (value == 'H')
\r
318 self.comm.sendCommand('M119')
\r
320 def mcProgress(self, lineNr):
\r
323 def mcZChange(self, newZ):
\r
326 class UltimakerCalibrationPage(InfoPage):
\r
327 def __init__(self, parent):
\r
328 super(UltimakerCalibrationPage, self).__init__(parent, "Ultimaker Calibration")
\r
330 self.AddText("Your Ultimaker requires some calibration.")
\r
331 self.AddText("This calibration is needed for a proper extrusion amount.")
\r
332 self.AddSeperator()
\r
333 self.AddText("The following values are needed:")
\r
334 self.AddText("* Diameter of filament")
\r
335 self.AddText("* Number of steps per mm of filament extrusion")
\r
336 self.AddSeperator()
\r
337 self.AddText("The better you have calibrated these values, the better your prints\nwill become.")
\r
338 self.AddSeperator()
\r
339 self.AddText("First we need the diameter of your filament:")
\r
340 self.filamentDiameter = self.AddTextCtrl(profile.getProfileSetting('filament_diameter'))
\r
341 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
342 self.AddText("Note: This value can be changed later at any time.")
\r
344 def StoreData(self):
\r
345 profile.putProfileSetting('filament_diameter', self.filamentDiameter.GetValue())
\r
347 class UltimakerCalibrateStepsPerEPage(InfoPage):
\r
348 def __init__(self, parent):
\r
349 super(UltimakerCalibrateStepsPerEPage, self).__init__(parent, "Ultimaker Calibration")
\r
351 if profile.getPreference('steps_per_e') == '0':
\r
352 profile.putPreference('steps_per_e', '865.888')
\r
354 self.AddText("Calibrating the Steps Per E requires some manual actions.")
\r
355 self.AddText("First remove any filament from your machine.")
\r
356 self.AddText("Next put in your filament so the tip is aligned with the\ntop of the extruder drive.")
\r
357 self.AddText("We'll push the filament 100mm")
\r
358 self.extrudeButton = self.AddButton("Extrude 100mm filament")
\r
359 self.AddText("Now measure the amount of extruded filament:\n(this can be more or less then 100mm)")
\r
360 self.lengthInput, self.saveLengthButton = self.AddTextCtrlButton('100', 'Save')
\r
361 self.AddText("This results in the following steps per E:")
\r
362 self.stepsPerEInput = self.AddTextCtrl(profile.getPreference('steps_per_e'))
\r
363 self.AddText("You can repeat these steps to get better calibration.")
\r
364 self.AddSeperator()
\r
365 self.AddText("If you still have filament in your printer which needs\nheat to remove, press the heat up button below:")
\r
366 self.heatButton = self.AddButton("Heatup for filament removal")
\r
368 self.saveLengthButton.Bind(wx.EVT_BUTTON, self.OnSaveLengthClick)
\r
369 self.extrudeButton.Bind(wx.EVT_BUTTON, self.OnExtrudeClick)
\r
370 self.heatButton.Bind(wx.EVT_BUTTON, self.OnHeatClick)
\r
372 def OnSaveLengthClick(self, e):
\r
373 currentEValue = float(self.stepsPerEInput.GetValue())
\r
374 realExtrudeLength = float(self.lengthInput.GetValue())
\r
375 newEValue = currentEValue * 100 / realExtrudeLength
\r
376 self.stepsPerEInput.SetValue(str(newEValue))
\r
377 self.lengthInput.SetValue("100")
\r
379 def OnExtrudeClick(self, e):
\r
380 threading.Thread(target=self.OnExtrudeRun).start()
\r
382 def OnExtrudeRun(self):
\r
383 self.heatButton.Enable(False)
\r
384 self.extrudeButton.Enable(False)
\r
385 currentEValue = float(self.stepsPerEInput.GetValue())
\r
386 self.comm = machineCom.MachineCom()
\r
387 if not self.comm.isOpen():
\r
388 wx.MessageBox("Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable", 'Printer error', wx.OK | wx.ICON_INFORMATION)
\r
389 self.heatButton.Enable(True)
\r
390 self.extrudeButton.Enable(True)
\r
393 line = self.comm.readline()
\r
396 if 'start' in line:
\r
398 #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
\r
401 self.sendGCommand('M302') #Disable cold extrusion protection
\r
402 self.sendGCommand("M92 E%f" % (currentEValue))
\r
403 self.sendGCommand("G92 E0")
\r
404 self.sendGCommand("G1 E100 F600")
\r
407 self.extrudeButton.Enable()
\r
408 self.heatButton.Enable()
\r
410 def OnHeatClick(self, e):
\r
411 threading.Thread(target=self.OnHeatRun).start()
\r
413 def OnHeatRun(self):
\r
414 self.heatButton.Enable(False)
\r
415 self.extrudeButton.Enable(False)
\r
416 self.comm = machineCom.MachineCom()
\r
417 if not self.comm.isOpen():
\r
418 wx.MessageBox("Error: Failed to open serial port to machine\nIf this keeps happening, try disconnecting and reconnecting the USB cable", 'Printer error', wx.OK | wx.ICON_INFORMATION)
\r
419 self.heatButton.Enable(True)
\r
420 self.extrudeButton.Enable(True)
\r
423 line = self.comm.readline()
\r
425 self.heatButton.Enable(True)
\r
426 self.extrudeButton.Enable(True)
\r
428 if 'start' in line:
\r
430 #Wait 3 seconds for the SD card init to timeout if we have SD in our firmware but there is no SD card found.
\r
433 self.sendGCommand('M104 S200') #Set the temperature to 200C, should be enough to get PLA and ABS out.
\r
434 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
435 self.sendGCommand('M104 S0')
\r
438 self.heatButton.Enable(True)
\r
439 self.extrudeButton.Enable(True)
\r
441 def sendGCommand(self, cmd):
\r
442 self.comm.sendCommand(cmd) #Disable cold extrusion protection
\r
444 line = self.comm.readline()
\r
447 if line.startswith('ok'):
\r
450 def StoreData(self):
\r
451 profile.putPreference('steps_per_e', self.stepsPerEInput.GetValue())
\r
453 class configWizard(wx.wizard.Wizard):
\r
454 def __init__(self):
\r
455 super(configWizard, self).__init__(None, -1, "Configuration Wizard")
\r
457 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGED, self.OnPageChanged)
\r
458 self.Bind(wx.wizard.EVT_WIZARD_PAGE_CHANGING, self.OnPageChanging)
\r
460 self.firstInfoPage = FirstInfoPage(self)
\r
461 self.machineSelectPage = MachineSelectPage(self)
\r
462 self.ultimakerFirmwareUpgradePage = FirmwareUpgradePage(self)
\r
463 self.ultimakerCheckupPage = UltimakerCheckupPage(self)
\r
464 self.ultimakerCalibrationPage = UltimakerCalibrationPage(self)
\r
465 self.ultimakerCalibrateStepsPerEPage = UltimakerCalibrateStepsPerEPage(self)
\r
466 self.repRapInfoPage = RepRapInfoPage(self)
\r
468 wx.wizard.WizardPageSimple.Chain(self.firstInfoPage, self.machineSelectPage)
\r
469 wx.wizard.WizardPageSimple.Chain(self.machineSelectPage, self.ultimakerFirmwareUpgradePage)
\r
470 wx.wizard.WizardPageSimple.Chain(self.ultimakerFirmwareUpgradePage, self.ultimakerCheckupPage)
\r
471 #wx.wizard.WizardPageSimple.Chain(self.ultimakerCheckupPage, self.ultimakerCalibrationPage)
\r
472 #wx.wizard.WizardPageSimple.Chain(self.ultimakerCalibrationPage, self.ultimakerCalibrateStepsPerEPage)
\r
474 self.FitToPage(self.firstInfoPage)
\r
475 self.GetPageAreaSizer().Add(self.firstInfoPage)
\r
477 self.RunWizard(self.firstInfoPage)
\r
480 def OnPageChanging(self, e):
\r
481 e.GetPage().StoreData()
\r
483 def OnPageChanged(self, e):
\r
484 if e.GetPage().AllowNext():
\r
485 self.FindWindowById(wx.ID_FORWARD).Enable()
\r
487 self.FindWindowById(wx.ID_FORWARD).Disable()
\r
488 self.FindWindowById(wx.ID_BACKWARD).Disable()
\r