chiark / gitweb /
Fix the splitter sizing issue on mac.
[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 - ' + 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                 try:
211                         if profile.getPreference('window_maximized') == 'True':
212                                 self.Maximize(True)
213                         else:
214                                 posx = int(profile.getPreference('window_pos_x'))
215                                 posy = int(profile.getPreference('window_pos_y'))
216                                 width = int(profile.getPreference('window_width'))
217                                 height = int(profile.getPreference('window_height'))
218                         if posx > 0 or posy > 0:
219                                 self.SetPosition((posx,posy))
220                         if width > 0 and height > 0:
221                                 self.SetSize((width,height))
222                                 
223                         self.normalSashPos = int(profile.getPreference('window_normal_sash'))
224                         if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
225                                 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
226                 except:
227                         self.Maximize(True)
228
229                 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
230
231                 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
232                         self.Centre()
233                 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
234                         self.Centre()
235                 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
236                         self.SetSize((800,600))
237                         self.Centre()
238
239                 self.updateSliceMode()
240
241                 self.Show(True)
242
243         def updateSliceMode(self):
244                 isSimple = profile.getPreference('startMode') == 'Simple'
245
246                 self.normalSettingsPanel.Show(not isSimple)
247                 self.simpleSettingsPanel.Show(isSimple)
248                 self.leftPane.Layout()
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                 # Set splitter sash position & size
256                 if isSimple:
257                         # Save normal mode sash
258                         self.normalSashPos = self.splitter.GetSashPosition()
259                         
260                         # Change location of sash to width of quick mode pane 
261                         (width, height) = self.simpleSettingsPanel.GetSizer().GetSize() 
262                         self.splitter.SetSashPosition(width, True)
263                         
264                         # Disable sash
265                         self.splitter.SetSashSize(0)
266                 else:
267                         self.splitter.SetSashPosition(self.normalSashPos, True)
268                         # Enabled sash
269                         self.splitter.SetSashSize(4)
270                                                                 
271         def OnPreferences(self, e):
272                 prefDialog = preferencesDialog.preferencesDialog(self)
273                 prefDialog.Centre()
274                 prefDialog.Show(True)
275
276         def _showOpenDialog(self, title, wildcard = meshLoader.wildcardFilter()):
277                 dlg=wx.FileDialog(self, title, os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
278                 dlg.SetWildcard(wildcard)
279                 if dlg.ShowModal() == wx.ID_OK:
280                         filename = dlg.GetPath()
281                         dlg.Destroy()
282                         if not(os.path.exists(filename)):
283                                 return False
284                         profile.putPreference('lastFile', filename)
285                         return filename
286                 dlg.Destroy()
287                 return False
288
289         def _showModelLoadDialog(self, amount):
290                 filelist = []
291                 for i in xrange(0, amount):
292                         filelist.append(self._showOpenDialog("Open file to print"))
293                         if filelist[-1] == False:
294                                 return
295                 self._loadModels(filelist)
296
297         def _loadModels(self, filelist):
298                 self.filelist = filelist
299                 self.SetTitle('Cura - %s - %s' % (version.getVersion(), filelist[-1]))
300                 profile.putPreference('lastFile', ';'.join(self.filelist))
301                 self.preview3d.loadModelFiles(self.filelist, True)
302                 self.preview3d.setViewMode("Normal")
303                 
304                 # Update the Model MRU
305                 for idx in xrange(0, len(self.filelist)):
306                         self.addToModelMRU(self.filelist[idx])
307
308         def OnDropFiles(self, files):
309                 profile.putProfileSetting('model_matrix', '1,0,0,0,1,0,0,0,1')
310                 profile.setPluginConfig([])
311                 self.updateProfileToControls()
312                 self._loadModels(files)
313
314         def OnLoadModel(self, e):
315                 self._showModelLoadDialog(1)
316
317         def OnLoadModel2(self, e):
318                 self._showModelLoadDialog(2)
319
320         def OnLoadModel3(self, e):
321                 self._showModelLoadDialog(3)
322
323         def OnLoadModel4(self, e):
324                 self._showModelLoadDialog(4)
325
326         def OnSlice(self, e):
327                 if len(self.filelist) < 1:
328                         wx.MessageBox('You need to load a file before you can prepare it.', 'Print error', wx.OK | wx.ICON_INFORMATION)
329                         return
330                 isSimple = profile.getPreference('startMode') == 'Simple'
331                 if isSimple:
332                         #save the current profile so we can put it back latter
333                         oldProfile = profile.getGlobalProfileString()
334                         self.simpleSettingsPanel.setupSlice()
335                 #Create a progress panel and add it to the window. The progress panel will start the Skein operation.
336                 spp = sliceProgressPanel.sliceProgressPanel(self, self, self.filelist)
337                 self.sizer.Add(spp, 0, flag=wx.EXPAND)
338                 self.sizer.Layout()
339                 newSize = self.GetSize()
340                 newSize.IncBy(0, spp.GetSize().GetHeight())
341                 if newSize.GetWidth() < wx.GetDisplaySize()[0]:
342                         self.SetSize(newSize)
343                 self.progressPanelList.append(spp)
344                 if isSimple:
345                         profile.loadGlobalProfileFromString(oldProfile)
346
347         def OnPrint(self, e):
348                 if len(self.filelist) < 1:
349                         wx.MessageBox('You need to load a file and prepare it before you can print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
350                         return
351                 if not os.path.exists(sliceRun.getExportFilename(self.filelist[0])):
352                         wx.MessageBox('You need to prepare a print before you can run the actual print.', 'Print error', wx.OK | wx.ICON_INFORMATION)
353                         return
354                 printWindow.printFile(sliceRun.getExportFilename(self.filelist[0]))
355
356         def OnModelMRU(self, e):
357                 fileNum = e.GetId() - self.ID_MRU_MODEL1
358                 path = self.modelFileHistory.GetHistoryFile(fileNum)
359                 # Update Model MRU
360                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
361                 self.config.SetPath("/ModelMRU")
362                 self.modelFileHistory.Save(self.config)
363                 self.config.Flush()
364                 # Load Model
365                 filelist = [ path ]
366                 self._loadModels(filelist)
367
368         def addToModelMRU(self, file):
369                 self.modelFileHistory.AddFileToHistory(file)
370                 self.config.SetPath("/ModelMRU")
371                 self.modelFileHistory.Save(self.config)
372                 self.config.Flush()
373         
374         def OnProfileMRU(self, e):
375                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
376                 path = self.profileFileHistory.GetHistoryFile(fileNum)
377                 # Update Profile MRU
378                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
379                 self.config.SetPath("/ProfileMRU")
380                 self.profileFileHistory.Save(self.config)
381                 self.config.Flush()
382                 # Load Profile  
383                 profile.loadGlobalProfile(path)
384                 self.updateProfileToControls()
385
386         def addToProfileMRU(self, file):
387                 self.profileFileHistory.AddFileToHistory(file)
388                 self.config.SetPath("/ProfileMRU")
389                 self.profileFileHistory.Save(self.config)
390                 self.config.Flush()                     
391
392         def removeSliceProgress(self, spp):
393                 self.progressPanelList.remove(spp)
394                 newSize = self.GetSize()
395                 newSize.IncBy(0, -spp.GetSize().GetHeight())
396                 if newSize.GetWidth() < wx.GetDisplaySize()[0]:
397                         self.SetSize(newSize)
398                 spp.Show(False)
399                 self.sizer.Detach(spp)
400                 self.sizer.Layout()
401
402         def updateProfileToControls(self):
403                 self.preview3d.updateProfileToControls()
404                 self.normalSettingsPanel.updateProfileToControls()
405                 self.simpleSettingsPanel.updateProfileToControls()
406
407         def OnLoadProfile(self, e):
408                 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)
409                 dlg.SetWildcard("ini files (*.ini)|*.ini")
410                 if dlg.ShowModal() == wx.ID_OK:
411                         profileFile = dlg.GetPath()
412                         profile.loadGlobalProfile(profileFile)
413                         self.updateProfileToControls()
414
415                         # Update the Profile MRU
416                         self.addToProfileMRU(profileFile)
417                 dlg.Destroy()
418
419         def OnLoadProfileFromGcode(self, e):
420                 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)
421                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
422                 if dlg.ShowModal() == wx.ID_OK:
423                         gcodeFile = dlg.GetPath()
424                         f = open(gcodeFile, 'r')
425                         hasProfile = False
426                         for line in f:
427                                 if line.startswith(';CURA_PROFILE_STRING:'):
428                                         profile.loadGlobalProfileFromString(line[line.find(':')+1:].strip())
429                                         hasProfile = True
430                         if hasProfile:
431                                 self.updateProfileToControls()
432                         else:
433                                 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)
434                 dlg.Destroy()
435
436         def OnSaveProfile(self, e):
437                 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
438                 dlg.SetWildcard("ini files (*.ini)|*.ini")
439                 if dlg.ShowModal() == wx.ID_OK:
440                         profileFile = dlg.GetPath()
441                         profile.saveGlobalProfile(profileFile)
442                 dlg.Destroy()
443
444         def OnResetProfile(self, e):
445                 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)
446                 result = dlg.ShowModal() == wx.ID_YES
447                 dlg.Destroy()
448                 if result:
449                         profile.resetGlobalProfile()
450                         self.updateProfileToControls()
451
452         def OnBatchRun(self, e):
453                 br = batchRun.batchRunWindow(self)
454                 br.Centre()
455                 br.Show(True)
456
457         def OnSimpleSwitch(self, e):
458                 profile.putPreference('startMode', 'Simple')
459                 self.updateSliceMode()
460
461         def OnNormalSwitch(self, e):
462                 profile.putPreference('startMode', 'Normal')
463                 self.updateSliceMode()
464
465         def OnDefaultMarlinFirmware(self, e):
466                 firmwareInstall.InstallFirmware()
467
468         def OnCustomFirmware(self, e):
469                 if profile.getPreference('machine_type') == 'ultimaker':
470                         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)
471                 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
472                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
473                 if dlg.ShowModal() == wx.ID_OK:
474                         filename = dlg.GetPath()
475                         if not(os.path.exists(filename)):
476                                 return
477                         #For some reason my Ubuntu 10.10 crashes here.
478                         firmwareInstall.InstallFirmware(filename)
479
480         def OnFirstRunWizard(self, e):
481                 configWizard.configWizard()
482                 self.updateProfileToControls()
483
484         def OnBedLevelWizard(self, e):
485                 configWizard.bedLevelWizard()
486
487         def OnExpertOpen(self, e):
488                 ecw = expertConfig.expertConfigWindow()
489                 ecw.Centre()
490                 ecw.Show(True)
491
492         def OnProjectPlanner(self, e):
493                 pp = projectPlanner.projectPlanner()
494                 pp.Centre()
495                 pp.Show(True)
496
497         def OnMinecraftImport(self, e):
498                 mi = minecraftImport.minecraftImportWindow(self)
499                 mi.Centre()
500                 mi.Show(True)
501
502         def OnSVGSlicerOpen(self, e):
503                 svgSlicer = flatSlicerWindow.flatSlicerWindow()
504                 svgSlicer.Centre()
505                 svgSlicer.Show(True)
506
507         def OnCheckForUpdate(self, e):
508                 newVersion = version.checkForNewerVersion()
509                 if newVersion is not None:
510                         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:
511                                 webbrowser.open(newVersion)
512                 else:
513                         wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
514
515         def OnClose(self, e):
516                 profile.saveGlobalProfile(profile.getDefaultProfilePath())
517
518                 # Save the window position, size & state from the preferences file
519                 profile.putPreference('window_maximized', self.IsMaximized())
520                 if not self.IsMaximized() and not self.IsIconized():
521                         (posx, posy) = self.GetPosition()
522                         profile.putPreference('window_pos_x', posx)
523                         profile.putPreference('window_pos_y', posy)
524                         (width, height) = self.GetSize()
525                         profile.putPreference('window_width', width)
526                         profile.putPreference('window_height', height)                  
527                         
528                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
529                         isSimple = profile.getPreference('startMode') == 'Simple'
530                         if not isSimple:
531                                 self.normalSashPos = self.splitter.GetSashPosition()
532                         profile.putPreference('window_normal_sash', self.normalSashPos)
533
534                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
535                 self.preview3d.glCanvas.OnPaint = lambda e : e
536                 self.Destroy()
537
538         def OnQuit(self, e):
539                 self.Close()
540
541 class normalSettingsPanel(configBase.configPanelBase):
542         "Main user interface window"
543         def __init__(self, parent):
544                 super(normalSettingsPanel, self).__init__(parent)
545
546                 #Main tabs
547                 self.nb = wx.Notebook(self)
548                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
549                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
550
551                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
552
553                 configBase.TitleRow(left, "Quality")
554                 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.')
555                 validators.validFloat(c, 0.0001)
556                 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.")
557                 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.')
558                 validators.validFloat(c, 0.0001)
559                 validators.wallThicknessValidator(c)
560                 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.')
561
562                 configBase.TitleRow(left, "Fill")
563                 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.')
564                 validators.validFloat(c, 0.0)
565                 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')
566                 validators.validFloat(c, 0.0, 100.0)
567
568                 configBase.TitleRow(right, "Speed && Temperature")
569                 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.')
570                 validators.validFloat(c, 1.0)
571                 validators.warningAbove(c, 150.0, "It is highly unlikely that your machine can achieve a printing speed above 150mm/s")
572                 validators.printSpeedValidator(c)
573
574                 #configBase.TitleRow(right, "Temperature")
575                 c = configBase.SettingRow(right, "Printing temperature", 'print_temperature', '0', 'Temperature used for printing. Set at 0 to pre-heat yourself')
576                 validators.validFloat(c, 0.0, 340.0)
577                 validators.warningAbove(c, 260.0, "Temperatures above 260C could damage your machine, be careful!")
578                 if int(profile.getPreference('extruder_amount')) > 1:
579                         c = configBase.SettingRow(right, "2nd nozzle temperature", 'print_temperature2', '0', 'Temperature used for printing with the 2nd nozzle. Set at 0 to use the same temperature as for nozzle 1')
580                         validators.validFloat(c, 0.0, 340.0)
581                         validators.warningAbove(c, 260.0, "Temperatures above 260C could damage your machine, be careful!")
582                 if int(profile.getPreference('extruder_amount')) > 2:
583                         c = configBase.SettingRow(right, "3th nozzle temperature", 'print_temperature3', '0', 'Temperature used for printing with the 3th nozzle. Set at 0 to use the same temperature as for nozzle 1')
584                         validators.validFloat(c, 0.0, 340.0)
585                         validators.warningAbove(c, 260.0, "Temperatures above 260C could damage your machine, be careful!")
586                 if int(profile.getPreference('extruder_amount')) > 3:
587                         c = configBase.SettingRow(right, "4th nozzle temperature", 'print_temperature4', '0', 'Temperature used for printing with the 4th nozzle. Set at 0 to use the same temperature as for nozzle 1')
588                         validators.validFloat(c, 0.0, 340.0)
589                         validators.warningAbove(c, 260.0, "Temperatures above 260C could damage your machine, be careful!")
590                 if profile.getPreference('has_heated_bed') == 'True':
591                         c = configBase.SettingRow(right, "Bed temperature", 'print_bed_temperature', '0', 'Temperature used for the heated printer bed. Set at 0 to pre-heat yourself')
592                         validators.validFloat(c, 0.0, 340.0)
593
594                 configBase.TitleRow(right, "Support structure")
595                 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.')
596                 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.')
597                 if int(profile.getPreference('extruder_amount')) > 1:
598                         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.')
599
600                 configBase.TitleRow(right, "Filament")
601                 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 calibrate it, a higher number means less extrusion, a smaller number generates more extrusion.')
602                 validators.validFloat(c, 1.0)
603                 validators.warningAbove(c, 3.5, "Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm.")
604                 if int(profile.getPreference('extruder_amount')) > 1:
605                         c = configBase.SettingRow(right, "Diameter (mm)", 'filament_diameter2', '2.89', 'Diameter of your filament for the 2nd nozzle, as accurately as possible.\nIf you cannot measure this value you will have to calibrate it, a higher number means less extrusion, a smaller number generates more extrusion. Use 0 to use the same diameter as for nozzle 1.')
606                         validators.validFloat(c, 0.0)
607                         validators.warningAbove(c, 3.5, "Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm.")
608                 if int(profile.getPreference('extruder_amount')) > 2:
609                         c = configBase.SettingRow(right, "Diameter (mm)", 'filament_diameter3', '2.89', 'Diameter of your filament for the 3th nozzle, as accurately as possible.\nIf you cannot measure this value you will have to calibrate it, a higher number means less extrusion, a smaller number generates more extrusion. Use 0 to use the same diameter as for nozzle 1.')
610                         validators.validFloat(c, 0.0)
611                         validators.warningAbove(c, 3.5, "Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm.")
612                 if int(profile.getPreference('extruder_amount')) > 3:
613                         c = configBase.SettingRow(right, "Diameter (mm)", 'filament_diameter4', '2.89', 'Diameter of your filament for the 4th nozzle, as accurately as possible.\nIf you cannot measure this value you will have to calibrate it, a higher number means less extrusion, a smaller number generates more extrusion. Use 0 to use the same diameter as for nozzle 1.')
614                         validators.validFloat(c, 0.0)
615                         validators.warningAbove(c, 3.5, "Are you sure your filament is that thick? Normal filament is around 3mm or 1.75mm.")
616                 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')
617                 validators.validFloat(c, 0.5, 1.5)
618
619                 self.SizeLabelWidths(left, right)
620                 
621                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
622                 
623                 configBase.TitleRow(left, "Machine size")
624                 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.')
625                 validators.validFloat(c, 0.1, 10.0)
626
627                 configBase.TitleRow(left, "Skirt")
628                 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.')
629                 validators.validInt(c, 0, 10)
630                 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.')
631                 validators.validFloat(c, 0.0)
632
633                 configBase.TitleRow(left, "Retraction")
634                 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')
635                 validators.validFloat(c, 0.0)
636                 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.')
637                 validators.validFloat(c, 0.1)
638                 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.')
639                 validators.validFloat(c, 0.0)
640                 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.')
641                 validators.validFloat(c, 0.0)
642
643                 configBase.TitleRow(right, "Speed")
644                 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.')
645                 validators.validFloat(c, 1.0)
646                 validators.warningAbove(c, 300.0, "It is highly unlikely that your machine can achieve a travel speed above 300mm/s")
647                 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.')
648                 validators.validFloat(c, 0.5)
649                 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.')
650                 validators.validFloat(c, 0.0)
651
652                 configBase.TitleRow(right, "Cool")
653                 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.')
654                 validators.validFloat(c, 0.0)
655                 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.')
656
657                 configBase.TitleRow(right, "Quality")
658                 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.')
659                 validators.validFloat(c, 0.0)
660                 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.")
661                 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.')
662                 validators.validFloat(c, 0.0)
663                 configBase.settingNotify(c, lambda : self.GetParent().GetParent().GetParent().preview3d.Refresh())
664                 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.')
665
666                 self.SizeLabelWidths(left, right)
667
668                 #Plugin page
669                 self.pluginPanel = pluginPanel.pluginPanel(self.nb)
670                 if len(self.pluginPanel.pluginList) > 0:
671                         self.nb.AddPage(self.pluginPanel, "Plugins")
672                 else:
673                         self.pluginPanel.Show(False)
674
675                 #Alteration page
676                 self.alterationPanel = alterationPanel.alterationPanel(self.nb)
677                 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
678
679                 self.Bind(wx.EVT_SIZE, self.OnSize)
680
681                 self.nb.SetSize(self.GetSize())
682                 self.UpdateSize(self.printPanel)
683                 self.UpdateSize(self.advancedPanel)
684
685         def SizeLabelWidths(self, left, right):
686                 leftWidth = self.getLabelColumnWidth(left)
687                 rightWidth = self.getLabelColumnWidth(right)
688                 maxWidth = max(leftWidth, rightWidth)
689                 self.setLabelColumnWidth(left, maxWidth)
690                 self.setLabelColumnWidth(right, maxWidth)
691
692         def OnSize(self, e):
693                 # Make the size of the Notebook control the same size as this control
694                 self.nb.SetSize(self.GetSize())
695                 
696                 # Propegate the OnSize() event (just in case)
697                 e.Skip()
698                 
699                 # Perform out resize magic
700                 self.UpdateSize(self.printPanel)
701                 self.UpdateSize(self.advancedPanel)
702         
703         def UpdateSize(self, configPanel):
704                 sizer = configPanel.GetSizer()
705                 
706                 # Pseudocde
707                 # if horizontal:
708                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
709                 #         switch to vertical
710                 # else:
711                 #     if width(col1) > (best_width(col1) + best_width(col1)):
712                 #         switch to horizontal
713                 #
714                                 
715                 col1 = configPanel.leftPanel
716                 colSize1 = col1.GetSize()
717                 colBestSize1 = col1.GetBestSize()
718                 col2 = configPanel.rightPanel
719                 colSize2 = col2.GetSize()
720                 colBestSize2 = col2.GetBestSize()
721
722                 orientation = sizer.GetOrientation()
723                 
724                 if orientation == wx.HORIZONTAL:
725                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
726                                 configPanel.Freeze()
727                                 sizer = wx.BoxSizer(wx.VERTICAL)
728                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
729                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
730                                 configPanel.SetSizer(sizer)
731                                 #sizer.Layout()
732                                 configPanel.Layout()
733                                 self.Layout()
734                                 configPanel.Thaw()
735                 else:
736                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
737                                 configPanel.Freeze()
738                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
739                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
740                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
741                                 configPanel.SetSizer(sizer)
742                                 #sizer.Layout()
743                                 configPanel.Layout()
744                                 self.Layout()
745                                 configPanel.Thaw()
746
747         def updateProfileToControls(self):
748                 super(normalSettingsPanel, self).updateProfileToControls()
749                 self.alterationPanel.updateProfileToControls()
750                 self.pluginPanel.updateProfileToControls()