chiark / gitweb /
Merge fix.
[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 sliceProgressPanel
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 Steam Engine BETA - ' + 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, 'Project planner...')
113                 self.Bind(wx.EVT_MENU, self.OnProjectPlanner, i)
114                 self.normalModeOnlyItems.append(i)
115                 i = toolsMenu.Append(-1, 'Batch run...')
116                 self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
117                 self.normalModeOnlyItems.append(i)
118                 #               i = toolsMenu.Append(-1, 'Open SVG (2D) slicer...')
119                 #               self.Bind(wx.EVT_MENU, self.OnSVGSlicerOpen, i)
120                 if minecraftImport.hasMinecraft():
121                         i = toolsMenu.Append(-1, 'Minecraft import...')
122                         self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
123                 self.menubar.Append(toolsMenu, 'Tools')
124
125                 expertMenu = wx.Menu()
126                 i = expertMenu.Append(-1, 'Open expert settings...')
127                 self.normalModeOnlyItems.append(i)
128                 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
129                 expertMenu.AppendSeparator()
130                 if firmwareInstall.getDefaultFirmware() is not None:
131                         i = expertMenu.Append(-1, 'Install default Marlin firmware')
132                         self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
133                 i = expertMenu.Append(-1, 'Install custom firmware')
134                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
135                 expertMenu.AppendSeparator()
136                 i = expertMenu.Append(-1, 'Run first run wizard...')
137                 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
138                 i = expertMenu.Append(-1, 'Run bed leveling wizard...')
139                 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
140                 self.menubar.Append(expertMenu, 'Expert')
141
142                 helpMenu = wx.Menu()
143                 i = helpMenu.Append(-1, 'Online documentation...')
144                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
145                 i = helpMenu.Append(-1, 'Report a problem...')
146                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
147                 i = helpMenu.Append(-1, 'Check for update...')
148                 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
149                 self.menubar.Append(helpMenu, 'Help')
150                 self.SetMenuBar(self.menubar)
151
152                 if profile.getPreference('lastFile') != '':
153                         self.filelist = profile.getPreference('lastFile').split(';')
154                         self.SetTitle('Cura - %s - %s' % (version.getVersion(), self.filelist[-1]))
155                 else:
156                         self.filelist = []
157                 self.progressPanelList = []
158
159                 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
160                 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
161                 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
162                 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
163
164                 ##Gui components##
165                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane)
166                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane)
167
168                 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
169                 self.leftSizer.Add(self.simpleSettingsPanel)
170                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
171                 self.leftPane.SetSizer(self.leftSizer)
172                 
173                 #Preview window
174                 self.preview3d = preview3d.previewPanel(self.rightPane)
175
176                 #Also bind double clicking the 3D preview to load an STL file.
177                 #self.preview3d.glCanvas.Bind(wx.EVT_LEFT_DCLICK, lambda e: self._showModelLoadDialog(1), self.preview3d.glCanvas)
178
179                 #Main sizer, to position the preview window, buttons and tab control
180                 sizer = wx.BoxSizer()
181                 self.rightPane.SetSizer(sizer)
182                 sizer.Add(self.preview3d, 1, flag=wx.EXPAND)
183
184                 # Main window sizer
185                 sizer = wx.BoxSizer(wx.VERTICAL)
186                 self.SetSizer(sizer)
187                 sizer.Add(self.splitter, 1, wx.EXPAND)
188                 sizer.Layout()
189                 self.sizer = sizer
190
191                 if len(self.filelist) > 0:
192                         self.preview3d.loadModelFiles(self.filelist)
193
194                         # Update the Model MRU
195                         for idx in xrange(0, len(self.filelist)):
196                                 self.addToModelMRU(self.filelist[idx])
197
198                 self.updateProfileToControls()
199
200                 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
201
202                 self.simpleSettingsPanel.Show(False)
203                 self.normalSettingsPanel.Show(False)
204
205                 # Set default window size & position
206                 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
207                 self.Centre()
208
209                 # Restore the window position, size & state from the preferences file
210                 self.normalSashPos = 320
211                 try:
212                         if profile.getPreference('window_maximized') == 'True':
213                                 self.Maximize(True)
214                         else:
215                                 posx = int(profile.getPreference('window_pos_x'))
216                                 posy = int(profile.getPreference('window_pos_y'))
217                                 width = int(profile.getPreference('window_width'))
218                                 height = int(profile.getPreference('window_height'))
219                         if posx > 0 or posy > 0:
220                                 self.SetPosition((posx,posy))
221                         if width > 0 and height > 0:
222                                 self.SetSize((width,height))
223                                 
224                         self.normalSashPos = int(profile.getPreference('window_normal_sash'))
225                 except:
226                         self.Maximize(True)
227
228                 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
229
230                 self.updateSliceMode()
231
232                 self.Show(True)
233
234         def updateSliceMode(self):
235                 isSimple = profile.getPreference('startMode') == 'Simple'
236
237                 self.normalSettingsPanel.Show(not isSimple)
238                 self.simpleSettingsPanel.Show(isSimple)
239                 self.leftPane.Layout()
240
241                 for i in self.normalModeOnlyItems:
242                         i.Enable(not isSimple)
243                 self.switchToQuickprintMenuItem.Enable(not isSimple)
244                 self.switchToNormalMenuItem.Enable(isSimple)
245
246                 # Set splitter sash position & size
247                 if isSimple:
248                         # Save normal mode sash
249                         self.normalSashPos = self.splitter.GetSashPosition()
250                         
251                         # Change location of sash to width of quick mode pane 
252                         (width, height) = self.simpleSettingsPanel.GetSizer().GetSize() 
253                         self.splitter.SetSashPosition(width, True)
254                         
255                         # Disable sash
256                         self.splitter.SetSashSize(0)
257                 else:
258                         self.splitter.SetSashPosition(self.normalSashPos, True)
259
260                         # Enabled sash
261                         self.splitter.SetSashSize(4)
262                                                                 
263         def OnPreferences(self, e):
264                 prefDialog = preferencesDialog.preferencesDialog(self)
265                 prefDialog.Centre()
266                 prefDialog.Show(True)
267
268         def _showOpenDialog(self, title, wildcard = meshLoader.wildcardFilter()):
269                 dlg=wx.FileDialog(self, title, os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
270                 dlg.SetWildcard(wildcard)
271                 if dlg.ShowModal() == wx.ID_OK:
272                         filename = dlg.GetPath()
273                         dlg.Destroy()
274                         if not(os.path.exists(filename)):
275                                 return False
276                         profile.putPreference('lastFile', filename)
277                         return filename
278                 dlg.Destroy()
279                 return False
280
281         def _showModelLoadDialog(self, amount):
282                 filelist = []
283                 for i in xrange(0, amount):
284                         filelist.append(self._showOpenDialog("Open file to print"))
285                         if filelist[-1] == False:
286                                 return
287                 self._loadModels(filelist)
288
289         def _loadModels(self, filelist):
290                 self.filelist = filelist
291                 self.SetTitle('Cura - %s - %s' % (version.getVersion(), filelist[-1]))
292                 profile.putPreference('lastFile', ';'.join(self.filelist))
293                 self.preview3d.loadModelFiles(self.filelist, True)
294                 self.preview3d.setViewMode("Normal")
295                 
296                 # Update the Model MRU
297                 for idx in xrange(0, len(self.filelist)):
298                         self.addToModelMRU(self.filelist[idx])
299
300         def OnDropFiles(self, files):
301                 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
302                 profile.setPluginConfig([])
303                 self.updateProfileToControls()
304                 self._loadModels(files)
305
306         def OnLoadModel(self, e):
307                 self._showModelLoadDialog(1)
308
309         def OnLoadModel2(self, e):
310                 self._showModelLoadDialog(2)
311
312         def OnLoadModel3(self, e):
313                 self._showModelLoadDialog(3)
314
315         def OnLoadModel4(self, e):
316                 self._showModelLoadDialog(4)
317
318         def OnSlice(self, e):
319                 if len(self.filelist) < 1:
320                         wx.MessageBox('You need to load a file before you can prepare it.', 'Print error', wx.OK | wx.ICON_INFORMATION)
321                         return
322                 isSimple = profile.getPreference('startMode') == 'Simple'
323                 if isSimple:
324                         #save the current profile so we can put it back latter
325                         oldProfile = profile.getGlobalProfileString()
326                         self.simpleSettingsPanel.setupSlice()
327                 #Create a progress panel and add it to the window. The progress panel will start the Skein operation.
328                 spp = sliceProgressPanel.sliceProgressPanel(self, self, self.filelist)
329                 self.sizer.Add(spp, 0, flag=wx.EXPAND)
330                 self.sizer.Layout()
331                 newSize = self.GetSize()
332                 newSize.IncBy(0, spp.GetSize().GetHeight())
333                 if newSize.GetWidth() < wx.GetDisplaySize()[0]:
334                         self.SetSize(newSize)
335                 self.progressPanelList.append(spp)
336                 if isSimple:
337                         profile.loadGlobalProfileFromString(oldProfile)
338
339         def OnPrint(self, e):
340                 if len(self.filelist) < 1:
341                         wx.MessageBox('You need to load a file and prepare it before you can print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
342                         return
343                 if not os.path.exists(sliceRun.getExportFilename(self.filelist[0])):
344                         wx.MessageBox('You need to prepare a print before you can run the actual print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
345                         return
346                 printWindow.printFile(sliceRun.getExportFilename(self.filelist[0]))
347
348         def OnModelMRU(self, e):
349                 fileNum = e.GetId() - self.ID_MRU_MODEL1
350                 path = self.modelFileHistory.GetHistoryFile(fileNum)
351                 # Update Model MRU
352                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
353                 self.config.SetPath("/ModelMRU")
354                 self.modelFileHistory.Save(self.config)
355                 self.config.Flush()
356                 # Load Model
357                 filelist = [ path ]
358                 self._loadModels(filelist)
359
360         def addToModelMRU(self, file):
361                 self.modelFileHistory.AddFileToHistory(file)
362                 self.config.SetPath("/ModelMRU")
363                 self.modelFileHistory.Save(self.config)
364                 self.config.Flush()
365         
366         def OnProfileMRU(self, e):
367                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
368                 path = self.profileFileHistory.GetHistoryFile(fileNum)
369                 # Update Profile MRU
370                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
371                 self.config.SetPath("/ProfileMRU")
372                 self.profileFileHistory.Save(self.config)
373                 self.config.Flush()
374                 # Load Profile  
375                 profile.loadGlobalProfile(path)
376                 self.updateProfileToControls()
377
378         def addToProfileMRU(self, file):
379                 self.profileFileHistory.AddFileToHistory(file)
380                 self.config.SetPath("/ProfileMRU")
381                 self.profileFileHistory.Save(self.config)
382                 self.config.Flush()                     
383
384         def removeSliceProgress(self, spp):
385                 self.progressPanelList.remove(spp)
386                 newSize = self.GetSize()
387                 newSize.IncBy(0, -spp.GetSize().GetHeight())
388                 if newSize.GetWidth() < wx.GetDisplaySize()[0]:
389                         self.SetSize(newSize)
390                 spp.Show(False)
391                 self.sizer.Detach(spp)
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() and not self.IsIconized():
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                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
521                         isSimple = profile.getPreference('startMode') == 'Simple'
522                         if not isSimple:
523                                 self.normalSashPos = self.splitter.GetSashPosition()
524                         profile.putPreference('window_normal_sash', self.normalSashPos)
525
526                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
527                 self.preview3d.glCanvas.OnPaint = lambda e : e
528                 self.Destroy()
529
530         def OnQuit(self, e):
531                 self.Close()
532
533 class normalSettingsPanel(configBase.configPanelBase):
534         "Main user interface window"
535         def __init__(self, parent):
536                 super(normalSettingsPanel, self).__init__(parent)
537
538                 #Main tabs
539                 self.nb = wx.Notebook(self)
540                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
541                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
542
543                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
544
545                 configBase.TitleRow(left, "Quality")
546                 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.')
547                 validators.validFloat(c, 0.0001)
548                 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.")
549                 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.')
550                 validators.validFloat(c, 0.0001)
551                 validators.wallThicknessValidator(c)
552 #               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.')
553
554                 configBase.TitleRow(left, "Fill")
555                 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.')
556                 validators.validFloat(c, 0.0)
557                 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')
558                 validators.validFloat(c, 0.0, 100.0)
559
560                 configBase.TitleRow(right, "Speed && Temperature")
561                 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.')
562                 validators.validFloat(c, 1.0)
563                 validators.warningAbove(c, 150.0, "It is highly unlikely that your machine can achieve a printing speed above 150mm/s")
564                 validators.printSpeedValidator(c)
565
566                 #configBase.TitleRow(right, "Temperature")
567                 c = configBase.SettingRow(right, "Printing temperature", 'print_temperature', '0', 'Temperature used for printing. Set at 0 to pre-heat yourself')
568                 validators.validFloat(c, 0.0, 340.0)
569                 validators.warningAbove(c, 260.0, "Temperatures above 260C could damage your machine, be careful!")
570                 if profile.getPreference('has_heated_bed') == 'True':
571                         c = configBase.SettingRow(right, "Bed temperature", 'print_bed_temperature', '0', 'Temperature used for the heated printer bed. Set at 0 to pre-heat yourself')
572                         validators.validFloat(c, 0.0, 340.0)
573
574 #               configBase.TitleRow(right, "Support structure")
575 #               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.')
576 #               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.')
577 #               if int(profile.getPreference('extruder_amount')) > 1:
578 #                       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.')
579
580                 configBase.TitleRow(right, "Filament")
581                 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.')
582                 validators.validFloat(c, 1.0)
583                 validators.warningAbove(c, 3.5, "Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm.")
584 #               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')
585 #               validators.validFloat(c, 0.5, 1.5)
586
587                 self.SizeLabelWidths(left, right)
588                 
589                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
590                 
591                 configBase.TitleRow(left, "Machine size")
592                 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.')
593                 validators.validFloat(c, 0.1, 10.0)
594
595                 configBase.TitleRow(left, "Skirt")
596                 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.')
597                 validators.validInt(c, 0, 10)
598                 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.')
599                 validators.validFloat(c, 0.0)
600
601 #               configBase.TitleRow(left, "Retraction")
602 #               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')
603 #               validators.validFloat(c, 0.0)
604 #               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.')
605 #               validators.validFloat(c, 0.1)
606 #               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.')
607 #               validators.validFloat(c, 0.0)
608 #               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.')
609 #               validators.validFloat(c, 0.0)
610
611                 configBase.TitleRow(right, "Speed")
612                 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.')
613                 validators.validFloat(c, 1.0)
614                 validators.warningAbove(c, 300.0, "It is highly unlikely that your machine can achieve a travel speed above 300mm/s")
615 #               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.')
616 #               validators.validFloat(c, 0.5)
617                 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.')
618                 validators.validFloat(c, 0.0)
619
620 #               configBase.TitleRow(right, "Cool")
621 #               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.')
622 #               validators.validFloat(c, 0.0)
623 #               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.')
624
625                 configBase.TitleRow(right, "Quality")
626                 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.')
627                 validators.validFloat(c, 0.0)
628                 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.")
629 #               c = configBase.SettingRow(right, "Cut off object bottom (mm)", 'object_sink', 0.05, 'Sinks the object into the platform, this can be used for objects that do not have a flat bottom and thus create a too small first layer.')
630 #               validators.validFloat(c, 0.0)
631 #               configBase.settingNotify(c, lambda : self.GetParent().GetParent().GetParent().preview3d.Refresh())
632 #               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.')
633
634                 self.SizeLabelWidths(left, right)
635
636                 #Plugin page
637                 self.pluginPanel = pluginPanel.pluginPanel(self.nb)
638                 if len(self.pluginPanel.pluginList) > 0:
639                         self.nb.AddPage(self.pluginPanel, "Plugins")
640                 else:
641                         self.pluginPanel.Show(False)
642
643                 #Alteration page
644                 self.alterationPanel = alterationPanel.alterationPanel(self.nb)
645                 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
646
647                 self.Bind(wx.EVT_SIZE, self.OnSize)
648
649         def SizeLabelWidths(self, left, right):
650                 leftWidth = self.getLabelColumnWidth(left)
651                 rightWidth = self.getLabelColumnWidth(right)
652                 maxWidth = max(leftWidth, rightWidth)
653                 self.setLabelColumnWidth(left, maxWidth)
654                 self.setLabelColumnWidth(right, maxWidth)
655
656         def OnSize(self, e):
657                 # Make the size of the Notebook control the same size as this control
658                 self.nb.SetSize(self.GetSize())
659                 
660                 # Propegate the OnSize() event (just in case)
661                 e.Skip()
662                 
663                 # Perform out resize magic
664                 self.UpdateSize(self.printPanel)
665                 self.UpdateSize(self.advancedPanel)
666         
667         def UpdateSize(self, configPanel):
668                 sizer = configPanel.GetSizer()
669                 
670                 # Pseudocde
671                 # if horizontal:
672                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
673                 #         switch to vertical
674                 # else:
675                 #     if width(col1) > (best_width(col1) + best_width(col1)):
676                 #         switch to horizontal
677                 #
678                                 
679                 col1 = configPanel.leftPanel
680                 colSize1 = col1.GetSize()
681                 colBestSize1 = col1.GetBestSize()
682                 col2 = configPanel.rightPanel
683                 colSize2 = col2.GetSize()
684                 colBestSize2 = col2.GetBestSize()
685
686                 orientation = sizer.GetOrientation()
687                 
688                 if orientation == wx.HORIZONTAL:
689                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
690                                 configPanel.Freeze()
691                                 sizer = wx.BoxSizer(wx.VERTICAL)
692                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
693                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
694                                 configPanel.SetSizer(sizer)
695                                 #sizer.Layout()
696                                 configPanel.Layout()
697                                 self.Layout()
698                                 configPanel.Thaw()
699                 else:
700                         if colSize1[0] > (colBestSize1[0] + colBestSize2[0]):
701                                 configPanel.Freeze()
702                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
703                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
704                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
705                                 configPanel.SetSizer(sizer)
706                                 #sizer.Layout()
707                                 configPanel.Layout()
708                                 self.Layout()
709                                 configPanel.Thaw()
710                                 
711         def updateProfileToControls(self):
712                 super(normalSettingsPanel, self).updateProfileToControls()
713                 self.alterationPanel.updateProfileToControls()
714                 self.pluginPanel.updateProfileToControls()