chiark / gitweb /
Fix typos in README.
[cura.git] / Cura / gui / mainWindow.py
1 from __future__ import absolute_import
2
3 import wx
4 import os
5 import webbrowser
6
7 from Cura.gui import configBase
8 from Cura.gui import expertConfig
9 from Cura.gui import preview3d
10 from Cura.gui import sliceProgessPanel
11 from Cura.gui import alterationPanel
12 from Cura.gui import pluginPanel
13 from Cura.gui import preferencesDialog
14 from Cura.gui import configWizard
15 from Cura.gui import firmwareInstall
16 from Cura.gui import printWindow
17 from Cura.gui import simpleMode
18 from Cura.gui import projectPlanner
19 from Cura.gui.tools import batchRun
20 from Cura.gui import flatSlicerWindow
21 from Cura.gui.util import dropTarget
22 from Cura.gui.tools import minecraftImport
23 from Cura.util import validators
24 from Cura.util import profile
25 from Cura.util import version
26 from Cura.util import sliceRun
27 from Cura.util import meshLoader
28
29 class mainWindow(wx.Frame):
30         def __init__(self):
31                 super(mainWindow, self).__init__(None, title='Cura - ' + version.getVersion())
32
33                 self.extruderCount = int(profile.getPreference('extruder_amount'))
34
35                 wx.EVT_CLOSE(self, self.OnClose)
36
37                 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.supportedExtensions()))
38
39                 self.normalModeOnlyItems = []
40
41                 mruFile = os.path.join(profile.getBasePath(), 'mru_filelist.ini')
42                 self.config = wx.FileConfig(appName="Cura", 
43                                                 localFilename=mruFile,
44                                                 style=wx.CONFIG_USE_LOCAL_FILE)
45                                                 
46                 self.ID_MRU_MODEL1, self.ID_MRU_MODEL2, self.ID_MRU_MODEL3, self.ID_MRU_MODEL4, self.ID_MRU_MODEL5, self.ID_MRU_MODEL6, self.ID_MRU_MODEL7, self.ID_MRU_MODEL8, self.ID_MRU_MODEL9, self.ID_MRU_MODEL10 = [wx.NewId() for line in xrange(10)]
47                 self.modelFileHistory = wx.FileHistory(10, self.ID_MRU_MODEL1)
48                 self.config.SetPath("/ModelMRU")
49                 self.modelFileHistory.Load(self.config)
50
51                 self.ID_MRU_PROFILE1, self.ID_MRU_PROFILE2, self.ID_MRU_PROFILE3, self.ID_MRU_PROFILE4, self.ID_MRU_PROFILE5, self.ID_MRU_PROFILE6, self.ID_MRU_PROFILE7, self.ID_MRU_PROFILE8, self.ID_MRU_PROFILE9, self.ID_MRU_PROFILE10 = [wx.NewId() for line in xrange(10)]
52                 self.profileFileHistory = wx.FileHistory(10, self.ID_MRU_PROFILE1)
53                 self.config.SetPath("/ProfileMRU")
54                 self.profileFileHistory.Load(self.config)
55
56                 self.menubar = wx.MenuBar()
57                 self.fileMenu = wx.Menu()
58                 i = self.fileMenu.Append(-1, 'Load model file...\tCTRL+L')
59                 self.Bind(wx.EVT_MENU, lambda e: self._showModelLoadDialog(1), i)
60                 i = self.fileMenu.Append(-1, 'Prepare print...\tCTRL+R')
61                 self.Bind(wx.EVT_MENU, self.OnSlice, i)
62                 i = self.fileMenu.Append(-1, 'Print...\tCTRL+P')
63                 self.Bind(wx.EVT_MENU, self.OnPrint, i)
64
65                 self.fileMenu.AppendSeparator()
66                 i = self.fileMenu.Append(-1, 'Open Profile...')
67                 self.normalModeOnlyItems.append(i)
68                 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
69                 i = self.fileMenu.Append(-1, 'Save Profile...')
70                 self.normalModeOnlyItems.append(i)
71                 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
72                 i = self.fileMenu.Append(-1, 'Load Profile from GCode...')
73                 self.normalModeOnlyItems.append(i)
74                 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
75                 self.fileMenu.AppendSeparator()
76                 i = self.fileMenu.Append(-1, 'Reset Profile to default')
77                 self.normalModeOnlyItems.append(i)
78                 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
79
80                 self.fileMenu.AppendSeparator()
81                 i = self.fileMenu.Append(-1, 'Preferences...\tCTRL+,')
82                 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
83                 self.fileMenu.AppendSeparator()
84
85                 # Model MRU list
86                 modelHistoryMenu = wx.Menu()
87                 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Model Files", modelHistoryMenu)
88                 self.modelFileHistory.UseMenu(modelHistoryMenu)
89                 self.modelFileHistory.AddFilesToMenu()
90                 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
91
92                 # Profle MRU list
93                 profileHistoryMenu = wx.Menu()
94                 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Profile Files", profileHistoryMenu)
95                 self.profileFileHistory.UseMenu(profileHistoryMenu)
96                 self.profileFileHistory.AddFilesToMenu()
97                 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
98                 
99                 self.fileMenu.AppendSeparator()
100                 i = self.fileMenu.Append(wx.ID_EXIT, 'Quit')
101                 self.Bind(wx.EVT_MENU, self.OnQuit, i)
102                 self.menubar.Append(self.fileMenu, '&File')
103
104                 toolsMenu = wx.Menu()
105                 i = toolsMenu.Append(-1, 'Switch to quickprint...')
106                 self.switchToQuickprintMenuItem = i
107                 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
108                 i = toolsMenu.Append(-1, 'Switch to full settings...')
109                 self.switchToNormalMenuItem = i
110                 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
111                 toolsMenu.AppendSeparator()
112                 i = toolsMenu.Append(-1, 'Batch run...')
113                 self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
114                 i = toolsMenu.Append(-1, 'Project planner...')
115                 self.Bind(wx.EVT_MENU, self.OnProjectPlanner, i)
116                 #               i = toolsMenu.Append(-1, 'Open SVG (2D) slicer...')
117                 #               self.Bind(wx.EVT_MENU, self.OnSVGSlicerOpen, i)
118                 if minecraftImport.hasMinecraft():
119                         i = toolsMenu.Append(-1, 'Minecraft import...')
120                         self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
121                 self.menubar.Append(toolsMenu, 'Tools')
122
123                 expertMenu = wx.Menu()
124                 i = expertMenu.Append(-1, 'Open expert settings...')
125                 self.normalModeOnlyItems.append(i)
126                 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
127                 expertMenu.AppendSeparator()
128                 if firmwareInstall.getDefaultFirmware() is not None:
129                         i = expertMenu.Append(-1, 'Install default Marlin firmware')
130                         self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
131                 i = expertMenu.Append(-1, 'Install custom firmware')
132                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
133                 expertMenu.AppendSeparator()
134                 i = expertMenu.Append(-1, 'Run first run wizard...')
135                 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
136                 i = expertMenu.Append(-1, 'Run bed leveling wizard...')
137                 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
138                 self.menubar.Append(expertMenu, 'Expert')
139
140                 helpMenu = wx.Menu()
141                 i = helpMenu.Append(-1, 'Online documentation...')
142                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
143                 i = helpMenu.Append(-1, 'Report a problem...')
144                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
145                 i = helpMenu.Append(-1, 'Check for update...')
146                 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
147                 self.menubar.Append(helpMenu, 'Help')
148                 self.SetMenuBar(self.menubar)
149
150                 if profile.getPreference('lastFile') != '':
151                         self.filelist = profile.getPreference('lastFile').split(';')
152                         self.SetTitle('Cura - %s - %s' % (version.getVersion(), self.filelist[-1]))
153                 else:
154                         self.filelist = []
155                 self.progressPanelList = []
156
157                 ##Gui components##
158                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self)
159                 self.normalSettingsPanel = normalSettingsPanel(self)
160
161                 #Preview window
162                 self.preview3d = preview3d.previewPanel(self)
163
164                 # load and slice buttons.
165                 loadButton = wx.Button(self, -1, '&Load model')
166                 sliceButton = wx.Button(self, -1, 'P&repare print')
167                 printButton = wx.Button(self, -1, '&Print')
168                 self.Bind(wx.EVT_BUTTON, lambda e: self._showModelLoadDialog(1), loadButton)
169                 self.Bind(wx.EVT_BUTTON, self.OnSlice, sliceButton)
170                 self.Bind(wx.EVT_BUTTON, self.OnPrint, printButton)
171
172                 if self.extruderCount > 1:
173                         loadButton2 = wx.Button(self, -1, 'Load Dual')
174                         self.Bind(wx.EVT_BUTTON, lambda e: self._showModelLoadDialog(2), loadButton2)
175                 if self.extruderCount > 2:
176                         loadButton3 = wx.Button(self, -1, 'Load Triple')
177                         self.Bind(wx.EVT_BUTTON, lambda e: self._showModelLoadDialog(3), loadButton3)
178                 if self.extruderCount > 3:
179                         loadButton4 = wx.Button(self, -1, 'Load Quad')
180                         self.Bind(wx.EVT_BUTTON, lambda e: self._showModelLoadDialog(4), loadButton4)
181
182                 #Also bind double clicking the 3D preview to load an STL file.
183                 self.preview3d.glCanvas.Bind(wx.EVT_LEFT_DCLICK, lambda e: self._showModelLoadDialog(1), self.preview3d.glCanvas)
184
185                 #Main sizer, to position the preview window, buttons and tab control
186                 sizer = wx.GridBagSizer()
187                 self.SetSizer(sizer)
188                 sizer.Add(self.preview3d, (0,1), span=(1,2+self.extruderCount), flag=wx.EXPAND)
189                 sizer.AddGrowableCol(2 + self.extruderCount)
190                 sizer.AddGrowableRow(0)
191                 sizer.Add(loadButton, (1,1), flag=wx.RIGHT|wx.BOTTOM|wx.TOP, border=5)
192                 if self.extruderCount > 1:
193                         sizer.Add(loadButton2, (1,2), flag=wx.RIGHT|wx.BOTTOM|wx.TOP, border=5)
194                 if self.extruderCount > 2:
195                         sizer.Add(loadButton3, (1,3), flag=wx.RIGHT|wx.BOTTOM|wx.TOP, border=5)
196                 if self.extruderCount > 3:
197                         sizer.Add(loadButton4, (1,4), flag=wx.RIGHT|wx.BOTTOM|wx.TOP, border=5)
198                 sizer.Add(sliceButton, (1,1+self.extruderCount), flag=wx.RIGHT|wx.BOTTOM|wx.TOP, border=5)
199                 sizer.Add(printButton, (1,2+self.extruderCount), flag=wx.RIGHT|wx.BOTTOM|wx.TOP, border=5)
200                 self.sizer = sizer
201
202                 if len(self.filelist) > 0:
203                         self.preview3d.loadModelFiles(self.filelist)
204
205                 self.updateProfileToControls()
206
207                 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
208
209                 self.simpleSettingsPanel.Show(False)
210                 self.normalSettingsPanel.Show(False)
211                 self.updateSliceMode()
212
213                 # Set default window size & position
214                 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
215                 self.Centre()
216
217                 # Restore the window position, size & state from the preferences file
218                 try:
219                         if profile.getPreference('window_maximized') == 'True':
220                                 self.Maximize(True)
221                         else:
222                                 posx = int(profile.getPreference('window_pos_x'))
223                                 posy = int(profile.getPreference('window_pos_y'))
224                                 width = int(profile.getPreference('window_width'))
225                                 height = int(profile.getPreference('window_height'))
226                                 self.SetPosition((posx,posy))
227                                 self.SetSize((width,height))
228                 except:
229                         pass
230                         
231                 self.Show(True)
232
233         def updateSliceMode(self):
234                 isSimple = profile.getPreference('startMode') == 'Simple'
235
236                 self.normalSettingsPanel.Show(not isSimple)
237                 self.simpleSettingsPanel.Show(isSimple)
238
239                 self.GetSizer().Detach(self.simpleSettingsPanel)
240                 self.GetSizer().Detach(self.normalSettingsPanel)
241                 if isSimple:
242                         self.GetSizer().Add(self.simpleSettingsPanel, (0,0), span=(1,1), flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=6)
243                 else:
244                         self.GetSizer().Add(self.normalSettingsPanel, (0,0), span=(1,1), flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=6)
245
246                 for i in self.normalModeOnlyItems:
247                         i.Enable(not isSimple)
248                 self.switchToQuickprintMenuItem.Enable(not isSimple)
249                 self.switchToNormalMenuItem.Enable(isSimple)
250
251                 self.normalSettingsPanel.Layout()
252                 self.simpleSettingsPanel.Layout()
253                 self.GetSizer().Layout()
254                 self.Refresh()
255
256         def OnPreferences(self, e):
257                 prefDialog = preferencesDialog.preferencesDialog(self)
258                 prefDialog.Centre()
259                 prefDialog.Show(True)
260
261         def _showOpenDialog(self, title, wildcard = meshLoader.wildcardFilter()):
262                 dlg=wx.FileDialog(self, title, os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
263                 dlg.SetWildcard(wildcard)
264                 if dlg.ShowModal() == wx.ID_OK:
265                         filename = dlg.GetPath()
266                         dlg.Destroy()
267                         if not(os.path.exists(filename)):
268                                 return False
269                         profile.putPreference('lastFile', filename)
270                         return filename
271                 dlg.Destroy()
272                 return False
273
274         def _showModelLoadDialog(self, amount):
275                 filelist = []
276                 for i in xrange(0, amount):
277                         filelist.append(self._showOpenDialog("Open file to print"))
278                         if filelist[-1] == False:
279                                 return
280                 self._loadModels(filelist)
281
282         def _loadModels(self, filelist):
283                 self.filelist = filelist
284                 self.SetTitle(filelist[-1] + ' - Cura - ' + version.getVersion())
285                 profile.putPreference('lastFile', ';'.join(self.filelist))
286                 self.preview3d.loadModelFiles(self.filelist, True)
287                 self.preview3d.setViewMode("Normal")
288                 # Update the Model MRU
289                 for idx in xrange(0, len(self.filelist)):
290                         self.modelFileHistory.AddFileToHistory(self.filelist[idx])
291                 self.config.SetPath("/ModelMRU")
292                 self.modelFileHistory.Save(self.config)
293                 self.config.Flush()
294
295         def OnDropFiles(self, files):
296                 self._loadModels(files)
297
298         def OnLoadModel(self, e):
299                 self._showModelLoadDialog(1)
300
301         def OnLoadModel2(self, e):
302                 self._showModelLoadDialog(2)
303
304         def OnLoadModel3(self, e):
305                 self._showModelLoadDialog(3)
306
307         def OnLoadModel4(self, e):
308                 self._showModelLoadDialog(4)
309
310         def OnSlice(self, e):
311                 if len(self.filelist) < 1:
312                         wx.MessageBox('You need to load a file before you can prepare it.', 'Print error', wx.OK | wx.ICON_INFORMATION)
313                         return
314                 isSimple = profile.getPreference('startMode') == 'Simple'
315                 if isSimple:
316                         #save the current profile so we can put it back latter
317                         oldProfile = profile.getGlobalProfileString()
318                         self.simpleSettingsPanel.setupSlice()
319                 #Create a progress panel and add it to the window. The progress panel will start the Skein operation.
320                 spp = sliceProgessPanel.sliceProgessPanel(self, self, self.filelist)
321                 self.sizer.Add(spp, (len(self.progressPanelList)+2,0), span=(1, 3 + self.extruderCount), flag=wx.EXPAND)
322                 self.sizer.Layout()
323                 newSize = self.GetSize()
324                 newSize.IncBy(0, spp.GetSize().GetHeight())
325                 if newSize.GetWidth() < wx.GetDisplaySize()[0]:
326                         self.SetSize(newSize)
327                 self.progressPanelList.append(spp)
328                 if isSimple:
329                         profile.loadGlobalProfileFromString(oldProfile)
330
331         def OnPrint(self, e):
332                 if len(self.filelist) < 1:
333                         wx.MessageBox('You need to load a file and prepare it before you can print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
334                         return
335                 if not os.path.exists(sliceRun.getExportFilename(self.filelist[0])):
336                         wx.MessageBox('You need to prepare a print before you can run the actual print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
337                         return
338                 printWindow.printFile(sliceRun.getExportFilename(self.filelist[0]))
339
340         def OnModelMRU(self, e):
341                 fileNum = e.GetId() - self.ID_MRU_MODEL1
342                 path = self.modelFileHistory.GetHistoryFile(fileNum)
343                 # Update Model MRU
344                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
345                 self.config.SetPath("/ModelMRU")
346                 self.modelFileHistory.Save(self.config)
347                 self.config.Flush()
348                 # Load Model
349                 filelist = [ path ]
350                 self._loadModels(filelist)
351
352         def OnProfileMRU(self, e):
353                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
354                 path = self.profileFileHistory.GetHistoryFile(fileNum)
355                 # Update Profile MRU
356                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
357                 self.config.SetPath("/ProfileMRU")
358                 self.profileFileHistory.Save(self.config)
359                 self.config.Flush()
360                 # Load Profile  
361                 profile.loadGlobalProfile(path)
362                 self.updateProfileToControls()
363
364         def removeSliceProgress(self, spp):
365                 self.progressPanelList.remove(spp)
366                 newSize = self.GetSize()
367                 newSize.IncBy(0, -spp.GetSize().GetHeight())
368                 if newSize.GetWidth() < wx.GetDisplaySize()[0]:
369                         self.SetSize(newSize)
370                 spp.Show(False)
371                 self.sizer.Detach(spp)
372                 for spp in self.progressPanelList:
373                         self.sizer.Detach(spp)
374                 i = 2
375                 for spp in self.progressPanelList:
376                         self.sizer.Add(spp, (i,0), span=(1,3 + self.extruderCount), flag=wx.EXPAND)
377                         i += 1
378                 self.sizer.Layout()
379
380         def updateProfileToControls(self):
381                 self.preview3d.updateProfileToControls()
382                 self.normalSettingsPanel.updateProfileToControls()
383                 self.simpleSettingsPanel.updateProfileToControls()
384
385         def OnLoadProfile(self, e):
386                 dlg=wx.FileDialog(self, "Select profile file to load", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
387                 dlg.SetWildcard("ini files (*.ini)|*.ini")
388                 if dlg.ShowModal() == wx.ID_OK:
389                         profileFile = dlg.GetPath()
390                         profile.loadGlobalProfile(profileFile)
391                         self.updateProfileToControls()
392
393                         # Update the Profile MRU
394                         self.profileFileHistory.AddFileToHistory(profileFile)
395                         self.config.SetPath("/ProfileMRU")
396                         self.profileFileHistory.Save(self.config)
397                         self.config.Flush()                     
398                 dlg.Destroy()
399
400         def OnLoadProfileFromGcode(self, e):
401                 dlg=wx.FileDialog(self, "Select gcode file to load profile from", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
402                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
403                 if dlg.ShowModal() == wx.ID_OK:
404                         gcodeFile = dlg.GetPath()
405                         f = open(gcodeFile, 'r')
406                         hasProfile = False
407                         for line in f:
408                                 if line.startswith(';CURA_PROFILE_STRING:'):
409                                         profile.loadGlobalProfileFromString(line[line.find(':')+1:].strip())
410                                         hasProfile = True
411                         if hasProfile:
412                                 self.updateProfileToControls()
413                         else:
414                                 wx.MessageBox('No profile found in GCode file.\nThis feature only works with GCode files made by Cura 12.07 or newer.', 'Profile load error', wx.OK | wx.ICON_INFORMATION)
415                 dlg.Destroy()
416
417         def OnSaveProfile(self, e):
418                 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
419                 dlg.SetWildcard("ini files (*.ini)|*.ini")
420                 if dlg.ShowModal() == wx.ID_OK:
421                         profileFile = dlg.GetPath()
422                         profile.saveGlobalProfile(profileFile)
423                 dlg.Destroy()
424
425         def OnResetProfile(self, e):
426                 dlg = wx.MessageDialog(self, 'This will reset all profile settings to defaults.\nUnless you have saved your current profile, all settings will be lost!\nDo you really want to reset?', 'Profile reset', wx.YES_NO | wx.ICON_QUESTION)
427                 result = dlg.ShowModal() == wx.ID_YES
428                 dlg.Destroy()
429                 if result:
430                         profile.resetGlobalProfile()
431                         self.updateProfileToControls()
432
433         def OnBatchRun(self, e):
434                 br = batchRun.batchRunWindow(self)
435                 br.Centre()
436                 br.Show(True)
437
438         def OnSimpleSwitch(self, e):
439                 profile.putPreference('startMode', 'Simple')
440                 self.updateSliceMode()
441
442         def OnNormalSwitch(self, e):
443                 profile.putPreference('startMode', 'Normal')
444                 self.updateSliceMode()
445
446         def OnDefaultMarlinFirmware(self, e):
447                 firmwareInstall.InstallFirmware()
448
449         def OnCustomFirmware(self, e):
450                 if profile.getPreference('machine_type') == 'ultimaker':
451                         wx.MessageBox('Warning: Installing a custom firmware does not garantee that you machine will function correctly, and could damage your machine.', 'Firmware update', wx.OK | wx.ICON_EXCLAMATION)
452                 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
453                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
454                 if dlg.ShowModal() == wx.ID_OK:
455                         filename = dlg.GetPath()
456                         if not(os.path.exists(filename)):
457                                 return
458                         #For some reason my Ubuntu 10.10 crashes here.
459                         firmwareInstall.InstallFirmware(filename)
460
461         def OnFirstRunWizard(self, e):
462                 configWizard.configWizard()
463                 self.updateProfileToControls()
464
465         def OnBedLevelWizard(self, e):
466                 configWizard.bedLevelWizard()
467
468         def OnExpertOpen(self, e):
469                 ecw = expertConfig.expertConfigWindow()
470                 ecw.Centre()
471                 ecw.Show(True)
472
473         def OnProjectPlanner(self, e):
474                 pp = projectPlanner.projectPlanner()
475                 pp.Centre()
476                 pp.Show(True)
477
478         def OnMinecraftImport(self, e):
479                 mi = minecraftImport.minecraftImportWindow(self)
480                 mi.Centre()
481                 mi.Show(True)
482
483         def OnSVGSlicerOpen(self, e):
484                 svgSlicer = flatSlicerWindow.flatSlicerWindow()
485                 svgSlicer.Centre()
486                 svgSlicer.Show(True)
487
488         def OnCheckForUpdate(self, e):
489                 newVersion = version.checkForNewerVersion()
490                 if newVersion is not None:
491                         if wx.MessageBox('A new version of Cura is available, would you like to download?', 'New version available', wx.YES_NO | wx.ICON_INFORMATION) == wx.YES:
492                                 webbrowser.open(newVersion)
493                 else:
494                         wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
495
496         def OnClose(self, e):
497                 profile.saveGlobalProfile(profile.getDefaultProfilePath())
498
499                 # Save the window position, size & state from the preferences file
500                 profile.putPreference('window_maximized', self.IsMaximized())
501                 if not self.IsMaximized():
502                         (posx, posy) = self.GetPosition()
503                         profile.putPreference('window_pos_x', posx)
504                         profile.putPreference('window_pos_y', posy)
505                         (width, height) = self.GetSize()
506                         profile.putPreference('window_width', width)
507                         profile.putPreference('window_height', height)
508                         
509                 self.Destroy()
510
511         def OnQuit(self, e):
512                 self.Close()
513
514 class normalSettingsPanel(configBase.configPanelBase):
515         "Main user interface window"
516         def __init__(self, parent):
517                 super(normalSettingsPanel, self).__init__(parent)
518
519                 #Main tabs
520                 nb = wx.Notebook(self)
521                 self.SetSizer(wx.BoxSizer(wx.VERTICAL))
522                 self.GetSizer().Add(nb, 1)
523
524                 (left, right) = self.CreateConfigTab(nb, 'Print config')
525
526                 configBase.TitleRow(left, "Quality")
527                 c = configBase.SettingRow(left, "Layer height (mm)", 'layer_height', '0.2', 'Layer height in millimeters.\n0.2 is a good value for quick prints.\n0.1 gives high quality prints.')
528                 validators.validFloat(c, 0.0001)
529                 validators.warningAbove(c, lambda : (float(profile.getProfileSetting('nozzle_size')) * 80.0 / 100.0), "Thicker layers then %.2fmm (80%% nozzle size) usually give bad results and are not recommended.")
530                 c = configBase.SettingRow(left, "Wall thickness (mm)", 'wall_thickness', '0.8', 'Thickness of the walls.\nThis is used in combination with the nozzle size to define the number\nof perimeter lines and the thickness of those perimeter lines.')
531                 validators.validFloat(c, 0.0001)
532                 validators.wallThicknessValidator(c)
533                 c = configBase.SettingRow(left, "Enable retraction", 'retraction_enable', False, 'Retract the filament when the nozzle is moving over a none-printed area. Details about the retraction can be configured in the advanced tab.')
534
535                 configBase.TitleRow(left, "Fill")
536                 c = configBase.SettingRow(left, "Bottom/Top thickness (mm)", 'solid_layer_thickness', '0.6', 'This controls the thickness of the bottom and top layers, the amount of solid layers put down is calculated by the layer thickness and this value.\nHaving this value a multiply of the layer thickness makes sense. And keep it near your wall thickness to make an evenly strong part.')
537                 validators.validFloat(c, 0.0)
538                 c = configBase.SettingRow(left, "Fill Density (%)", 'fill_density', '20', 'This controls how densily filled the insides of your print will be. For a solid part use 100%, for an empty part use 0%. A value around 20% is usually enough')
539                 validators.validFloat(c, 0.0, 100.0)
540
541                 configBase.TitleRow(right, "Speed && Temperature")
542                 c = configBase.SettingRow(right, "Print speed (mm/s)", 'print_speed', '50', 'Speed at which printing happens. A well adjusted Ultimaker can reach 150mm/s, but for good quality prints you want to print slower. Printing speed depends on a lot of factors. So you will be experimenting with optimal settings for this.')
543                 validators.validFloat(c, 1.0)
544                 validators.warningAbove(c, 150.0, "It is highly unlikely that your machine can achieve a printing speed above 150mm/s")
545                 validators.printSpeedValidator(c)
546
547                 #configBase.TitleRow(right, "Temperature")
548                 c = configBase.SettingRow(right, "Printing temperature", 'print_temperature', '0', 'Temperature used for printing. Set at 0 to pre-heat yourself')
549                 validators.validFloat(c, 0.0, 340.0)
550                 validators.warningAbove(c, 260.0, "Temperatures above 260C could damage your machine, be careful!")
551                 if profile.getPreference('has_heated_bed') == 'True':
552                         c = configBase.SettingRow(right, "Bed temperature", 'print_bed_temperature', '0', 'Temperature used for the heated printer bed. Set at 0 to pre-heat yourself')
553                         validators.validFloat(c, 0.0, 340.0)
554
555                 configBase.TitleRow(right, "Support structure")
556                 c = configBase.SettingRow(right, "Support type", 'support', ['None', 'Exterior Only', 'Everywhere'], 'Type of support structure build.\n"Exterior only" is the most commonly used support setting.\n\nNone does not do any support.\nExterior only only creates support where the support structure will touch the build platform.\nEverywhere creates support even on the insides of the model.')
557                 c = configBase.SettingRow(right, "Add raft", 'enable_raft', False, 'A raft is a few layers of lines below the bottom of the object. It prevents warping. Full raft settings can be found in the expert settings.\nFor PLA this is usually not required. But if you print with ABS it is almost required.')
558                 if int(profile.getPreference('extruder_amount')) > 1:
559                         c = configBase.SettingRow(right, "Support dual extrusion", 'support_dual_extrusion', False, 'Print the support material with the 2nd extruder in a dual extrusion setup. The primary extruder will be used for normal material, while the second extruder is used to print support material.')
560
561                 configBase.TitleRow(right, "Filament")
562                 c = configBase.SettingRow(right, "Diameter (mm)", 'filament_diameter', '2.89', 'Diameter of your filament, as accurately as possible.\nIf you cannot measure this value you will have to callibrate it, a higher number means less extrusion, a smaller number generates more extrusion.')
563                 validators.validFloat(c, 1.0)
564                 validators.warningAbove(c, 3.5, "Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm.")
565                 c = configBase.SettingRow(right, "Packing Density", 'filament_density', '1.00', 'Packing density of your filament. This should be 1.00 for PLA and 0.85 for ABS')
566                 validators.validFloat(c, 0.5, 1.5)
567
568                 (left, right) = self.CreateConfigTab(nb, 'Advanced config')
569
570                 configBase.TitleRow(left, "Machine size")
571                 c = configBase.SettingRow(left, "Nozzle size (mm)", 'nozzle_size', '0.4', 'The nozzle size is very important, this is used to calculate the line width of the infill, and used to calculate the amount of outside wall lines and thickness for the wall thickness you entered in the print settings.')
572                 validators.validFloat(c, 0.1, 10.0)
573
574                 configBase.TitleRow(left, "Skirt")
575                 c = configBase.SettingRow(left, "Line count", 'skirt_line_count', '1', 'The skirt is a line drawn around the object at the first layer. This helps to prime your extruder, and to see if the object fits on your platform.\nSetting this to 0 will disable the skirt. Multiple skirt lines can help priming your extruder better for small objects.')
576                 validators.validInt(c, 0, 10)
577                 c = configBase.SettingRow(left, "Start distance (mm)", 'skirt_gap', '6.0', 'The distance between the skirt and the first layer.\nThis is the minimal distance, multiple skirt lines will be put outwards from this distance.')
578                 validators.validFloat(c, 0.0)
579
580                 configBase.TitleRow(left, "Retraction")
581                 c = configBase.SettingRow(left, "Minimum travel (mm)", 'retraction_min_travel', '5.0', 'Minimum amount of travel needed for a retraction to happen at all. To make sure you do not get a lot of retractions in a small area')
582                 validators.validFloat(c, 0.0)
583                 c = configBase.SettingRow(left, "Speed (mm/s)", 'retraction_speed', '40.0', 'Speed at which the filament is retracted, a higher retraction speed works better. But a very high retraction speed can lead to filament grinding.')
584                 validators.validFloat(c, 0.1)
585                 c = configBase.SettingRow(left, "Distance (mm)", 'retraction_amount', '0.0', 'Amount of retraction, set at 0 for no retraction at all. A value of 2.0mm seems to generate good results.')
586                 validators.validFloat(c, 0.0)
587                 c = configBase.SettingRow(left, "Extra length on start (mm)", 'retraction_extra', '0.0', 'Extra extrusion amount when restarting after a retraction, to better "Prime" your extruder after retraction.')
588                 validators.validFloat(c, 0.0)
589
590                 configBase.TitleRow(right, "Speed")
591                 c = configBase.SettingRow(right, "Travel speed (mm/s)", 'travel_speed', '150', 'Speed at which travel moves are done, a high quality build Ultimaker can reach speeds of 250mm/s. But some machines might miss steps then.')
592                 validators.validFloat(c, 1.0)
593                 validators.warningAbove(c, 300.0, "It is highly unlikely that your machine can achieve a travel speed above 300mm/s")
594                 c = configBase.SettingRow(right, "Max Z speed (mm/s)", 'max_z_speed', '1.0', 'Speed at which Z moves are done. When you Z axis is properly lubercated you can increase this for less Z blob.')
595                 validators.validFloat(c, 0.5)
596                 c = configBase.SettingRow(right, "Bottom layer speed (mm/s)", 'bottom_layer_speed', '25', 'Print speed for the bottom layer, you want to print the first layer slower so it sticks better to the printer bed.')
597                 validators.validFloat(c, 0.0)
598
599                 configBase.TitleRow(right, "Cool")
600                 c = configBase.SettingRow(right, "Minimal layer time (sec)", 'cool_min_layer_time', '10', 'Minimum time spend in a layer, gives the layer time to cool down before the next layer is put on top. If the layer will be placed down too fast the printer will slow down to make sure it has spend atleast this amount of seconds printing this layer.')
601                 validators.validFloat(c, 0.0)
602                 c = configBase.SettingRow(right, "Enable cooling fan", 'fan_enabled', True, 'Enable the cooling fan during the print. The extra cooling from the cooling fan is essensial during faster prints.')
603
604                 configBase.TitleRow(right, "Quality")
605                 c = configBase.SettingRow(right, "Initial layer thickness (mm)", 'bottom_thickness', '0.0', 'Layer thickness of the bottom layer. A thicker bottom layer makes sticking to the bed easier. Set to 0.0 to have the bottom layer thickness the same as the other layers.')
606                 validators.validFloat(c, 0.0)
607                 validators.warningAbove(c, lambda : (float(profile.getProfileSetting('nozzle_size')) * 3.0 / 4.0), "A bottom layer of more then %.2fmm (3/4 nozzle size) usually give bad results and is not recommended.")
608                 c = configBase.SettingRow(right, "Duplicate outlines", 'enable_skin', False, 'Skin prints the outer lines of the prints twice, each time with half the thickness. This gives the illusion of a higher print quality.')
609
610                 #Plugin page
611                 self.pluginPanel = pluginPanel.pluginPanel(nb)
612                 if len(self.pluginPanel.pluginList) > 0:
613                         nb.AddPage(self.pluginPanel, "Plugins")
614                 else:
615                         self.pluginPanel.Show(False)
616
617                 #Alteration page
618                 self.alterationPanel = alterationPanel.alterationPanel(nb)
619                 nb.AddPage(self.alterationPanel, "Start/End-GCode")
620
621         def updateProfileToControls(self):
622                 super(normalSettingsPanel, self).updateProfileToControls()
623                 self.alterationPanel.updateProfileToControls()
624                 self.pluginPanel.updateProfileToControls()