chiark / gitweb /
Fix to add initial model file(s) to MRU list
[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                         # Update the Model MRU
206                         for idx in xrange(0, len(self.filelist)):
207                                 self.addToModelMRU(self.filelist[idx])
208
209                 self.updateProfileToControls()
210
211                 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
212
213                 self.simpleSettingsPanel.Show(False)
214                 self.normalSettingsPanel.Show(False)
215                 self.updateSliceMode()
216
217                 # Set default window size & position
218                 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
219                 self.Centre()
220
221                 # Restore the window position, size & state from the preferences file
222                 try:
223                         if profile.getPreference('window_maximized') == 'True':
224                                 self.Maximize(True)
225                         else:
226                                 posx = int(profile.getPreference('window_pos_x'))
227                                 posy = int(profile.getPreference('window_pos_y'))
228                                 width = int(profile.getPreference('window_width'))
229                                 height = int(profile.getPreference('window_height'))
230                                 self.SetPosition((posx,posy))
231                                 self.SetSize((width,height))
232                 except:
233                         pass
234                         
235                 self.Show(True)
236
237         def updateSliceMode(self):
238                 isSimple = profile.getPreference('startMode') == 'Simple'
239
240                 self.normalSettingsPanel.Show(not isSimple)
241                 self.simpleSettingsPanel.Show(isSimple)
242
243                 self.GetSizer().Detach(self.simpleSettingsPanel)
244                 self.GetSizer().Detach(self.normalSettingsPanel)
245                 if isSimple:
246                         self.GetSizer().Add(self.simpleSettingsPanel, (0,0), span=(1,1), flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=6)
247                 else:
248                         self.GetSizer().Add(self.normalSettingsPanel, (0,0), span=(1,1), flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=6)
249
250                 for i in self.normalModeOnlyItems:
251                         i.Enable(not isSimple)
252                 self.switchToQuickprintMenuItem.Enable(not isSimple)
253                 self.switchToNormalMenuItem.Enable(isSimple)
254
255                 self.normalSettingsPanel.Layout()
256                 self.simpleSettingsPanel.Layout()
257                 self.GetSizer().Layout()
258                 self.Refresh()
259
260         def OnPreferences(self, e):
261                 prefDialog = preferencesDialog.preferencesDialog(self)
262                 prefDialog.Centre()
263                 prefDialog.Show(True)
264
265         def _showOpenDialog(self, title, wildcard = meshLoader.wildcardFilter()):
266                 dlg=wx.FileDialog(self, title, os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
267                 dlg.SetWildcard(wildcard)
268                 if dlg.ShowModal() == wx.ID_OK:
269                         filename = dlg.GetPath()
270                         dlg.Destroy()
271                         if not(os.path.exists(filename)):
272                                 return False
273                         profile.putPreference('lastFile', filename)
274                         return filename
275                 dlg.Destroy()
276                 return False
277
278         def _showModelLoadDialog(self, amount):
279                 filelist = []
280                 for i in xrange(0, amount):
281                         filelist.append(self._showOpenDialog("Open file to print"))
282                         if filelist[-1] == False:
283                                 return
284                 self._loadModels(filelist)
285
286         def _loadModels(self, filelist):
287                 self.filelist = filelist
288                 self.SetTitle(filelist[-1] + ' - Cura - ' + version.getVersion())
289                 profile.putPreference('lastFile', ';'.join(self.filelist))
290                 self.preview3d.loadModelFiles(self.filelist, True)
291                 self.preview3d.setViewMode("Normal")
292                 
293                 # Update the Model MRU
294                 for idx in xrange(0, len(self.filelist)):
295                         self.addToModelMRU(self.filelist[idx])
296
297         def OnDropFiles(self, files):
298                 self._loadModels(files)
299
300         def OnLoadModel(self, e):
301                 self._showModelLoadDialog(1)
302
303         def OnLoadModel2(self, e):
304                 self._showModelLoadDialog(2)
305
306         def OnLoadModel3(self, e):
307                 self._showModelLoadDialog(3)
308
309         def OnLoadModel4(self, e):
310                 self._showModelLoadDialog(4)
311
312         def OnSlice(self, e):
313                 if len(self.filelist) < 1:
314                         wx.MessageBox('You need to load a file before you can prepare it.', 'Print error', wx.OK | wx.ICON_INFORMATION)
315                         return
316                 isSimple = profile.getPreference('startMode') == 'Simple'
317                 if isSimple:
318                         #save the current profile so we can put it back latter
319                         oldProfile = profile.getGlobalProfileString()
320                         self.simpleSettingsPanel.setupSlice()
321                 #Create a progress panel and add it to the window. The progress panel will start the Skein operation.
322                 spp = sliceProgessPanel.sliceProgessPanel(self, self, self.filelist)
323                 self.sizer.Add(spp, (len(self.progressPanelList)+2,0), span=(1, 3 + self.extruderCount), flag=wx.EXPAND)
324                 self.sizer.Layout()
325                 newSize = self.GetSize()
326                 newSize.IncBy(0, spp.GetSize().GetHeight())
327                 if newSize.GetWidth() < wx.GetDisplaySize()[0]:
328                         self.SetSize(newSize)
329                 self.progressPanelList.append(spp)
330                 if isSimple:
331                         profile.loadGlobalProfileFromString(oldProfile)
332
333         def OnPrint(self, e):
334                 if len(self.filelist) < 1:
335                         wx.MessageBox('You need to load a file and prepare it before you can print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
336                         return
337                 if not os.path.exists(sliceRun.getExportFilename(self.filelist[0])):
338                         wx.MessageBox('You need to prepare a print before you can run the actual print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
339                         return
340                 printWindow.printFile(sliceRun.getExportFilename(self.filelist[0]))
341
342         def OnModelMRU(self, e):
343                 fileNum = e.GetId() - self.ID_MRU_MODEL1
344                 path = self.modelFileHistory.GetHistoryFile(fileNum)
345                 # Update Model MRU
346                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
347                 self.config.SetPath("/ModelMRU")
348                 self.modelFileHistory.Save(self.config)
349                 self.config.Flush()
350                 # Load Model
351                 filelist = [ path ]
352                 self._loadModels(filelist)
353
354         def addToModelMRU(self, file):
355                 self.modelFileHistory.AddFileToHistory(file)
356                 self.config.SetPath("/ModelMRU")
357                 self.modelFileHistory.Save(self.config)
358                 self.config.Flush()
359         
360         def OnProfileMRU(self, e):
361                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
362                 path = self.profileFileHistory.GetHistoryFile(fileNum)
363                 # Update Profile MRU
364                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
365                 self.config.SetPath("/ProfileMRU")
366                 self.profileFileHistory.Save(self.config)
367                 self.config.Flush()
368                 # Load Profile  
369                 profile.loadGlobalProfile(path)
370                 self.updateProfileToControls()
371
372         def addToProfileMRU(self, file):
373                 self.profileFileHistory.AddFileToHistory(file)
374                 self.config.SetPath("/ProfileMRU")
375                 self.profileFileHistory.Save(self.config)
376                 self.config.Flush()                     
377
378         def removeSliceProgress(self, spp):
379                 self.progressPanelList.remove(spp)
380                 newSize = self.GetSize()
381                 newSize.IncBy(0, -spp.GetSize().GetHeight())
382                 if newSize.GetWidth() < wx.GetDisplaySize()[0]:
383                         self.SetSize(newSize)
384                 spp.Show(False)
385                 self.sizer.Detach(spp)
386                 for spp in self.progressPanelList:
387                         self.sizer.Detach(spp)
388                 i = 2
389                 for spp in self.progressPanelList:
390                         self.sizer.Add(spp, (i,0), span=(1,3 + self.extruderCount), flag=wx.EXPAND)
391                         i += 1
392                 self.sizer.Layout()
393
394         def updateProfileToControls(self):
395                 self.preview3d.updateProfileToControls()
396                 self.normalSettingsPanel.updateProfileToControls()
397                 self.simpleSettingsPanel.updateProfileToControls()
398
399         def OnLoadProfile(self, e):
400                 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)
401                 dlg.SetWildcard("ini files (*.ini)|*.ini")
402                 if dlg.ShowModal() == wx.ID_OK:
403                         profileFile = dlg.GetPath()
404                         profile.loadGlobalProfile(profileFile)
405                         self.updateProfileToControls()
406
407                         # Update the Profile MRU
408                         self.addToProfileMRU(profileFile)
409                 dlg.Destroy()
410
411         def OnLoadProfileFromGcode(self, e):
412                 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)
413                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
414                 if dlg.ShowModal() == wx.ID_OK:
415                         gcodeFile = dlg.GetPath()
416                         f = open(gcodeFile, 'r')
417                         hasProfile = False
418                         for line in f:
419                                 if line.startswith(';CURA_PROFILE_STRING:'):
420                                         profile.loadGlobalProfileFromString(line[line.find(':')+1:].strip())
421                                         hasProfile = True
422                         if hasProfile:
423                                 self.updateProfileToControls()
424                         else:
425                                 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)
426                 dlg.Destroy()
427
428         def OnSaveProfile(self, e):
429                 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
430                 dlg.SetWildcard("ini files (*.ini)|*.ini")
431                 if dlg.ShowModal() == wx.ID_OK:
432                         profileFile = dlg.GetPath()
433                         profile.saveGlobalProfile(profileFile)
434                 dlg.Destroy()
435
436         def OnResetProfile(self, e):
437                 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)
438                 result = dlg.ShowModal() == wx.ID_YES
439                 dlg.Destroy()
440                 if result:
441                         profile.resetGlobalProfile()
442                         self.updateProfileToControls()
443
444         def OnBatchRun(self, e):
445                 br = batchRun.batchRunWindow(self)
446                 br.Centre()
447                 br.Show(True)
448
449         def OnSimpleSwitch(self, e):
450                 profile.putPreference('startMode', 'Simple')
451                 self.updateSliceMode()
452
453         def OnNormalSwitch(self, e):
454                 profile.putPreference('startMode', 'Normal')
455                 self.updateSliceMode()
456
457         def OnDefaultMarlinFirmware(self, e):
458                 firmwareInstall.InstallFirmware()
459
460         def OnCustomFirmware(self, e):
461                 if profile.getPreference('machine_type') == 'ultimaker':
462                         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)
463                 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
464                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
465                 if dlg.ShowModal() == wx.ID_OK:
466                         filename = dlg.GetPath()
467                         if not(os.path.exists(filename)):
468                                 return
469                         #For some reason my Ubuntu 10.10 crashes here.
470                         firmwareInstall.InstallFirmware(filename)
471
472         def OnFirstRunWizard(self, e):
473                 configWizard.configWizard()
474                 self.updateProfileToControls()
475
476         def OnBedLevelWizard(self, e):
477                 configWizard.bedLevelWizard()
478
479         def OnExpertOpen(self, e):
480                 ecw = expertConfig.expertConfigWindow()
481                 ecw.Centre()
482                 ecw.Show(True)
483
484         def OnProjectPlanner(self, e):
485                 pp = projectPlanner.projectPlanner()
486                 pp.Centre()
487                 pp.Show(True)
488
489         def OnMinecraftImport(self, e):
490                 mi = minecraftImport.minecraftImportWindow(self)
491                 mi.Centre()
492                 mi.Show(True)
493
494         def OnSVGSlicerOpen(self, e):
495                 svgSlicer = flatSlicerWindow.flatSlicerWindow()
496                 svgSlicer.Centre()
497                 svgSlicer.Show(True)
498
499         def OnCheckForUpdate(self, e):
500                 newVersion = version.checkForNewerVersion()
501                 if newVersion is not None:
502                         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:
503                                 webbrowser.open(newVersion)
504                 else:
505                         wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
506
507         def OnClose(self, e):
508                 profile.saveGlobalProfile(profile.getDefaultProfilePath())
509
510                 # Save the window position, size & state from the preferences file
511                 profile.putPreference('window_maximized', self.IsMaximized())
512                 if not self.IsMaximized():
513                         (posx, posy) = self.GetPosition()
514                         profile.putPreference('window_pos_x', posx)
515                         profile.putPreference('window_pos_y', posy)
516                         (width, height) = self.GetSize()
517                         profile.putPreference('window_width', width)
518                         profile.putPreference('window_height', height)
519                         
520                 self.Destroy()
521
522         def OnQuit(self, e):
523                 self.Close()
524
525 class normalSettingsPanel(configBase.configPanelBase):
526         "Main user interface window"
527         def __init__(self, parent):
528                 super(normalSettingsPanel, self).__init__(parent)
529
530                 #Main tabs
531                 nb = wx.Notebook(self)
532                 self.SetSizer(wx.BoxSizer(wx.VERTICAL))
533                 self.GetSizer().Add(nb, 1)
534
535                 (left, right) = self.CreateConfigTab(nb, 'Print config')
536
537                 configBase.TitleRow(left, "Quality")
538                 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.')
539                 validators.validFloat(c, 0.0001)
540                 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.")
541                 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.')
542                 validators.validFloat(c, 0.0001)
543                 validators.wallThicknessValidator(c)
544                 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.')
545
546                 configBase.TitleRow(left, "Fill")
547                 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.')
548                 validators.validFloat(c, 0.0)
549                 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')
550                 validators.validFloat(c, 0.0, 100.0)
551
552                 configBase.TitleRow(right, "Speed && Temperature")
553                 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.')
554                 validators.validFloat(c, 1.0)
555                 validators.warningAbove(c, 150.0, "It is highly unlikely that your machine can achieve a printing speed above 150mm/s")
556                 validators.printSpeedValidator(c)
557
558                 #configBase.TitleRow(right, "Temperature")
559                 c = configBase.SettingRow(right, "Printing temperature", 'print_temperature', '0', 'Temperature used for printing. Set at 0 to pre-heat yourself')
560                 validators.validFloat(c, 0.0, 340.0)
561                 validators.warningAbove(c, 260.0, "Temperatures above 260C could damage your machine, be careful!")
562                 if profile.getPreference('has_heated_bed') == 'True':
563                         c = configBase.SettingRow(right, "Bed temperature", 'print_bed_temperature', '0', 'Temperature used for the heated printer bed. Set at 0 to pre-heat yourself')
564                         validators.validFloat(c, 0.0, 340.0)
565
566                 configBase.TitleRow(right, "Support structure")
567                 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.')
568                 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.')
569                 if int(profile.getPreference('extruder_amount')) > 1:
570                         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.')
571
572                 configBase.TitleRow(right, "Filament")
573                 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.')
574                 validators.validFloat(c, 1.0)
575                 validators.warningAbove(c, 3.5, "Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm.")
576                 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')
577                 validators.validFloat(c, 0.5, 1.5)
578
579                 (left, right) = self.CreateConfigTab(nb, 'Advanced config')
580
581                 configBase.TitleRow(left, "Machine size")
582                 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.')
583                 validators.validFloat(c, 0.1, 10.0)
584
585                 configBase.TitleRow(left, "Skirt")
586                 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.')
587                 validators.validInt(c, 0, 10)
588                 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.')
589                 validators.validFloat(c, 0.0)
590
591                 configBase.TitleRow(left, "Retraction")
592                 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')
593                 validators.validFloat(c, 0.0)
594                 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.')
595                 validators.validFloat(c, 0.1)
596                 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.')
597                 validators.validFloat(c, 0.0)
598                 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.')
599                 validators.validFloat(c, 0.0)
600
601                 configBase.TitleRow(right, "Speed")
602                 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.')
603                 validators.validFloat(c, 1.0)
604                 validators.warningAbove(c, 300.0, "It is highly unlikely that your machine can achieve a travel speed above 300mm/s")
605                 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.')
606                 validators.validFloat(c, 0.5)
607                 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.')
608                 validators.validFloat(c, 0.0)
609
610                 configBase.TitleRow(right, "Cool")
611                 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.')
612                 validators.validFloat(c, 0.0)
613                 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.')
614
615                 configBase.TitleRow(right, "Quality")
616                 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.')
617                 validators.validFloat(c, 0.0)
618                 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.")
619                 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.')
620
621                 #Plugin page
622                 self.pluginPanel = pluginPanel.pluginPanel(nb)
623                 if len(self.pluginPanel.pluginList) > 0:
624                         nb.AddPage(self.pluginPanel, "Plugins")
625                 else:
626                         self.pluginPanel.Show(False)
627
628                 #Alteration page
629                 self.alterationPanel = alterationPanel.alterationPanel(nb)
630                 nb.AddPage(self.alterationPanel, "Start/End-GCode")
631
632         def updateProfileToControls(self):
633                 super(normalSettingsPanel, self).updateProfileToControls()
634                 self.alterationPanel.updateProfileToControls()
635                 self.pluginPanel.updateProfileToControls()